-- Processore standard macchine tipo PF1250 by EgalWare s.r.l.   2024/01/22

-- Funzioni generiche indipendenti dal controllo

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

-- Carico libreria
local BD = require( 'BeamData')

---------------------------------------------------------------------
-- *** GENERATION ***
---------------------------------------------------------------------
local sBaseDir = EgtGetCurrMachineDir()
if NumericalControl == 'NUM' then
   error( 'Numerical Control error : NUM not yet managed')
elseif NumericalControl == 'TPA' then
   dofile( sBaseDir .. '\\Common_PF1250.TPA.mlpe')
else
   error( 'Numerical Control error : unkwnown type')
end

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

---------------------------------------------------------------------
function OnSimulInit()
   -- se macchina con carico destro, imposto offset direzioni di vista standard
   if BD.RIGHT_LOAD then
      local nOrigViewOffs = EgtGetViewOrizzOffsStep()
      local dOrigViewAngV, dOrigViewAngH = EgtGetGenericView()
      if nOrigViewOffs ~= 2 then
         EgtSetViewOrizzOffsStep( 2)
         if dOrigViewAngV < 0.1 then
            EgtSetView( SCE_VD.TOP, false)
         elseif dOrigViewAngV > 179.9 then
            EgtSetView( SCE_VD.BOTTOM, false)
         else
            local dViewAngH = dOrigViewAngH + EgtIf( dOrigViewAngH > 180, 2 * 90, 0)
            EgtSetGenericView( dOrigViewAngV, dViewAngH, false)
         end
      end
   end
end

---------------------------------------------------------------------
function OnSimulExit()
   -- se macchina con carico destro, annullo offset direzioni di vista standard
   if BD.RIGHT_LOAD then
      local nOrigViewOffs = EgtGetViewOrizzOffsStep()
      local dOrigViewAngV, dOrigViewAngH = EgtGetGenericView()
      if nOrigViewOffs == 2 then
         EgtSetViewOrizzOffsStep( 0)
         if dOrigViewAngV < 0.1 then
            EgtSetView( SCE_VD.TOP, false)
         elseif dOrigViewAngV > 179.9 then
            EgtSetView( SCE_VD.BOTTOM, false)
         else
            EgtSetGenericView( dOrigViewAngV, dOrigViewAngH - 2 * 90, false)
         end
      end
   end
end

---------------------------------------------------------------------
function OnSimulStart()
   -- controllo versione programma
   if not EMT.VER or EMT.VER < MIN_MACH_VER then
      EmtSetLastError( 1200, 'A newer version of the program is required (minimum EgtMachKernel '..MIN_MACH_VER..')')
   end
   -- 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 gli utensili iniziali o di default sulle due teste
   LoadFirstTool( 1, DefTcPos1)
   LoadFirstTool( 2, DefTcPos2)
   ExecStartHome()
   -- 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( 'Z1', 'COLLISION', EMT.COLLOBJ)
   AddToCollisionCheck( 'C1', 'COLLISION', EMT.COLLOBJ)
   AddToCollisionCheck( 'B1', 'COLLISION', EMT.COLLOBJ)
   AddToCollisionCheck( 'X2', 'COLLISION', EMT.COLLOBJ)
   AddToCollisionCheck( 'C2', 'COLLISION', EMT.COLLOBJ)
   AddToCollisionCheck( 'B2', 'COLLISION', EMT.COLLOBJ)
   if IsHeadExisting( 3) then
      AddToCollisionCheck( 'Z3', 'COLLISION', EMT.COLLOBJ)
      AddToCollisionCheck( 'C3', 'COLLISION', EMT.COLLOBJ)
      AddToCollisionCheck( 'B3', 'COLLISION', EMT.COLLOBJ)
   end
   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 = 'V1', Sub = 'COLLISION', Name = 'STM'},
                     { Grp = 'V2', Sub = 'COLLISION', Name = 'STM'},
                     { Grp = 'Base', Sub = 'COLLISION', Name = 'TRAV'},
                     { Grp = 'Base', Sub = 'COLLISION', Name = 'COL1'},
                     { Grp = 'Base', Sub = 'COLLISION', Name = 'COL2'},
                     { Grp = 'Base', Sub = 'COLLISION', Name = 'CHSAW'},
                     { Grp = 'Base', Sub = 'COLLISION', Name = 'CEIL'},
                     { Grp = 'Base', Sub = 'COLLISION', Name = 'TC1'},
                     { Grp = 'Base', Sub = 'COLLISION', Name = 'TC2'},
                     { Grp = 'Base', Sub = 'COLLISION', Name = 'TCR'},
                     { Grp = 'Z2', Sub = 'COLLISION', Name = 'COL1'},
                     { Grp = 'Z2', Sub = 'COLLISION', Name = 'CHAIN'},
                     { Grp = 'Z2', Sub = 'COLLISION', Name = 'COL2'},
                     { Grp = 'X1', Sub = 'COLLISION', Name = 'STM'}}
   
   if IsHeadExisting( 3) then
      table.insert( McdData, { Grp = 'X3', Sub = 'COLLISION', Name = 'STM'})
   end

   -- inserisco gruppi opzionali
   if SecondChain then table.insert( McdData, { Grp = 'Base', Sub = 'COLLISION', Name = 'CHSAW2'}) end
   if TcSpecialTools then table.insert( McdData,  {Grp = 'Base', Sub = 'COLLISION', Name = 'SPTOOLS'}) end
   if TcAggreBladeUnder then table.insert( McdData, {Grp = 'Base', Sub = 'COLLISION', Name = 'AGGREBLADEUNDER'}) end

   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()
   EMT.Y1DELTA = nil
   EMT.Y2DELTA = nil
   ExecParkRoller()
end

---------------------------------------------------------------------
function OnSimulDispositionStarting()
   EmtUnlinkAllRawPartsFromGroups()
   if EMT.PHASE > 1 then
      if IsStartOrRestPhase( EMT.PHASE) then
         local ParkT = GetParkT()
         EgtSetAxisPos( 'T', ParkT)
      end
   end
end

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

   -- Se prima disposizione
   if EMT.PHASE == 1 then
      -- Determino sezione del grezzo
      local nSolId = EgtGetFirstNameInGroup( EgtGetFirstRawPart() or GDB_ID.NULL, 'RawSolid') or GDB_ID.NULL
      local b3Sol = EgtGetBBoxGlob( nSolId, GDB_BB.STANDARD)
      EMT.HB = 0
      EMT.SB = 0
      if b3Sol then
         EMT.HB = b3Sol:getDimY()
         EMT.SB = 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 = 4.71
            if EmtGetVMillStep then
               dTol = EmtGetVMillStep( b3Raw:getDimX(), b3Raw:getDimY(), b3Raw:getDimZ(), dTol)
            else
               local dArea = b3Raw:getDimX() * b3Raw:getDimY() + b3Raw:getDimX() * b3Raw:getDimZ() + b3Raw:getDimY() * b3Raw:getDimZ()
               if dArea < 0.075e6 then
                  dTol = 0.71
               elseif dArea < 0.15e6 then
                  dTol = 1.01
               elseif dArea < 0.3e6 then
                  dTol = 1.51
               elseif dArea < 0.6e6 then
                  dTol = 1.97
               elseif dArea < 1.2e6 then
                  dTol = 2.81
               elseif dArea < 2.4e6 then
                  dTol = 3.77
               end
            end
            dTol = min( dTol, EMT.VMILLTOL or dTol) -- imposto dTol al valore minore tra quello per volume e quello per spessore lama
            -- 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
   end

   -- 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( 'Barra ruotata di ' .. tostring( -90 * nRot) .. '°')
         else
            EgtOutText( 'Barra non ruotata')
         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.LB = b3Bar:getDimX()
      EMT.LR = b3Raw:getDimX()
      EMT.LT = b3Part:getDimX()
      EMT.CUTID = EgtGetInfo( EgtGetFirstPartInRawPart( nPartRawId or GDB_ID.NULL) or GDB_ID.NULL, 'CUTID', 'i') or 0
      EMT.Y1SPEC = nil
      -- se vero inizio, assegno solidi per verifica collisione
      if not EMT.SIM1ST then
         EMT.CODET = {}
         for i = 1, #EMT.MCODET 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 and #EMT.VMILL > 0 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
      -- verifico posizione di carico
      local ParkT = GetParkT()
      -- 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( - ParkT - 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)
               EgtSetStatus( nRawId, GDB_ST.OFF)
            end
         end
         nRawId = nNextRawId
      end
      EMT.LB = b3Bar:getDimX()

   -- altrimenti fase finale, aggancio primo grezzo alla tavola e gli altri in posizione carico al carro Y
   else
      -- verifico posizione di carico
      local ParkT = GetParkT()
      -- 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( - ParkT - 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, 'Y1')
            end
         end
         nRawId = nNextRawId
      end
      EMT.LB = b3Bar:getDimX()
   end
end

---------------------------------------------------------------------
function OnSimulDispositionEnd()
   if EMT.UNLOADING or EMT.FALL then
      ExecUnloading()
      EMT.UNLOADING = false
      EMT.FALL = false
      EMT.TO_FALL = false
   end
   EMT.SPLIT = false
   EMT.OPEISDISP = false
end

---------------------------------------------------------------------
function OnSimulToolSelect( dPosA)
   -- se utensile non definito, è disposizione ed esco
   if EMT.TOOL == '' then return end
   -- recupero dati utensile
   EMT.TOOLTYPE = EgtTdbGetCurrToolParam( MCH_TP.TYPE)
   EMT.TLEN = EgtTdbGetCurrToolParam( MCH_TP.LEN)
   EMT.TTOTLEN = EgtTdbGetCurrToolParam( MCH_TP.TOTLEN)
   EMT.TUSERNOTES = EgtTdbGetCurrToolParam( MCH_TP.USERNOTES)
   -- recupero il gruppo
   local nSetHead = GetHeadSet( EMT.HEAD)
   -- se fresa o lama su testa 1, reset assi rotanti
   if nSetHead == 1 then
      -- se ho la testa 3, la mando a parcheggio
      if IsHeadExisting( 3) then
         EgtSetAxisPos( 'X3', ParkX3)
      end
      if EMT.HEAD == 'H12' or EMT.HEAD == 'H16' then
         EgtSetAxisPos( 'Z1', MaxZ1Blade)
      elseif EMT.HEAD == 'H13' then
         EgtSetAxisPos( 'Z1', ParkCSawZ1)
      else
         EgtSetAxisPos( 'Z1', MaxZ1)
      end
      EgtResetAxisPos( 'C1')
      EgtResetAxisPos( 'B1')
   -- se testa 3 parcheggio la 1
   elseif nSetHead == 3 then
      EgtSetAxisPos( 'X1', ParkX1)
   end
   -- se sega a catena, imposto subito angolo scelto per asse virtuale A
   if EMT.HEAD == 'H13' then
      -- se prima lavorazione con motosega, parto dalla sua posizione di parcheggio
      EgtSetAxisPos( 'X1', ParkCSawX1)
      -- valore dell'asse virtuale
      dPosA = dPosA or GetNextChainSawingVirtualAxis( EMT.MCHID)
      -- imposto il valore di A
      EgtSetAxisPos( 'A1', dPosA)
      -- imposto home dell'asse C1 (A1=0 -> T101, A1=90 -> T104)
      local MyParkCSawC1 = GetChainSawCHomeFromVirtualAxis( dPosA, EMT.TTOTLEN)
      EmtModifyAxisHome( 'C1', MyParkCSawC1)
      EgtSetAxisPos( 'C1', MyParkCSawC1)
      -- Imposto visualizzazione
      EgtSetMode( EgtGetHeadId( EMT.HEAD) or GDB_ID.NULL, GDB_MD.STD)
   end
   -- se aggregato foratura multipla, imposto subito angolo scelto per asse virtuale A
   if EMT.HEAD == 'H14' then
      -- valore dell'asse virtuale
      dPosA = dPosA or GetNextMultiDrillVirtualAxis( EMT.MCHID)
      -- imposto il valore di A
      EgtSetAxisPos( 'A1', dPosA)
      -- imposto home dell'asse C1 (A1=0 -> T121, A1=90 -> T124)
      local MyParkCSawC1 = GetMultiDrillCHomeFromVirtualAxis( dPosA, EMT.TTOTLEN)
      EmtModifyAxisHome( 'C1', MyParkCSawC1)
      EgtSetAxisPos( 'C1', MyParkCSawC1)
      -- Imposto visualizzazione
      EgtSetMode( EgtGetHeadId( EMT.HEAD) or GDB_ID.NULL, GDB_MD.STD)
   end
   -- se lama su aggregato da sotto, imposto subito angolo scelto per asse virtuale A
   if EMT.HEAD == 'H22' then
      -- se c'era un altro utensile, imposto subito asse virtuale
      if EMT.PREVHEAD_H2 ~= 'H22' then
         dPosA = dPosA or GetNextSawingVirtualAxis( EMT.MCHID)      
      end
      if dPosA then
         -- imposto il valore di A
         EgtSetAxisPos( 'A2', dPosA)
         -- imposto home dell'asse C1 (A2=0 -> T201, A2=90 -> T204)
         local MyParkSawC2 = GetSawCHomeFromVirtualAxis( dPosA)
         EmtModifyAxisHome( 'C2', MyParkSawC2)
         EgtSetAxisPos( 'C2', MyParkSawC2)
         -- Imposto visualizzazione
         EgtSetMode( EgtGetHeadId( EMT.HEAD) or GDB_ID.NULL, GDB_MD.STD)
      end
   end
   -- carico utensile, con breve pausa
   EgtPause( 100)
   EgtOutText( '')
   -- lo nascondo sul portautensili
   ShowToolInTcPos( EMT.TCPOS, false)
   -- dichiaro assi ausiliari da visualizzare
   EMT.AuxAxes = 4
   EMT.A1n = 'Y1'
   EMT.A2n = 'Y2'
   EMT.A3n = 'V1'
   EMT.A4n = 'V2'
   -- se utensile non flottante, abilito per Vmill
   if not EMT.TFLOAT then
      EmtSetToolForVmill( EMT.TOOL, EMT.HEAD, EMT.EXIT, EMT.VMILL)
   end
   -- se attivo Collision Check
   EMT.SAFEDIST = COLL_SAFE_DIST
   if EMT.COLLOBJ then
      local nInd = EgtIf( nSetHead ~= 2, 1001, 1011)
      AddToolToCollisionObj( EMT.TOOL, EMT.HEAD, EMT.EXIT, nInd, true)
      AddToolHolderToCollisionObj( EMT.TOOL, EMT.HEAD, EMT.EXIT, nInd + 1)
      for i, Coll in ipairs( EMT.COLLOBJ or {}) do
         EmtAddCollisionObjEx( i, Coll.Fr, Coll.Ty, Coll.Mv, Coll.P1, Coll.P2, Coll.P3)
      end
   end
end

