-- Processore macchina Saomad-KAIROS by EgalWare s.r.l.   2023/12/03
-- Funzioni generiche indipendenti dal controllo

-- Intestazioni
require( 'EmtGenerator')
EgtEnableDebug( false)

LONG_TOOL_MINLEN = 221
BIG_TOOL_DIAM = 300
ALL_CLAMP_POS = {}
CLAMP_POS = {}

---------------------------------------------------------------------
-- *** GENERATION ***
---------------------------------------------------------------------
local sBaseDir = EgtGetCurrMachineDir()
if NumericalControl == 'SIEMENS' then
   dofile( sBaseDir .. '\\Saomad-KAIROS-MK2.SIEMENS.mlpe')
else
   EmtSetLastError( 1201, 'Numerical Control error : unkwnown type')
end

---------------------------------------------------------------------
-- *** SIMULATION ***
---------------------------------------------------------------------
local COLL_SAFE_DIST = 3

---------------------------------------------------------------------
function OnSimulStart()
   -- Carico gli utensili sulle barre portautensili
   local vTcPos = EgtGetAllTcPosNames()
   if vTcPos then
      for i = 1, #vTcPos do
         local vTools = EgtGetToolsInCurrSetupPos( vTcPos[i])
         for j = 1, #( vTools or {}) do
            if vTools[j] ~= '' then
               EgtLoadTool( vTcPos[i], j, vTools[j])
               -- Aggiunto controllo lunghezza lama minima * 0.9 per ricavare la tolleranza del VMILL
               if EgtTdbSetCurrTool(vTools[j]) then -- set utensile corrente
                  if EgtTdbGetCurrToolParam( MCH_TP.TYPE) == MCH_TY.SAW_STD or EgtTdbGetCurrToolParam( MCH_TP.TYPE) == MCH_TY.SAW_FLAT then -- controllo tipo utensile sega
                     local dCurrSawLen = EgtTdbGetCurrToolParam( MCH_TP.LEN) * 0.9
                     -- se non definito o minore del valore precedente aggiorna la tolleranza
                     EMT.VMILLTOL = EgtIf( not EMT.VMILLTOL or dCurrSawLen < EMT.VMILLTOL, dCurrSawLen, EMT.VMILLTOL) 
                  end
               end
            end
         end
         ShowToolInTcPos( vTcPos[i], true)
      end
   end
   -- Carico l'utensile iniziale o di default sulla testa
   LoadFirstTool()
   -- Se reset o home, esco
   if EMT.SIM1ST then return end
   -- Creo o svuoto gruppo per copia finale degli oggetti virtual milling
   local nVmGrpId = EgtGetFirstNameInGroup( GDB_ID.ROOT, 'VMill')
   if nVmGrpId then
      EgtSetStatus( nVmGrpId, GDB_ST.ON)
      EgtEmptyGroup( nVmGrpId)
   else
      nVmGrpId = EgtGroup( GDB_ID.ROOT)
      EgtSetName( nVmGrpId, 'VMill')
      EgtSetLevel( nVmGrpId, GDB_LV.TEMP)
   end
   -- Preparo lista oggetti da verificare per collisioni
   EMT.COLLOBJ = {}
   AddToCollisionCheck( 'Z', 'COLLISION', EMT.COLLOBJ)
   AddToCollisionCheck( 'A', 'COLLISION', EMT.COLLOBJ)
   AddToCollisionCheck( 'C', 'COLLISION', EMT.COLLOBJ)
   DumpCollisionCheck( EMT.COLLOBJ, 'Collision Objects :', 4)
   -- Preparo lista solidi macchina con cui possono collidere gli oggetti sopra riportati (in aggiunta a VMill)
   EMT.MCODET = {}
   local McdData = { { Grp = 'X1', Sub = 'COLLISION', Name = 'STM1'},
                     { Grp = 'X1', Sub = 'COLLISION', Name = 'STM2'},
                     { Grp = 'PX1', Sub = 'COLLISION', Name = 'STM1'},
                     { Grp = 'X2', Sub = 'COLLISION', Name = 'STM1'},
                     { Grp = 'X2', Sub = 'COLLISION', Name = 'STM2'},
                     { Grp = 'PX2', Sub = 'COLLISION', Name = 'STM1'},
                     { Grp = 'Base', Sub = 'COLLISION', Name = 'STM1'},
                     { Grp = 'Base', Sub = 'COLLISION', Name = 'STM2'},
                     { Grp = 'Base', Sub = 'COLLISION', Name = 'STM3'},
                     { Grp = 'Base', Sub = 'COLLISION', Name = 'STM4'},
                     { Grp = 'Base', Sub = 'COLLISION', Name = 'STM5'},
                     --{ Grp = 'Base', Sub = 'COLLISION', Name = 'STM6'},
                     { Grp = 'Base', Sub = 'COLLISION', Name = 'STM7'},
                     { Grp = 'Base', Sub = 'COLLISION', Name = 'STM8'},
                     { Grp = 'Base', Sub = 'COLLISION', Name = 'STM9'},
                     { Grp = 'Base', Sub = 'COLLISION', Name = 'STM10'}}
   EgtOutLog( 'MCODET Objects :', 4)
   local nMcdNullCnt = 0
   for i = 1, #McdData do
      local nGrpId
      if McdData[i].Grp == 'Base' then
         nGrpId = EgtGetBaseId( 'Base')
      else
         nGrpId = EgtGetAxisId( McdData[i].Grp)
      end
      local nId = EgtGetFirstNameInGroup( EgtGetFirstNameInGroup( nGrpId, McdData[i].Sub), McdData[i].Name)
      if nId then
         table.insert( EMT.MCODET, nId)
         EgtOutLog( 'Element ' .. McdData[i].Grp .. '/' .. McdData[i].Sub .. '/' .. McdData[i].Name .. ' (' .. tostring( nId) .. ') is ok', 4)
      else
         nMcdNullCnt = nMcdNullCnt + 1
         EgtOutLog( 'Element ' .. McdData[i].Grp .. '/' .. McdData[i].Sub .. '/' .. McdData[i].Name .. ' is null', 4)
      end
   end
   if nMcdNullCnt > 0 then
      EgtOutLog( 'Warning : MCODET with one or more null Element(s) ')
   end
   -- Preparo lista collisioni vuota
   EMT.COLLIDE = {}

   -- si crea gruppo temporaneo appoggio controllo clamping
   CLAMP_CHECK_GROUP = EgtGetFirstNameInGroup( GDB_ID.ROOT, 'CLAMP_CHECK')
   CLAMP_CHECK_INTERS = EgtGetFirstNameInGroup( GDB_ID.ROOT, 'CLAMP_INTERS')

   if CLAMP_CHECK_GROUP then
      EgtEmptyGroup( CLAMP_CHECK_GROUP)
   else
      local frClampCheckGroup = Frame3d( ORIG(), GDB_FR.FRONT)
      CLAMP_CHECK_GROUP = EgtGroup( GDB_ID.ROOT, frClampCheckGroup)
      EgtSetName( CLAMP_CHECK_GROUP, 'CLAMP_CHECK')
      EgtSetLevel( CLAMP_CHECK_GROUP, GDB_LV.TEMP)
      EgtSetStatus( CLAMP_CHECK_GROUP, GDB_ST.OFF)
   end
   
   if CLAMP_CHECK_INTERS then
      EgtEmptyGroup( CLAMP_CHECK_INTERS)
   else
      CLAMP_CHECK_INTERS = EgtGroup( GDB_ID.ROOT)
      EgtSetName( CLAMP_CHECK_INTERS, 'CLAMP_INTERS')
      EgtSetLevel( CLAMP_CHECK_INTERS, GDB_LV.TEMP)
      EgtSetStatus( CLAMP_CHECK_INTERS, GDB_ST.OFF)
   end
end

---------------------------------------------------------------------
function OnSimulEnd()
end

---------------------------------------------------------------------
function OnSimulDispositionStarting()
   EmtUnlinkAllRawPartsFromGroups()
   if EMT.PHASE > 1 then
      if IsStartOrRestPhase( EMT.PHASE) then
         EgtSetAxisPos( 'T', LoadT)
      end
   end
end

---------------------------------------------------------------------
function OnSimulDispositionStart()
   EMT.OPEISDISP = true

   -- Se prima disposizione
   if EMT.PHASE == 1 then
      -- Determino dimensioni del grezzo
      local nSolId = EgtGetFirstNameInGroup( EgtGetFirstRawPart() or GDB_ID.NULL, 'RawSolid') or GDB_ID.NULL
      local b3Sol = EgtGetBBoxGlob( nSolId, GDB_BB.STANDARD)
      EMT.LENGTHBEAM = 0
      EMT.WIDTHBEAM = 0
      EMT.HEIGHTBEAM = 0
      if b3Sol then
         EMT.LENGTHBEAM = b3Sol:getDimX()
         EMT.WIDTHBEAM = b3Sol:getDimY()
         EMT.HEIGHTBEAM = b3Sol:getDimZ()
      end
      -- Se vero inizio e abilitato creo gli Zmap
      EMT.VMILL = {}
      if not EMT.SIM1ST and EgtGetInfo( EgtGetCurrMachGroup(), 'Vm', 'b') then
         local nLastOrd = GetPhaseOrd( EgtGetPhaseCount())
         local nPartRawId = EgtGetFirstRawPart() 
         while nPartRawId do
            -- se è lo scarto finale tagliato a pezzi, esco
            local nRawOrd = EgtGetInfo( nPartRawId, 'ORD', 'i')
            if nRawOrd == nLastOrd + 1 then break end
            -- elimino eventuale vecchio Zmap
            EgtErase( EgtGetFirstNameInGroup( nPartRawId, 'VMill') or GDB_ID.NULL)
            -- recupero il solido
            local nSolId = EgtGetFirstNameInGroup( nPartRawId, 'RawSolid')
            local b3Raw = EgtGetBBoxGlob( nSolId, GDB_BB.STANDARD)
            -- aggiungo eventuale scrap successivo
            if nRawOrd == nLastOrd then
               local nScrapId = EgtGetNextRawPart( nPartRawId)
               if nScrapId then
                  local nScrapSolId = EgtGetFirstNameInGroup( nScrapId, 'RawSolid')
                  local b3ScrapRaw = EgtGetBBoxGlob( nScrapSolId, GDB_BB.STANDARD)
                  if b3ScrapRaw then
                     b3Raw:Add( b3ScrapRaw)
                  end 
               end
            end
            -- determino la risoluzione dello Zmap
            local dTol
            if EmtGetVMillStep then
               dTol = EmtGetVMillStep( b3Raw:getDimX(), b3Raw:getDimY(), b3Raw:getDimZ(), 4.71)
            else
               local dArea = b3Raw:getDimX() * b3Raw:getDimY() + b3Raw:getDimX() * b3Raw:getDimZ() + b3Raw:getDimY() * b3Raw:getDimZ()
               if dArea < CoeffVM * 0.15e6 then
                  dTol = 0.71
               elseif dArea < CoeffVM * 0.3e6 then
                  dTol = 1.01
               elseif dArea < CoeffVM * 0.6e6 then
                  dTol = 1.51
               elseif dArea < CoeffVM * 1.2e6 then
                  dTol = 1.97
               elseif dArea < CoeffVM * 2.4e6 then
                  dTol = 2.81
               elseif dArea < CoeffVM * 4.8e6 then
                  dTol = 3.77
               else
                  dTol = 4.71
               end
            end
            -- creo lo Zmap
            local VMillId = EgtVolZmapBox( nPartRawId, b3Raw:getMin(), b3Raw:getDimX(), b3Raw:getDimY(), b3Raw:getDimZ(), dTol, true, GDB_RT.GLOB)
            if VMillId then
               EgtSetName( VMillId, 'VMill')
               EgtSetLevel( VMillId, GDB_LV.TEMP)
               EgtSetColor( VMillId, EgtGetColor( nSolId), false)
               -- nascondo le altre geometrie
               local nId = EgtGetFirstInGroup( nPartRawId)
               while nId do
                  if nId ~= VMillId then
                     EgtSetStatus( nId, GDB_ST.OFF)
                  end
                  nId = EgtGetNext( nId)
               end
               table.insert( EMT.VMILL, VMillId)
            end
            nPartRawId = EgtGetNextRawPart( nPartRawId)
         end
      end
   -- fasi successive
   else
      EMT.LOAD = false
   end
   -- recupero sovramateriale di testa del pezzo corrente
   local nCurrRawId = EgtGetFirstRawPart()
   while nCurrRawId do
      if EgtVerifyRawPartPhase( nCurrRawId, EMT.PHASE) then
         break
      end
      nCurrRawId = EgtGetNextRawPart( nCurrRawId)
   end
   EMT.HOVM = EgtGetInfo( nCurrRawId or GDB_ID.NULL, 'HOVM', 'd') or 0

   -- Nascondo tutte le lavorazioni
   local nMchId = EgtGetFirstOperation()
   while nMchId do
      if EgtGetOperationType( nMchId) ~= MCH_OY.DISP then
         EgtSetOperationStatus( nMchId, false)
      end
      nMchId = EgtGetNextOperation( nMchId)
   end

   -- Se fase inizio o rimanenza, aggancio grezzi della fase alla tavola
   if IsStartOrRestPhase( EMT.PHASE) then
      -- se fase inizio, segnalo giacitura del grezzo
      if IsStartPhase( EMT.PHASE) then
         local nRot = GetPhaseRot( EMT.PHASE)
         if nRot ~= 0 then
            EgtOutText( tostring( -90 * nRot) .. '° rotated bar')
         else
            EgtOutText( 'Not rotated bar')
         end
      end
      -- indice primo grezzo della fase
      local nOrd = GetPhaseOrd( EMT.PHASE)
      local nScrapOrd = GetPhaseOrd( EgtGetPhaseCount()) + 1
      local b3Raw = BBox3d()
      local b3Bar = BBox3d()
      local b3Part = BBox3d()
      local nPartRawId, nScrapRawId
      local nRawId = EgtGetFirstRawPart()
      while nRawId do
         local nNextRawId = EgtGetNextRawPart( nRawId)
         if EgtVerifyRawPartPhase( nRawId, EMT.PHASE) then
            EmtLinkRawPartToGroup( nRawId, 'Tab')
            local b3Tmp = EgtGetRawPartBBox( nRawId)
            b3Bar:Add( b3Tmp)
            local nRawOrd = EgtGetInfo( nRawId, 'ORD', 'i')
            if nRawOrd == nOrd then
               b3Raw:Add( b3Tmp)
               b3Part:Add( b3Tmp)
               nPartRawId = nRawId
            elseif nRawOrd == nOrd + 1 and nRawOrd == nScrapOrd then
               b3Raw:Add( b3Tmp)
               nScrapRawId = nRawId
            end
         end
         nRawId = nNextRawId
      end
      -- gestione eventuale scarto affettato successivo
      if EMT.VMILL and #EMT.VMILL > 0 then
         EMT.SCRAP = nScrapRawId
         EgtSetStatus( EMT.SCRAP or GDB_ID.NULL, GDB_ST.OFF)
      else
         EMT.SCRAP = nil
      end
      EMT.LENGTHBEAM = b3Bar:getDimX()
      EMT.LENGTHRAW = b3Raw:getDimX()
      EMT.LENGTHPART = b3Part:getDimX()
      -- recupero CutId del pezzo in lavorazione
      EMT.CUTID = EgtGetInfo( EgtGetFirstPartInRawPart( nPartRawId or GDB_ID.NULL) or GDB_ID.NULL, 'CUTID', 'i') or 0
      EMT.X1SPEC = nil
      -- se vero inizio, assegno solidi per verifica collisione
      if not EMT.SIM1ST then
         EMT.CODET = {}
         for i = 1, #( EMT.MCODET or {}) do
            EMT.CODET[i] = EMT.MCODET[i]
         end
         for i = 1, #( EMT.VMILL or {}) do
            table.insert( EMT.CODET, EMT.VMILL[i])
         end
      end
   -- se altrimenti fase intermedia, aggancio grezzi della fase alla tavola
   elseif IsMidPhase( EMT.PHASE) then
      -- se cambiata giacitura, lo segnalo
      local nPrevRot = GetPhaseRot( EMT.PHASE - 1)
      local nRot = GetPhaseRot( EMT.PHASE)
      if nRot ~= nPrevRot then
         if nRot ~= 0 then
            EgtOutText( 'Barra ruotata di ' .. tostring( -90 * nRot) .. '°')
         else
            EgtOutText( 'Barra non ruotata')
         end
      end
      -- eseguo aggancio
      local nRawId = EgtGetFirstRawPart()
      while nRawId do
         local nNextRawId = EgtGetNextRawPart( nRawId)
         if EgtVerifyRawPartPhase( nRawId, EMT.PHASE) then
            EmtLinkRawPartToGroup( nRawId, 'Tab')
         end
         nRawId = nNextRawId
      end
      -- se Vmill, nascondo eventuale scrap
      if EMT.VMILL then
         EgtSetStatus( EMT.SCRAP or GDB_ID.NULL, GDB_ST.OFF)
      end
   -- se altrimenti fasi intermedia o finale speciali, aggancio primo grezzo alla tavola e gli altri in posizione pre-carico
   elseif IsMid2Phase( EMT.PHASE) or IsEnd2Phase( EMT.PHASE) then
      -- se cambiata giacitura, lo segnalo
      local nPrevRot = GetPhaseRot( EMT.PHASE - 1)
      local nRot = GetPhaseRot( EMT.PHASE)
      if nRot ~= nPrevRot then
         if nRot ~= 0 then
            EgtOutText( 'Barra ruotata di ' .. tostring( -90 * nRot) .. '°')
         else
            EgtOutText( 'Barra non ruotata')
         end
      end
      -- indice primo grezzo della fase
      local nOrd = GetPhaseOrd( EMT.PHASE)
      -- ricerco vettore movimento per i successivi
      local vtMove = Vector3d()
      local nRawId = EgtGetFirstRawPart()
      while nRawId do
         if EgtGetInfo( nRawId, 'ORD', 'i') == nOrd + 1 then
            vtMove = Vector3d( - LoadT - EgtGetRawPartBBox( nRawId):getMax():getX(), 0, 0)
            break
         end   
         nRawId = EgtGetNextRawPart( nRawId)
      end
      -- eseguo
      nRawId = EgtGetFirstRawPart()
      while nRawId do
         local nNextRawId = EgtGetNextRawPart( nRawId)
         if EgtVerifyRawPartPhase( nRawId, EMT.PHASE) then
            if EgtGetInfo( nRawId, 'ORD', 'i') == nOrd then
               EmtLinkRawPartToGroup( nRawId, 'Tab')
               b3Bar = EgtGetRawPartBBox( nRawId)
            else
               EgtMove( nRawId, vtMove, GDB_RT.GLOB)
               EgtSetStatus( nRawId, GDB_ST.OFF)
            end
         end
         nRawId = nNextRawId
      end
      EMT.LENGTHBEAM = b3Bar:getDimX()
   -- altrimenti fase finale, aggancio primo grezzo alla tavola e gli altri in posizione carico al carro Y
   else
      -- indice primo grezzo della fase
      local nOrd = GetPhaseOrd( EMT.PHASE)
      -- ricerco vettore movimento per i successivi
      local vtMove = Vector3d()
      local nRawId = EgtGetFirstRawPart()
      while nRawId do
         if EgtGetInfo( nRawId, 'ORD', 'i') == nOrd + 1 then
            vtMove = Vector3d( - LoadT - EgtGetRawPartBBox( nRawId):getMax():getX(), 0, 0)
            break
         end   
         nRawId = EgtGetNextRawPart( nRawId)
      end
      -- eseguo
      local b3Bar = BBox3d()
      nRawId = EgtGetFirstRawPart()
      while nRawId do
         local nNextRawId = EgtGetNextRawPart( nRawId)
         if EgtVerifyRawPartPhase( nRawId, EMT.PHASE) then
            if EgtGetInfo( nRawId, 'ORD', 'i') == nOrd then
               EmtLinkRawPartToGroup( nRawId, 'Tab')
               b3Bar = EgtGetRawPartBBox( nRawId)
            else
               EgtMove( nRawId, vtMove, GDB_RT.GLOB)
               EmtLinkRawPartToGroup( nRawId, 'X1')
            end
         end
         nRawId = nNextRawId
      end
      EMT.LENGTHBEAM = b3Bar:getDimX()
   end
   -- Indicazione angolo rotazione pezzo
   EMT.ROT = EgtGetInfo( EMT.DISPID, 'ROT', 'i') or 0
   local SignId = EgtGetFirstNameInGroup( EgtGetBaseId( 'Base'), 'SIGN')
   EgtSetStatus( EgtGetFirstNameInGroup( SignId, '0'), EgtIf( EMT.ROT == 0, GDB_ST.ON, GDB_ST.OFF))
   EgtSetStatus( EgtGetFirstNameInGroup( SignId, '90'), EgtIf( EMT.ROT == -1, GDB_ST.ON, GDB_ST.OFF))
   EgtSetStatus( EgtGetFirstNameInGroup( SignId, '180'), EgtIf( EMT.ROT == -2, GDB_ST.ON, GDB_ST.OFF))