---------------------------------------------------------------------
function OnSimulToolDeselect( dPrevA)
   -- se prossimo utensile non cambia e non è sega a catena o lama su rinvio da sotto, esco
   if ( EMT.NEXTTOOL == EMT.PREVTOOL_H1 or EMT.NEXTTOOL == EMT.PREVTOOL_H2) and ( EMT.NEXTHEAD ~= 'H13' or EMT.NEXTHEAD ~= 'H14') and ( EMT.NEXTHEAD ~= 'H22' or not dPrevA) then return end
   -- deposito utensile
   EgtOutText( 'Tool change in progress...')
   -- recupero il gruppo attuale e il successivo
   local nNextSetHead = GetHeadSet( EMT.NEXTHEAD)
   local nSetHead = GetHeadSet( EMT.HEAD)
   -- se devo deselezionare testa 3, la mando a parcheggio
   if nSetHead == 3 then
      SimulMoveAxis( 'X3', ParkX3, MCH_SIM_STEP.RAPID)
   end
   -- per testa gruppo 2 
   if nNextSetHead == 2 then
      -- deposito lama su aggregato testa sotto
      if EMT.PREVHEAD_H2 == 'H22' then
         -- simulo movimento
         local ParkC2 = EgtGetAxisHomePos( 'C2')
         if dPrevA then
            ParkC2 = GetSawCHomeFromVirtualAxis( dPrevA)
         end
         SimulMoveAxis( 'Z2', MinZ2, MCH_SIM_STEP.RAPID)
         SimulMoveAxis( 'X2', ParkSawX2, MCH_SIM_STEP.RAPID)
         SimulMoveAxes( 'B2', ParkSawB2, MCH_SIM_STEP.COLLROT, 'C2', ParkC2, MCH_SIM_STEP.COLLROT)
      end
      -- breve pausa
      EgtPause( 100)
      -- visualizzo utensile su TcPos
      ShowToolInTcPos( EMT.PREVTCPOS_H2, true)
      -- nascondo l'utensile sulla testa
      EgtSetMode( EgtGetHeadId( EMT.PREVHEAD_H2 or '') or GDB_ID.NULL, GDB_MD.HIDDEN)
   -- se testa gruppo 1
   elseif nNextSetHead == 1 and EMT.PREVTCPOS_H1 then
      -- deposito utensile fresa
      if EMT.PREVHEAD_H1 == 'H11' then
         -- simulo movimento
         local nPrevTc = GetTcForTopHeadTool( EMT.PREVTCPOS_H1)
         if EMT.PREVTTOTLEN_H1 < LongTool then
            SimulMoveAxes( 'B1', ParkB1, MCH_SIM_STEP.RAPROT, 'C1', ParkC1, MCH_SIM_STEP.RAPROT)
            local dPosX = EgtIf( nPrevTc ~= 2, ParkTc1X1, ParkTc2X1)
            SimulMoveAxis( 'X1', dPosX, MCH_SIM_STEP.RAPID)
         else
            if nPrevTc ~= 2 then
               SimulMoveAxes( 'B1', ParkLongB1, MCH_SIM_STEP.RAPROT, 'C1', ParkLongTc1C1, MCH_SIM_STEP.RAPROT)
               local dPosX = ParkTc1X1
               SimulMoveAxis( 'X1', dPosX, MCH_SIM_STEP.RAPID)
            else
               SimulMoveAxes( 'B1', ParkLongB1, MCH_SIM_STEP.RAPROT, 'C1', ParkLongTc2C1, MCH_SIM_STEP.RAPROT)
               local dPosX = ParkTc2X1
               SimulMoveAxis( 'X1', dPosX, MCH_SIM_STEP.RAPID)
            end
         end
      -- deposito utensile lama
      elseif EMT.PREVHEAD_H1 == 'H12' then
         -- simulo movimento
         SimulMoveAxes( 'B1', ParkB1, MCH_SIM_STEP.RAPROT, 'C1', ParkC1, MCH_SIM_STEP.RAPROT)
         SimulMoveAxis( 'X1', ParkTc1X1, MCH_SIM_STEP.RAPID)
      -- deposito utensile sega a catena
      elseif EMT.PREVHEAD_H1 == 'H13' then
         -- simulo movimento
         local ParkC1 = EgtGetAxisHomePos( 'C1')
         if dPrevA then
            ParkC1 = GetChainSawCHomeFromVirtualAxis( dPrevA, EMT.TTOTLEN)
         end
         SimulMoveAxes( 'B1', ParkCSawB1, MCH_SIM_STEP.RAPROT, 'C1', ParkC1, MCH_SIM_STEP.RAPROT)
         SimulMoveAxis( 'X1', ParkCSawX1, MCH_SIM_STEP.RAPID)
      -- deposito utensile aggregato foratore multiplo
      elseif EMT.PREVHEAD_H1 == 'H14' then
         -- simulo movimento
         local ParkC1 = EgtGetAxisHomePos( 'C1')
         if dPrevA then
            ParkC1 = GetMultiDrillCHomeFromVirtualAxis( dPrevA)
         end
         -- simulo movimento
         SimulMoveAxes( 'B1', ParkMultiDrillB1, MCH_SIM_STEP.RAPROT, 'C1', ParkC1, MCH_SIM_STEP.RAPROT)
         SimulMoveAxis( 'X1', ParkMultiDrillX1, MCH_SIM_STEP.RAPID)
      -- deposito utensile seconda lama
      elseif EMT.PREVHEAD_H1 == 'H16' then
         -- simulo movimento
         SimulMoveAxes( 'B1', ParkB1, MCH_SIM_STEP.RAPROT, 'C1', ParkC1, MCH_SIM_STEP.RAPROT)
         SimulMoveAxis( 'X1', ParkTc2X1, MCH_SIM_STEP.RAPID)
      end

      -- breve pausa
      EgtPause( 100)
      -- visualizzo utensile su TcPos
      ShowToolInTcPos( EMT.PREVTCPOS_H1, true)
      -- nascondo l'utensile sulla testa
      EgtSetMode( EgtGetHeadId( EMT.PREVHEAD_H1 or '') or GDB_ID.NULL, GDB_MD.HIDDEN)
   end
   -- se passo da testa 1 a 3 scarico aggregato
   if nNextSetHead == 3 then
      -- deposito utensile prelevato da posizioni speciali
      if EMT.PREVTCPOS_H1 == 'T101' or EMT.PREVTCPOS_H1 == 'T111' or EMT.PREVTCPOS_H1 == 'T121' then
         -- visualizzo utensile su TcPos
         ShowToolInTcPos( EMT.PREVTCPOS_H1, true)
         -- nascondo l'utensile sulla testa
         EgtSetMode( EgtGetHeadId( EMT.PREVHEAD_H1 or '') or GDB_ID.NULL, GDB_MD.HIDDEN)

         local sTool, sTcPos, sHead
         local vTools = EgtGetToolsInCurrSetupPos( DefTcPos1)
         if vTools and vTools[1] then
            sTool = vTools[1]
            sTcPos = DefTcPos1
            sHead = GetAdjHeadFromTcPos( 1, sTcPos)
            -- carico l'utensile
            EgtLoadTool( sHead, 1, sTool)
            ShowToolInTcPos( DefTcPos1, false)
         end
         EMT.PREVTOOL_H1 = sTool
         EMT.PREVHEAD_H1 = sHead
         EMT.PREVTCPOS_H1 = DefTcPos1
      end
      SimulMoveAxis( 'X1', ParkX1, MCH_SIM_STEP.RAPID)
   end
   -- movimento per prendere nuovo utensile
   -- prendo utensile fresa
   if EMT.NEXTHEAD == 'H11' then
      -- simulo movimento
      SimulMoveAxes( 'B1', ParkB1, MCH_SIM_STEP.RAPROT, 'C1', ParkC1, MCH_SIM_STEP.RAPROT)
      local dPosX = EgtIf( GetTcForTopHeadTool( EMT.NEXTTCPOS) ~= 2, ParkTc1X1, ParkTc2X1)
      SimulMoveAxis( 'X1', dPosX, MCH_SIM_STEP.RAPID)
      SimulMoveAxis( 'Z1', MaxZ1, MCH_SIM_STEP.RAPID)
   -- prendo utensile lama
   elseif EMT.NEXTHEAD == 'H12' then
      -- simulo movimento
      SimulMoveAxes( 'B1', ParkB1, MCH_SIM_STEP.RAPROT, 'C1', ParkC1, MCH_SIM_STEP.RAPROT)
      SimulMoveAxis( 'X1', ParkTc1X1, MCH_SIM_STEP.RAPID)
   -- prendo utensile sega a catena
   elseif EMT.NEXTHEAD == 'H13' then
      -- simulo movimento
      SimulMoveAxes( 'B1', ParkCSawB1, MCH_SIM_STEP.RAPROT, 'C1', EgtGetAxisHomePos( 'C1'), MCH_SIM_STEP.RAPROT)
      SimulMoveAxes( 'X1', ParkCSawX1, MCH_SIM_STEP.RAPID, 'Z1', ParkCSawZ1, MCH_SIM_STEP.RAPID)
   -- prendo aggregato multi drill
   elseif EMT.NEXTHEAD == 'H14' then
      -- simulo movimento
      SimulMoveAxes( 'B1', ParkMultiDrillB1, MCH_SIM_STEP.RAPROT, 'C1', EgtGetAxisHomePos( 'C1'), MCH_SIM_STEP.RAPROT)
      SimulMoveAxes( 'X1', ParkMultiDrillX1, MCH_SIM_STEP.RAPID, 'Z1', ParkMultiDrillZ1, MCH_SIM_STEP.RAPID)
   -- prendo utensile seconda lama
   elseif EMT.NEXTHEAD == 'H16' then
      -- simulo movimento
      SimulMoveAxes( 'B1', ParkB1, MCH_SIM_STEP.RAPROT, 'C1', ParkC1, MCH_SIM_STEP.RAPROT)
      SimulMoveAxis( 'X1', ParkTc2X1, MCH_SIM_STEP.RAPID)
   -- prendo utensile su CU margherita testa sotto
   elseif EMT.NEXTHEAD == 'H21' then
      -- simulo movimento
      SimulMoveAxes( 'B2', ParkB2, MCH_SIM_STEP.RAPROT, 'C2', ParkC2, MCH_SIM_STEP.RAPROT)
      SimulMoveAxis( 'X2', ParkX2, MCH_SIM_STEP.RAPID)
   -- prendo utensile lama su aggregato
   elseif EMT.NEXTHEAD == 'H22' then
      -- aggiungo utensile per verifica collisione
      AddToolToCollisionObj( EMT.NEXTTOOL, EMT.NEXTHEAD, EMT.NEXTEXIT, 1011, false)
      AddToolHolderToCollisionObj( EMT.NEXTTOOL, EMT.NEXTHEAD, EMT.NEXTEXIT, 1011 + 1)
      -- simulo movimento
      SimulMoveAxis( 'X2', ParkSawX2, MCH_SIM_STEP.RAPID)
      SimulMoveAxes( 'B2', ParkSawB2, MCH_SIM_STEP.COLLROT, 'C2', ParkSawC2, MCH_SIM_STEP.COLLROT)
   end
end