end

---------------------------------------------------------------------
function OnSimulDispositionEnd()
   if EMT.UNLOADING or EMT.FALL then
      ExecUnloading()
      EMT.UNLOADING = false
      EMT.FALL = false
   end
   -- se disposizione intermedia
   if IsMidPhase( EMT.PHASE) or IsEnd2Phase( EMT.PHASE) then
      -- se le rotazioni delle fasi corrente e precedente sono diverse
      if GetPhaseRot( EMT.PHASE) ~= GetPhaseRot( EMT.PHASE - 1) then
         -- imposto stato post-rotazione
         EMT.POSTROT = true
      end
   -- se altrimenti disposizione intermedia speciale con eventuale rotazione
   elseif IsMid2Phase( EMT.PHASE) then
      -- se le rotazioni delle fasi corrente e precedente sono diverse
      if GetPhaseRot( EMT.PHASE) ~= GetPhaseRot( EMT.PHASE - 1) then
         -- imposto stato post-rotazione
         EMT.POSTROT = true
      end
   end
   EMT.SPLIT = false
   EMT.OPEISDISP = false
end

---------------------------------------------------------------------
function OnSimulToolSelect( dPosCS)
   -- se utensile non definito, è disposizione ed esco
   if EMT.TOOL == '' then return end
   -- recupero dati utensile
   EMT.TOOLTYPE = EgtTdbGetCurrToolParam( MCH_TP.TYPE)
   EMT.TOTLEN = EgtTdbGetCurrToolParam( MCH_TP.TOTLEN)
   EMT.TOTDIAM = EgtTdbGetCurrToolParam( MCH_TP.TOTDIAM)
   -- se non è chiamato da altro script (non c'è parametro)
   if not dPosCS then
      -- se attivo Vmill
      SetToolForVmill( EMT.TOOL, EMT.HEAD, EMT.EXIT, EMT.VMILL)
      -- se attivo Collision Check
      EMT.SAFEDIST = COLL_SAFE_DIST
      if EMT.COLLOBJ then
         for i, Coll in ipairs( EMT.COLLOBJ) do
            EmtAddCollisionObjEx( i, Coll.Fr, Coll.Ty, Coll.Mv, Coll.P1, Coll.P2, Coll.P3)
         end
         AddToolToCollisionObj( EMT.TOOL, EMT.HEAD, EMT.EXIT, 1001)
         AddToolHolderToCollisionObj( EMT.TOOL, EMT.HEAD, EMT.EXIT, 1002)
      end
      -- dichiaro assi ausiliari da visualizzare
      EMT.AuxAxes = 2
      EMT.A1n = 'X1'
      EMT.A2n = 'X2'
   end
   -- se sega a catena, imposto subito angolo scelto per asse virtuale CS
   if EMT.HEAD == 'H3' then
      if not dPosCS then
         -- recupero la lavorazione successiva
         local NextMchId
         if EMT.MCHID then
            NextMchId = EgtGetNextActiveOperation( EMT.MCHID)
         else
            NextMchId = EgtGetFirstActiveOperation()
         end
         while NextMchId and EgtGetOperationType( NextMchId) == MCH_OY.DISP do
            NextMchId = EgtGetNextActiveOperation( NextMchId)
         end
         EgtSetCurrMachining( NextMchId)
         -- recupero il valore dell'asse virtuale bloccato CS
         local sVal = EgtGetMachiningParam( MCH_MP.BLOCKEDAXIS)
         dPosCS = tonumber( sVal:sub( 4))
      else
         -- imposto visualizzazione
         EgtSetMode( EgtGetHeadId( EMT.HEAD) or GDB_ID.NULL, GDB_MD.STD)
      end
      -- imposto il valore di CS
      EgtSetAxisPos( 'CS', dPosCS)
      EgtSetAxisPos( 'C', GetChainSawCHomeFromVirtualAxis( dPosCS))
      EgtSetAxisPos( 'A', 0)
   end
   -- breve pausa
   EgtPause( 100)
   EgtOutText( '')
   EMT.TOOL_1 = EMT.TOOL
   EMT.TCPOS_1 = EMT.TCPOS
   -- lo nascondo sul portautensili
   ShowToolInTcPos( EMT.TCPOS, false)
end

---------------------------------------------------------------------
function OnSimulToolDeselect()
   -- se prossimo utensile non definito, è disposizione ed esco
   if EMT.NEXTTOOL == '' then return end
   -- se sega a catena o punta lunga o utensile di grosso diametro, devo cambiare
   if EMT.HEAD == 'H3' or ( EMT.HEAD == 'H1' and ( EMT.TOTLEN > LONG_TOOL_MINLEN or EMT.TOTDIAM > BIG_TOOL_DIAM)) then
      EgtOutText( 'Tool change in progress...')
      -- movimento scarico sega a catena
      if EMT.HEAD == 'H3' then
         local dPosCS = EgtGetAxisPos( 'CS')
         if abs( dPosCS) > 0.1 then
            SimulMoveAxis( 'A', ParkA, MCH_SIM_STEP.COLLROT)
            SimulMoveAxis( 'C', GetChainSawCHomeFromVirtualAxis( dPosCS), MCH_SIM_STEP.COLLROT)
         else
            SimulMoveAxes( 'A', ParkA, MCH_SIM_STEP.COLLROT, 'C', GetChainSawCHomeFromVirtualAxis( dPosCS), MCH_SIM_STEP.COLLROT)
         end
         SimulMoveAxis( 'Y', ParkY, MCH_SIM_STEP.RAPID)
      end
      -- breve pausa
      EgtPause( 100)
      -- nascondo utensile su testa e lo visualizzo su TcPos
      EgtSetMode( EgtGetHeadId( EMT.HEAD), GDB_MD.HIDDEN)
      ShowToolInTcPos( EMT.TCPOS_1, true)
      -- movimento per carico utensile
      SimulMoveAxes( 'A', ParkA, MCH_SIM_STEP.RAPROT, 'C', ParkC, MCH_SIM_STEP.RAPROT)
      EgtOutText( '')
   -- deposito utensile
   else
      if EMT.NEXTTOOL ~= EMT.TOOL_1 then
         EgtOutText( 'Tool change in progress...')
         -- simulo movimento
         SimulMoveAxes( 'A', ParkA, MCH_SIM_STEP.COLLROT, 'C', ParkC, MCH_SIM_STEP.COLLROT)
         SimulMoveAxis( 'Y', ParkY, MCH_SIM_STEP.RAPID)
         -- breve pausa
         EgtPause( 100)
         ShowToolInTcPos( EMT.TCPOS_1, true)
          -- nascondo l'utensile corrente
          EgtSetStatus( EgtGetHeadId( EMT.HEAD), GDB_ST.OFF)
          -- eseguo movimento opportuno
          SimulMoveAxes( 'A', ParkA, MCH_SIM_STEP.RAPROT, 'C', ParkC, MCH_SIM_STEP.RAPROT)
      else
         EMT.TOOL_1 = nil
         EMT.TCPOS_1 = nil
      end
   end
end

---------------------------------------------------------------------
function OnSimulMachiningStart()
   -- se lavorazione attuale e precedente con sega a catena con angolo CS diverso, devo scaricare e ricaricare
   if EMT.HEAD == 'H3' and EMT.HEAD == EMT.PREVHEAD then
      local dPrevCS = EgtGetAxisPos( 'CS')
      local sVal = EgtGetMachiningParam( MCH_MP.BLOCKEDAXIS)
      local dPosCS = tonumber( sVal:sub( 4))
      if abs( dPosCS - dPrevCS) > 1 then
         OnSimulToolDeselect()
         EgtSetStatus( EgtGetHeadId( EMT.HEAD), GDB_ST.ON)
         OnSimulToolSelect( dPosCS)
      end
   end
   -- recupero alcuni dati della lavorazione
   EMT.MCHNAME = EgtGetOperationName( EMT.MCHID)
   EMT.MCHTYPE = EgtGetMachiningParam( MCH_MP.TYPE)
   local sNotes = EgtGetMachiningParam( MCH_MP.USERNOTES)
   EMT.VMRS = ( EMT.MCHTYPE ~= MCH_MY.DRILLING and not ( sNotes and sNotes:find( 'VMRS=0;', 1, true)))
   -- recupero TASKID della feature lavorata
   local vId = EgtGetMachiningGeometry()
   if vId and #vId > 0 and #vId[1] > 0 then
      EMT.TASKID = EgtGetInfo( vId[1][1], 'TASKID', 'i') or 0 ;
   else
      EMT.TASKID = 0
   end
   -- non ancora iniziata la lavorazione
   EMT.MCHFIRST = true
end

---------------------------------------------------------------------
function OnSimulMachiningEnd()
   if EMT.LOAD then
      EMT.LOAD = false
   elseif EMT.UNLOADING or EMT.FALL then
      ExecUnloading()
      EMT.UNLOADING = false
      EMT.FALL = false
   end
   EMT.SPLIT = nil
   EMT.PREVHEAD = EMT.HEAD
   EMT.PREVEXIT = EMT.EXIT
end

---------------------------------------------------------------------
--function OnSimulPathStart()
--end

---------------------------------------------------------------------
function OnSimulPathEnd()
   -- se attivo VMILL, lavorazione ed è richiesto di eliminare gli sfridi
   if EMT.VMILL and #EMT.VMILL > 0 and not EMT.OPEISDISP and EMT.VMRS then
      EgtOutLog( 'OnSimulPathEnd', 5)
      local vMillId = EMT.VMILL[1]
      local nPart = EgtVolZmapPartCount( vMillId)
      if nPart > 1 then
         -- ricerca del pezzo con massimo volume
         local nPartMax = 0
         local dVolMax = 0
         for i = 1, nPart do
            local dVol = EgtVolZmapPartVolume( vMillId, i - 1)
            if dVol > dVolMax then
               dVolMax = dVol
               nPartMax = i
            end
         end
         -- eliminazione di tutti i pezzi piccoli
         for i = nPart, 1, -1 do
            if i ~= nPartMax then
               local b3Vmill = EgtVolZmapGetPartBBoxGlob( vMillId, i - 1, GDB_BB.STANDARD)
               if b3Vmill:getDimX() < 1200 then
                  EgtRemoveVolZmapPart( vMillId, i - 1)
               end
            end
         end
         -- aggiorno visualizzazione
         EgtDraw()
      end
   end
end

---------------------------------------------------------------------
function OnSimulPathStartAux()
   --EgtOutLog( 'OnSimulPathStartAux - ' .. EgtNumToString( EMT.AUXIND, 0) .. ' - ' .. EMT.AUX)
   EgtOutLog( 'OnSimulPathStartAux', 5)
   -- eseguo il comando
   ExecAuxCmd( EMT.AUX)
end

---------------------------------------------------------------------
function OnSimulPathEndAux()
   --EgtOutLog( 'OnSimulPathEndAux - ' .. EgtNumToString( EMT.AUXIND, 0) .. ' - ' .. EMT.AUX)
   EgtOutLog( 'OnSimulPathEndAux', 5)
   -- eseguo il comando
   ExecAuxCmd( EMT.AUX)
end

---------------------------------------------------------------------
function OnSimulMoveStart()
   -- Recupero la posizione corrente dei carrelli
   local X1p = EgtGetAxisPos( 'X1')
   local X2p = EgtGetAxisPos( 'X2')
   -- Imposto movimento carrelli insieme con la tavola :
   -- entrambe le pinze
   if EMT.X1DELTA and EMT.X2DELTA then
      EMT.AuxAxes = 2
      EMT.A1n = 'X1'
      EMT.A1m = 'T'
      EMT.A1 = EMT.L1 + EMT.X1DELTA
      EMT.A2n = 'X2'
      EMT.A2m = 'T'
      EMT.A2 = EMT.L1 + EMT.X2DELTA
   -- solo pinza Y
   elseif EMT.X1DELTA then
      EMT.AuxAxes = 2
      EMT.A1n = 'X1'
      EMT.A1m = 'T'
      EMT.A1 = EMT.L1 + EMT.X1DELTA
      EMT.A2n = 'X2'
      EMT.A2m = nil
      EMT.A2 = ParkX2
   -- solo pinza V
   elseif EMT.X2DELTA then
      EMT.AuxAxes = 2
      EMT.A1n = 'X1'
      EMT.A1m = nil
      EMT.A1 = EgtIf( EMT.X1SPEC, X1p, ParkX1)
      EMT.A2n = 'X2'
      EMT.A2m = 'T'
      EMT.A2 = EMT.L1 + EMT.X2DELTA
   end
   -- Controllo scorrimento pinze chiuse X1 e X2
   if EMT.X1DELTA then
      local dX1DeltaP = X1p - EMT.L1p
      if abs( EMT.X1DELTA - dX1DeltaP) > 0.1 then
         EMT.ERR = 2
         local sErr = 'X1 slide : ' .. EmtLenToString( dX1DeltaP, 3) .. ' -> ' .. EmtLenToString( EMT.X1DELTA, 3)
         EmtSetLastError( 1202, sErr)
      end
   end
   if EMT.X2DELTA then
      local dX2DeltaP = X2p - EMT.L1p
      if abs( EMT.X2DELTA - dX2DeltaP) > 0.1 then
         EMT.ERR = 2
         local sErr = 'X2 slide : ' .. EgtNumToString( dX2DeltaP, 3) .. ' -> ' .. EgtNumToString( EMT.X2DELTA, 3)
         EmtSetLastError( 1202, sErr)
      end
   end
   -- Controllo corse assi X1 e X2
   VerifyX1Stroke( EMT.A1)
   VerifyX2Stroke( EMT.A2)
   -- se inizio lavorazione
   if EMT.MCHFIRST then
      if EMT.HEAD == 'H3' and abs( EgtGetAxisPos( 'CS')) > 0.1 and ( not EMT.R1p or abs( EMT.R1 - EMT.R1p) > 15) then
         SimulMoveAxis( 'A', ParkA, MCH_SIM_STEP.COLLROT)
         SimulMoveAxis( 'C', EMT.R1, MCH_SIM_STEP.COLLROT)
      end
      EgtOutText( '')
      EMT.MCHFIRST = false
      EMT.POSTROT = nil
   end
end

---------------------------------------------------------------------
--function OnSimulMoveEnd()
--end

---------------------------------------------------------------------
function OnSimulCollision()
   -- se prima collisione della lavorazione, la segnalo
   if EMT.MCHNAME ~= EMT.LAST_MCHNAME_COLLIDE then
      local Class = ''
      if EMT.SIMCOBIND == 1001 then
         Class = 'T_H1'
      elseif EMT.SIMCOBIND == 1002 then
         Class = 'TH_H1'
      else
         Class = EMT.COLLOBJ[EMT.SIMCOBIND].Cl
      end
      table.insert( EMT.COLLIDE, { Mc = EMT.MCHNAME, Cl = Class, Vm = EMT.SIMVMID})
      EMT.LAST_MCHNAME_COLLIDE = EMT.MCHNAME
      EMT.ERR = 1
      local sErr = 'CUTID='..tostring( EMT.CUTID)..'; TASKID='..tostring( EMT.TASKID)..'; Mach='..EMT.MCHNAME..'; Class='..Class..'; VMill='..EMT.SIMVMID
      EmtSetLastError( 1221, sErr, true)
      EgtOutLog( 'Collision : ' .. sErr, 1)
   end
end

---------------------------------------------------------------------
function ExecAuxCmd( sCmd)
   local Cmd = EgtSplitString( sCmd)
   if Cmd[1] == '0' then
      if Cmd[2] == 'Unloading' then
         EMT.UNLOADING = true
      elseif Cmd[2] == 'Split' then
         EMT.SPLIT = true
      elseif  Cmd[2] == 'Fall' then
         EMT.FALL = true
      end
      EgtOutText( EgtIf( Cmd[3], Cmd[3], Cmd[2]))
   elseif Cmd[1] == '1' then
      if not SimulMoveAxis( Cmd[2], tonumber( Cmd[3]), MCH_SIM_STEP.RAPID) then
         if VerifyX12Stroke( Cmd[2], tonumber( Cmd[3])) == nil then 
            EgtOutLog( 'Error on ExecAuxCmd : ' .. sCmd)
         end
      end
   elseif Cmd[1] == '2' then
      if Cmd[2] == 'X1' and EMT.POSTROT then
         local dPosY = tonumber( Cmd[3]) - EMT.HOVM
         Cmd[3] = EgtNumToString( dPosY, 3)
         EMT.HOVM = 0
      end
      -- Verifico movimento carrello con trave agganciata
      VerifyOneChariotSlide( Cmd[2], Cmd[3], Cmd[4], Cmd[5])
      -- Eseguo il movimento
      local _, bOk, bOk2 = SimulMoveAxes( Cmd[2], tonumber( Cmd[3]), MCH_SIM_STEP.RAPID,
                                          Cmd[4], tonumber( Cmd[5]), MCH_SIM_STEP.RAPID)
      if not ( bOk and bOk2) then
         local nI = EgtIf( not bOk, 2, 4)
         if VerifyX12Stroke( Cmd[nI], tonumber( Cmd[nI+1])) == nil then 
            EgtOutLog( 'Error on ExecAuxCmd : ' .. sCmd)
         end
      end
   elseif Cmd[1] == '3' then
      -- Verifico movimento carrelli con trave agganciata
      VerifyTwoChariotsSlide( Cmd[2], Cmd[3], Cmd[4], Cmd[5], Cmd[6], Cmd[7])
      -- Eseguo il movimento
      local _, bOk, bOk2, bOk3 = SimulMoveAxes( Cmd[2], tonumber( Cmd[3]), MCH_SIM_STEP.RAPID,
                                                Cmd[4], tonumber( Cmd[5]), MCH_SIM_STEP.RAPID,
                                                Cmd[6], tonumber( Cmd[7]), MCH_SIM_STEP.RAPID)
      if not ( bOk and bOk2 and bOk3) then
         local nI = EgtIf( not bOk, 2, EgtIf( not bOk2, 4, 6))
         if VerifyX12Stroke( Cmd[nI], tonumber( Cmd[nI+1])) == nil then 
            EgtOutLog( 'Error on ExecAuxCmd : ' .. sCmd)
         end
      end
   elseif Cmd[1] == '11' then
      ExecMovePX1( Cmd)
   elseif Cmd[1] == '12' then
      ExecMovePX2( Cmd)
   elseif Cmd[1] == '21' then
      local nX1Delta = tonumber( Cmd[2])
      local nX2Delta = tonumber( Cmd[3])
      if nX1Delta > 0.01 and nX2Delta > 0.01 then
         EMT.X1DELTA = nX1Delta
         EMT.X2DELTA = nX2Delta
      elseif nX1Delta > 0.01 then
         EMT.X1DELTA = nX1Delta
         EMT.X2DELTA = nil
      elseif nX2Delta > 0.01 then
         EMT.X1DELTA = nil
         EMT.X2DELTA = nX2Delta
      end
   elseif Cmd[1] == '31' then
      local nRawId = tonumber( Cmd[2])
      EmtUnlinkRawPartFromGroup( nRawId)
      EmtLinkRawPartToGroup( nRawId, Cmd[3])
      EMT.X1SPEC = true
   end
end

---------------------------------------------------------------------
function ExecUnloading()
   if EMT.VMILL and #EMT.VMILL > 0 then
      local vMillId = EMT.VMILL[1]
      -- gruppo dei Vmill
      local nVmGrpId = EgtGetFirstNameInGroup( GDB_ID.ROOT, 'VMill')
      -- li sposto per lasciare spazio al nuovo pezzo
      local nId = EgtGetFirstInGroup( nVmGrpId)
      while nId do
         EgtMove( nId, Vector3d( 0, -( EMT.WIDTHBEAM + 50.0), 0), GDB_RT.GLOB)
         nId = EgtGetNext( nId)
      end
      -- creo un nuovo layer e vi inserisco il nuovo pezzo
      local nLayId = EgtGroup( nVmGrpId, EgtGetGlobFrame( vMillId))
      EgtRelocate( vMillId, nLayId)
      local vtMove = Vector3d( 0, -800, 0)
      if EMT.FALL then vtMove = Vector3d( -300, -800, -600) end
      EgtMove( nLayId, vtMove, GDB_RT.GLOB)
      EgtSetLevel( vMillId, GDB_LV.USER)
      -- aggiungo gli spigoli
      local nFirstId, nCount = EgtVolZmapGetEdges( vMillId, nLayId)
      if nFirstId then
         for nId = nFirstId, nFirstId + nCount - 1 do
            EgtSetColor( nId, Color3d( 96, 96, 96))
         end
      end
      -- rilascio Vmill
      table.remove( EMT.VMILL, 1)
      -- aggiorno la visualizzazione
      EgtDraw()
      -- se finito
      if EMT.PHASE == EgtGetPhaseCount() then
         -- se impostato di salvare i Vmill, lo faccio
         local sMachIni = EgtGetCurrMachineDir() .. '\\' .. EgtGetCurrMachineName() .. '.ini'
         if EgtGetStringFromIni( 'VMill', 'Save', '', sMachIni) == '1' then
            local sFile = EgtGetCurrFilePath()
            if sFile then
               local sDir, sName, sExt = EgtSplitPath( sFile)
               if sExt and sExt:lower() == ".nge" then
                  sName = sName .. '_VM_' .. EgtGetMachGroupName( EgtGetCurrMachGroup())
                  EgtSetLevel( nVmGrpId, GDB_LV.USER)
                  EgtSaveObjToFile( nVmGrpId, sDir .. sName .. '.Nge')
                  EgtSetLevel( nVmGrpId, GDB_LV.TEMP)
               end
            end
         end
      end
   end
end

---------------------------------------------------------------------
function CheckClamping( sClampName)

   local nClampId = EgtGetAxisId( sClampName) or GDB_ID.NULL
   local nClampPathId = EgtGetFirstInGroup( EgtGetFirstNameInGroup( nClampId, 'CLAMP_CHECK') or GDB_ID.NULL)
   local b3ClampingArea = EgtGetBBoxGlob( nClampPathId or GDB_ID.NULL, GDB_BB.STANDARD)
   -- se non trovo percorso area di clamping, esco subito
   if not nClampPathId or not EMT.VMILL or not ClampingCoeffMin then
      return
   end

   local function GetCurveListFromIntersection( sPosIntersPlane, sIntersPlane, dDepth)
      local vCurveListId = {}
      local vtIntersPlane
      -- piano di interpolazione 
      if sIntersPlane == 'X' then
         vtIntersPlane = X_AX()
      elseif sIntersPlane == 'Y' then
         vtIntersPlane = Y_AX()
      elseif sIntersPlane == 'Z' then
         vtIntersPlane = Z_AX()
      end

      for i = 1, #EMT.VMILL do
         local b3VMill = EgtGetBBoxGlob( EMT.VMILL[i], GDB_BB.EXACT)
         local ptPosIntersPlane
         if sPosIntersPlane == 'MIN' then
            ptPosIntersPlane = b3VMill:getMin() + dDepth * vtIntersPlane
         elseif sPosIntersPlane == 'MAX' then
            ptPosIntersPlane = b3VMill:getMax() - dDepth * vtIntersPlane
         end
         local nLoopId, nLoopCnt = EgtPlaneVolZmapInters( ptPosIntersPlane, vtIntersPlane, EMT.VMILL[i], CLAMP_CHECK_INTERS, GDB_RT.GLOB)
         vCurveListId = EgtTableAdd( vCurveListId, nLoopId, nLoopCnt)
      end

      return vCurveListId
   end

   local function CalculateIntersectionArea( sPosIntersPlane, sIntersPlane, dDepth)
      -- test piano frontale
      local vCurveListId = GetCurveListFromIntersection( sPosIntersPlane, sIntersPlane, dDepth)
      -- si copia curva intersezione e curva pinza in gruppo di confronto
      local nFlatSurfId, nFlatSurfCnt = EgtSurfFlatRegion( CLAMP_CHECK_GROUP, vCurveListId)
      local vFlatSurfId = EgtTableFill( nFlatSurfId, nFlatSurfCnt) or {}
      local nClampSurfId = EgtSurfFlatRegion( CLAMP_CHECK_GROUP, nClampPathId)

      local dTotalArea = 0
      local dTotalXLenght = 0
      for i = 1, #vFlatSurfId do
         local nTempSurfId = vFlatSurfId[i]
         EgtSurfFrIntersect( nTempSurfId, nClampSurfId)
         if nTempSurfId then
            dTotalArea = dTotalArea + ceil( EgtSurfArea( nTempSurfId) or 0)
            local b3BoxIntersectionBox = EgtGetBBoxGlob( nTempSurfId, GDB_BB.STANDARD)
            if b3BoxIntersectionBox then
               dTotalXLenght = dTotalXLenght + ceil( b3BoxIntersectionBox:getDimX()) -- somma lunghezze (x) delle aree pinzate
            end
         end
      end
      return dTotalArea, dTotalXLenght
   end

   -- minima area considerata per un corretto pinzaggio
   DistZClampToTable = DistZClampToTable or 0
   --local MinJoin = BD.GetMinJoin( EMT.WIDTHPART, EMT.HEIGHTPART, EgtIf( EMT.SPLIT, EMT.LENGTHPART , EMT.LENGTHBEAM))
   UpdateMinJoinDeltaTol( EMT.WIDTHPART, EMT.HEIGHTPART, EgtIf( EMT.SPLIT, EMT.LENGTHPART , EMT.LENGTHBEAM))
   local MinZClamping =  min( b3ClampingArea:getDimZ() + DistZClampToTable, EMT.HEIGHTPART) - DistZClampToTable
   local dMinClamping = ( MinJoin * MinZClamping)
   -- si moltiplica per un coefficiente minimo sotto al quale si da l'errore di pinzaggio
   ClampingCoeffMin = EgtClamp( ClampingCoeffMin, 0.01, 1)
   if MinZClamping > ClampSmallH then
      -- somma area parallelepipedo sotto + area triangolo superiore
      dMinClamping = MinJoin * ClampSmallH + ( ( MinJoin * ( MinJoin * tan( ClampFrontAngle))) / 2)
      local dTriangleBase = ClampL - ClampSmallL
      if MinJoin > dTriangleBase then
         local dTriangleHeight = ClampH - ClampSmallH
         -- somma area parallelepipedo sotto + area triangolo superiore + parallelepipedo grande
         dMinClamping = ( MinJoin * ClampSmallH) + ( dTriangleBase * dTriangleHeight / 2) + ( ( MinJoin - dTriangleBase) * dTriangleHeight)
      end
   end

   local dMinClampingAreaWarn = dMinClamping * ClampingCoeffMin
   local dMinClampingAreaErr = dMinClamping * ( ClampingCoeffMin / 3)

   local bError = true
   local sWrn, sErr
   local bWriteWarnMessage = false
   local bWriteErrMessage = false
   -- controllo faccia frontale
   local dArea, dXClampedLenght = CalculateIntersectionArea( 'MIN', 'Y', 3)
   -- ERRORE: pinza troppo poco (meno di 1/3 del minimo richiesto)
   if dArea and dArea < dMinClampingAreaErr then
      -- solo se pinza almeno 1cm2, e la somma della lunghezza pinzata è maggiore della lunghezza minima pinzabile da Warning e non errore
      if dXClampedLenght >= MinJoin * ClampingCoeffMin and dArea > 1000 then
         bError = false
      end
      -- pinzaggio non fattibile, errore
      if bError then
         sErr = 'ERROR CLAMPING ' .. sClampName .. ' : '..tostring( ceil( ( dArea / dMinClampingAreaWarn) * 100))..'% ( '.. tostring( dArea/1000)..'cm2)'
         bWriteErrMessage = true
      end
   end
   -- WARNING: pinza meno del minimo richiesto
   if dArea and dArea < dMinClampingAreaWarn and not bWriteErrMessage then
      sWrn = 'WARNING CLAMPING ' .. sClampName .. ' : '..tostring( ceil( ( dArea / dMinClampingAreaWarn) * 100))..'% ( '.. tostring( dArea/1000)..'cm2)'
      bWriteWarnMessage = true
   end
   EgtEmptyGroup( CLAMP_CHECK_GROUP)
   EgtEmptyGroup( CLAMP_CHECK_INTERS)

   -- controllo altro lato solo se non sono già in errore
   if not bWriteErrMessage then
      -- controllo faccia posteriore
      dArea, dXClampedLenght = CalculateIntersectionArea( 'MAX', 'Y', 3)
      -- ERRORE: pinza troppo poco (meno di 1/3 del minimo richiesto)
      if dArea and dArea < dMinClampingAreaErr then
         -- solo se pinza almeno 1cm2, e la somma della lunghezza pinzata è maggiore della lunghezza minima pinzabile da Warning e non errore
         if dXClampedLenght >= MinJoin * ClampingCoeffMin and dArea > 1000 then 
            bError = false
         end
         -- pinzaggio non fattibile, errore
         if bError then
            sErr = 'ERROR CLAMPING ' .. sClampName .. ' : '..tostring( ceil( ( dArea / dMinClampingAreaWarn) * 100))..'% ( '.. tostring( dArea/1000)..'cm2)'
            bWriteErrMessage = true
         end
      end
      -- WARNING: pinza meno del minimo richiesto
      if dArea and dArea < dMinClampingAreaWarn and not bWriteWarnMessage and not bWriteErrMessage then
         sWrn = 'WARNING CLAMPING ' .. sClampName .. ' : '..tostring( ceil( ( dArea / dMinClampingAreaWarn) * 100))..'% ( '.. tostring( dArea/1000)..'cm2)'
         bWriteWarnMessage = true
      end
      EgtEmptyGroup( CLAMP_CHECK_GROUP)
      EgtEmptyGroup( CLAMP_CHECK_INTERS)
   end

   if bWriteErrMessage then
      EmtSetLastError( 1213, sErr, EgtGetEnableUI())
      EgtOutBox( sErr, 'CLAMPING', 'ERROR', 'OK')
      EmtSetSimulPause()
   elseif bWriteWarnMessage then
      EgtOutLog( sWrn)
      EgtOutBox( sWrn, 'CLAMPING', 'WARNING', 'OK')
      EmtSetSimulPause()
   end
end

---------------------------------------------------------------------
function ExecMovePX1( Cmd)
   --SimulMoveAxes( 'PX1', EgtIf( not bClose, MaxHoOpen, EMT.HEIGHTBEAM), MCH_SIM_STEP.RAPID)
   local dPX1 = MaxHOpen
   if Cmd[2] ~= '0' then
      dPX1 = EgtIf( EMT.ROT == -1, EMT.HEIGHTBEAM, EMT.WIDTHBEAM)
   end
   SimulMoveAxis( 'PX1', dPX1, MCH_SIM_STEP.RAPID)
   local dQX1 = MaxVOpen
   if Cmd[2] == '2' then
      dQX1 = EgtIf( EMT.ROT == -1, EMT.WIDTHBEAM, EMT.HEIGHTBEAM)
   end
   SimulMoveAxis( 'QX1', dQX1, MCH_SIM_STEP.RAPID)
   SetPX1Light( Cmd[2] ~= '0')
   if Cmd[2] ~= '0' then
      CheckClamping( 'PX1')
   end
end

---------------------------------------------------------------------
function ExecMovePX2( Cmd)
   --EgtIf( not bClose, MaxHoOpen, EMT.HEIGHTBEAM), MCH_SIM_STEP.RAPID)
   local dPX2 = MaxHOpen
   if Cmd[2] ~= '0' then
      dPX2 = EgtIf( EMT.ROT == -1, EMT.HEIGHTBEAM, EMT.WIDTHBEAM)
   end
   SimulMoveAxis( 'PX2', dPX2, MCH_SIM_STEP.RAPID)
   local dQX2 = MaxVOpen
   if Cmd[2] == '2' then
      dQX2 = EgtIf( EMT.ROT == -1, EMT.WIDTHBEAM, EMT.HEIGHTBEAM)
   end
   SimulMoveAxis( 'QX2', dQX2, MCH_SIM_STEP.RAPID)
   SetPX2Light( Cmd[2] ~= '0')
   if Cmd[2] ~= '0' then
      CheckClamping( 'PX2')
   end
end

---------------------------------------------------------------------
function VerifyX1Slide( sName1, dVal1, sName2, dVal2)
   -- Se movimento trave agganciata con carrello X1
   if sName1 == 'T' and sName2 == 'X1' and GetPX1Light() then
      local dYDeltaP = EgtGetAxisPos( 'T') - EgtGetAxisPos( 'X1')
      local dYDeltaA = tonumber( dVal1) - tonumber( dVal2)
      EgtOutLog( string.format( 'X1DeltaP=%.3f X1DeltaA=%.3f', dYDeltaP, dYDeltaA), 5)
      if abs( dYDeltaA - dYDeltaP) > 0.5 then
         EMT.ERR = 2
         local sErr = 'X1 slide : ' .. EmtLenToString( dYDeltaP, 3) .. ' -> ' .. EmtLenToString( dYDeltaA, 3)
         EmtSetLastError( 1202, sErr)
      end
   end
   -- Tutto bene
   return true
end

---------------------------------------------------------------------
function VerifyX2Slide( sName1, dVal1, sName2, dVal2)
   -- Se movimento trave agganciata con carrello X1
   if sName1 == 'T' and sName2 == 'X2' and GetPX2Light() then
      local dVDeltaP = EgtGetAxisPos( 'T') - EgtGetAxisPos( 'X2')
      local dVDeltaA = tonumber( dVal1) - tonumber( dVal2)
      EgtOutLog( string.format( 'X2DeltaP=%.3f X2DeltaA=%.3f', dVDeltaP, dVDeltaA), 5)
      if abs( dVDeltaA - dVDeltaP) > 0.5 then
         EMT.ERR = 2
         local sErr = 'X2 slide : ' .. EmtLenToString( dVDeltaP, 3) .. ' -> ' .. EmtLenToString( dVDeltaA, 3)
         EmtSetLastError( 1202, sErr)
      end
   end
   return true