---------------------------------------------------------------------
function OnSimulMachiningStart()
   -- se lavorazione è con gruppo a forare, abilito le altre punte alla lavorazione del VMILL
   if EMT.HEAD == 'H14' then
      local OtherTools = EgtGetToolsInCurrSetupPos( 'T121')
      if OtherTools and #OtherTools > 1 then
         for i = 1, #OtherTools do
            if OtherTools[i] ~= '' and OtherTools[i] ~= EMT.TOOL then
               EmtAddToolForVmill( OtherTools[i], 'H14', i, EMT.VMILL)
               -- dichiaro utensile per verifica collisioni
               local nInd = 1011
               AddToolToCollisionObj( OtherTools[i], 'H14', i, nInd, true)
            end
         end
      end
   end
   -- se lavorazione attuale e precedente con sega a catena o aggregato foratore multiplo e con angolo A diverso, devo scaricare e ricaricare
   if ( EMT.HEAD == 'H13' or EMT.HEAD == 'H14') and EMT.HEAD == EMT.PREVHEAD_H1 then
      local dPrevA = EgtGetAxisPos( 'A1')
      local sVal = EgtGetMachiningParam( MCH_MP.BLOCKEDAXIS)
      local dPosA = tonumber( sVal:sub( 4))
      if abs( dPosA - dPrevA) > 1 then
         OnSimulToolDeselect( dPrevA)
         EgtSetStatus( EgtGetHeadId( EMT.HEAD), GDB_ST.ON)
         OnSimulToolSelect( dPosA)
      end
   end
   -- se lavorazione attuale e precedente con aggregato lama da sotto con angolo A diverso, devo scaricare e ricaricare
   if EMT.HEAD == 'H22' and EMT.HEAD == EMT.PREVHEAD_H2 then
      local dPrevA = EgtGetAxisPos( 'A2')
      local sVal = EgtGetMachiningParam( MCH_MP.BLOCKEDAXIS)
      local dPosA = tonumber( sVal:sub( 4)) or 0
      if abs( dPosA - dPrevA) > 1 then
         ExecParkRoller( nil, nil, nil, nil, false, bAgg)
         -- setto i valori per la funzione ToolDesel
         EMT.NEXTHEAD = EMT.HEAD
         EMT.NEXTTOOL = EMT.TOOL 
         EMT.NEXTEXIT = EMT.EXIT
         OnSimulToolDeselect( dPrevA)
         EgtSetStatus( EgtGetHeadId( EMT.HEAD), GDB_ST.ON)
         OnSimulToolSelect( dPosA)
         -- aggiorno dati utensile su testa
         EMT.TCPOSREAL = 'T' .. AdjustTcPos( false, EMT.TCPOS, dPosA)
         EMT.PREVTCPOSREAL_H2 = EMT.TCPOSREAL
      end
   end
   -- recupero alcuni dati della lavorazione
   EMT.MCHNAME = EgtGetOperationName( EMT.MCHID)
   EMT.MCHTYPE = EgtGetMachiningParam( MCH_MP.TYPE)
   EMT.MCHUSERNOTES = EgtGetMachiningParam( MCH_MP.USERNOTES) or ''
   EMT.VMRS = ( EMT.MCHTYPE ~= MCH_MY.DRILLING and not EMT.MCHUSERNOTES:find( 'VMRS=0;', 1, true))
   EMT.MCHSPLIT = ( EMT.MCHUSERNOTES:find( 'Split;', 1, true) ~= nil)
   -- recupero TASKID e CUTID della feature lavorata (CUTID va recuperato qui per le pareti)
   local vId = EgtGetMachiningGeometry()
   if vId and #vId > 0 and #vId[1] > 0 then
      local nPartId = EgtGetParent( EgtGetParent( vId[1][1]) or GDB_ID.NULL)
      EMT.CUTID = EgtGetInfo( nPartId or GDB_ID.NULL, 'CUTID', 'i') or 0
      EMT.TASKID = EgtGetInfo( vId[1][1], 'TASKID', 'i') or 0 ;
   else
      EMT.CUTID = 0
      EMT.TASKID = 0
   end
   -- gestione eventuale lavorazione in doppio
   local nDouType = EgtGetValInNotes( EMT.MCHUSERNOTES, 'DOUBLE', 'i')
   if nDouType == 2 or nDouType == 3 then
      local sDouTool = EgtGetValInNotes( EMT.TUSERNOTES, 'DOUBLE', 's') or ''
      local sOldTool = EgtTdbGetCurrToolParam( MCH_TP.NAME)
      if EgtTdbSetCurrTool( sDouTool) and EgtTdbGetCurrToolParam( MCH_TP.ACTIVE) then
         local sDouHead = 'H21'
         local nDouExit = 1
         -- carico l'utensile sulla testa
         local sTcPos = EgtTdbGetCurrToolParam( MCH_TP.TCPOS)
         EgtLoadTool( sDouHead, nDouExit, sDouTool)
         ShowToolInTcPos( sTcPos, false)
         -- dichiaro utensile per Vmill
         EmtAddToolForVmill( sDouTool, sDouHead, nDouExit, EMT.VMILL)
         -- dichiaro utensile per verifica collisioni
         local nInd = 1011
         AddToolToCollisionObj( sDouTool, sDouHead, nDouExit, nInd, true)
         AddToolHolderToCollisionObj( sDouTool, sDouHead, nDouExit, nInd + 1)
         -- salvo dati
         EMT.DOU_TLEN = EgtTdbGetCurrToolParam( MCH_TP.LEN)
         EMT.DOU_TTOTLEN = EgtTdbGetCurrToolParam( MCH_TP.TOTLEN)
         EMT.DOU_TYPE = nDouType
         EMT.DOU_TOOL = sDouTool
         -- reset necessità movimento in sicurezza sola testa in doppio
         EMT.DOU_TO_ZMAX = nil
      else
         EmtSetLastError( 1211, 'Missing or not active double tool of '..sOldTool)
      end
      EgtTdbSetCurrTool( sOldTool)
   else
      -- recupero il gruppo
      local nSetHead = GetHeadSet( EMT.HEAD)
      if nSetHead ~= 1 and ( EMT.PREVTOOL_H1 and EMT.PREVHEAD_H1) then
         local nPrevExit_H1 = 1
         local nInd_H1 = 1001
         AddToolToCollisionObj( EMT.PREVTOOL_H1, EMT.PREVHEAD_H1, nPrevExit_H1, nInd_H1, true)
         AddToolHolderToCollisionObj( EMT.PREVTOOL_H1, EMT.PREVHEAD_H1, nPrevExit_H1, nInd_H1 + 1)
      end
      if nSetHead ~= 2 and ( EMT.PREVTOOL_H2 and EMT.PREVHEAD_H2) then
         local nPrevExit_H2 = 1
         local nInd_H2 = 1011
         AddToolToCollisionObj( EMT.PREVTOOL_H2, EMT.PREVHEAD_H2, nPrevExit_H2, nInd_H2, true)
         AddToolHolderToCollisionObj( EMT.PREVTOOL_H2, EMT.PREVHEAD_H2, nPrevExit_H2, nInd_H2 + 1)
      end
      if nSetHead ~= 3 and IsHeadExisting( 3) then
         local nPrevExit_H3 = 1
         local nInd_H3 = 1001
         local sHeadName
         if EgtGetHeadId( 'H39') then
            sHeadName = 'H39'
         elseif EgtGetHeadId( 'H38') then
            sHeadName = 'H38'
         else
            EmtSetLastError( 1212, "HEAD not managed")
         end
         AddToolToCollisionObj( nil, sHeadName, nPrevExit_H3, 1001)
         AddToolHolderToCollisionObj( nil, sHeadName, nPrevExit_H3, 1002)
      end
   end
   -- se precedente in doppio ma ora solo testa 1 e non in sicurezza
   if EMT.DOU_TO_ZMAX then
      -- parcheggio i rulli
      local bAgg = EgtExistsInfo( EMT.PATHID, 'CNT')
      ExecParkRoller( nil, nil, nil, nil, false, bAgg)
      -- eseguo
      if EMT.DOU_TO_ZMAX == 2 then
         SimulMoveAxis( 'X2', SafeX2, MCH_SIM_STEP.RAPID)
      end
      SimulMoveAxes( 'Z2', MinZ2, MCH_SIM_STEP.RAPID)
      SimulMoveAxes( 'B2', ParkB2, MCH_SIM_STEP.COLLROT, 'C2', ParkC2, MCH_SIM_STEP.COLLROT)
      SimulMoveAxis( 'X2', ParkX2, MCH_SIM_STEP.RAPID)
      EMT.DOU_TO_ZMAX = nil
   end

   -- non ancora iniziata la lavorazione
   EMT.MCHFIRST = true
end

---------------------------------------------------------------------
function OnSimulMachiningEnd()
   if EMT.UNLOADING or EMT.FALL then
      ExecUnloading()
      EMT.UNLOADING = false
      EMT.FALL = false
      EMT.TO_FALL = false
   end
   EMT.MCHSPLIT = nil
   EMT.SPLIT = nil
   if EMT.DOU_TYPE and not EMT.ZMAX then EMT.DOU_TO_ZMAX = EMT.DOU_TYPE end
   EMT.DOU_TYPE = nil
   EMT.DOU_TOOL = nil

   -- salvo dati utensile
   local nSetHead = GetHeadSet( EMT.HEAD)
   -- per gruppo testa 1
   if nSetHead == 1 then
      EMT.PREVTOOL_H1 = EMT.TOOL
      EMT.PREVHEAD_H1 = EMT.HEAD
      EMT.PREVTCPOS_H1 = EMT.TCPOS
      EMT.PREVTTOTLEN_H1 = EMT.TTOTLEN
      EMT.PREVTCPOSREAL_H1 = EMT.TCPOSREAL
   -- per gruppo testa 2
   elseif nSetHead == 2 then
      EMT.PREVTOOL_H2 = EMT.TOOL
      EMT.PREVHEAD_H2 = EMT.HEAD
      EMT.PREVTCPOS_H2 = EMT.TCPOS
      EMT.PREVTTOTLEN_H2 = EMT.TTOTLEN
      EMT.PREVTCPOSREAL_H2 = EMT.TCPOSREAL
   -- per gruppo testa 3 
   else
      EMT.PREVTOOL_H3 = EMT.TOOL
      EMT.PREVHEAD_H3 = EMT.HEAD
      EMT.PREVTCPOS_H3 = EMT.TCPOS
      EMT.PREVTTOTLEN_H3 = EMT.TTOTLEN
   end

end

---------------------------------------------------------------------
function OnSimulPathStart()
   -- se taglio di separazione, verifico se ci sarà caduta
   if EMT.MCHSPLIT then
      local sCmd = EgtGetInfo( EMT.PATHID, 'AE1') or ''
      if sCmd:find( 'Fall', 1, true) then
         EMT.TO_FALL = true
      end
   end
end

---------------------------------------------------------------------
function OnSimulPathEnd()
   -- rimozione sfridi
   ExecRemoveScraps()
   -- se non ci sono comandi ausiliari associati e richiesta risalita a Zmax
   if EMT.AUXTOT == 0 and EMT.TO_ZMAX then
      if EMT.MCHSPLIT and not EMT.TO_FALL then
         EMT.SPLIT_Y1DELTA = EMT.Y1DELTA
         EMT.Y1DELTA = nil
         ExecMovePY1( false)
      end
      ExecMoveZmax( EMT.MCHSPLIT)
      EMT.TO_ZMAX = nil
   end
end

---------------------------------------------------------------------
function OnSimulPathStartAux()
   -- eseguo il comando
   ExecAuxCmd( EMT.AUX, true)
   -- se ultimo comando e lavorazione di split, sgancio il carro Y1
   if EMT.AUXIND == EMT.AUXTOT and EMT.MCHSPLIT and not EMT.TO_FALL then
      EMT.SPLIT_Y1DELTA = EMT.Y1DELTA
      EMT.Y1DELTA = nil
      ExecMovePY1( false)
   end
end

---------------------------------------------------------------------
function OnSimulPathEndAux()
   -- eseguo il comando
   ExecAuxCmd( EMT.AUX, false)
   -- se ultimo comando e richiesta risalita a Zmax
   if EMT.AUXIND == EMT.AUXTOT and EMT.TO_ZMAX then
      ExecMoveZmax( EMT.MCHSPLIT)
      EMT.TO_ZMAX = nil
   end
end

---------------------------------------------------------------------
function OnSimulMoveStart()
   if EMT.MCHFIRST then EgtOutText( '') end
   -- set della testa
   local nSetHead = GetHeadSet( EMT.HEAD)
   -- Posizioni correnti
   local Y1Pos = EgtGetAxisPos( 'Y1')
   local Y2Pos = EgtGetAxisPos( 'Y2')
   local V1Pos = EgtGetAxisPos( 'V1')
   local V2Pos = EgtGetAxisPos( 'V2')
   -- Imposto movimento pinze e rulli insieme con la tavola :
   EMT.AuxAxes = 4
   -- pinze
   EMT.A1n = 'Y1'
   if EMT.Y1DELTA then
      EMT.A1m = 'T'
      EMT.A1 = EMT.L1 + EMT.Y1DELTA
   else
      EMT.A1m = nil
      EMT.A1 = EgtIf( EMT.Y1SPEC, EgtGetAxisPos( 'Y1'), ParkY1)
   end
   EMT.A2n = 'Y2'
   if EMT.Y2DELTA then
      EMT.A2m = 'T'
      EMT.A2 = EMT.L1 + EMT.Y2DELTA
   else
      EMT.A2m = nil
      EMT.A2 = ParkY2
   end
   -- Controllo scorrimento pinze chiuse Y1 e Y2
   if EMT.Y1DELTA then
      local dY1DeltaP = Y1Pos - EMT.L1p
      if abs( EMT.Y1DELTA - dY1DeltaP) > 0.1 then
         EMT.ERR = 2
         local sErr = 'Y1 slide : ' .. EmtLenToString( dY1DeltaP, 3) .. ' -> ' .. EmtLenToString( EMT.Y1DELTA, 3)
         EmtSetLastError( 1202, sErr)
      end
   end
   if EMT.Y2DELTA then
      local dY2DeltaP = Y2Pos - EMT.L1p
      if abs( EMT.Y2DELTA - dY2DeltaP) > 0.1 then
         EMT.ERR = 2
         local sErr = 'Y2 slide : ' .. EgtNumToString( dY2DeltaP, 3) .. ' -> ' .. EgtNumToString( EMT.Y2DELTA, 3)
         EmtSetLastError( 1202, sErr)
      end
   end
   -- rulli
   EMT.A3n = 'V1'
   EMT.A3 = EgtIf( GetV1ToClose(), EMT.V1NEXTPOS, ParkV1)
   EMT.A4n = 'V2'
   EMT.A4 = EgtIf( GetV2ToClose(), EMT.V2NEXTPOS, ParkV2)
   local bParkV = false
   if EMT.MCHFIRST then
      -- se rulli più chiusi del richiesto o cambio direzione utensile (tranne solo asse B con C0 o equivalenti) devo mettere i rulli in parcheggio
      if V1Pos < EMT.V1NEXTPOS - 1 or V2Pos > EMT.V2NEXTPOS + 1 or RollerParkingNeeded( EMT.HEAD, EMT.R1p, EMT.R2p, EMT.R1, EMT.R2) then
         bParkV = true
         EMT.A3 = ParkV1
         EMT.A4 = ParkV2
      end
   end
   -- Controllo corse assi ausiliari
   VerifyY1Stroke( EMT.A1)
   VerifyY2Stroke( EMT.A2)
   VerifyV1Stroke( EMT.A3)
   VerifyV2Stroke( EMT.A4)
   -- se devo subito parcheggiare i rulli
   local nRes = 0
   if bParkV then
      local bAgg = EgtExistsInfo( EMT.PATHID, 'CNT')
      nRes = ExecParkRoller( Y1Pos, Y2Pos, V1Pos, V2Pos, false, bAgg)
   end
   -- se movimento in rapido
   if EMT.MOVE == 0 then
      -- se testa 1
      if nSetHead == 1 then
         -- Dati
         local B1Pos = EgtGetAxisPos( 'B1')
         local B1Home = EgtGetAxisHomePos( 'B1')
         local C1Pos = EgtGetAxisPos( 'C1')
         local C1Home = EgtGetAxisHomePos( 'C1')
         local Z1Pos = EgtGetAxisPos( 'Z1')
         local Z1Home = EgtGetAxisHomePos( 'Z1')
         local MyMaxZ1 = EgtGetAxisMax( 'Z1')
         -- se fresa o lama
         if EMT.HEAD ~= 'H13' then
            -- se movimento iniziale da Zmax con lama o fresa
            if EMT.ZMAX or ( EMT.FLAG == 2 and EMT.FLAG2 == 1) then
               local bXSpec = EgtIf( BD.RIGHT_LOAD, EMT.L2 < DeltaTabY, EMT.L2 > DeltaTabY)
               -- in caso di rotazione della lama lontano dalla posizione di home degli assi rotanti
               if ( EMT.HEAD == 'H12' or EMT.HEAD == 'H16') and ( abs( C1Pos - EMT.R1) > 1 or abs( B1Pos - EMT.R2) > 1) and ( abs( C1Home - EMT.R1) > 30.1 or abs( B1Home - EMT.R2) > 30.1) then
                  SimulMoveAxis( 'X1', EgtIf( bXSpec, DeltaTabY, EMT.L2), MCH_SIM_STEP.RAPID)
                  SimulMoveAxes( 'Z1', MyMaxZ1, MCH_SIM_STEP.RAPID, 'B1', 0, MCH_SIM_STEP.COLLROT)
                  SimulMoveAxis( 'C1', EMT.R1, MCH_SIM_STEP.COLLROT)
                  SimulMoveAxis( 'X1', EMT.L2, MCH_SIM_STEP.RAPID)
                  -- se movimento di B > di 90°, spezza il movimento in 2 arrivando alla quota Z con B+-90 e poi muove l'ultimo segmento
                  -- evita collisione con carro X
                  if EMT.R2 > 91 or EMT.R2 < -91 then
                     local dBref = EgtClamp( EMT.R2, -91, 91)
                     SimulMoveAxes( 'Z1', EMT.L3, MCH_SIM_STEP.RAPID, 'B1', dBref, MCH_SIM_STEP.COLLROT)
                     SimulMoveAxis( 'B1', EMT.R2, MCH_SIM_STEP.COLLROT)
                  else
                     SimulMoveAxes( 'Z1', EMT.L3, MCH_SIM_STEP.RAPID, 'B1', EMT.R2, MCH_SIM_STEP.COLLROT)
                  end
               -- caso standard
               else
                  -- se bisogna scrivere tutti gli assi
                  if WriteAllCoordsOnFirstM101 then
                     SimulMoveAxis( 'X1', EgtIf( bXSpec, DeltaTabY, EMT.L2), MCH_SIM_STEP.RAPID)
                  end
                  -- se ero in posizione speciale, prima ruoto poi scendo
                  if bXSpec then
                     local dZPos = max( Z1Home, EMT.L3)
                     SimulMoveAxis( 'Z1', dZPos, MCH_SIM_STEP.RAPID)
                     SimulMoveAxes( 'B1', EMT.R2, MCH_SIM_STEP.COLLROT, 'C1', EMT.R1, MCH_SIM_STEP.COLLROT)
                     SimulMoveAxis( 'X1', EMT.L2, MCH_SIM_STEP.RAPID)
                     if abs( dZPos - EMT.L3) > 1 then
                        SimulMoveAxis( 'Z1', EMT.L3, MCH_SIM_STEP.RAPID)
                     end
                  else
                     SimulMoveAxis( 'Z1', EMT.L3, MCH_SIM_STEP.RAPID)
                     SimulMoveAxes( 'B1', EMT.R2, MCH_SIM_STEP.COLLROT)
                     SimulMoveAxis( 'C1', EMT.R1, MCH_SIM_STEP.COLLROT)
                     SimulMoveAxis( 'X1', EMT.L2, MCH_SIM_STEP.RAPID)
                  end
               end
            end
         -- altrimenti sega a catena
         else
            local dSafeZ1 = EgtIf( EMT.TTOTLEN < MinLengthLongCSaw, ParkCSawZ1, ParkLongCSawZ1)
            -- Porto la Z alla giusta quota
            if Z1Pos > dSafeZ1 + 1 and abs( B1Pos) > 89.9  then
               SimulMoveAxis( 'X1', EMT.L2, MCH_SIM_STEP.RAPID)
               SimulMoveAxis( 'Z1', dSafeZ1, MCH_SIM_STEP.RAPID)
            end
               
            -- se movimento iniziale da Zmax
            if EMT.ZMAX or ( EMT.FLAG == 2 and EMT.FLAG2 == 1) then
               local dChSawEncumbrance = EMT.L3 + MillOffs - ( ( ChSawLen + MillOffs) * EMT.ADIR[3]) - ( EMT.TTOTLEN * EMT.TDIR[3])
               local dBeamQuote = DeltaTabZ + EMT.SB + 30
               if dChSawEncumbrance < dBeamQuote then
                  SimulMoveAxes( 'Z1', dSafeZ1, MCH_SIM_STEP.RAPID, 'B1', EMT.R2, MCH_SIM_STEP.COLLROT)
                  SimulMoveAxes( 'X1', EMT.L2, MCH_SIM_STEP.RAPID, 'C1', EMT.R1, MCH_SIM_STEP.COLLROT)
                  SimulMoveAxis( 'Z1', EMT.L3, MCH_SIM_STEP.RAPID)
               else
                  SimulMoveAxes( 'Z1', EMT.L3, MCH_SIM_STEP.RAPID, 'B1', EMT.R2, MCH_SIM_STEP.COLLROT)
               end
               -- se motosega molto lunga, ruoto prima di muovermi in X
               if EMT.TTOTLEN >= MinLengthLongCSaw then
                  SimulMoveAxis( 'C1', EMT.R1, MCH_SIM_STEP.COLLROT)
               end
            end
         end
      -- se altrimenti testa 2
      elseif nSetHead == 2 then
         local B2Home = EgtGetAxisHomePos( 'B2')
         local C2Home = EgtGetAxisHomePos( 'C2')
         local Z2Home = EgtGetAxisHomePos( 'Z2')
         local X2Home = EgtGetAxisHomePos( 'X2')
         local X2Pos = EgtGetAxisPos( 'X2')
         local B2Pos = EgtGetAxisPos( 'B2')
         local C2Pos = EgtGetAxisPos( 'C2')
         -- calcolo posizione reale
         EMT.TCPOSREAL = 'T' .. AdjustTcPos( false, EMT.TCPOS, EMT.R3)
         -- se utensile cambiato (controllo il reale perchè per aggregato dipende come è stato caricato)
         if EMT.PREVTCPOSREAL_H2 ~= EMT.TCPOSREAL then
            if EMT.ZMAX then
               -- se bisogna scrivere tutti gli assi
               if WriteAllCoordsOnFirstM101 then
                  if EMT.HEAD == 'H22' then
                     SimulMoveAxes( 'X2', ParkSawX2, MCH_SIM_STEP.RAPID, 'B2', 0, MCH_SIM_STEP.COLLROT, 'C2', C2Home, MCH_SIM_STEP.COLLROT)
                     X2Pos = ParkSawX2
                  else
                     SimulMoveAxes( 'X2', SafeX2, MCH_SIM_STEP.RAPID, 'B2', B2Home, MCH_SIM_STEP.COLLROT, 'C2', C2Home, MCH_SIM_STEP.COLLROT)
                     X2Pos = SafeX2
                  end
               else
                  if EMT.HEAD == 'H22' then
                     SimulMoveAxis( 'X2', ParkSawX2, MCH_SIM_STEP.RAPID)
                     X2Pos = ParkSawX2
                  else
                     SimulMoveAxis( 'X2', SafeX2, MCH_SIM_STEP.RAPID)
                     X2Pos = SafeX2
                  end
               end
               SimulMoveAxis( 'Z2', Z2Home, MCH_SIM_STEP.RAPID)
               -- Porto la X alla giusta quota
               if X2Pos > SafeX2RotAxis and ( B2Pos ~= EMT.R2  or  C2Pos ~= EMT.R1) then
                  SimulMoveAxis( 'X2', SafeX2RotAxis, MCH_SIM_STEP.RAPID)
               end
            end
         else
            -- solo se è macchina a 3 teste con gruppo truciolatore 4 assi, devo andare prima in Z0
            if EgtGetHeadId( 'H39') then
               SimulMoveAxis( 'Z2', Z2Home, MCH_SIM_STEP.RAPID)
            end
            -- Porto la X alla giusta quota
            if EMT.ZMAX and X2Pos > SafeX2RotAxis and ( B2Pos ~= EMT.R2  or  C2Pos ~= EMT.R1) then
               SimulMoveAxis( 'X2', SafeX2RotAxis, MCH_SIM_STEP.RAPID)
            end
         end
         if EMT.HEAD == 'H22' then
            SimulMoveAxes( 'B2', EMT.R2, MCH_SIM_STEP.COLLROT, 'C2', EMT.R1, MCH_SIM_STEP.COLLROT)
         end
         -- se ero a ZMAX, mi sposto in posizione a Z home. Ultimo movimento muove in Z
         if EMT.ZMAX then
            SimulMoveAxes( 'X2', EMT.L2, MCH_SIM_STEP.RAPID, 'B2', EMT.R2, MCH_SIM_STEP.COLLROT, 'C2', EMT.R1, MCH_SIM_STEP.COLLROT)
         end
      -- altrimenti testa 3
      else
         -- testa 4 assi dedicata truciolatore
         if EgtGetHeadId( 'H39') then
            if EMT.ZMAX then
               local MaxZ3 = EgtGetAxisMax( 'Z3')
               SimulMoveAxes( 'X3', EMT.L2, MCH_SIM_STEP.RAPID, 'Z3', MaxZ3, MCH_SIM_STEP.RAPID)
            end
            SimulMoveAxis( 'Z3', EMT.L3, MCH_SIM_STEP.RAPID)
            SimulMoveAxes( 'B3', EMT.R2, MCH_SIM_STEP.COLLROT, 'C3', EMT.R1, MCH_SIM_STEP.COLLROT)
         -- testa 5 assi dedicata lama
         elseif EgtGetHeadId( 'H38') then
            if EMT.ZMAX then
               local B3Home = EgtGetAxisHomePos( 'B3')
               local C3Home = EgtGetAxisHomePos( 'C3')
               SimulMoveAxis( 'X3', SafeX3RotAxis, MCH_SIM_STEP.RAPID)
               -- se non sono esattamente in home, devo ruotare in zona sicura
               if abs( C3Home - EMT.R1) > 1 or abs( B3Home - EMT.R2) > 1 then
                  EMT.L3 = min( EMT.L3, SafeZ3RotAxis)
                  SimulMoveAxis( 'Z3', SafeZ3RotAxis, MCH_SIM_STEP.RAPID)
                  SimulMoveAxes( 'B3', EMT.R2, MCH_SIM_STEP.COLLROT, 'C3', EMT.R1, MCH_SIM_STEP.COLLROT)
                  if ( EMT.L3 - SafeZ3RotAxis) > 100 * GEO.EPS_SMALL then
                     SimulMoveAxis( 'Z3', EMT.L3, MCH_SIM_STEP.RAPID)
                  end
                  SimulMoveAxis( 'X3', EMT.L2, MCH_SIM_STEP.RAPID)
               end
            end
            -- si muovono tutti gli assi assieme
         -- altre configurazioni
         else
            EmtSetLastError( 1212, "HEAD not managed")
         end
      end
   end
   -- se lavorazione split, muovo per riaggancio del carro Y1
   if EMT.SPLIT_Y1DELTA then
      EMT.Y1DELTA = EMT.SPLIT_Y1DELTA
      EMT.A1 = EMT.L1 + EMT.Y1DELTA
   end
   -- se Zmax dopo fine lavorazione
   if EMT.MOVE == 0 and EMT.FLAG == 3 then
      -- demando movimento completo a MoveEnd
      EMT.EnabAxes = false
      EMT.ShowAxes = true
   end
   -- se lavorazione in doppio
   if not ( EMT.MOVE == 0 and EMT.FLAG == 3) then 
      -- doppio in orizzontale
      if EMT.DOU_TYPE == 2 then
         -- se primo movimento, eseguo movimento rotatorio preliminare
         if EMT.ZMAX or ( EMT.FLAG == 2 and EMT.FLAG2 == 1) then
            SimulMoveAxis( 'X2', SafeX2, MCH_SIM_STEP.RAPID)
            SimulMoveAxes( 'C2', EMT.R1, MCH_SIM_STEP.RAPROT, 'B2', EMT.R2, MCH_SIM_STEP.RAPROT)
         else
            EMT.AuxAxes = 4 + 4
            EMT.A5n = 'X2'
            local X2 = Delta2TabY + EMT.DOU_TLEN - ( -DeltaTabY + EMT.L2 + EMT.TLEN + EgtIf( BD.RIGHT_LOAD, -EMT.HB, EMT.HB))
            if EMT.MCHTYPE == MCH_MY.DRILLING and EMT.FLAG == 101 then
               EMT.DOU_DRILL_END = -( EMT.L2p - EMT.L2)
               X2 = X2 + 2 * EMT.DOU_DRILL_END
            end
            EMT.A5 = X2
            EMT.A5m = nil
            EMT.A6n = 'Z2'
            local Z2 = -Head2Z + MillOffs + Mill2Offs + EMT.L3
            EMT.A6 = EgtClamp( Z2, MinZ2, MaxZ2)
            -- dato che la testa 2 è slave, bisogna controllare eventuale extra-corsa da post
            if not( EMT.MOVE == 0 and EMT.FLAG == 2) and Z2 > MaxZ2 then
               local sErr = EMT.MCHNAME..'  ==>  Out of Stroke: Z2 : '.. EgtNumToString( dZ2-MaxZ2, 2)
               EmtSetLastError( 1220, sErr, true)
               EgtOutBox( sErr, 'ERROR')
            end
            EMT.A6m = EgtIf( ( EMT.MOVE == 2 or EMT.MOVE == 3), 'Z1', nil)
            EMT.A7n = 'C2'
            EMT.A7 = EMT.R1
            EMT.A8n = 'B2'
            EMT.A8 = EMT.R2
            -- controllo minima distanza tra utensili di X1 e X2
            local dDeltaX1X2 = EMT.L2 - EMT.A5 - Head2Y
            local dDistToolX1X2 = -dDeltaX1X2 - MillOffs - EMT.TTOTLEN - Mill2Offs - EMT.DOU_TTOTLEN
            if dDistToolX1X2 < MinDistToolX1X2 then
               local sErr = 'X1 X2 tools in collision (distance=' .. EgtNumToString( dDistToolX1X2, 1) .. ')'
               EmtSetLastError( 1208, sErr)
            end
            -- se appena sopra inizio, sistemo subito la quota in Z
            if EMT.MOVE == 0 then
               SimulMoveAxes( 'Z2', EMT.A6, MCH_SIM_STEP.RAPID)
            end
         end
      -- doppio in verticale
      elseif EMT.DOU_TYPE == 3 then
         EMT.AuxAxes = 4 + 4
         EMT.A5n = 'X2'
         local X2 = -Head2Y + EMT.L2
         EMT.A5 = X2
         EMT.A5m = EgtIf( ( EMT.MOVE == 2 or EMT.MOVE == 3), 'X1', nil)
         EMT.A6n = 'Z2'
         local Z2 = Delta2TabZ - EMT.DOU_TLEN - ( -DeltaTabZ + EMT.L3 - EMT.TLEN - EMT.SB)
         if EMT.MCHTYPE == MCH_MY.DRILLING and EMT.FLAG == 101 then
            EMT.DOU_DRILL_END = EMT.L3p - EMT.L3
            Z2 = Z2 - 2 * EMT.DOU_DRILL_END
         end
         -- dato che la testa 2 è slave, bisogna controllare eventuale extra-corsa da post
         if not( EMT.MOVE == 0 and EMT.FLAG == 2) and Z2 < MinZ2 then
            local sErr = EMT.MCHNAME..'  ==>  Out of Stroke: Z2 : '.. EgtNumToString( MinZ2-Z2, 2)
            EmtSetLastError( 1220, sErr, true)
            EgtOutBox( sErr, 'ERROR')
         end
         EMT.A6 = EgtClamp( Z2, MinZ2, MaxZ2)
         EMT.A6m = nil
         EMT.A7n = 'C2'
         EMT.A7 = EMT.R1
         EMT.A8n = 'B2'
         EMT.A8 = EMT.R2
         -- controllo minima distanza tra utensili di X1 e X2 (lungo Z)
         local dDeltaZ1Z2 = EMT.L3 - EMT.A6 - Head2Z
         local dDistToolZ1Z2 = dDeltaZ1Z2 - EMT.TTOTLEN - EMT.DOU_TTOTLEN
         if dDistToolZ1Z2 < MinDistToolX1X2 then
            local sErr = 'Z1 Z2 tools in collision (distance=' .. EgtNumToString( dDistToolZ1Z2, 1) .. ')'
            EmtSetLastError( 1208, sErr)
         end
      end
   end
   -- se necessario ...
   if EMT.MCHFIRST and EMT.EnabAxes ~= false then
      if nSetHead == 1 then
         SimulMoveAxes( 'X1', EMT.L2, MCH_SIM_STEP.RAPID,
                        'Z1', EMT.L3, MCH_SIM_STEP.RAPID,
                        'C1', EMT.R1, MCH_SIM_STEP.COLLROT,
                        'B1', EMT.R2, MCH_SIM_STEP.COLLROT)
         if EMT.DOU_TYPE and EMT.AuxAxes == 8 then
            SimulMoveAxes( 'X2', EMT.A5, MCH_SIM_STEP.RAPID,
                           'Z2', EMT.A6, MCH_SIM_STEP.RAPID,
                           'C2', EMT.A7, MCH_SIM_STEP.COLLROT,
                           'B2', EMT.A8, MCH_SIM_STEP.COLLROT)
         end
      elseif nSetHead == 2 then
         SimulMoveAxes( 'X2', EMT.L2, MCH_SIM_STEP.RAPID,
                        'Z2', EMT.L3, MCH_SIM_STEP.RAPID,
                        'C2', EMT.R1, MCH_SIM_STEP.COLLROT,
                        'B2', EMT.R2, MCH_SIM_STEP.COLLROT)
      else
         SimulMoveAxes( 'X3', EMT.L2, MCH_SIM_STEP.RAPID,
                        'Z3', EMT.L3, MCH_SIM_STEP.RAPID,
                        'C3', EMT.R1, MCH_SIM_STEP.COLLROT,
                        'B3', EMT.R2, MCH_SIM_STEP.COLLROT)
      end
   end
   if nRes ~= 0 then
      EMT.A3 = EgtIf( GetV1ToClose(), EMT.V1NEXTPOS, ParkV1)
      EMT.A4 = EgtIf( GetV2ToClose(), EMT.V2NEXTPOS, ParkV2)
   end
   EMT.ZMAX = nil
   EMT.TO_ZMAX = nil

   -- se aggregato flottante su inizio attacco va compresso
   if EMT.TFLOAT and not EMT.TFLOAT_CMP and EMT.MOVE == 1 and EgtGetName( EMT.MOVEID) == 'LI' then
      -- recupero lunghezza 
      local dOffsL = - EgtGetMachiningParam( MCH_MP.OFFSL)
      -- imposto compressione della parte flottante
      SetFloatPos( EMT.HEAD, dOffsL)
      -- imposto dati utensile in posizione compressa
      EmtSetToolForVmill( EMT.TOOL, EMT.HEAD, EMT.EXIT, EMT.VMILL, 2, -dOffsL)
      -- dichiaro che è compresso (assegnando Id entità di movimento)
      EMT.TFLOAT_CMP = EMT.MOVEID
   end