end

---------------------------------------------------------------------
function VerifyOneChariotSlide( sName1, dVal1, sName2, dVal2)
   -- Metto in prima posizione la trave
   if sName2 == 'T' then
      sName1, sName2 = sName2, sName1
      dVal1, dVal2 = dVal2, dVal1
   end
   -- Eseguo verifica
   if sName2 == 'X1' then
      return VerifyX1Slide( sName1, dVal1, sName2, dVal2)
   elseif sName2 == 'X2' then
      return VerifyX2Slide( sName1, dVal1, sName2, dVal2)
   end
   return true
end

---------------------------------------------------------------------
function VerifyTwoChariotsSlide( sName1, dVal1, sName2, dVal2, sName3, dVal3)
   -- Metto in prima posizione la trave
   if sName2 == 'T' then
      sName1, sName2 = sName2, sName1
      dVal1, dVal2 = dVal2, dVal1
   elseif sName3 == 'T' then
      sName1, sName3 = sName3, sName1
      dVal1, dVal3 = dVal3, dVal1
   end
   -- Eseguo verifica
   if sName2 == 'X1' then
      return VerifyX1Slide( sName1, dVal1, sName2, dVal2) and VerifyX2Slide( sName1, dVal1, sName3, dVal3)
   elseif sName2 == 'X2' then
      return VerifyX2Slide( sName1, dVal1, sName2, dVal2) and VerifyX1Slide( sName1, dVal1, sName3, dVal3)
   end
   return true