end

---------------------------------------------------------------------
function OnSimulMoveEnd()
   -- gruppo della testa
   local nHSet = GetHeadSet( EMT.HEAD)
   -- rimozione eventuali sfridi
   if EMT.FLAG == 301 then 
      ExecRemoveScraps()
   end
   -- se primo rapido della lavorazione
   if EMT.MCHFIRST and EMT.MOVE == 0 then
      -- se lavorazione split, dichiaro carro Y1 riagganciato
      if EMT.SPLIT_Y1DELTA then
         EMT.SPLIT_Y1DELTA = nil
         ExecMovePY1( true)
      end
      -- chiusura o apertura rulli V1
      if GetV1ToClose() then
         ExecCloseRoller( 1)
      else
         ExecOpenRoller( 1)
      end
      -- chiusura o apertura rulli V2
      if GetV2ToClose() then
         ExecCloseRoller( 2)
      else
         ExecOpenRoller( 2)
      end
      EMT.MCHFIRST = false
      EgtOutText( '')
   end
   -- se movimento finale di foratura in doppio in Z
   if EMT.MCHTYPE == MCH_MY.DRILLING and EMT.FLAG == 101 then
      if EMT.DOU_TYPE == 2 then
         SimulMoveAxes( 1, 'X1', EMT.L2 - 2 * EMT.DOU_DRILL_END, MCH_SIM_STEP.FEED,
                           'X2', EMT.A5 - 2 * EMT.DOU_DRILL_END, MCH_SIM_STEP.FEED)
         SimulMoveAxes( 1, 'X1', EMT.L2 - EMT.DOU_DRILL_END, MCH_SIM_STEP.FEED,
                           'X2', EMT.A5 - EMT.DOU_DRILL_END, MCH_SIM_STEP.FEED)
      elseif EMT.DOU_TYPE == 3 then
         SimulMoveAxes( 1, 'Z1', EMT.L3 + 2 * EMT.DOU_DRILL_END, MCH_SIM_STEP.FEED,
                           'Z2', EMT.A6 + 2 * EMT.DOU_DRILL_END, MCH_SIM_STEP.FEED)
         SimulMoveAxes( 1, 'Z1', EMT.L3 + EMT.DOU_DRILL_END, MCH_SIM_STEP.FEED,
                           'Z2', EMT.A6 + EMT.DOU_DRILL_END, MCH_SIM_STEP.FEED)
      end
   end
   -- se Zmax dopo fine lavorazione
   if EMT.MOVE == 0 and EMT.FLAG == 3 then
      -- eventuale rimozione sfridi
      ExecRemoveScraps()
      -- ricavo prossimo utensile
      local sNextTool = GetNextTool( EMT.MCHID)
      -- vado in home se è ultimo movimento ed è ultima lavorazione
      local bToXhome = ( IsLastPath( EMT.PATHID) and not sNextTool and nHSet == 3) or EMT.HEAD == 'H38' or ( nHSet == 1 and EMT.TTOTLEN > LongTool) 
      -- eseguo
      ExecMoveZmax( EMT.MCHSPLIT, bToXhome)
      EMT.TO_ZMAX = nil
   end

   -- se utensile flottante e movimento di compressione
   if EMT.TFLOAT and EMT.MOVEID == EMT.TFLOAT_CMP then
      -- verifico ci sia stata collisione tra ghiera flottante (portautensile e pezzo)
      if not EMT.TFLOAT_TH_COMPR_COLL then
         local sErr = 'CUTID='..tostring( EMT.CUTID)..'; TASKID='..tostring( EMT.TASKID)..'; Mach='..EMT.MCHNAME..'; Floating Ring not compressed on approach'
         EmtSetLastError( 1222, sErr)
         EgtOutLog( 'Error : ' .. sErr, 1)
      else
         EgtOutLog( 'Floating Ring compressed on approach (MOVEID='..EgtNumToString( EMT.MOVEID)..')', 1)
      end
      EMT.TFLOAT_TH_COMPR_COLL = nil
   end
   -- se aggregato flottante su fine uscita va rilasciato
   if EMT.TFLOAT and EMT.TFLOAT_CMP and EMT.MOVE == 1 and EgtGetName( EMT.MOVEID) == 'LO' and EgtGetName( EgtGetNext( EMT.MOVEID) or GDB_ID.NULL) ~= 'LO' then
      -- reset compressione della parte flottante
      SetFloatPos( EMT.HEAD, 0)
      -- dichiaro che è rilasciato
      EMT.TFLOAT_CMP = nil
   end   
end

---------------------------------------------------------------------
function OnSimulCollision()
   -- se prima collisione della lavorazione, la segnalo
   if EMT.MCHNAME ~= EMT.LAST_MCHNAME_COLLIDE then
      -- speciale per utensile flottante (suo holder contro il grezzo)
      if EMT.TFLOAT and EMT.TFLOAT_CMP and EMT.SIMCOBIND == 1002 and EMT.SIMVMID == EMT.VMILL[1] then
         if EMT.MOVEID == EMT.TFLOAT_CMP then
            EMT.TFLOAT_TH_COMPR_COLL = true
         end
         return
      end
      local Class = ''
      if EMT.SIMCOBIND == 1001 or EMT.SIMCOBIND == 1011 then
         Class = 'T_'..EMT.HEAD
      elseif EMT.SIMCOBIND == 1002 or EMT.SIMCOBIND == 1012 then
         Class = 'TH_'..EMT.HEAD
      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 = 11
      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, bPathStart)
   -- analizzo il comando
   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( Cmd[2])
   elseif Cmd[1] == '1' then
      if Cmd[2] ~= 'Z' then
         ExecOpenRoller( 1)
         ExecOpenRoller( 2)
         local sV1, MoveV1, sV2, MoveV2 = CalcMoveV1V2ForAuxCmd( Cmd)
         local bOk, bOk1, bOk2, bOk3 = SimulMoveAxes( Cmd[2], tonumber( Cmd[3]), MCH_SIM_STEP.RAPID,
                                                      sV1, MoveV1, MCH_SIM_STEP.RAPID,
                                                      sV2, MoveV2, MCH_SIM_STEP.RAPID)
         if not bOk then
            if not bOk1 then
               if VerifyY1Y2Stroke( Cmd[2], tonumber( Cmd[3])) == nil then 
                  EgtOutLog( 'Error on ExecAuxCmd : ' .. sCmd)
               end
            elseif not bOk2 then
               VerifyV1V2Stroke( sV1, MoveV1)
            elseif not bOk3 then
               VerifyV1V2Stroke( sV2, MoveV2)
            end
         end
      end
   elseif Cmd[1] == '2' then
      -- Verifico movimento carrello con trave agganciata
      VerifyOneChariotSlide( Cmd[2], Cmd[3], Cmd[4], Cmd[5])
      -- Eseguo i movimenti necessari
      ExecOpenRoller( 1)
      ExecOpenRoller( 2)
      local sV1, MoveV1, sV2, MoveV2 = CalcMoveV1V2ForAuxCmd( Cmd)
      local bOk, bOk1, bOk2, bOk3, bOk4 =  SimulMoveAxes( Cmd[2], tonumber( Cmd[3]), MCH_SIM_STEP.RAPID,
                                                          Cmd[4], tonumber( Cmd[5]), MCH_SIM_STEP.RAPID,
                                                          sV1, MoveV1, MCH_SIM_STEP.RAPID,
                                                          sV2, MoveV2, MCH_SIM_STEP.RAPID)
      if not bOk then
         if not bOk1 or not bOk2 then
            local nI = EgtIf( not bOk1, 2, 4)
            if VerifyY1Y2Stroke( Cmd[nI], tonumber( Cmd[nI+1])) == nil then 
               EgtOutLog( 'Error on ExecAuxCmd : ' .. sCmd)
            end
         elseif not bOk3 then
            VerifyV1V2Stroke( sV1, MoveV1)
         elseif not bOk4 then
            VerifyV1V2Stroke( sV2, MoveV2)
         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 i movimenti necessari
      ExecOpenRoller( 1)
      ExecOpenRoller( 2)
      local sV1, MoveV1, sV2, MoveV2 = CalcMoveV1V2ForAuxCmd( Cmd)
      local bOk, bOk1, bOk2, bOk3, bOk4, bOk5 = 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,
                                                               sV1, MoveV1, MCH_SIM_STEP.RAPID,
                                                               sV2, MoveV2, MCH_SIM_STEP.RAPID)
      if not bOk then
         if not bOk1 or not bOk2 or not bOk3 then
            local nI = EgtIf( not bOk1, 2, EgtIf( not bOk2, 4, 6))
            if VerifyY1Y2Stroke( Cmd[nI], tonumber( Cmd[nI+1])) == nil then 
               EgtOutLog( 'Error on ExecAuxCmd : ' .. sCmd)
            end
         elseif not bOk4 then
            VerifyV1V2Stroke( sV1, MoveV1)
         elseif not bOk5 then
            VerifyV1V2Stroke( sV2, MoveV2)
         end
      end
   elseif Cmd[1] == '4' then
      ExecMoveHome( Cmd[2] == '1', EgtIf( bPathStart, false, EMT.MCHSPLIT))
   elseif Cmd[1] == '11' then
      local bClose = Cmd[2] ~= '0'
      if bPathStart and EMT.MCHSPLIT and not ( EMT.FALL or EMT.TO_FALL) and GetPY2Light() then bClose = false end
      ExecMovePY1( bClose)
   elseif Cmd[1] == '12' then
      ExecMovePY2( Cmd[2] ~= '0')
   elseif Cmd[1] == '21' then
      local nY1Delta = tonumber( Cmd[2])
      local nY2Delta = tonumber( Cmd[3])
      EMT.Y1DELTA = EgtIf( nY1Delta > 0.01, nY1Delta, nil)
      EMT.Y2DELTA = EgtIf( nY2Delta > 0.01, nY2Delta, nil)
   elseif Cmd[1] == '22' then
      EMT.V1NEXTPOS = tonumber( Cmd[2])
      EMT.V2NEXTPOS = tonumber( Cmd[3])
   elseif Cmd[1] == '31' then
      local nRawId = tonumber( Cmd[2])
      EmtUnlinkRawPartFromGroup( nRawId)
      EmtLinkRawPartToGroup( nRawId, Cmd[3])
      EMT.Y1SPEC = true
   elseif Cmd[1] == '99' then
      EMT.ERR = Cmd[2]
      EmtSetLastError( 1200 + EMT.ERR, Cmd[3])
   end
end

---------------------------------------------------------------------
function CalcMoveV1V2ForAuxCmd( Cmd)
   -- verifico se necessario muovere V1
   local MoveV1
   local MoveY1 = GetCmdAxMove( Cmd, 'Y1')
   local V1Pos = EgtGetAxisPos( 'V1')
   if MoveY1 and V1Pos > MoveY1 - MinDeltaYV + 0.1 then
      MoveV1 = MoveY1 - MinDeltaYV
   end
   local sV1 = EgtIf( MoveV1, 'V1', nil)
   -- verifico se necessario muovere V2
   local MoveV2
   local MoveY2 = GetCmdAxMove( Cmd, 'Y2')
   local V2Pos = EgtGetAxisPos( 'V2')
   if MoveY2 and V2Pos < MoveY2 + MinDeltaYV - 0.1 then
      MoveV2 = MoveY2 + MinDeltaYV
   end
   local sV2 = EgtIf( MoveV2, 'V2', nil)
   -- restituisco i risultati
   if MoveV1 and MoveV2 then
      return sV1, MoveV1, sV2, MoveV2
   elseif MoveV1 then
      return sV1, MoveV1
   elseif MoveV2 then
      return sV2, MoveV2
   end
end

---------------------------------------------------------------------
function ExecStartHome()
   -- Testa 1
   EgtResetAxisPos( 'X1')
   EgtSetAxisPos( 'Z1', MaxZ1)
   EgtResetAxisPos( 'C1')
   EgtResetAxisPos( 'B1')
   -- Testa 2
   local _, sHead, _, _, _, _ = FindFirstToolOnHeadSet( 2)
   local PosXStart = EgtIf( sHead == 'H22', SafeX2, ParkX2)
   EgtSetAxisPos( 'X2', PosXStart)
   EgtSetAxisPos( 'Z2', MinZ2)
   EgtResetAxisPos( 'C2')
   EgtResetAxisPos( 'B2')
end

---------------------------------------------------------------------
function ExecMoveHome( bNearV, bMchSplit)
   -- risalita a Zmax
   ExecMoveZmax( bMchSplit, true)
   -- se testa sotto e macchina a 3 teste
   if GetHeadSet( EMT.HEAD) == 2 and IsHeadExisting( 3) then
      if not SimulMoveAxis( 'X2', EgtIf( EMT.HEAD == 'H22', SafeX2, ParkX2), MCH_SIM_STEP.RAPID) then
         EgtOutLog( 'Error on MoveHome : X2')
      end
      --  se gruppo truciolatore a 4 assi
      if EgtGetHeadId( 'H39') then
         if not SimulMoveAxis( 'Z2', ParkInLavZ2, MCH_SIM_STEP.RAPID) then
           EgtOutLog( 'Error on MoveHome : Z2')
         end
      end
   end
   EMT.TO_ZMAX = nil
   -- se richiesto, avvicino i rulli
   if bNearV then
      ExecOpenRoller( 1)
      ExecOpenRoller( 2)
      if not SimulMoveAxes( 'V1', MinV1, MCH_SIM_STEP.RAPID,
                            'V2', MaxV2, MCH_SIM_STEP.RAPID) then
         EgtOutLog( 'Error on MoveHome : NearV1V2')
      end
   end
end

---------------------------------------------------------------------
function ExecMoveZmax( bMchSplit, bGoToHome)
   -- set della testa
   local nSetHead = GetHeadSet( EMT.HEAD)
   -- posizioni correnti degli assi testa
   local CurrX = EgtGetAxisPos( 'X'..nSetHead)
   local CurrZ = EgtGetAxisPos( 'Z'..nSetHead)
   local CurrC = EgtGetAxisPos( 'C'..nSetHead)
   local CurrB = EgtGetAxisPos( 'B'..nSetHead)
   -- posizioni home degli assi testa
   local HomeX = EgtGetAxisHomePos( 'X'..nSetHead)
   local HomeZ = EgtGetAxisHomePos( 'Z'..nSetHead)
   local HomeC = EgtGetAxisHomePos( 'C'..nSetHead)
   local HomeB = EgtGetAxisHomePos( 'B'..nSetHead)
   -- verifico se necessario ruotare la testa
   local bRot = ( abs( HomeC - CurrC) > 1 or abs( HomeB - CurrB) > 1)
   -- se necessario ruotare la testa, allargo i carrelli
   if RollerParkingNeeded( EMT.HEAD, CurrC, CurrB, HomeC, HomeB) then
      ExecOpenRoller( 1)
      ExecOpenRoller( 2)
      local bAgg = EgtExistsInfo( EMT.PATHID, 'CNT')
      ExecParkRoller( nil, nil, nil, nil, bMchSplit, bAgg)
   end
   -- se testa sopra
   if nSetHead == 1 then
      local MyMaxZ1 = EgtGetAxisMax( 'Z1')
      -- se fresa o lama
      if EMT.HEAD ~= 'H13' then
         if abs( HomeC - CurrC) > 0.1 or abs( HomeB - CurrB) > 0.1 then
            local dZref = HomeZ + GetZExtra( EMT.HEAD, CurrB)
            SimulMoveAxes( 'Z1', EgtIf( dZref > CurrZ, dZref, CurrZ), MCH_SIM_STEP.RAPID, 'B1', EgtClamp( CurrB, -90, 90), MCH_SIM_STEP.COLLROT)
            if ( EMT.HEAD == 'H12' or EMT.HEAD == 'H16') and ( abs( HomeC - CurrC) > 30.1 or abs( HomeB - CurrB) > 30.1) then
               SimulMoveAxes( 'Z1', MyMaxZ1, MCH_SIM_STEP.RAPID, 'B1', 0, MCH_SIM_STEP.COLLROT)
            end
            if  ( not BD.RIGHT_LOAD and EMT.L2 > DeltaTabY) or ( BD.RIGHT_LOAD and EMT.L2 < DeltaTabY) then
               SimulMoveAxis( 'X1', DeltaTabY, MCH_SIM_STEP.RAPID)
            end
            SimulMoveAxis( 'C1', HomeC, MCH_SIM_STEP.COLLROT)
            SimulMoveAxis( 'B1', HomeB, MCH_SIM_STEP.COLLROT)
         end
         SimulMoveAxis( 'Z1', MyMaxZ1, MCH_SIM_STEP.RAPID)
         if bGoToHome then
            SimulMoveAxis( 'X1', ParkX1, MCH_SIM_STEP.RAPID)
         end
      -- altrimenti sega a catena
      else
         -- salgo in Z sicurezza raddrizzando la B
         local SafeZ1 = EgtIf( EMT.TTOTLEN < MinLengthLongCSaw, ParkCSawZ1, ParkLongCSawZ1)
         SimulMoveAxes( 'Z1', SafeZ1, MCH_SIM_STEP.RAPID, 'B1', HomeB, MCH_SIM_STEP.COLLROT)
         SimulMoveAxes( 'X1', HomeX, MCH_SIM_STEP.RAPID, 'C1', HomeC, MCH_SIM_STEP.COLLROT)
      end
      -- se lavorazione in doppio
      if EMT.DOU_TYPE == 2 then
         SimulMoveAxis( 'X2', SafeX2, MCH_SIM_STEP.RAPID)
         SimulMoveAxis( 'Z2', ParkZ2, MCH_SIM_STEP.RAPID)
         SimulMoveAxes( 'B2', ParkB2, MCH_SIM_STEP.COLLROT, 'C2', ParkC2, MCH_SIM_STEP.COLLROT)
         SimulMoveAxis( 'X2', ParkX2, MCH_SIM_STEP.RAPID)
         EMT.DOU_TO_ZMAX = nil
      elseif EMT.DOU_TYPE == 3 then
         SimulMoveAxis( 'Z2', ParkZ2, MCH_SIM_STEP.RAPID)
         SimulMoveAxes( 'B2', ParkB2, MCH_SIM_STEP.COLLROT, 'C2', ParkC2, MCH_SIM_STEP.COLLROT)
         SimulMoveAxis( 'X2', ParkX2, MCH_SIM_STEP.RAPID)
         EMT.DOU_TO_ZMAX = nil
      end
   -- se altrimenti testa sotto
   elseif nSetHead == 2 then
      SimulMoveAxis( 'Z2', ParkZ2, MCH_SIM_STEP.RAPID)
      SimulMoveAxes( 'B2', ParkB2, MCH_SIM_STEP.COLLROT, 'C2', ParkC2, MCH_SIM_STEP.COLLROT)
      if EMT.HEAD == 'H22' then
         SimulMoveAxis( 'X2', SafeX2, MCH_SIM_STEP.RAPID)
      else
         SimulMoveAxis( 'X2', ParkX2, MCH_SIM_STEP.RAPID)
      end
      -- solo se è macchina a 3 teste con gruppo truciolatore 4 assi, devo stare più alto
      if EgtGetHeadId( 'H39') then
         SimulMoveAxis( 'Z2', ParkInLavZ2, MCH_SIM_STEP.RAPID)
      end
   -- altrimenti testa 3
   else
      if EgtGetHeadId( 'H39') then
         SimulMoveAxes( 'Z3', MaxZ3, MCH_SIM_STEP.RAPID)
         SimulMoveAxes( 'B3', ParkB3, MCH_SIM_STEP.COLLROT)
      elseif EgtGetHeadId( 'H38') then
         local MyMaxZ3 = EgtGetAxisMax( 'Z3')
         -- se non sono esattamente in home, ruoto dove sono
         if abs( HomeC - CurrC) > 1 or abs( HomeB - CurrB) > 1 then
            -- SimulMoveAxis( 'X3', SafeX3RotAxis, MCH_SIM_STEP.RAPID)
            SimulMoveAxis( 'Z3', SafeZ3RotAxis, MCH_SIM_STEP.RAPID)
            SimulMoveAxes( 'C3', HomeC, MCH_SIM_STEP.COLLROT, 'B3', HomeB, MCH_SIM_STEP.COLLROT)
         end
         SimulMoveAxis( 'Z3', MyMaxZ3, MCH_SIM_STEP.RAPID)
         if bGoToHome then
            SimulMoveAxis( 'X3', ParkX3, MCH_SIM_STEP.RAPID)
         end
      else
         EmtSetLastError( 1212, "HEAD not managed")
      end
   end
   EMT.ZMAX = true
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, EgtIf( BD.RIGHT_LOAD, ( EMT.HB + 50.0), -( EMT.HB + 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, EgtIf( BD.RIGHT_LOAD, 1600, -1600), 0)
      if EMT.FALL then vtMove = Vector3d( 0, EgtIf( BD.RIGHT_LOAD, 3200, -2600), -1150) end
      EgtMove( nLayId, vtMove, GDB_RT.GLOB)
      EgtSetLevel( vMillId, GDB_LV.USER)
      -- aggiungo gli spigoli
      if EgtVolZmapSetShowEdges then
         EgtVolZmapSetShowEdges( vMillId, true)
      else
         local nFirstId, nCount = EgtVolZmapGetEdges( vMillId, nLayId)
         if nFirstId then
            for nId = nFirstId, nFirstId + nCount - 1 do
               EgtSetColor( nId, Color3d( 96, 96, 96))
            end
         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)
               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

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

   local function GetCurveListFromIntersection( sPosIntersPlane, sIntersPlane, dDepth)
      local idCurveList = {}
      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 idLoop, nLoopCnt = EgtPlaneVolZmapInters( ptPosIntersPlane, vtIntersPlane, EMT.VMILL[i], CLAMP_CHECK_INTERS, GDB_RT.GLOB)
         -- se c'è almeno una curva
         if idLoop then
            for j = 1, nLoopCnt do
               local idLoopTemp = idLoop + j - 1
               table.insert( idCurveList, idLoopTemp)
            end
         end
      end
      return idCurveList
   end

   local function CalculateIntersectionArea( sPosIntersPlane, sIntersPlane, dDepth)
      -- test piano frontale
      local idCurveList = GetCurveListFromIntersection( sPosIntersPlane, sIntersPlane, dDepth)

      -- si copia curva intersezione e curva pinza in gruppo di confronto
      local idFlatSurf, nFlatSurfCnt = EgtSurfFlatRegion( CLAMP_CHECK_GROUP, idCurveList)
      local idClampSurf = EgtSurfFlatRegion( CLAMP_CHECK_GROUP, idClampPath)

      if idFlatSurf then
         local dTotalArea = 0
         local dTotalXLenght = 0
         for k = 1, nFlatSurfCnt do
            local idTempSurf = idFlatSurf + k - 1
            EgtSurfFrIntersect( idTempSurf, idClampSurf)
            if idTempSurf then
               dTotalArea = dTotalArea + ceil( EgtSurfArea( idTempSurf) or 0)
               local b3BoxIntersectionBox = EgtGetBBoxGlob( idTempSurf, GDB_BB.STANDARD)
               if b3BoxIntersectionBox then
                  dTotalXLenght = dTotalXLenght + ceil( b3BoxIntersectionBox:getDimX()) -- somma lunghezze (x) delle aree pinzate
               end
            end
         end
         return dTotalArea, dTotalXLenght
      end
      return 0
   end

   -- minima area considerata per un corretto pinzaggio
   DistZClampToTable = DistZClampToTable or 0
   local MinJoin = BD.GetMinJoin( EMT.HB, EMT.SB, EgtIf( EMT.SPLIT, EMT.LT, EMT.LB))
   local MinZClamping =  min( b3ClampingArea:getDimZ() + DistZClampToTable, EMT.SB) - DistZClampToTable
   -- si moltiplica per un coefficiente minimo sotto al quale si da l'errore di pinzaggio
   ClampingCoeffMin = EgtClamp( ClampingCoeffMin, 0.01, 1)
   local dMinClampingAreaWarn = ( MinJoin * MinZClamping) * ClampingCoeffMin
   local dMinClampingAreaErr = ( MinJoin * MinZClamping) * ( 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 = 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 ExecMovePY1( bClose)
   SimulMoveAxes( 'PY1', EgtIf( not bClose, MaxHoOpen, EMT.HB), MCH_SIM_STEP.RAPID)
   SetPY1Light( bClose)
   if bClose then
      CheckClamping( 'PY1')
   end
end

---------------------------------------------------------------------
function ExecMovePY2( bClose)
   SimulMoveAxes( 'PY2', EgtIf( not bClose, MaxHoOpen, EMT.HB), MCH_SIM_STEP.RAPID)
   SetPY2Light( bClose)
   if bClose then
      CheckClamping( 'PY2')
   end
end

---------------------------------------------------------------------
local function LinkRemainingPartsToY1()
   local nCurrOrd = GetPhaseOrd( EMT.PHASE)
   local TabId = EgtGetTableId( 'Tab')
   local RawId = EgtGetFirstInGroup( TabId)
   while RawId do
      local NextRawId = EgtGetNext( RawId)
      if EgtGetName( RawId) == 'RawPart' then
         local nRawOrd = EgtGetInfo( RawId, 'ORD', 'i')
         if nRawOrd and nRawOrd ~= nCurrOrd then
            local bUlOk = EmtUnlinkRawPartFromGroup( RawId)
            local bLkOk = EmtLinkRawPartToGroup( RawId, 'Y1')
         end
      end
      RawId = NextRawId
   end
end

---------------------------------------------------------------------
function ExecParkRoller( PosY1, PosY2, PosV1, PosV2, bSpliCut, bAgg)
   -- Posizioni
   local PosT = EgtGetAxisPos( 'T')
   PosY1 = PosY1 or EgtGetAxisPos( 'Y1')
   PosY2 = PosY2 or EgtGetAxisPos( 'Y2')
   PosV1 = PosV1 or EgtGetAxisPos( 'V1')
   PosV2 = PosV2 or EgtGetAxisPos( 'V2')
   -- Parcheggi pinze
   local MyParkY1 = ParkY1 + EgtIf( bAgg, AggLoad, 0)
   local MyParkY2 = ParkY2
   -- Apro i rulli, se necessario
   if abs( ParkV1 - PosV1) > 0.1 then ExecOpenRoller( 1) end
   if abs( ParkV2 - PosV2) > 0.1 then ExecOpenRoller( 2) end
   -- Eseguo spostamenti
   if EMT.Y1DELTA and EMT.Y2DELTA then
      local DiffY1 = MyParkY1 - PosY1
      local DiffY2 = MyParkY2 - PosY2
      -- se appena eseguito taglio di separazione
      if bSpliCut then
         -- aggancio i pezzi rimanenti all'asse Y1
         LinkRemainingPartsToY1()
         -- eseguo i movimenti
         if not SimulMoveAxes( 'T',  PosT + min( DiffY2, 0.0), MCH_SIM_STEP.RAPID,
                               'V1', ParkV1, MCH_SIM_STEP.RAPID,
                               'V2', ParkV2, MCH_SIM_STEP.RAPID,
                               'Y1', PosY1 + max( DiffY1, 0.0), MCH_SIM_STEP.RAPID,
                               'Y2', PosY2 + min( DiffY2, 0.0), MCH_SIM_STEP.RAPID) then
            EMT.ERR = 2
            EmtSetLastError( 1202, 'Error on MoveAxes in ParkRoller (12A)')
         end
         return 32
      elseif DiffY1 > 0.1 and DiffY2 < -0.1 then
         EMT.ERR = 2
         EmtSetLastError( 1201, 'Error Collision in ParkRoller')
      elseif DiffY1 > 0.1  then
         if not SimulMoveAxes( 'T',  PosT + DiffY1, MCH_SIM_STEP.RAPID,
                               'V1', ParkV1, MCH_SIM_STEP.RAPID,
                               'V2', ParkV2, MCH_SIM_STEP.RAPID,
                               'Y1', PosY1 + DiffY1, MCH_SIM_STEP.RAPID,
                               'Y2', PosY2 + DiffY1, MCH_SIM_STEP.RAPID) then
            EMT.ERR = 2
            EmtSetLastError( 1202, 'Error on MoveAxes in ParkRoller (12B)')
         end
         return 12
      elseif DiffY2 < -0.1 then
         if not SimulMoveAxes( 'T',  PosT + DiffY2, MCH_SIM_STEP.RAPID,
                               'V1', ParkV1, MCH_SIM_STEP.RAPID,
                               'V2', ParkV2, MCH_SIM_STEP.RAPID,
                               'Y1', PosY1 + DiffY2, MCH_SIM_STEP.RAPID,
                               'Y2', PosY2 + DiffY2, MCH_SIM_STEP.RAPID) then
            EMT.ERR = 2
            EmtSetLastError( 1203, 'Error on MoveAxes in ParkRoller (12C)')
         end
         return 21
      else
         if not SimulMoveAxes( 'V1', ParkV1, MCH_SIM_STEP.RAPID,
                               'V2', ParkV2, MCH_SIM_STEP.RAPID) then
            EMT.ERR = 2
            EmtSetLastError( 1204, 'Error on MoveAxes in MoveStart')
         end
         return 30
      end
   elseif EMT.Y1DELTA then
      local MoveV1 = ParkV1 - PosV1
      local DiffY1 = MyParkY1 - PosY1
      local MoveY1 = EgtIf( DiffY1 > 0.1, MoveV1, 0)
      local TryMoveY1 = min( ParkV1 - PosT - EgtIf( bSpliCut or EMT.FALL, EMT.LT, 0), MaxY1 - PosY1 - 10)
      if ( PosT > ParkV2 - ExtraParkV and PosT < ParkV1 and PosY1 + TryMoveY1 < MaxY1) then MoveY1 = max( MoveY1, TryMoveY1) end
      -- se appena eseguito taglio di separazione
      if bSpliCut then
         -- aggancio i pezzi rimanenti all'asse Y1
         LinkRemainingPartsToY1()
         -- eseguo movimenti
         if not SimulMoveAxes( 'T',  PosT, MCH_SIM_STEP.RAPID,
                               'V1', ParkV1, MCH_SIM_STEP.RAPID,
                               'V2', ParkV2, MCH_SIM_STEP.RAPID,
                               'Y1', PosY1 + MoveY1, MCH_SIM_STEP.RAPID,
                               'Y2', MyParkY2, MCH_SIM_STEP.RAPID) then
            EMT.ERR = 2
            EmtSetLastError( 1205, 'Error on MoveAxes in ParkRoller (1)')
         end
      else
         if not SimulMoveAxes( 'T',  PosT + MoveY1, MCH_SIM_STEP.RAPID,
                               'V1', ParkV1, MCH_SIM_STEP.RAPID,
                               'V2', ParkV2, MCH_SIM_STEP.RAPID,
                               'Y1', PosY1 + MoveY1, MCH_SIM_STEP.RAPID,
                               'Y2', MyParkY2, MCH_SIM_STEP.RAPID) then
            EMT.ERR = 2
            EmtSetLastError( 1205, 'Error on MoveAxes in ParkRoller (1)')
         end
      end
      return 11
   elseif EMT.Y2DELTA then
      local MoveV2 = ParkV2 - PosV2
      local DiffY2 = MyParkY2 - PosY2
      local MoveY2 = EgtIf( DiffY2 < -0.1, MoveV2, 0)
      local TryMoveY2 = max( ParkV2 - PosT - EMT.LT, MinY2 - PosY2 + 10)
      if ( PosT + EMT.LT < ParkV1 + ExtraParkV and PosT + EMT.LT > ParkV2 and PosY2 + TryMoveY2 > MinY2) then MoveY2 = min( MoveY2, TryMoveY2) end
      if not SimulMoveAxes( 'T',  PosT + MoveY2, MCH_SIM_STEP.RAPID,
                            'V1', ParkV1, MCH_SIM_STEP.RAPID,
                            'V2', ParkV2, MCH_SIM_STEP.RAPID,
                            'Y1', EgtIf( PosY1 > MyParkY1, PosY1, MyParkY1), MCH_SIM_STEP.RAPID,
                            'Y2', PosY2 + MoveY2, MCH_SIM_STEP.RAPID) then
         EMT.ERR = 2
         EmtSetLastError( 1206, 'Error on MoveAxes in ParkRoller (2)')
      end
      return 22
   else
      if not SimulMoveAxes( 'V1', ParkV1, MCH_SIM_STEP.RAPID,
                            'V2', ParkV2, MCH_SIM_STEP.RAPID,
                            'Y1', MyParkY1, MCH_SIM_STEP.RAPID,
                            'Y2', MyParkY2, MCH_SIM_STEP.RAPID) then
         EMT.ERR = 2
         EmtSetLastError( 1207, 'Error on MoveAxes in ParkRoller (0)')
      end
      return 0
   end
end

---------------------------------------------------------------------
function ExecCloseRoller( nInd) 
   EgtSetAxisPos( EgtIf( nInd == 1, 'PV1', 'PV2'), EMT.HB)
   EgtSetAxisPos( EgtIf( nInd == 1, 'QV1', 'QV2'), EMT.SB)
end

---------------------------------------------------------------------
function ExecOpenRoller( nInd)
   EgtSetAxisPos( EgtIf( nInd == 1, 'PV1', 'PV2'), MaxHoOpen)
   EgtSetAxisPos( EgtIf( nInd == 1, 'QV1', 'QV2'), MaxVeOpen)
end

---------------------------------------------------------------------
function ExecRemoveScraps()
   -- 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
      local vMillId = EMT.VMILL[1]
      local nPart = EgtVolZmapPartCount( vMillId)
      if nPart > 1 then
         -- ricerca del pezzo con massimo volume
         local dTCenX = -EgtGetAxisPos( 'T') - EMT.LT / 2
         local nPartMax = 0
         local dVolMax = 0
         for i = 1, nPart do
            local dVol = EgtVolZmapPartVolume( vMillId, i - 1)
            if dVol > dVolMax then
               local b3VmPart = EgtVolZmapGetPartBBoxGlob( vMillId, i - 1, GDB_BB.STANDARD)
               if b3VmPart and b3VmPart:getMax():getX() > dTCenX and b3VmPart:getMin():getX() < dTCenX then
                  dVolMax = dVol
                  nPartMax = i
               end
            end
         end
         -- eliminazione di tutti i pezzi piccoli
         for i = nPart, 1, -1 do
            if i ~= nPartMax then
               EgtRemoveVolZmapPart( vMillId, i - 1)
            end
         end
         -- aggiorno visualizzazione
         EgtDraw()
      end
      EMT.VMRS = false
   end
end

---------------------------------------------------------------------
function VerifyY1Slide( sName1, dVal1, sName2, dVal2)
   -- Se movimento trave agganciata con carrello Y1
   if sName1 == 'T' and sName2 == 'Y1' and GetPY1Light() then
      local dY1DeltaP = EgtGetAxisPos( 'Y1') - EgtGetAxisPos( 'T')
      local dY1DeltaA = tonumber( dVal2) - tonumber( dVal1)
      EgtOutLog( string.format( 'Y1DeltaP=%.3f Y1DeltaA=%.3f', dY1DeltaP, dY1DeltaA), 5)
      if abs( dY1DeltaA - dY1DeltaP) > 0.5 then
         EMT.ERR = 2
         local sErr = 'Y1 slide : ' .. EmtLenToString( dY1DeltaP, 3) .. ' -> ' .. EmtLenToString( dY1DeltaA, 3)
         EmtSetLastError( 1202, sErr)
      end
   end
   -- Tutto bene
   return true
end

---------------------------------------------------------------------
function VerifyY2Slide( sName1, dVal1, sName2, dVal2)
   -- Se movimento trave agganciata con carrello Y2
   if sName1 == 'T' and sName2 == 'Y2' and GetPY2Light() then
      local dY2DeltaP = EgtGetAxisPos( 'Y2') - EgtGetAxisPos( 'T')
      local dY2DeltaA = tonumber( dVal2) - tonumber( dVal1)
      EgtOutLog( string.format( 'Y2VDeltaP=%.3f Y2DeltaA=%.3f', dY2DeltaP, dY2DeltaA), 5)
      if abs( dY2DeltaA - dY2DeltaP) > 0.5 then
         EMT.ERR = 2
         local sErr = 'Y2 slide : ' .. EmtLenToString( dY2DeltaP, 3) .. ' -> ' .. EmtLenToString( dY2DeltaA, 3)
         EmtSetLastError( 1202, sErr)
      end
   end
   -- Tutto bene
   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 == 'Y1' then
      return VerifyY1Slide( sName1, dVal1, sName2, dVal2)
   elseif sName2 == 'Y2' then
      return VerifyY2Slide( 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 == 'Y1' then
      return VerifyY1Slide( sName1, dVal1, sName2, dVal2) and VerifyY2Slide( sName1, dVal1, sName3, dVal3)
   elseif sName2 == 'Y2' then
      return VerifyY2Slide( sName1, dVal1, sName2, dVal2) and VerifyY1Slide( sName1, dVal1, sName3, dVal3)
   end
   return true
end

---------------------------------------------------------------------
function VerifyY1Stroke( dY1)
   if dY1 < MinY1 then
      EmtSetOutstrokeInfo( 'Y1', 'Y1', true, dY1 - MinY1, ' (L1-)')
      EMT.ERR = 1
      local sErr = 'Y1 axis outstroke ' .. EgtNumToString( dY1 - MinY1, 3)
      EgtOutLog( sErr)
      return false
   elseif dY1 > MaxY1 then
      EmtSetOutstrokeInfo( 'Y1', 'Y1', true, dY1 - MaxY1, ' (L1+)')
      EMT.ERR = 1
      local sErr = 'Y1 axis outstroke ' .. EgtNumToString( dY1 - MaxY1, 3)
      EgtOutLog( sErr)
      return false
   end
   return true
end

---------------------------------------------------------------------
function VerifyY2Stroke( dY2)
   if dY2 < MinY2 then
      EmtSetOutstrokeInfo( 'Y2', 'Y2', true, dY2 - MinY2, ' (L1-)')
      EMT.ERR = 1
      local sErr = 'Y2 axis outstroke ' .. EgtNumToString( dY2 - MinY2, 3)
      EgtOutLog( sErr)
      return false
   elseif dY2 > MaxY2 then
      EmtSetOutstrokeInfo( 'Y2', 'Y2', true, dY2 - MaxY2, ' (L1+)')
      EMT.ERR = 1
      local sErr = 'Y1 axis outstroke ' .. EgtNumToString( dY2 - MaxY2, 3)
      EgtOutLog( sErr)
      return false
   end
   return true
end

---------------------------------------------------------------------
function VerifyY1Y2Stroke( sName, dVal)
   if sName == 'Y1' then
      return VerifyY1Stroke( dVal)
   elseif sName == 'Y2' then
      return VerifyY2Stroke( dVal)
   else
      return nil
   end
end

---------------------------------------------------------------------
function VerifyV1Stroke( dV1)
   if dV1 < MinV1 then
      EmtSetOutstrokeInfo( 'V1', 'V1', true, dV1 - MinV1, ' (L1-)')
      EMT.ERR = 1
      local sErr = 'V1 axis outstroke ' .. EgtNumToString( dV1 - MinV1, 3)
      EgtOutLog( sErr)
      return false
   elseif dV1 > MaxV1 then
      EmtSetOutstrokeInfo( 'V1', 'V1', true, dV1 - MaxV1, ' (L1+)')
      EMT.ERR = 1
      local sErr = 'V1 axis outstroke ' .. EgtNumToString( dV1 - MaxV1, 3)
      EgtOutLog( sErr)
      return false
   end
   return true
end

---------------------------------------------------------------------
function VerifyV2Stroke( dV2)
   if dV2 < MinV2 then
      EmtSetOutstrokeInfo( 'V2', 'V2', true, dV2 - MinV2, ' (L1-)')
      EMT.ERR = 1
      local sErr = 'V2 axis outstroke ' .. EgtNumToString( dV2 - MinV2, 3)
      EgtOutLog( sErr)
      return false
   elseif dV2 > MaxV2 then
      EmtSetOutstrokeInfo( 'V2', 'V2', true, dV2 - MaxV2, ' (L1+)')
      EMT.ERR = 1
      local sErr = 'V2 axis outstroke ' .. EgtNumToString( dV2 - MaxV2, 3)
      EgtOutLog( sErr)
      return false
   end
   return true
end

---------------------------------------------------------------------
function VerifyV1V2Stroke( sName, dVal)
   if sName == 'V1' then
      return VerifyV1Stroke( dVal)
   elseif sName == 'V2' then
      return VerifyV2Stroke( dVal)
   else
      return nil
   end
end

---------------------------------------------------------------------
function GetV1ToClose()
   --EgtOutLog( string.format( 'L1m=%.3f L1M=%.3f V2NP=%.3f LB=%.3f', EMT.MAXMIN[1], EMT.MAXMAX[1], EMT.V2NEXTPOS, EMT.LB))
   --EgtOutLog( string.format( 'LB=%.3f LT=%.3f', EMT.LB, EMT.LT or 0))
   return ( EMT.MAXMAX[1] <= EMT.V1NEXTPOS + RollCageMin and EMT.MAXMIN[1] + EMT.LB >= EMT.V1NEXTPOS + RollCageMax)
end

---------------------------------------------------------------------
function GetV2ToClose()
   --EgtOutLog( string.format( 'L1m=%.3f L1M=%.3f V2NP=%.3f LB=%.3f', EMT.MAXMIN[1], EMT.MAXMAX[1], EMT.V2NEXTPOS, EMT.LB))
   --EgtOutLog( string.format( 'LB=%.3f LT=%.3f', EMT.LB, EMT.LT or 0))
   return ( EMT.MAXMAX[1] <= EMT.V2NEXTPOS - RollCageMax and EMT.MAXMIN[1] + EMT.LB >= EMT.V2NEXTPOS - RollCageMin)
end

---------------------------------------------------------------------
function ShowToolInTcPos( sTcPos, bShow)
   -- recupero identificativo della posizione sul TC
   local TcPosId = EgtGetTcPosId( sTcPos or '')
   if not TcPosId then return end
   -- ciclo sulle possibili uscite
   for i = 1, 100 do
      -- recupero il gruppo dell'utensile
      local TcExitId = EgtGetFirstNameInGroup( TcPosId, 'T'..tostring( i))
      if not TcExitId then break end
      -- imposto lo stato di visualizzazione
      EgtSetStatus( TcExitId, EgtIf( bShow, GDB_ST.ON, GDB_ST.OFF))
   end
   -- recupero eventuale gruppo ausiliario da visualizzare/nascondere
   local TcHSId = EgtGetFirstNameInGroup( TcPosId, sTcPos..'_HS')
   if TcHSId then EgtSetStatus( TcHSId, EgtIf( bShow, GDB_ST.ON, GDB_ST.OFF)) end
end

---------------------------------------------------------------------
function LoadFirstTool( nHSet, sTcPosDef)
   if nHSet ~= 1 and nHSet ~= 2 then return end
   EgtUnloadTool( EgtIf( nHSet == 1, 'H11', 'H21'), 1)
   if GetHeadSetFromTcPos( sTcPosDef) ~= nHSet then return end
   local sTool, sHead, sTcPos, sTTotLen, sTotDiam, sBlockedAxis = FindFirstToolOnHeadSet( nHSet)
   -- se primo utensile motosega, non lo carico e visualizzo quello di default
   if sHead == 'H13' then
      sTool = nil
   end
   if not sTool then
      local vTools = EgtGetToolsInCurrSetupPos( sTcPosDef)
      if vTools and vTools[1] then
         sTool = vTools[1]
         sTcPos = sTcPosDef
         sHead = GetAdjHeadFromTcPos( nHSet, sTcPos)
      end
   end
   if sTool then
      -- imposto correttamente i dati di testa
      local OrigEMC = EMC
      EMC = { HEAD = sHead, TOOL = sTool, TCPOS = sTcPos, TOTLEN = sTTotLen, TOTDIAM = sTotDiam, BLOCKEDAXIS = sBlockedAxis}
      OnSetHead()
      EMC = OrigEMC
      if sBlockedAxis then
         local dPosA = tonumber( sBlockedAxis:sub( 4) or '') or 0
         if dPosA ~= 0 then
            -- imposto il valore di A
            EgtSetAxisPos( EgtIf( nHSet == 1 , 'A1', 'A2'), dPosA)
         end
      end
      -- carico l'utensile
      EgtLoadTool( sHead, 1, sTool)
      ShowToolInTcPos( sTcPos, false)
      -- salvo utensili caricati
      if nHSet == 1 then
         EMT.PREVTOOL_H1 = sTool
         EMT.PREVHEAD_H1 = sHead
         EMT.PREVTCPOS_H1 = sTcPos
         EMT.PREVTTOTLEN_H1 = sTTotLen
      -- per gruppo testa 2
      elseif nHSet == 2 then
         EMT.PREVTOOL_H2 = sTool
         EMT.PREVHEAD_H2 = sHead
         EMT.PREVTCPOS_H2 = sTcPos
         EMT.PREVTTOTLEN_H2 = sTTotLen
      end
   end
   return sTool
end

---------------------------------------------------------------------
--   *** ESTIMATION T&L ***
---------------------------------------------------------------------
local ESTIMATION_RAPID_COEFF = EstimationRapidMultiplier or 1
local RAPID_X_FEED =  70000 / ESTIMATION_RAPID_COEFF    -- mm/min
local RAPID_Y_FEED = 100000 / ESTIMATION_RAPID_COEFF    -- mm/min
local RAPID_Z_FEED =  32000 / ESTIMATION_RAPID_COEFF    -- mm/min
local RAPID_C_FEED =  15000 / ESTIMATION_RAPID_COEFF    -- deg/min
local RAPID_B_FEED =  15000 / ESTIMATION_RAPID_COEFF    -- deg/min
local RAPID_MIN_T = 0.1 * ESTIMATION_RAPID_COEFF        -- s
local LOAD_T = 2 * ESTIMATION_RAPID_COEFF               -- s
local CHAR_ONE_MOVE_T = 1 * ESTIMATION_RAPID_COEFF      -- s
local ROTATION_T = 40 * ESTIMATION_RAPID_COEFF          -- s
local SPLIT_T = 6 * ESTIMATION_RAPID_COEFF              -- s
local UNLOAD_T = 4 * ESTIMATION_RAPID_COEFF             -- s
local FALL_T = 2 * ESTIMATION_RAPID_COEFF               -- s

---------------------------------------------------------------------
function OnEstimStart()
   EMT.INCHES = not EgtUiUnitsAreMM()      -- unità di misura mm/inches
   EMT.FMAXPINZE = EgtClamp( MaxFeedPinze or 116000, 20000, 130000)  -- feed massima pinze
   EMT.MAXACC = MaxAcc or 6000    -- accelerazione massima pinze. In realtà è il tempo in millisecondi, quindi MAXACC corrisponde al tempo massimo per raggiungere la velocità desiderata
   EMT.MINACC = MinAcc or 600     -- accelerazione minima pinze. In realtà è il tempo in millisecondi, quindi MINACC corrisponde al tempo massimo per raggiungere la velocità desiderata
end

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

---------------------------------------------------------------------
function OnEstimProgramStart()
   -- imposto inizio movimenti da Home
   EMT.L1 = EgtGetAxisHomePos( 'T')
   EMT.L2 = EgtGetAxisHomePos( 'X1')
   EMT.L3 = EgtGetAxisHomePos( 'Z1')
   EMT.R1 = EgtGetAxisHomePos( 'C1')
   EMT.R2 = EgtGetAxisHomePos( 'B1')
   -- 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, eventuale rotazione
   elseif IsMidPhase( 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 OnEstimRawMoveData()
   -- se start del pezzo
   if IsStartOrRestPhase( EMT.PHASE) then
      EMT.SPLIT = false
   end
   OnRawMoveData()
end

---------------------------------------------------------------------
function OnEstimToolSelect()
   -- 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
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

   EMT.MCHUSERNOTES = EgtGetMachiningParam( MCH_MP.USERNOTES) or ''
   EMT.MCHSPLIT = ( EMT.MCHUSERNOTES:find( 'Split;', 1, true) ~= nil)
   if EMT.MCHSPLIT then
      EMT.SPLIT = true
   end
end

---------------------------------------------------------------------
function OnEstimPathStart()
   EMT.AUXTYPE = nil
   EMT.MCHMOVEFIRST = true
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
      if EMT.AUXTOT > 3 and EMT.AUXIND == EMT.AUXTOT then
         local dTime = ( EMT.AUXTOT - 3) * 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 = CalcMoveTime( abs( dL1), RAPID_X_FEED, EMT.SPLIT)
   if dT1 > dTime then dTime = dT1 end
   local dT2 = CalcMoveTime( abs( dL2), EMT.FMAXPINZE, EMT.SPLIT)
   if dT2 > dTime then dTime = dT2 end
   local dT3 = CalcMoveTime( abs( dL3), RAPID_Z_FEED, EMT.SPLIT)
   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 = CalcMoveTime( dLen, EMT.F, EMT.SPLIT)
   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 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 IsStartOrMidPhase( nPhase)
   local sVal = GetPhaseType( nPhase)
   return ( sVal == 'START' or 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 GetParkT()
   local dTmp = EgtGetInfo( EMT.DISPID, 'TPOS', 'd') or EgtGetInfo( EMT.DISPID, 'TPARK', 'd')
   if dTmp then
      return dTmp
   else
      return LoadT
   end
end

---------------------------------------------------------------------
function FindFirstToolOnHeadSet( nHSet)
   -- salvo stato iniziale
   local CurrMachId = EgtGetCurrMachining()
   local CurrTool = EgtTdbGetCurrToolParam( MCH_TP.NAME)
   -- cerco lavorazione con utensile su gruppo testa indicato
   local sTool, sHead, sTcPos, sTTotLen, sTotDiam, sBlockedAxis
   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
               sHead = EgtTdbGetCurrToolParam( MCH_TP.HEAD)
               if GetHeadSet( sHead) == nHSet then
                  sTcPos = EgtTdbGetCurrToolParam( MCH_TP.TCPOS)
                  -- aggregato lama non si può mai preselezionare e viene sempre scaricato, quindi non sarà mai già caricato
                  if nHSet == 2 and sTcPos == 'T201' then
                     sTcPos = nil
                     break
                  end
                  sTool = sTest
                  sTTotLen = EgtTdbGetCurrToolParam( MCH_TP.TOTLEN)
                  sTotDiam = EgtTdbGetCurrToolParam( MCH_TP.TOTDIAM)
                  sBlockedAxis = EgtGetMachiningParam( MCH_MP.BLOCKEDAXIS)
                  break
               end
            end
         end
      end
      OpId = EgtGetNextActiveOperation( OpId)
   end
   -- ripristino stato iniziale
   EgtSetCurrMachining( CurrMachId or GDB_ID.NULL)
   EgtTdbSetCurrTool( CurrTool or '')
   -- restituisco risultato
   return sTool, sHead, sTcPos, sTTotLen, sTotDiam, sBlockedAxis
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
   EgtTdbSetCurrTool( CurrTool or '')
   -- restituisco risultato
   return sTcPos
end

---------------------------------------------------------------------
function GetNextChainSawingVirtualAxis( MchId)
   -- recupero la lavorazione successiva
   local NextMchId
   if MchId then
      NextMchId = EgtGetNextActiveOperation( MchId)
   else
      NextMchId = EgtGetFirstActiveOperation()
   end
   while NextMchId and EgtGetOperationType( NextMchId) == MCH_OY.DISP do
      NextMchId = EgtGetNextActiveOperation( NextMchId)
   end
   -- verifico sia un taglio con sega a catena
   if EgtGetOperationType( NextMchId) ~= MCH_OY.MORTISING then
      return nil
   end
   -- la imposto come lavorazione corrente
   EgtSetCurrMachining( NextMchId)
   -- recupero il valore dell'asse virtuale bloccato A
   local dPosA = GetCurrChainSawingVirtualAxis()
   -- ripristino la lavorazione corrente
   if MchId then
      EgtSetCurrMachining( MchId)
   end
   return dPosA
end

---------------------------------------------------------------------
function GetNextMultiDrillVirtualAxis( MchId)
   -- recupero la lavorazione successiva
   local NextMchId
   if MchId then
      NextMchId = EgtGetNextActiveOperation( MchId)
   else
      NextMchId = EgtGetFirstActiveOperation()
   end
   while NextMchId and EgtGetOperationType( NextMchId) == MCH_OY.DISP do
      NextMchId = EgtGetNextActiveOperation( NextMchId)
   end
   -- verifico sia un taglio con sega a catena
   if EgtGetOperationType( NextMchId) ~= MCH_OY.DRILLING then
      return nil
   end
   -- la imposto come lavorazione corrente
   EgtSetCurrMachining( NextMchId)
   -- recupero il valore dell'asse virtuale bloccato A
   local dPosA = GetCurrMultiDrillVirtualAxis()
   -- ripristino la lavorazione corrente
   if MchId then
      EgtSetCurrMachining( MchId)
   end
   return dPosA
end

---------------------------------------------------------------------
function GetCurrSawingVirtualAxis()
   -- recupero il valore dell'asse virtuale bloccato A
   local sVal = EgtGetMachiningParam( MCH_MP.BLOCKEDAXIS) or 'A2=0'
   local dPosA = tonumber( sVal:sub( 4)) or 0
   return dPosA
end

---------------------------------------------------------------------
function GetNextSawingVirtualAxis( MchId)
   -- recupero la lavorazione successiva
   local NextMchId
   if MchId then
      NextMchId = EgtGetNextActiveOperation( MchId)
   else
      NextMchId = EgtGetFirstActiveOperation()
   end
   while NextMchId and EgtGetOperationType( NextMchId) == MCH_OY.DISP do
      NextMchId = EgtGetNextActiveOperation( NextMchId)
   end
   -- verifico sia un taglio o fresatura con lama su aggregato da sotto
   if ( EgtGetOperationType( NextMchId) ~= MCH_OY.MILLING and EgtGetOperationType( NextMchId) ~= MCH_OY.SAWING) or EMT.HEAD ~= 'H22' then
      return nil
   end
   -- la imposto come lavorazione corrente
   EgtSetCurrMachining( NextMchId)
   -- recupero il valore dell'asse virtuale bloccato A
   local dPosA = GetCurrSawingVirtualAxis()
   -- ripristino la lavorazione corrente
   if MchId then
      EgtSetCurrMachining( MchId)
   end
   return dPosA
end
---------------------------------------------------------------------
function GetStartMachiningXaxis( nMchId)
   -- Recupero quota X (nostro -L2)
   local nClEntId = EgtGetFirstInGroup( EgtGetFirstNameInGroup( EgtGetFirstNameInGroup( nMchId, 'CL') or GDB_ID.NULL, 'P1') or GDB_ID.NULL)
   local vAxes = EmtGetAxesPos( nClEntId or GDB_ID.NULL)
   if vAxes and #vAxes >= 2 then
      return -vAxes[2]
   else
      return nil
   end
end

---------------------------------------------------------------------
function IsLastPath( nPathId)
   return not EgtGetNext( nPathId)
end

---------------------------------------------------------------------
function RollerParkingNeeded( sHead, dAng1p, dAng2p, dAng1, dAng2)
   if sHead == 'H11' or sHead == 'H12' or sHead == 'H13' or sHead == 'H16' then
      return ( abs( dAng1 - dAng1p) > 1 or ( abs( dAng2 - dAng2p) > 1 and abs( dAng1) % 180.0 > 1))
   elseif sHead == 'H21' or sHead == 'H22' then
      return ( abs( dAng1 - dAng1p) > 1 or ( abs( dAng2 - dAng2p) > 1 and abs( dAng1) % 180.0 > 1))
   elseif sHead == 'H38' then
      return ( abs( dAng1 - dAng1p) > 1 or  abs( dAng2 - dAng2p) > 1)
   end
end

---------------------------------------------------------------------
function GetCmdAxMove( Cmd, sAx)
   if #Cmd >= 3 and Cmd[2] == sAx then
      return tonumber( Cmd[3])
   elseif #Cmd >= 5 and Cmd[4] == sAx then
      return tonumber( Cmd[5])
   elseif #Cmd >= 7 and Cmd[6] == sAx then
      return tonumber( Cmd[7])
   end
end

---------------------------------------------------------------------
function CalcDinamicaPinze( dH, dS, dL)
   local MinTempoAcc = EMT.MINACC        -- [ms] ~600
   local MaxTempoAcc = EMT.MAXACC        -- [ms] ~6000
   local KgMtCubo= WoodDensity or 550              -- densità legno [Kg / metro cubo]
   local Massa = ( dH * dS * dL * KgMtCubo ) / 1e9 -- massa [Kg]
   local FMaxPinze = EMT.FMAXPINZE                 -- Feed massima pinze [mm/min]
   local ForzaAttrito = 350 * 9.8 * 0.2            -- Forza chiusura pinze [Kgf] * g * Coeff_Attrito -> [N]
   local TempoAcc = EgtClamp( ( Massa * FMaxPinze) / ( 60 * ForzaAttrito), MinTempoAcc, MaxTempoAcc)
   local RidFeed = 100 / Massa * 100
   if RidFeed > 100 then
      RidFeed = 100
   elseif RidFeed < 10 then
      RidFeed = 10
   end
   return TempoAcc, MinTempoAcc, RidFeed
end

---------------------------------------------------------------------
function CalcMoveTime( dPathLen, dFeed, bIsSplitted)
   local dTime
   local dFeedInSec = dFeed / 60
   local dTempoAcc, _, _ = CalcDinamicaPinze( EMT.HB, EMT.SB, EgtIf( bIsSplitted, EMT.LT, EMT.LB))
   dTempoAcc = dTempoAcc / 1000
   -- se si raggiunge la velocità massima
   if dPathLen >= dFeedInSec * dTempoAcc then
      dTime = dTempoAcc * 2 + ( ( dPathLen - dFeedInSec * dTempoAcc) / dFeedInSec)
   else
      dTime = 2 * sqrt( (dPathLen * dTempoAcc) / dFeedInSec)
   end
   return dTime
end

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