end

---------------------------------------------------------------------
function VerifyX1Stroke( dX1)
   if dX1 < MinX1 then
      EmtSetOutstrokeInfo( 'X1', 'X1', true, dX1 - MinX1, ' (L1-)')
      EMT.ERR = 1
      local sErr = 'X1 axis outstroke ' .. EgtNumToString( dX1 - MinX1, 3)
      EgtOutLog( sErr)
      return false
   elseif dX1 > MaxX1 then
      EmtSetOutstrokeInfo( 'X1', 'X1', true, dX1 - MaxX1, ' (L1+)')
      EMT.ERR = 1
      local sErr = 'X1 axis outstroke ' .. EgtNumToString( dX1 - MaxX1, 3)
      EgtOutLog( sErr)
      return false
   end
   return true
end

---------------------------------------------------------------------
function VerifyX2Stroke( dX2)
   if dX2 > MaxX2 then
      EmtSetOutstrokeInfo( 'X2', 'X2', true, dX2 - MaxX2, ' (L1+)')
      EMT.ERR = 1
      local sErr = 'X2 axis outstroke ' .. EgtNumToString( dX2 - MaxX2, 3)
      EgtOutLog( sErr)
      return false
   elseif dX2 < MinX2 then
      EmtSetOutstrokeInfo( 'X2', 'X2', true, dX2 - MinX2, ' (L1-)')
      EMT.ERR = 1
      local sErr = 'X2 axis outstroke ' .. EgtNumToString( dX2 - MinX2, 3)
      EgtOutLog( sErr)
      return false
   end
   return true
end

---------------------------------------------------------------------
function VerifyX12Stroke( sName, dVal)
   if sName == 'X1' then
      return VerifyX1Stroke( dVal)
   elseif sName == 'X2' then
      return VerifyX2Stroke( dVal)
   else
      return nil
   end
end

---------------------------------------------------------------------
function ShowToolInTcPos( sTcPos, bShow)
   -- recupero il gruppo dell'utensile
   local TcExitId = EgtGetFirstNameInGroup( EgtGetTcPosId( sTcPos or '') or GDB_ID.NULL, 'T1')
   if not TcExitId then return end
   -- imposto lo stato di visualizzazione
   EgtSetStatus( TcExitId, EgtIf( bShow, GDB_ST.ON, GDB_ST.OFF))
end

---------------------------------------------------------------------
function GetToolTcPos( sTool)
   -- salvo stato iniziale
   local CurrTool = EgtTdbGetCurrToolParam( MCH_TP.NAME)
   -- recupero la posizione di cambio utensile dell'utensile indicato
   local sTcPos
   if EgtTdbSetCurrTool( sTool) then
      sTcPos = EgtTdbGetCurrToolParam( MCH_TP.TCPOS)
   end
   -- ripristino stato iniziale
   if CurrTool then
      EgtTdbSetCurrTool( CurrTool)
   else
      EgtTdbSetCurrTool( '')
   end
   -- restituisco risultato
   return sTcPos
end

---------------------------------------------------------------------
function LoadFirstTool()
   -- Carico primo utensile sulla testa 1
   local sTool, nTlen = FindFirstToolOnHead( 'H1')
   if sTool then EMT.TOOL_1 = sTool else EMT.TOOL_1 = GetDefaultToolName() end
   EMT.TCPOS_1 = GetToolTcPos( EMT.TOOL_1)
   local OrigEMC = EMC
   EMC = { HEAD = 'H1', TOOL = EMT.TOOL_1, TCPOS = EMT.TCPOS_1}
   OnSetHead()
   EMC = OrigEMC
   EgtResetAxisPos( 'C') ; EgtResetAxisPos( 'A')
   EgtLoadTool( 'H1', 1, EMT.TOOL_1)
   ShowToolInTcPos( EMT.TCPOS_1, false)
   EMT.LOAD = true
end

---------------------------------------------------------------------
--   *** ESTIMATION T&L ***
---------------------------------------------------------------------
local RAPID_X_FEED =  FmaxX1    -- mm/min                   ex: 45000
local RAPID_Y_FEED =  FmaxY    -- mm/min                    ex: 45000
local RAPID_Z_FEED =  FmaxZ    -- mm/min                    ex: 45000
local RAPID_C_FEED =  FmaxC    -- deg/min                   ex: 18000
local RAPID_B_FEED =  FmaxA    -- deg/min                   ex: 10440
local RAPID_MIN_T = 1        -- s                         ex: 0.2
local TOOL_CHANGE = 20         -- s                         ex: 14
local LOAD_T = 10              -- s                         ex: 16
local CHAR_ONE_MOVE_T = 1.7      -- s                       ex: 2
local ROTATION_T = 40          -- s                         ex: 40
local SPLIT_T = 3              -- s                         ex: 9
local UNLOAD_T = 10            -- s                         ex: 12
local FALL_T = 2               -- s                         ex: 4

---------------------------------------------------------------------
function OnEstimStart()
   EMT.INCHES = not EgtUiUnitsAreMM()      -- unità di misura mm/inches
end

---------------------------------------------------------------------
function OnEstimEnd()
end

---------------------------------------------------------------------
function OnEstimProgramStart()
   -- imposto inizio movimenti da Home
   EMT.L1 = EgtGetAxisHomePos( 'T')
   EMT.L2 = EgtGetAxisHomePos( 'Y')
   EMT.L3 = EgtGetAxisHomePos( 'Z')
   EMT.R1 = EgtGetAxisHomePos( 'C')
   EMT.R2 = EgtGetAxisHomePos( 'A')
   -- aggiorno valori come precedenti
   EmtUpdatePrev()
   -- totalizzatori tempi e lunghezze
   EMT.TOTCUTLEN = 0
   EMT.TOTCUTTIME = 0
   EMT.TOTEXTLEN = 0
   EMT.TOTEXTTIME = 0
   -- variabile per lunghezza taglio utensili
   EMT.TOOLCUTLEN = {}
   -- intestazioni
   EmtTleStart( EMT.INFO)
end

---------------------------------------------------------------------
function OnEstimProgramEnd()
   -- stampa dei totali delle lavorazioni
   EmtTleAddTotal( EmtSecToHMS( EMT.TOTCUTTIME + EMT.TOTEXTTIME), EmtLenToMF( EMT.TOTCUTLEN))
   -- stampa dei totali degli utensili
   for i = 1, #EMT.TOOLCUTLEN do
      local TCL = EMT.TOOLCUTLEN[i]
      EmtTleAddTool( TCL.Name, EmtLenToMF( TCL.Len))
   end
   -- completo il file
   local _, _, sExt = EgtSplitPath( EMT.FILE)
   EmtTleEnd( sExt:sub( 2))
   -- salvo i dati principali nel progetto
   EgtSetInfo( EgtGetCurrMachGroup(), 'Ttot', EgtNumToString( EMT.TOTCUTTIME + EMT.TOTEXTTIME, 0))
   EgtSetInfo( EgtGetCurrMachGroup(), 'Ltot', EgtNumToString( EMT.TOTCUTLEN, 0))
end

---------------------------------------------------------------------
function OnEstimDispositionStart()
   -- inizio disposizione
   EMT.OPEISDISP = true
   -- sulla prima fase dichiaro carico barra
   if EMT.PHASE == 1 then
      EMT.LOAD = true
   else
      EMT.LOAD = false
   end
end

---------------------------------------------------------------------
function OnEstimDispositionEnd()
   -- Se disposizione inizio o rimanenza
   if IsStartOrRestPhase( EMT.PHASE) then
      ;
   -- se altrimenti disposizione intermedia o finale dopo separazione e rotazione, eventuale rotazione
   elseif IsMidPhase( EMT.PHASE) or IsMid2Phase( EMT.PHASE) or IsEnd2Phase( EMT.PHASE) then
      -- recupero le rotazioni delle fasi corrente e precedente
      local nRot = GetPhaseRot( EMT.PHASE)
      local nPrevRot = GetPhaseRot( EMT.PHASE - 1)
      -- verifico se sono diverse
      if nRot ~= nPrevRot then
         -- imposto stato post-rotazione
         EMT.POSTROT = true
      end
   -- altrimenti disposizione finale, eventuale scarico pezzo lavorato se non ci sono lavorazioni
   else
      ;
   end
   -- emetto dati in sospeso
   if EMT.TLE_NAME then
      EmtTleAddMachining( EMT.TLE_NAME, EmtSecToHMS( EMT.TLE_TIME), '  -  ', '  -  ')
      EMT.TLE_NAME = nil
      EMT.TLE_TIME = nil
   end
   -- termine disposizione
   EMT.OPEISDISP = false
end

---------------------------------------------------------------------
function OnEstimToolSelect()
   -- flag di utensile cambiato
   EMT.NEWTOOL = true
   -- reset indice utensile in tabella lunghezze
   EMT.TCLIND = 0
   -- verifico che l'utensile sia definito
   if #EMT.TOOL == 0 then return end
   -- cerco l'utensile nella tabella
   for i = 1, #EMT.TOOLCUTLEN do
      if EMT.TOOLCUTLEN[i].Name == EMT.TOOL then
         EMT.TCLIND = i
         break
      end
   end
   -- se non trovato, lo aggiungo
   if EMT.TCLIND == 0 then
      table.insert( EMT.TOOLCUTLEN, { Name = EMT.TOOL, Len = 0})
      EMT.TCLIND = #EMT.TOOLCUTLEN
   end
end

---------------------------------------------------------------------
function OnEstimToolDeselect()
end

---------------------------------------------------------------------
function OnEstimMachiningStart()
   EMT.MCHNAME = EgtGetOperationName( EMT.MCHID)
   EMT.MCHTYPE = EgtGetMachiningParam( MCH_MP.TYPE)
   EgtOutLog( 'Mach : ' .. EMT.MCHNAME, 5)
   -- reset contatori di lavorazione
   EMT.MCHCUTLEN = 0
   EMT.MCHCUTTIME = 0
   EMT.MCHEXTLEN = 0
   EMT.MCHEXTTIME = 0
   if EMT.NEWTOOL then
      EMT.NEWTOOL = nil
      EMT.MCHEXTTIME = EMT.MCHEXTTIME + TOOL_CHANGE
   end
end

---------------------------------------------------------------------
function OnEstimMachiningEnd()
   -- nel caso di foratura devo dimezzare la lunghezza di taglio perchè comprende anche l'uscita
   if EMT.MCHTYPE == MCH_MY.DRILLING then
      EMT.MCHCUTLEN = EMT.MCHCUTLEN / 2
   end
   local sName = EgtGetName( EMT.MCHID)
   EmtTleAddMachining( sName, EmtSecToHMS( EMT.MCHCUTTIME + EMT.MCHEXTTIME), EmtLenToMF( EMT.MCHCUTLEN), EMT.TOOL)
   -- aggiorno totali e utensili
   EMT.TOTCUTLEN = EMT.TOTCUTLEN + EMT.MCHCUTLEN
   EMT.TOTCUTTIME = EMT.TOTCUTTIME + EMT.MCHCUTTIME
   EMT.TOTEXTLEN = EMT.TOTEXTLEN + EMT.MCHEXTLEN
   EMT.TOTEXTTIME = EMT.TOTEXTTIME + EMT.MCHEXTTIME
   EMT.TOOLCUTLEN[EMT.TCLIND].Len = EMT.TOOLCUTLEN[EMT.TCLIND].Len + EMT.MCHCUTLEN
   -- emetto dati in sospeso
   if EMT.TLE_NAME then
      EmtTleAddMachining( EMT.TLE_NAME, EmtSecToHMS( EMT.TLE_TIME), '  -  ', '  -  ')
      EMT.TLE_NAME = nil
      EMT.TLE_TIME = nil
   end
end

---------------------------------------------------------------------
function OnEstimPathStart()
   EMT.AUXTYPE = nil
   EMT.MCHMOVEFIRST = true
   EMT.CHARMOVE = nil
end

---------------------------------------------------------------------
function OnEstimPathEnd()
   EMT.AUXTYPE = nil
end

---------------------------------------------------------------------
function OnEstimPathStartAux()
   -- se richiesto, preparo il carico barra
   if EMT.LOAD then
      if EMT.AUXIND == EMT.AUXTOT then
         local dTime = LOAD_T
         EMT.TOTEXTTIME = EMT.TOTEXTTIME + dTime
         EmtTleAddMachining( 'Loading', EmtSecToHMS( dTime), '  -  ', '  -  ')
         EMT.LOAD = false
      end
   -- se altrimenti carico dopo rotazione
   elseif EMT.POSTROT then
      if EMT.AUXIND == EMT.AUXTOT then
         local dTime = ROTATION_T
         EMT.TOTEXTTIME = EMT.TOTEXTTIME + dTime
         EmtTleAddMachining( 'Rotation', EmtSecToHMS( dTime), '  -  ', '  -  ')
         EMT.POSTROT = false
      end
   -- altrimenti, spostamento carrelli
   else
      local Cmd = EgtSplitString( EMT.AUX)
      if ( Cmd[1] == '1' and Cmd[2] ~= 'Z') or Cmd[1] == '2' or Cmd[1] == '3' then
         EMT.CHARMOVE = true
      end
      if EMT.AUXIND == EMT.AUXTOT and EMT.CHARMOVE then
         local dTime = ( EMT.AUXTOT - 2) * CHAR_ONE_MOVE_T
         EMT.TOTEXTTIME = EMT.TOTEXTTIME + dTime
         EmtTleAddMachining( 'Charriots move', EmtSecToHMS( dTime), '  -  ', '  -  ')
      end
   end
end

---------------------------------------------------------------------
function OnEstimPathEndAux()
   -- verifico tipo di emissione
   if EMT.OPEISDISP then
      if not EMT.AUXTYPE then
         local Cmd = EgtSplitString( EMT.AUX)
         if Cmd[1] == '0'  and  Cmd[2] == 'Unloading' then
            EMT.AUXTYPE = 'U'
         else
            EMT.AUXTYPE = 'R'
         end
      end
   else
      if not EMT.AUXTYPE then
         local Cmd = EgtSplitString( EMT.AUX)
         if Cmd[1] == '0'  and  Cmd[2] == 'Split' then
            EMT.AUXTYPE = 'S'
         elseif Cmd[1] == '0'  and  Cmd[2] == 'Unloading' then
            EMT.AUXTYPE = 'U'
         elseif Cmd[1] == '0'  and  Cmd[2] == 'Fall' then
            EMT.AUXTYPE = 'F'
         else
            EMT.AUXTYPE = 'P'
         end
      end
   end
   -- per lo scarico della rimanenza
   if EMT.AUXTYPE == 'R' then
      if EMT.AUXIND == EMT.AUXTOT then
         local dTime = LOAD_T + UNLOAD_T
         EMT.TOTEXTTIME = EMT.TOTEXTTIME + dTime
         EMT.TLE_NAME = 'Remnant unloading'
         EMT.TLE_TIME = dTime
      end
   -- per lo split
   elseif EMT.AUXTYPE == 'S' then
      if EMT.AUXIND == EMT.AUXTOT then
         local dTime = SPLIT_T
         EMT.TOTEXTTIME = EMT.TOTEXTTIME + dTime
         EMT.TLE_NAME = 'Splitting'
         EMT.TLE_TIME = dTime
      end
   -- per lo scarico
   elseif EMT.AUXTYPE == 'U' then
      if EMT.AUXIND == EMT.AUXTOT then
         local dTime = UNLOAD_T
         EMT.TOTEXTTIME = EMT.TOTEXTTIME + dTime
         EMT.TLE_NAME = 'Unloading'
         EMT.TLE_TIME = dTime
      end
   -- per lo scarico a caduta
   elseif EMT.AUXTYPE == 'F' then
      if EMT.AUXIND == EMT.AUXTOT then
         local dTime = FALL_T
         EMT.TOTEXTTIME = EMT.TOTEXTTIME + dTime
         EMT.TLE_NAME = 'Fall'
         EMT.TLE_TIME = dTime
      end
   -- per la pre-rotazione
   elseif EMT.AUXTYPE == 'P' then
      ; -- calcolato come parte della rotazione
   end
end

---------------------------------------------------------------------
function OnEstimRapid()
   -- dati movimento
   local dL1 = EMT.L1 - EMT.L1p
   local dL2 = EMT.L2 - EMT.L2p
   local dL3 = EMT.L3 - EMT.L3p
   local dR1 = 0
   if EMT.R1 and EMT.R1p then dR1 = EMT.R1 - EMT.R1p end
   local dR2 = 0
   if EMT.R2 and EMT.R2p then dR2 = EMT.R2 - EMT.R2p end
   -- se primo posizionamento della lavorazione il movimento di L1 è già conteggiato in quello dei carrelli
   if EMT.MCHMOVEFIRST then
      EMT.MCHMOVEFIRST = false
      dL1 = 0
   end
   -- calcolo lunghezza
   local dLen = sqrt( dL1 * dL1 + dL2 * dL2 + dL3 * dL3)
   EMT.MCHEXTLEN = EMT.MCHEXTLEN + dLen
   -- calcolo tempo
   local dTime = RAPID_MIN_T
   local dT1 = abs( dL1) / RAPID_X_FEED * 60
   if dT1 > dTime then dTime = dT1 end
   local dT2 = abs( dL2) / RAPID_Y_FEED * 60
   if dT2 > dTime then dTime = dT2 end
   local dT3 = abs( dL3) / RAPID_Z_FEED * 60
   if dT3 > dTime then dTime = dT3 end
   local dT4 = abs( dR1) / RAPID_C_FEED * 60
   if dT4 > dTime then dTime = dT4 end
   local dT5 = abs( dR2) / RAPID_B_FEED * 60
   if dT5 > dTime then dTime = dT5 end
   EMT.MCHEXTTIME = EMT.MCHEXTTIME + dTime
   EgtOutLog( string.format( '  G0 Len=%.0f Rot=%.0f° Time=%.2f', dLen, abs( dR1) + abs( dR2), dTime), 5)
   -- aggiorno valori come precedenti
   EmtUpdatePrev()
end

---------------------------------------------------------------------
function OnEstimLinear()
   -- dati movimento
   local dL1 = EMT.L1 - EMT.L1p
   local dL2 = EMT.L2 - EMT.L2p
   local dL3 = EMT.L3 - EMT.L3p
   -- calcolo lunghezza
   local dLen = sqrt( dL1 * dL1 + dL2 * dL2 + dL3 * dL3)
   EMT.MCHCUTLEN = EMT.MCHCUTLEN + dLen
   -- calcolo tempo
   local dTime = dLen / EMT.F * 60
   EMT.MCHCUTTIME = EMT.MCHCUTTIME + dTime
   EgtOutLog( string.format( '  G1 Len=%.0f Time=%.2f', dLen, dTime), 5)
   -- aggiorno valori come precedenti
   EmtUpdatePrev()
end

---------------------------------------------------------------------
function OnEstimArc()
   -- dati movimento
   local dLxy = EMT.RR * abs( EMT.AC) * pi / 180
   local dLz = abs( ( Point3d( EMT.L1, EMT.L2, EMT.L3) - Point3d( EMT.L1p, EMT.L2p, EMT.L3p)) * Vector3d( EMT.EXTR))
   -- calcolo lunghezza
   local dLen = sqrt( dLxy * dLxy + dLz * dLz)
   EMT.MCHCUTLEN = EMT.MCHCUTLEN + dLen
   -- calcolo tempo
   local dTime = dLen / EMT.F * 60
   EMT.MCHCUTTIME = EMT.MCHCUTTIME + dTime
   EgtOutLog( string.format( '  G2 Len=%.0f Time=%.2f', dLen, dTime), 5)
   -- aggiorno valori come precedenti
   EmtUpdatePrev()
end

---------------------------------------------------------------------
-- *** GENERAL ***
---------------------------------------------------------------------
function FindFirstToolOnHead( sH1)
   -- salvo stato iniziale
   local CurrMachId = EgtGetCurrMachining()
   local CurrTool = EgtTdbGetCurrToolParam( MCH_TP.NAME)
   -- cerco lavorazione con utensile su testa indicata
   local sTool, nTlen
   local OpId = EgtGetFirstActiveOperation()
   while OpId do
      local nType = EgtGetOperationType( OpId)
      if nType ~= MCH_OY.NONE  and  nType ~= MCH_OY.DISP then
         if EgtSetCurrMachining( OpId) then
            local sTest = EgtGetMachiningParam( MCH_MP.TOOL)
            if EgtTdbSetCurrTool( sTest) then
               local sHead = EgtTdbGetCurrToolParam( MCH_TP.HEAD)
               if sHead  and  ( sHead == sH1) then
                  sTool = sTest
                  nTlen = EgtTdbGetCurrToolParam( MCH_TP.TOTLEN)
                  break
               end
            end
         end
      end
      OpId = EgtGetNextActiveOperation( OpId)
   end
   -- ripristino stato iniziale
   if CurrMachId then
      EgtSetCurrMachining( CurrMachId)
   else
      EgtResetCurrMachining()
   end
   if CurrTool then
      EgtTdbSetCurrTool( CurrTool)
   else
      EgtTdbSetCurrTool( '')
   end
   -- restituisco risultato
   return sTool, nTlen
end

---------------------------------------------------------------------
function GetDefaultToolName()
   local vTools = EgtGetToolsInCurrSetupPos( DefTcPos)
   if vTools and vTools[1] and #vTools[1] > 0 then
      return vTools[1]
   else
      local sErr = 'Missing tool in Default Position ' .. DefTcPos
      EgtOutLog( 'Error : ' .. sErr)
      if EMT.VER and EMT.VER >= '2.3a2' and EMT.SIM1ST then
         EgtOutBox( sErr, 'ERROR', 'ERROR')
      end
      return ''
   end
end

---------------------------------------------------------------------
function IsStartPhase( nPhase)
   local sVal = GetPhaseType( nPhase)
   return ( sVal == 'START')
end

---------------------------------------------------------------------
function IsRestPhase( nPhase)
   local sVal = GetPhaseType( nPhase)
   return ( sVal == 'REST')
end

---------------------------------------------------------------------
function IsStartOrRestPhase( nPhase)
   local sVal = GetPhaseType( nPhase)
   return ( sVal == 'START' or sVal == 'REST')
end

---------------------------------------------------------------------
function IsMidPhase( nPhase)
   local sVal = GetPhaseType( nPhase)
   return ( sVal == 'MID')
end

---------------------------------------------------------------------
function IsMid2Phase( nPhase)
   local sVal = GetPhaseType( nPhase)
   return ( sVal == 'MID2')
end

---------------------------------------------------------------------
function IsEnd2Phase( nPhase)
   local sVal = GetPhaseType( nPhase)
   return ( sVal == 'END2')
end

---------------------------------------------------------------------
function GetPhaseType( nPhase)
   return ( EgtGetInfo( EgtGetPhaseDisposition( nPhase) or GDB_ID.NULL, 'TYPE') or '')
end

---------------------------------------------------------------------
function GetPhaseOrd( nPhase)
   return ( EgtGetInfo( EgtGetPhaseDisposition( nPhase) or GDB_ID.NULL, 'ORD', 'i') or 0)
end

---------------------------------------------------------------------
function GetPhaseRot( nPhase)
   return ( EgtGetInfo( EgtGetPhaseDisposition( nPhase) or GDB_ID.NULL, 'ROT', 'i') or 0)
end

---------------------------------------------------------------------
function IsLastMachining( nMchId)
   local nOpeId = EgtGetNextActiveOperation( nMchId)
   while nOpeId do
      local nType = EgtGetOperationType( nOpeId)
      if nType ~= MCH_OY.NONE  and  nType ~= MCH_OY.DISP then
         return false
      end
      nOpeId = EgtGetNextActiveOperation( nOpeId)
   end
   return true
end

---------------------------------------------------------------------
-- *** END GENERAL ***
---------------------------------------------------------------------
