-- Processore macchina Essetre-PF1250 by Egalware s.r.l.   2024/01/22
-- Con controllo numerico TPA

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

-- Variabili di modulo
local CSP_INFO =     INFO_STD_PP.NAME..'  ver.'..INFO_STD_PP.VERSION..' by EgalWare s.r.l.'
local MACHINE_INFO = MACH_NAME..'  ver.'..PP_VER

local TEST_USE = false

---------------------------------------------------------------------
-- *** GENERATION ***
---------------------------------------------------------------------
function OnStart()
   -- 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
   EMT.USETO1 = false     -- abilitazione uso origine tavola
   EMT.MODAL = true       -- abilitazione emissione modale
   EMT.INCHES = false     -- unità di misura mm/inches
   EMT.DECNUM = 5         -- numero di decimali dopo la virgola
   EMT.NUM = false        -- abilitazione numerazione linee
   --EMT.Nt = 'N'           -- token per la numerazione di linea
   --EMT.LINENBR = 0        -- numero di linea
   --EMT.LINEINC = 1        -- incremento numerazione linee
   --EMT.Ft = 'F'           -- token per feed
   --EMT.St = 'S'           -- token per speed
   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
   SetToParkLine()        -- si inizia con linee da parcheggiare su stack

   -- se il pezzo dev'essere scaricato al carico
   local dUnloadType = EgtGetInfo( EgtGetFirstNameInGroup( GDB_ID.ROOT, 'BtlInfo') or GDB_ID.NULL, 'UNLOAD', 'd')
   EMT.UNLOAD = dUnloadType == 1 or dUnloadType == -1
end

---------------------------------------------------------------------
function OnEnd()
   -- Ripristino fase iniziale come corrente
   EgtSetCurrPhase( 1)
end

---------------------------------------------------------------------
function OnProgramStart()
   -- aggiungo prefisso per commentae commenti iniziali se la macchina è utilizzata per fare i test
   local sPrefixCommentLine = '' ; if IS_TEST_MACHINE or EgtEndsWith( EgtGetCurrMachineDir(), '.TEST') then sPrefixCommentLine = '//' end

   -- Intestazioni
   if EMT.INFO then
      MyOutput( sPrefixCommentLine..'('..EMT.INFO..')')
   else
      MyOutput( '(Program Start)')
   end
   MyOutput( sPrefixCommentLine..'('.. CSP_INFO..')')
   MyOutput( sPrefixCommentLine..'('.. MACHINE_INFO..')')
   MyOutput( '(HEADER)')

   -- Se modalità test, aggiungo linee per muovere tappeto e alzare la testa (in automatico viene fatto dal main residente)
   if TEST_USE then
      MyOutput( 'M199')
   end
   -- Dichiaro inizio
   EMT.FIRST = true
   -- Inizializzazioni varie
   MyOutput( 'G49')
   -- Inizio lista utensili
   MyOutput( 'M993 (Tool List Start)')
   -- Dichiaro cabine con rulli in posizione di parcheggio
   EMT.V1POS = ParkV1
   EMT.V2POS = ParkV2
end

---------------------------------------------------------------------
function OnProgramEnd()
   -- Arresto mandrino
   MyOutput( 'M05')
   -- Emissione scarico
   for i = 1, #EMT.MDCHAR do
      EmitMoveDataChars( EMT.MDCHAR[i])
   end
   if #EMT.MDCHAR > 0 then
      if EMT.AUXTYPE == 'S' then
         EmitMoveStartChars( 3)
         EmitMoveWaitChars( 3)
      elseif EMT.AUXTYPE == 'R' then
         EmitMoveDataChars( { Y1=ParkY1, Y2=ParkY2, V1=ParkV1, V2=ParkV2, BeamVise=0, MoveType=3})
         EmitMoveStartChars( 3)
         EmitMoveWaitChars( 3)
      elseif EMT.AUXTYPE == 'U' then
         EmitMoveStartChars( EgtIf( EMT.CHY_ON, 3, 2))
         EmitMoveWaitChars( EgtIf( EMT.CHY_ON, 3, 2), true)
         -- emissione conclusione pezzo precedente (se non si è in test)
         if not TEST_USE and EMT.PRODID then
            local sEnd= 'M113 P1='..tostring( EMT.PRODID)..' P2='..tostring( EMT.PATTID)..' P3='..tostring( EMT.CUTID)..' P4=2' 
            MyOutput( sEnd)
         end
      end
   end
   EMT.MDCHAR = {}
   EMT.AUXTYPE = nil
   EMT.UNL = nil
   EMT.PREROT = nil
   EMT.SPLITROT = nil
   EMT.CHY_ON = nil
   EMT.FALL = nil
   EMT.RELOAD = nil
   EMT.RELOAD2 = nil
   MyOutput( '(FOOTER)')
   -- Termino il programma
   MyOutput( 'M202')
   MyOutput( 'M02')
end

---------------------------------------------------------------------
function OnToolData()
   -- emissione dati utensile
   -- lama su aggregato da sotto
   if EMT.HEAD == 'H22' then
      -- recupero posizione lama
      local dPosBase = tonumber( AdjustTcPos( false, EMT.TCPOS, 0))
      dAddLen = SawUnderOffsZ
      local sData = ' P2=' .. EmtLenToString( dAddLen + EMT.TLEN, 3) .. ' P3=' .. EmtLenToString( EMT.TDIAM, 3) ..
                   ' P4=' .. EmtLenToString( EMT.SMAX, 3) .. ' P5=' .. EmtLenToString( dAddLen + EMT.TTOTLEN, 3)
      MyOutput( 'M992 P1='..tostring( dPosBase).. sData)
      MyOutput( 'M992 P1='..tostring( dPosBase+1).. sData)
      MyOutput( 'M992 P1='..tostring( dPosBase+2).. sData)
      MyOutput( 'M992 P1='..tostring( dPosBase+3).. sData)
   -- altri utensili tranne sega a catena
   elseif EMT.HEAD ~= 'H13' then
      local dAddLen = 0
      if EMT.HEAD == 'H12' then
         dAddLen = -SawOffsZ
      elseif EMT.HEAD == 'H16' then
         dAddLen = -Saw2OffsZ
      elseif EMT.HEAD == 'H14' then
         dAddLen = 206 -- dimensione aggregato
      end
      local sOut = 'M992 P1=' .. EMT.TCPOS:gsub( 'T', '') .. ' P2=' .. EmtLenToString( dAddLen + EMT.TLEN, 3) .. ' P3=' .. EmtLenToString( EMT.TDIAM, 3) ..
                   ' P4=' .. EmtLenToString( EMT.SMAX, 3) .. ' P5=' .. EmtLenToString( dAddLen + EMT.TTOTLEN, 3)
      MyOutput( sOut)
   -- emissione dati sega a catena
   else
      -- recupero posizione motosega
      local dPosBase = tonumber( AdjustTcPos( false, EMT.TCPOS, 0))
      if EMT.TDIST and abs( EMT.TDIST) < 0.1 then EMT.TDIST = nil end
      local sData = ' P2=' .. EmtLenToString( ( EMT.TDIST or ChSawLen), 3) .. ' P3=' .. EmtLenToString( EMT.TLEN, 3) ..
                    ' P4=' .. EmtLenToString( EMT.SMAX, 3) .. ' P5=' .. EmtLenToString( ( EMT.TDIST or ChSawLen), 3)
      MyOutput( 'M992 P1='..tostring( dPosBase).. sData)
      MyOutput( 'M992 P1='..tostring( dPosBase+1).. sData)
      MyOutput( 'M992 P1='..tostring( dPosBase+2).. sData)
      MyOutput( 'M992 P1='..tostring( dPosBase+3).. sData)
   end
end

---------------------------------------------------------------------
function OnDispositionStart()
   EMT.OPEISDISP = true
   -- Assegnazione parametri disposizione, se impostati
   EMT.TPOS = EgtGetInfo( EMT.DISPID, 'TPOS', 'd') or EMT.TPOS
   EMT.Y1POS = EgtGetInfo( EMT.DISPID, 'Y1POS', 'd')
   -- Se prima disposizione
   if EMT.PHASE == 1 then
      -- terminazione lista utensili
      MyOutput( 'G990 (Tool List End)')
      MyOutput( 'M28')
      -- emissione dati di macchina
      local sOut = 'M114'..' P1='..EmtLenToString( LoadT, 2)..' P2='..EmtLenToString( MinMchY1, 2)..' P3='..EmtLenToString( MaxY1, 2)..
                   ' P4='..EmtLenToString( MinY2, 2)..' P5='..EmtLenToString( MaxMchY2, 2)..' P6='..EmtLenToString( MillOffs, 2)..
                   ' P7='..EmtLenToString( Mill2Offs, 2)..' P8='..EmtLenToString( ParkMchY1, 2)..' P9='..EmtLenToString( ParkMchY2, 2)..
                   ' P10='..EmtLenToString( -TurnerOffs, 2)..' P11='..EmtLenToString( MinV1, 2)..' P12='..EmtLenToString( MaxV1, 2)..
                   ' P13='..EmtLenToString( MinV2, 2)..' P14='..EmtLenToString( MaxV2, 2)..
                   ' P15='..EmtLenToString( -DeltaTabY, 2)..' P16='..EmtLenToString( DeltaTabZ - MillOffs, 2)..
                   ' P17='..EmtLenToString( Delta2TabY, 2)..' P18='..EmtLenToString( -Delta2TabZ - Mill2Offs, 2)
      -- se è in configurazione a 3 teste
      if IsHeadExisting( 3) then
         sOut = sOut .. ' P19='..EmtLenToString( -Delta3TabY, 2)..' P20='..EmtLenToString( Delta3TabZ - Mill3Offs, 2)..
                        ' P21='..EmtLenToString( Mill3Offs, 2).. ' P22='.. EmtLenToString( -ParkInLavZ2, 2)
      else
         sOut = sOut .. ' P22='..EmtLenToString( -MinZ2, 2)
      end

      MyOutput( sOut)
      -- carico barra
      EMT.LOAD = true
   else
      EMT.LOAD = false
      if IsEnd2Phase( EMT.PHASE - 1) then
         EMT.RELOAD = true
         EMT.RELOAD2 = false
      end
   end
end

---------------------------------------------------------------------
function OnDispositionEnd()
   -- Se disposizione inizio o rimanenza
   if IsStartOrRestPhase( EMT.PHASE) then
      -- reset recupero sovramateriale in X non più presente
      EMT.X_OFF = nil
      -- gruppo con info da BTL
      local BtlInfoId = EgtGetFirstNameInGroup( GDB_ID.ROOT, 'BtlInfo') or GDB_ID.NULL
      -- dati del grezzo
      local LBarra = EMT.LB
      local HBarra = EMT.HB
      local SBarra = EMT.SB
      -- dati del pezzo
      local IdTrave = EMT.IT
      local LTrave = EMT.LT
      local HTrave = EMT.HT
      local STrave = EMT.ST
      local HOverM = EMT.HOVM
      -- calcolo dati pinze
      local TempAccPinze, TempMinAccPinze, RidFeed = CalcDinamicaPinze( HTrave, STrave, LBarra)
      local TempAccPinz1, TempMinAccPinz1, RidFeed1 = CalcDinamicaPinze( HTrave, STrave, LBarra - LTrave)
      local TempAccPinz2, TempMinAccPinz2, RidFeed2 = CalcDinamicaPinze( HTrave, STrave, LTrave)
      local ForzaPinze = CalcForzaPinze( HTrave, STrave, LBarra, LTrave)
      if IdTrave >= 0 then
         local sRem = ' SN=' .. IdTrave .. ' LBarra=' .. EmtLenToString( LBarra, 3) .. ' L='..EmtLenToString( LTrave, 3) ..
                      ' H=' .. EmtLenToString( HTrave, 3) .. ' S=' .. EmtLenToString( STrave, 3) .. ' '
         EMT.PRODID = EgtGetInfo( BtlInfoId, 'PRODID', 'i') or 0
         EMT.PATTID = EgtGetInfo( BtlInfoId, 'PATTID', 'i') or 0
         if EMT.PARTTYPE ~= 1 then
            EMT.CUTID = EgtGetInfo( EMT.IDT, 'CUTID', 'i') or 0
         else
            EMT.CUTID = -1
         end
         local sStart = 'M113 P1='..tostring( EMT.PRODID)..' P2='..tostring( EMT.PATTID)..' P3='..tostring( EMT.CUTID)..' P4=1' 
         EmitRemark( sRem)
         if not TEST_USE then MyOutput( sStart) end
      else
         EmitRemark( 'REMAIN UNLOAD')
         EMT.PRODID = nil
         EMT.PATTID = nil
         EMT.CUTID = nil
      end
      -- se carico barra
      if EMT.LOAD or EMT.RELOAD then
         local nLoad90 = EgtGetInfo( BtlInfoId, 'LOAD90', 'i') or 0
         sOut = 'M115 P1=' .. EmtLenToString( LBarra, 2) .. ' P2=' .. EmtLenToString( max( HTrave, HBarra), 2) .. ' P3=' .. EmtLenToString( STrave, 2) ..
                    ' P4=' .. EmtLenToString( HOverM, 2) .. ' P5=' .. tostring( nLoad90) .. ' P6=' .. EmtLenToString( LTrave, 2) ..
                    ' P7=' .. EgtNumToString( TempAccPinze, 2) .. ' P8=' .. EgtNumToString( TempAccPinz2, 2) .. ' P9=' .. EgtNumToString( TempAccPinz1, 2) ..
                    ' P10=' .. EgtNumToString( EMT.PARTTYPE or 0, 0)
         -- su macchine vecchie non si poteva settare forza pinzaggio. Parametro da attivare in MLDE
         if EmitAccClamps then
            sOut = sOut .. ' P11=' .. EgtNumToString( ForzaPinze, 2) .. ' P12=' .. EgtNumToString( ForzaPinze, 2)
         end
         MyOutput( sOut)
         if EMT.LOAD then
            SetStartValue( 1, LBarra)
            SetStartValue( 2, max( HTrave, HBarra))
            SetStartValue( 3, STrave)
            SetStartValue( 6, HOverM)
         end
      -- altrimenti recupero rimanenza
      else
         sOut = 'M115 P1=' .. EmtLenToString( LBarra, 2) .. ' P2=' .. EmtLenToString( HTrave, 2) .. ' P3=' .. EmtLenToString( STrave, 2) ..
                    ' P4=' .. EmtLenToString( HOverM, 2) .. ' P5=' .. tostring( 0) .. ' P6=' .. EmtLenToString( EgtIf( IdTrave >= 0, LTrave, LBarra), 2) ..
                    ' P7=' .. EgtNumToString( TempAccPinze, 2) .. ' P8=' .. EgtNumToString( TempAccPinz2, 2) .. ' P9=' .. EgtNumToString( TempAccPinz1, 2)..
                    ' P10=' .. EgtNumToString( EMT.PARTTYPE or 0, 0)
         -- su macchine vecchie non si poteva settare forza pinzaggio. Parametro da attivare in MLDE
         if EmitAccClamps then
            sOut = sOut .. ' P11=' .. EgtNumToString( ForzaPinze, 2) .. ' P12=' .. EgtNumToString( ForzaPinze, 2)
         end
         MyOutput( sOut)
         -- determino la quota di parcheggio della trave
         local ParkT = GetParkT()
         -- aggiorno dati aggancio carrelli alla trave
         EMT.Y1DELTA = EMT.Y1POS - ParkT
         EMT.Y2DELTA = nil
         EmitBeamHeadData( { T=ParkT, Y1=EMT.Y1POS, SetHead=1, F=0})
      end
   -- se altrimenti disposizione intermedia, eventuale rotazione
   elseif IsMidPhase( EMT.PHASE) or IsEnd2Phase( EMT.PHASE) then
      -- recupero le rotazioni delle fasi corrente e precedente
      local nRot = GetPhaseRot( EMT.PHASE)
      local nPrevRot = GetPhaseRot( EMT.PHASE - 1)
      -- verifico se sono diverse
      if nRot ~= nPrevRot then
         if nRot > nPrevRot then nRot = nRot - 4 end
         local nDeltaRot = nRot - nPrevRot
         -- rotazione automatica o manuale (sempre con il medesimo comando)
         local sOut = 'M180 P1=' .. tostring( -nDeltaRot)
         MyOutput( sOut)
         -- imposto stato post-rotazione
         EMT.POSTROT = true
         -- imposto recupero sovramateriale in X non più presente
         EMT.X_OFF = EMT.HOVM
      end
   -- altrimenti disposizione finale, eventuale scarico pezzo lavorato se non ci sono lavorazioni
   else   
      if #EMT.MDCHAR > 0 then
         if EMT.AUXTYPE == 'R' and EMT.UNLOAD then
            EmitRemark( 'PART UNLOAD ON LOAD POSITION')
         else
            EmitRemark( 'PART UNLOAD')
         end
      end
      for i = 1, #EMT.MDCHAR do
         -- Se è un'istruzione del piano ferma pezzo
         if EMT.MDCHAR[i].StopBeamStat then
            EmitStopBeam( EMT.MDCHAR[i])
         else
            EmitMoveDataChars( EMT.MDCHAR[i])
            if EMT.MDCHAR[i].Y1 then EMT.CHY_ON = true end
            if EMT.MDCHAR[i].V1 then EMT.V1POS = EMT.MDCHAR[i].V1 end
            if EMT.MDCHAR[i].V2 then EMT.V2POS = EMT.MDCHAR[i].V2 end
         end
      end
      if #EMT.MDCHAR > 0 then
         local nMoveType = EgtIf( EMT.CHY_ON, 3, 2)
         EmitMoveStartChars( nMoveType)
         -- se dopo c'è scarico spezzone devo mettere attesa termine esecuzione
         if IsRestPhase( EMT.PHASE + 1) then
            EmitMoveWaitChars( nMoveType)
         else
            EmitMoveWaitChars( nMoveType, true)
         end
         -- emissione conclusione pezzo precedente (se non in modalità test)
         if not TEST_USE and EMT.PRODID then
            local sEnd= 'M113 P1='..tostring( EMT.PRODID)..' P2='..tostring( EMT.PATTID)..' P3='..tostring( EMT.CUTID)..' P4=2' 
            MyOutput( sEnd)
         end
         if EMT.UNLOAD then
            MyOutput('M77')
         end
      end
      EMT.MDCHAR = {}
      EMT.AUXTYPE = nil
   end

   EMT.OPEISDISP = false
end

---------------------------------------------------------------------
function OnTableData()
end

---------------------------------------------------------------------
function OnFixtureData()
end

---------------------------------------------------------------------
function OnRawMoveData()
   -- Se disposizione inizio o rimanenza
   if IsStartOrRestPhase( EMT.PHASE) then
      -- se primo grezzo, reset lunghezza barra
      if EMT.RAWIND == 1 then
         EMT.LB = 0
         EMT.HB = 0
         EMT.SB = 0
      end   
      -- aggiungo la lunghezza del grezzo (solo se movimento in corner)
      if EMT.RAWTYPE == 1 then
         local b3Raw = EgtGetBBoxGlob( EgtGetFirstNameInGroup( EMT.RAWID, 'RawSolid'), GDB_BB.STANDARD)
         local LBarra = b3Raw:getDimX()
         EMT.LB = EMT.LB + LBarra
         local HBarra = b3Raw:getDimY()
         EMT.HB = max( EMT.HB, HBarra)
         local SBarra = b3Raw:getDimZ()
         EMT.SB = max( EMT.SB, SBarra)
      end
      -- se primo grezzo, calcolo dati del pezzo
      if EMT.RAWIND == 1 then
         local PartId = EgtGetFirstPartInRawPart( EMT.RAWID)
         if PartId then
            EMT.IDT = PartId
            EMT.IT = EgtGetInfo( PartId, 'PDN', 'i') or 0
            local b3Part = EgtGetBBoxGlob( EgtGetFirstNameInGroup( PartId, 'Box'), GDB_BB.STANDARD)
            EMT.LT = b3Part:getDimX()
            EMT.HT = b3Part:getDimY()
            EMT.ST = b3Part:getDimZ()
            EMT.HOVM = EgtGetInfo( EMT.RAWID, 'HOVM', 'd') or 0
            EMT.PARTTYPE = 0
            local sMaterial = EgtGetInfo( PartId, 'MATERIAL')
            if sMaterial and sMaterial:find( 'CLT-', 1, true) == 1 then
               EMT.PARTTYPE = 1
            elseif sMaterial and sMaterial:find( 'UT-', 1, true) == 1 then
               EMT.PARTTYPE = 2
            end
         else
            EMT.IDT = GDB_ID.NULL
            EMT.IT = -1
            EMT.LT = 0
            EMT.HT = EMT.HT or 0
            EMT.ST = EMT.ST or 0
            EMT.HOVM = 0
         end
      end
   end
end

---------------------------------------------------------------------
function OnToolSelect()
   -- se utensile definito (non definito per disposizioni con movimento)
   if EMT.TOOL ~= '' then
      -- verifiche su utensile
      local sTool = EgtTdbGetToolFromUUID( EgtGetMachiningParam( MCH_MP.TUUID) or "")
      if sTool ~= EMT.TOOL then error( "Tool name mismatch") end
      -- predefinite EMT.TCPOS EMT.HEAD EMT.EXIT
      EMT.TOOLTYPE = EgtTdbGetCurrToolParam( MCH_TP.TYPE)
      EMT.TDIAM = EgtTdbGetCurrToolParam( MCH_TP.DIAM)
      EMT.TTOTDIAM = EgtTdbGetCurrToolParam( MCH_TP.TOTDIAM)
      EMT.TLEN = EgtTdbGetCurrToolParam( MCH_TP.LEN)
      EMT.TTOTLEN = EgtTdbGetCurrToolParam( MCH_TP.TOTLEN)
      if MCH_TP.DIST then EMT.TDIST = EgtTdbGetCurrToolParam( MCH_TP.DIST) end
      if EMT.TDIST and abs( EMT.TDIST) < 0.1 then EMT.TDIST = nil end
      EMT.TUSERNOTES = EgtTdbGetCurrToolParam( MCH_TP.USERNOTES)
   end
end

---------------------------------------------------------------------
function OnToolDeselect()
end

---------------------------------------------------------------------
function OnMachiningStart()
   EMT.MCHNAME = EgtGetOperationName( EMT.MCHID)
   EMT.MCHTYPE = EgtGetMachiningParam( MCH_MP.TYPE)
   EMT.MCHUSERNOTES = EgtGetMachiningParam( MCH_MP.USERNOTES)
   EMT.MCHCUT = ( EMT.MCHUSERNOTES:find( 'Cut;', 1, true) ~= nil)

   -- se sega a catena, aggiusto subito angolo scelto per asse virtuale A
   if EMT.HEAD == 'H13' then
      -- valore dell'asse virtuale
      dPosA = GetCurrChainSawingVirtualAxis()
      -- imposto home dell'asse C1 (A=0 -> T101, A=90 -> T104)
      local MyParkCSawC1 = GetChainSawCHomeFromVirtualAxis( dPosA, EMT.TTOTLEN)
      EmtModifyAxisHome( 'C1', MyParkCSawC1)
   -- aggregato foratura multipla, aggiusto subito angolo scelto per asse virtuale A
   elseif EMT.HEAD == 'H14' then
      -- valore dell'asse virtuale
      dPosA = GetCurrMultiDrillVirtualAxis()
      -- imposto home dell'asse C1 (A=0 -> T121, A=90 -> T124)
      local MyParkMultiDrillC1 = GetMultiDrillCHomeFromVirtualAxis( dPosA, EMT.TTOTLEN)
      EmtModifyAxisHome( 'C1', MyParkMultiDrillC1)
   -- se lama su aggregato testa sotto, aggiusto subito angolo scelto per asse virtuale A
   elseif EMT.HEAD == 'H22' then
      -- valore dell'asse virtuale
      local dPrevA = dPosA
      dPosA = GetCurrSawingVirtualAxis()
      -- imposto home dell'asse C2 (A=0 -> T201, A=90 -> T204)
      local MyParkCSawC2 = GetSawCHomeFromVirtualAxis( dPosA)
      EmtModifyAxisHome( 'C2', MyParkCSawC2)
      -- se lavorazione attuale e precedente con lama su aggregato da sotto con angolo A diverso, devo scaricare e ricaricare
      if EMT.HEAD == EMT.PREVHEAD_H2 then
         if abs( dPosA - dPrevA) > 1 then
            EMT.TO_ZMAX = true
            EMT.R3_CHANGED = true
         end
      end
   end

   -- gestione eventuale lavorazione in doppio
   local nDouType = EgtGetValInNotes( EMT.MCHUSERNOTES, 'DOUBLE', 'i')
   if nDouType == 2 or nDouType == 3 then
      local sDouTool = EgtGetValInNotes( EMT.MCHUSERNOTES, 'TOOLDOUBLE', 's') or EgtGetValInNotes( EMT.TUSERNOTES, 'DOUBLE', 's') or ''
      local sOldTool = EgtTdbGetCurrToolParam( MCH_TP.NAME)
      if EgtTdbSetCurrTool( sDouTool) and EgtTdbGetCurrToolParam( MCH_TP.ACTIVE) then
         -- salvo dati
         EMT.DOU_TLEN = EgtTdbGetCurrToolParam( MCH_TP.LEN)
         EMT.DOU_TYPE = nDouType
         EMT.DOU_TOOL = sDouTool
         EMT.DOU_HEAD = 'H21'
         EMT.DOU_EXIT = 1
         EMT.DOU_TPOS = AdjustTcPos( false, EgtTdbGetCurrToolParam( MCH_TP.TCPOS))
         EMT.DOU_SPEED = EgtTdbGetCurrToolParam( MCH_TP.SPEED)
         -- non va in sicurezza testa in doppio
         EMT.DOU_TO_ZMAX = nil
      else
         EmtSetLastError( 1211, 'Missing or not active double tool of '..sOldTool)
      end
      EgtTdbSetCurrTool( sOldTool)
   end
   -- se testa in doppio ancora da rimettere in sicurezza
   if EMT.DOU_TO_ZMAX then
      local dPosT = EMT.TPOS or EMT.L1op
      EmitParkRoller( dPosT, false)
      EmitDouZMax( EMT.DOU_TO_ZMAX)
   end
   -- controllo seguente trovato nella 3T. Da verificare
   -- determino subito se taglio di separazione di pezzo a caduta
   EMT.PREFALLCUT = nil
   if EMT.MCHUSERNOTES and EMT.MCHUSERNOTES:find( 'Split') then
      local ClId = EgtGetFirstNameInGroup( EMT.MCHID, 'CL')
      local P1Id = EgtGetFirstNameInGroup( ClId or GDB_ID.NULL, 'P1')
      local sAE1 = EgtGetInfo( P1Id or GDB_ID.NULL, 'AE1') or ''
      if sAE1 == '0,Fall' then
         EMT.PREFALLCUT = true
      end
   end
end

---------------------------------------------------------------------
function OnMachiningEnd()
   --MyOutput( ';Mach End')
   -- Emissione split
   if #EMT.MDCHAR > 0 then
      if EMT.AUXTYPE == 'S' then
         EmitRemark( 'PART SPLIT')
         EMT.LB = EMT.LT
      elseif EMT.AUXTYPE == 'U' then
         EmitRemark( 'PART UNLOAD')
      elseif EMT.AUXTYPE == 'P' then
         local nNextPathId
         local nNextMchId
         if EMT.PATHID then nNextPathId = EgtGetNextActiveOperation( EMT.PATHID) end
         if EMT.MCHID then nNextMchId = EgtGetNextActiveOperation( EMT.MCHID) end
         if EMT.PREROT then
            EmitZmax( false, true, EMT.R1, EMT.R2, false, false, false)
            EMT.ZMAX = true
            EmitRemark( 'PART ROTATION')
         elseif EMT.FALL then
            if EMT.TO_ZMAX and not EMT.ZMAX then
               EmitZmax( false, true, EMT.R1, EMT.R2, false, false, false)
               EMT.ZMAX = true
               EMT.TO_ZMAX = nil
            end
            EmitRemark( 'PART FALL')
         elseif EMT.UNLOAD and not nNextPathId and not nNextMchId then
            if not EMT.ZMAX then
               EmitZmax( false, true, EMT.R1, EMT.R2, false, false, false)
               EMT.ZMAX = true
            end
            EmitRemark( 'PART UNLOAD ON LOAD POSITION')
         else
            EmitZmax( false, true, EMT.R1, EMT.R2, false, false, false)
            EMT.ZMAX = true
            EmitRemark( 'PART SPLIT 2')
         end
      end
   end
   for i = 1, #EMT.MDCHAR do
      -- Se è un'istruzione del piano ferma pezzo
      if EMT.MDCHAR[i].StopBeamStat then
         EmitStopBeam( EMT.MDCHAR[i])
      else
         EmitMoveDataChars( EMT.MDCHAR[i])
      end
   end
   if #EMT.MDCHAR > 0 then
      if EMT.AUXTYPE == 'S' then
         EmitMoveStartChars( 1)
         EmitMoveWaitChars( 1)
      elseif EMT.AUXTYPE == 'U' then
         local nMoveType = EgtIf( EMT.CHY_ON, 3, 2)
         EmitMoveStartChars( nMoveType)
         -- se dopo cè scarico spezzone devo mettere attesa termine esecuzione
         if IsRestPhase( EMT.PHASE + 1) then
            EmitMoveWaitChars( nMoveType)
         else
            EmitMoveWaitChars( nMoveType, true)
         end
         -- emissione conclusione pezzo precedente (se non è modalità test)
         if not TEST_USE and EMT.PRODID then
            local sEnd= 'M113 P1='..tostring( EMT.PRODID)..' P2='..tostring( EMT.PATTID)..' P3='..tostring( EMT.CUTID)..' P4=2' 
            MyOutput( sEnd)
         end
      elseif EMT.AUXTYPE == 'P' then
         EmitMoveStartChars( 3)
         EmitMoveWaitChars( 3)
         if EMT.FALL then
            MyOutput( 'M155')
            -- emissione conclusione pezzo (se non in modalità test)
            if not TEST_USE and EMT.PRODID then
               local sEnd= 'M113 P1='..tostring( EMT.PRODID)..' P2='..tostring( EMT.PATTID)..' P3='..tostring( EMT.CUTID)..' P4=2' 
               MyOutput( sEnd)
            end
            EMT.FALL = nil
         elseif not EMT.PREROT then
            -- emissione conclusione pezzo (se non in modalità test)
            if not TEST_USE and EMT.PRODID and ( EMT.UNLOAD and ( not nNextPathId) and ( not nNextMchId)) then
               local sEnd= 'M113 P1='..tostring( EMT.PRODID)..' P2='..tostring( EMT.PATTID)..' P3='..tostring( EMT.CUTID)..' P4=2' 
               MyOutput( sEnd)
            end
            MyOutput( 'M77')
         end
      end
   end
   -- Reset 
   EMT.MDCHAR = {}
   EMT.AUXTYPE = nil
   EMT.TO_ZMAX = nil
   EMT.PREVTOOL = EMT.TOOL
   if EMT.DOU_TYPE and not EMT.ZMAX then EMT.DOU_TO_ZMAX = EMT.DOU_TYPE end
   EMT.DOU_TYPE = nil
   -- se taglio finale, aggiorno lunghezza barra
   if EMT.MCHCUT then
      EMT.LB = EMT.LT + ( EMT.X_OFF or 0)
   end
   -- 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.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.PREVTCPOSREAL_H2 = EMT.TCPOSREAL
   end
end

---------------------------------------------------------------------
function OnPathStart()
   if EMT.OPEISDISP then
      return
   end
   -- non ancora iniziata la lavorazione
   EMT.MCHFIRST = true
   EMT.MCHFIRSTFEED = true
   -- primo posizionamento sempre in globale
   EMT.REFLOC = nil
   EMT.IPLGL = false
   -- leggo se ancora presa iniziale carrello
   --EMT.CNT = EgtGetInfo( EMT.PATHID, 'CNT', 'i')
   --MyOutput( 'CNT='.. tostring( EMT.CNT or 0))
   -- se utensile non cambiato, salvo eventuali precedenti rotanti
   if EMT.TOOL == EMT.PREVTOOL and not EMT.ZMAX then
      EMT.L2pp = EMT.L2op
      EMT.L3pp = EMT.L3op
      EMT.R1pp = EMT.R1p or EMT.R1pp
      EMT.R2pp = EMT.R2p or EMT.R2pp
   else
      EMT.L2pp = nil
      EMT.L3pp = nil
      EMT.R1pp = nil
      EMT.R2pp = nil
   end
   -- reset valori precedenti (per forzare emissione di tutti gli assi del 1° movimento)
   EmtResetPrev()
   EMT.AUXCMD = {}
   EMT.MDCHAR = {}
end

---------------------------------------------------------------------
function OnPathEnd()
   if EMT.OPEISDISP then
      return
   end
   if not EMT.ZMAX then
      EmitResetMachining()
   end

   -- salvo dati precedenti
   EMT.L2pp = EMT.L2o

   EMT.AUXTYPE = nil
   EMT.UNL = nil
   EMT.PREROT = nil
   EMT.SPLITROT = nil
   EMT.CHY_ON = nil
   EMT.FALL = nil
   EMT.MDCHAR = {}
   EMT.PREVTOOL = EMT.TOOL
end

---------------------------------------------------------------------
function OnPathStartAux()
   --EgtOutLog( 'OnPathStartAux - ' .. EgtNumToString( EMT.AUXIND, 0) .. ' - ' .. EMT.AUX)
   -- salvo il comando per processarlo successivamente
   table.insert( EMT.AUXCMD, EMT.AUX)
   -- gestione speciale per richiesta di movimento a ZMAX
   local Cmd = EgtSplitString( EMT.AUX)
   if Cmd[1] == '4' then
      EMT.TO_ZMAX = true
      if Cmd[2] == '1' then
         EMT.ROLL_IN = true
      end
   end
end

---------------------------------------------------------------------
function ProcessPathStartAux( sCmd, nInd)
   -- se richiesto, preparo il carico barra
   if EMT.LOAD or EMT.RELOAD then
      PrepareLoad( sCmd, nInd, true)
   -- se altrimenti carico dopo rotazione
   elseif EMT.POSTROT then
      PreparePostRotation( sCmd, nInd)
   -- altrimenti, preparo lo spostamento carrelli
   else
      PrepareMoveChar( sCmd, nInd)
   end
end

---------------------------------------------------------------------
function OnPathEndAux()
   --EgtOutLog( 'OnPathEndAux - ' .. EgtNumToString( EMT.AUXIND, 0) .. ' - ' .. EMT.AUX)
   -- 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'
         else
            EMT.AUXTYPE = 'P'
            if Cmd[1] == '0' and Cmd[2] == 'Fall' then
               EMT.FALL = true
            end
         end
      elseif EMT.AUXTYPE == 'P' then
         local Cmd = EgtSplitString( EMT.AUX)
         if  Cmd[1] == '0'  and  Cmd[2] == 'Unloading' then
            EMT.CHY_ON = true
            EMT.AUXTYPE = 'U'
         end
      end
   end
   -- preparo a seconda del tipo
   if EMT.AUXTYPE == 'R' then
      -- per il carico della rimanenza dopo rotazione speciale
      if EMT.RELOAD and not EMT.RELOAD2 then
         PrepareLoad( EMT.AUX, EMT.AUXIND, false)
      -- per lo scarico della rimanenza
      else
         PrepareResidue( EMT.AUX, EMT.AUXIND)
      end
   elseif EMT.AUXTYPE == 'S' then
      -- per lo split
      PrepareSplit( EMT.AUX, EMT.AUXIND)
   elseif EMT.AUXTYPE == 'U' then
      -- per lo scarico
      PrepareUnload( EMT.AUX, EMT.AUXIND)
   elseif EMT.AUXTYPE == 'P' then
      -- per la pre-rotazione
      PreparePreRotation( EMT.AUX, EMT.AUXIND)
   end
end

---------------------------------------------------------------------
function OnRapid()
   -- gruppo della testa
   local nHSet = GetHeadSet( EMT.HEAD)
   -- se primo movimento della lavorazione, gestione speciale
   if EMT.MCHFIRST and not EMT.OPEISDISP then
      MyAdjustLinearAxes()
      EmtAdjustRotaryAxes()
      local HomeZ1 = EgtGetAxisHomePos( 'Z1')
      local HomeR1 = EgtGetAxisHomePos( EgtIf( nHSet == 1, 'C1', 'C2'))
      local HomeR2 = EgtGetAxisHomePos( EgtIf( nHSet == 1, 'B1', 'B2'))
      local PrevR1 = EMT.R1p or EMT.R1pp or HomeR1
      local PrevR2 = EMT.R2p or EMT.R2pp or HomeR2
      EmtResetPrev()
      -- se prima lavorazione
      if EMT.LOAD then
         EMT.V2POS = ParkV2
         -- primo posizionamento
         local Speed = EMT.S
         if EMT.HEAD == 'H12' or EMT.HEAD == 'H16' or EMT.HEAD == 'H22' then Speed = 1.65 * EMT.S end
         local bOnlyCharY = true
         for i = 1, #EMT.AUXCMD do
            ProcessPathStartAux( EMT.AUXCMD[i], i)
         end
         EMT.AUXCMD = {}
         -- se Split e non Caduta lascio agganciata solo la pinza Y2 alla fine dei movimenti
         local bSplitCut = ( EMT.MCHUSERNOTES:find( 'Split', 1, true) ~= nil)
         local bFall = ( bSplitCut and FindFallAtEndAux( EMT.PATHID))
         if ( bSplitCut and not bFall and #EMT.MDCHAR > 0) then
            EMT.MDCHAR[#EMT.MDCHAR].FinStatY1 = 1
         end
         for i = 1, #EMT.MDCHAR do
            EmitMoveDataChars( EMT.MDCHAR[i])
            if EMT.MDCHAR[i].MovType ~= 1 then bOnlyCharY = false end
         end
         EMT.MDCHAR = {}
         EmitMoveStartChars( EgtIf( bOnlyCharY, 1, 3))
         EmitRemark( ' *** '..EMT.MCHNAME..'/'..EMT.TOOL..' *** ')
         -- se testa 1
         if nHSet == 1 then
            -- selezione testa (posso muovere X solo a Zmax)
            local MyMaxZ1 = EgtGetAxisMax( 'Z1')
            -- in caso di rotazione della lama lontano dalla posizione di home degli assi rotanti
            local bXSpec = EgtIf( BD.RIGHT_LOAD, ( EMT.L2 > -DeltaTabY), ( EMT.L2 < -DeltaTabY))
            -- se non è aggregato, posso preselezionare testa e andare in posizione
            if EMT.HEAD ~= 'H13' and EMT.HEAD ~= 'H14' then
               EmitMoveDataHead( 1, { X=EgtIf( bXSpec, -DeltaTabY, EMT.L2), Z=MyMaxZ1, S=Speed})
               EmitMoveStartHead( 1)
               EmitMoveWaitHead( 1)
            end
            EmitMoveWaitChars( EgtIf( bOnlyCharY, 1, 3))
            -- se necessario allargo le cabine
            if RollerParkingNeeded( EMT.HEAD, PrevR1, PrevR2, EMT.R1, EMT.R2) or
               EMT.V1POS < EMT.V1NEXTPOS - 1 or EMT.V2POS > EMT.V2NEXTPOS + 1 then
               local dPosT = EMT.TPOS or EMT.L1op
               EmitParkRoller( dPosT, bSplitCut)
            end
            if ( EMT.HEAD == 'H12' or EMT.HEAD == 'H16') and ( abs( PrevR1 - EMT.R1) > 1 or abs( PrevR2 - EMT.R2) > 1) and ( abs( HomeR1 - EMT.R1) > 30.1 or abs( HomeR2 - EMT.R2) > 30.1) then
               -- se bisogna scrivere tutti gli assi
               if WriteAllCoordsOnFirstM101 then
                  EmitMoveDataHead( 1, { X=EgtIf( bXSpec, -DeltaTabY, EMT.L2), Z=MyMaxZ1, B=ParkB1, C=ParkC1, S=Speed})
               else
                  EmitMoveDataHead( 1, { X=EgtIf( bXSpec, -DeltaTabY, EMT.L2), Z=MyMaxZ1, S=Speed})
               end
               EmitMoveDataHead( 1, { B=0, S=Speed})
               EmitMoveDataHead( 1, { C=EMT.R1, S=Speed})
               EmitMoveDataHead( 1, { X=EMT.L2, S=Speed})
               -- 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)
                  EmitMoveDataHead( 1, { Z=EMT.L3, B=dBref, S=Speed})
                  EmitMoveDataHead( 1, { B=EMT.R2, S=Speed})
               else
                  EmitMoveDataHead( 1, { Z=EMT.L3, B=EMT.R2, S=Speed})
               end
            -- caso standard
            else
               -- se bisogna scrivere tutti gli assi
               if WriteAllCoordsOnFirstM101 then
                  -- se sega a catena
                  if EMT.HEAD == 'H13' then
                     EmitMoveDataHead( 1, { X=-ParkCSawX1, Z=HomeZ1, B=HomeR2, C=HomeR1, TRad=dTRad, TLen=dTLen, S=Speed})
                  -- aggregato foratore multiplo
                  elseif EMT.HEAD == 'H14' then
                     EmitMoveDataHead( 1, { X=-ParkMultiDrillX1, Z=HomeZ1, B=HomeR2, C=HomeR1, TRad=dTRad, TLen=dTLen, S=Speed})
                  -- utensili standard
                  else
                     -- utensili lunghi
                     if EMT.TTOTLEN > LongTool then
                        EmitMoveDataHead( 1, { X=EgtIf( bXSpec, -DeltaTabY, EMT.L2), Z=EMT.L3, B=ParkLongB1, C=ParkLongTc1C1, S=Speed})
                     else
                        EmitMoveDataHead( 1, { X=EgtIf( bXSpec, -DeltaTabY, EMT.L2), Z=EMT.L3, B=ParkB1, C=ParkC1, S=Speed})
                     end
                  end
               end
               if EMT.HEAD ~= 'H13' then 
                  EmitMoveDataHead( 1, { Z=EMT.L3, S=Speed})
                  EmitMoveDataHead( 1, { B=EMT.R2, S=Speed})
                  EmitMoveDataHead( 1, { C=EMT.R1, S=Speed})
                  EmitMoveDataHead( 1, { X=EMT.L2, S=Speed})
               else
                  EmitMoveDataHead( 1, { Z=EMT.L3, B=EMT.R2, S=Speed})
                  EmitMoveDataHead( 1, { C=EMT.R1, S=Speed})
                  EmitMoveDataHead( 1, { X=EMT.L2, S=Speed})
               end
            end
            EmitMoveStartHead( 1)
            -- se lavorazione in doppio
            if EMT.DOU_TYPE == 2 then
               local dX2 = Delta2TabY + EMT.DOU_TLEN - ( -DeltaTabY - EMT.L2 + EMT.TLEN + EgtIf( BD.RIGHT_LOAD, -EMT.HB, EMT.HB))
               local dZ2 = -Head2Z + MillOffs + Mill2Offs + EMT.L3
               dZ2 = min( dZ2, MinZ2)
               local dC2 = EMT.R1
               local dB2 = EMT.R2
               -- se bisogna scrivere tutti gli assi
               if WriteAllCoordsOnFirstM101 then
                  EmitMoveDataHead( 2, { X=dX2, Z=MinZ2, B=ParkB2, C=ParkC2, TPos=EMT.DOU_TPOS, S=EMT.DOU_SPEED})
               else
                  EmitMoveDataHead( 2, { X=dX2, TPos=EMT.DOU_TPOS, S=EMT.DOU_SPEED})
               end
               EmitMoveDataHead( 2, { Z=-dZ2, B=dB2, C=dC2, TPos=EMT.DOU_TPOS, S=EMT.DOU_SPEED})
               EmitMoveStartHead( 2)
               -- aspetto esecuzione movimento testa 2
               EmitMoveWaitHead( 2)
            elseif EMT.DOU_TYPE == 3 then
               local dX2 = -Head2Y - EMT.L2
               local dZ2 = Delta2TabZ - EMT.DOU_TLEN - ( -DeltaTabZ + EMT.L3 - EMT.TLEN - EMT.SB)
               dZ2 = max( dZ2, MinZ2)
               local dC2 = EMT.R1
               local dB2 = EMT.R2
               -- se bisogna scrivere tutti gli assi
               if WriteAllCoordsOnFirstM101 then
                  EmitMoveDataHead( 2, { X=dX2, Z=MinZ2, B=ParkB2, C=ParkC2, TPos=EMT.DOU_TPOS, S=EMT.DOU_SPEED})
               else
                  EmitMoveDataHead( 2, { X=dX2, TPos=EMT.DOU_TPOS, S=EMT.DOU_SPEED})
               end
               EmitMoveDataHead( 2, { Z=-dZ2, B=dB2, C=dC2, TPos=EMT.DOU_TPOS, S=EMT.DOU_SPEED})
               EmitMoveStartHead( 2)
               -- aspetto esecuzione movimento testa 2
               EmitMoveWaitHead( 2)
            else
               -- eventuale preselezione successiva testa 2
               PreselectNextDiffHead( EMT.MCHID, EMT.HEAD)
            end
            -- aspetto esecuzione movimento testa 1
            EmitMoveWaitHead( 1)
         -- se altrimenti testa 2
         elseif nHSet == 2 then
            -- selezione testa (non posso muovere X)
            -- se bisogna scrivere tutti gli assi
            if WriteAllCoordsOnFirstM101 then
               if EMT.HEAD == 'H22' then
                  EmitMoveDataHead( 2, { X=ParkSawX2, Z=ParkZ2, B=0, C=ParkC2, S=Speed})
                  EMT.L2pp = ParkSawX2
               else
                  EmitMoveDataHead( 2, { X=ParkX2, Z=ParkZ2, B=ParkB2, C=ParkC2, S=Speed})
                  EMT.L2pp = ParkX2
               end
            else
               if EMT.HEAD == 'H22' then
                  EmitMoveDataHead( 2, { X=ParkSawX2, S=Speed})
                  EMT.L2pp = ParkSawX2
               else
                  EmitMoveDataHead( 2, { X=ParkX2, S=Speed})
                  EMT.L2pp = ParkX2
               end
            end
            EmitMoveStartHead( 2)
            EmitMoveWaitHead( 2)
            EmitMoveWaitChars( EgtIf( bOnlyCharY, 1, 3))
            -- se necessario allargo le cabine
            if RollerParkingNeeded( EMT.HEAD, PrevR1, PrevR2, EMT.R1, EMT.R2) or
               EMT.V1POS < EMT.V1NEXTPOS - 1 or EMT.V2POS > EMT.V2NEXTPOS + 1 then
               local dPosT = EMT.TPOS or EMT.L1op
               EmitParkRoller( dPosT, bSplitCut)
            end
            -- vado a quota sicurezza in X per permettere rotazione assi rotanti
            if ( not EMT.L2pp or ( EMT.L2pp and EMT.L2pp > SafeX2RotAxis)) and ( ParkB2 ~= EMT.R2  or  ParkC2 ~= EMT.R1) then
               EmitMoveDataHead( 2, { X=SafeX2RotAxis, Z=ParkZ2, B=ParkB2, C=ParkC2, S=Speed}) 
            end
            -- scrivo solo se gli assi rotanti sono cambiati
            if EMT.HEAD == 'H22' and ( CurrB2 ~= EMT.R2  or  CurrC2 ~= EMT.R1) then
               EmitMoveDataHead( 2, { B=EMT.R2, C=EMT.R1, S=Speed})
            end    
            if EMT.ZMAX then
               EmitMoveDataHead( 2, { X=EMT.L2, B=EMT.R2, C=EMT.R1, S=Speed})            
            else
               EmitMoveDataHead( 2, { X=EMT.L2, Z=EMT.L3, B=EMT.R2, C=EMT.R1, S=Speed})            
            end    
            EmitMoveStartHead( 2)
            -- eventuale preselezione successiva testa 1
            PreselectNextDiffHead( EMT.MCHID, EMT.HEAD)
            -- aspetto esecuzione movimento testa 2
            EmitMoveWaitHead( 2)
         -- altrimenti testa 3
         else
            -- selezione testa (posso muovere X solo a Zmax)
            local MaxZ3 = EgtGetAxisMax( 'Z3')
            local MyParkX3 = EgtGetAxisHomePos( 'X3')
            -- se bisogna scrivere tutti gli assi
            if WriteAllCoordsOnFirstM101 then
               EmitMoveDataHead( 3, { X=MyParkX3, Z=MaxZ3, B=ParkB3, C=ParkC3, S=Speed})
            else
               EmitMoveDataHead( 3, { X=MyParkX3, Z=MaxZ3, S=Speed})
            end
            EmitMoveStartHead( 3)
            EmitMoveWaitHead( 3)
            EmitMoveWaitChars( EgtIf( bOnlyCharY, 1, 3))
            -- se necessario allargo le cabine
            if RollerParkingNeeded( EMT.HEAD, PrevR1, PrevR2, EMT.R1, EMT.R2) or
               EMT.V1POS < EMT.V1NEXTPOS - 1 or EMT.V2POS > EMT.V2NEXTPOS + 1 then
               local dPosT = EMT.TPOS or EMT.L1op
               EmitParkRoller( dPosT, bSplitCut)
            end
            -- eventuale preselezione successiva testa 2
            PreselectNextDiffHead( EMT.MCHID, EMT.HEAD)
            -- eseguo movimenti
            local Speed = EMT.S
            -- se è macchina a 3 teste con gruppo truciolatore 4 assi
            if EgtGetHeadId( 'H39') then
               EmitMoveDataHead( 3, { Z=EMT.L3, S=Speed})
               EmitMoveDataHead( 3, { B=EMT.R2, C=EMT.R1, S=Speed})
               EmitMoveStartHead( 3)
            -- se gruppo lama dedicato, senza cambio utensile
            elseif EgtGetHeadId( 'H38') then
               local B3Home = EgtGetAxisHomePos( 'B3')
               local C3Home = EgtGetAxisHomePos( 'C3')
               EmitMoveDataHead( 3, { X=-SafeX3RotAxis, S=Speed})
               -- 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)
                  EmitMoveDataHead( 3, { Z=SafeZ3RotAxis, S=Speed})
                  EmitMoveDataHead( 3, { B=EMT.R2, C=EMT.R1, S=Speed})
                  if ( EMT.L3 - SafeZ3RotAxis) > 100 * GEO.EPS_SMALL then
                     EmitMoveDataHead( 3, { Z=EMT.L3, S=Speed})
                  end
                  EmitMoveDataHead( 3, { X=EMT.L2, S=Speed})
               end
               -- muovo in posizione finale
               EmitMoveDataHead( 3, { X=EMT.L2, Z=EMT.L3, B=EMT.R2, C=EMT.R1, S=Speed})
            else
               EmtSetLastError( 1212, "HEAD not managed")
            end

            EmitMoveStartHead( 3)
            -- aspetto esecuzione movimento testa 3
            EmitMoveWaitHead( 3)
            -- errore, testa non gestita
         end
         -- dati aggancio a trave
         local BhData = { T=EMT.L1, SetHead=0}
         if EMT.Y1DELTA then
            BhData.Y1 = EMT.L1 + EMT.Y1DELTA
            BhData.ViseY1 = 1
         else
            BhData.Y1 = ParkY1
            BhData.ViseY1 = 2
         end
         if bSplitCut and EMT.Y2DELTA then
            BhData.ViseY1 = 3
         end
         if EMT.Y2DELTA then
            BhData.Y2 = EMT.L1 + EMT.Y2DELTA
            BhData.ViseY2 = 1
         else
            BhData.Y2 = ParkY2
            BhData.ViseY2 = 2
         end
         if GetV1ToCloseTPA() then
            BhData.V1 = EMT.V1NEXTPOS ; EMT.V1POS = BhData.V1
            BhData.StatV1 = -2
         elseif abs( EMT.V1POS - ParkV1) > 0.1 then
            BhData.V1 = ParkV1 ; EMT.V1POS = BhData.V1
            BhData.StatV1 = 1
         end
         if GetV2ToCloseTPA() then
            BhData.V2 = EMT.V2NEXTPOS ; EMT.V2POS = BhData.V2
            BhData.StatV2 = -2
         elseif abs( EMT.V2POS - ParkV2) > 0.1 then
            BhData.V2 = ParkV2 ; EMT.V2POS = BhData.V2
            BhData.StatV2 = 1
         end
         EmitBeamHeadData( BhData)
         EMT.LOAD = false
         -- emissione prime linee speciali e linee parcheggiate
         EmitStartValues()
         local ProdId = EgtGetInfo( EgtGetFirstNameInGroup( GDB_ID.ROOT, 'BtlInfo') or GDB_ID.NULL, 'PRODID', 'i') or 0
         MyOutput( ';LISTA = ' .. tostring( ProdId))
         EmitParkedLines()
         -- se sfrido grande, emetto comando speciale
         if EMT.HOVM > 99 then
            if IsHeadExisting( 3) then
               MyOutput( 'M107')
            else
               MyOutput( ';M107')
            end
         end
      -- altrimenti lavorazione successiva
      else
         --MyOutput( string.format( 'V1Pos=%.3f V2Pos=%.3f', EMT.V1POS, EMT.V2POS))
         if not EMT.RELOAD and not EMT.ZMAX and #EMT.AUXCMD > 0 and EMT.TO_ZMAX then
            local bGotoHome = EMT.HEAD == 'H38' and EMT.ROLL_IN
            -- caso speciale in cui bisogna solo cambiare presa aggregato lama sotto
            if EMT.R3_CHANGED then
               -- EmitZmax è fatta per essere chiamata con utensile attuale. In questo caso l'attuale è già quello con nuova rotazione
               -- Allora salvo l'attuale e imposto il vecchio solo momentanemente, per poi ripristinarlo
               local sBckTcPos = EMT.TCPOS
               EMT.TCPOS = EMT.PREVTCPOSREAL_H2
               EmitZmax( false, false, PrevR1, PrevR2, false, bGotoHome, true)
               EMT.TCPOS = sBckTcPos
               EMT.R3_CHANGED = nil
            else
               EmitZmax( false, false, PrevR1, PrevR2, false, bGotoHome, true)
            end
            EMT.ZMAX = true
            EMT.TO_ZMAX = nil
            PrevR1 = EgtGetAxisHomePos( EgtIf( nHSet == 1, 'C1', 'C2'))
            PrevR2 = EgtGetAxisHomePos( EgtIf( nHSet == 1, 'B1', 'B2'))
         end
         EmitRemark( ' *** '..EMT.MCHNAME..'/'..EMT.TOOL..' *** ')
         -- eventuale movimento carrelli
         for i = 1, #EMT.AUXCMD do
            ProcessPathStartAux( EMT.AUXCMD[i], i)
         end
         EMT.AUXCMD = {}
         -- se Split e non Caduta lascio agganciata solo la pinza Y2 alla fine dei movimenti
         local bSplitCut = ( EMT.MCHUSERNOTES:find( 'Split', 1, true) ~= nil)
         local bFall = ( bSplitCut and FindFallAtEndAux( EMT.PATHID))
         if ( bSplitCut and not bFall and #EMT.MDCHAR > 0) then
            EMT.MDCHAR[#EMT.MDCHAR].FinStatY1 = 1
         end
         for i = 1, #EMT.MDCHAR do
            EmitMoveDataChars( EMT.MDCHAR[i])
         end
         if #EMT.MDCHAR > 0 then
            local nMoveType = EgtIf( EMT.POSTROT or EMT.RELOAD, 1, 3)
            EmitMoveStartChars( nMoveType)
            EmitMoveWaitChars( nMoveType)
         end
         EMT.MDCHAR = {}
         -- se necessario allargo le cabine
         if RollerParkingNeeded( EMT.HEAD, PrevR1, PrevR2, EMT.R1, EMT.R2) or
            EMT.V1POS < EMT.V1NEXTPOS - 1 or EMT.V2POS > EMT.V2NEXTPOS + 1 then
            local dPosT = EMT.TPOS or EMT.L1op
            EmitParkRoller( dPosT, bSplitCut)
         end
         -- se taglio di coda senza residuo da scaricare
         if EMT.MCHUSERNOTES and EMT.MCHUSERNOTES:find( 'Cut', 1, true) then
            -- se sfrido grande, emetto comando speciale
            if EMT.LB - EMT.LT > 99 then
               if IsHeadExisting( 3) then
                  MyOutput( 'M107')
               else
                  MyOutput( ';M107')
               end
            end
            -- emetto M175 per accelerare il carico della barra successiva
            MyOutput( ';M175')
         end
         -- se testa 1
         if nHSet == 1 then
            -- movimento testa
            local HomeZ1 = EgtGetAxisHomePos( 'Z1')
            local MyMaxZ1 = EgtGetAxisMax( 'Z1')
            local HomeC1 = EgtGetAxisHomePos( 'C1')
            local HomeB1 = EgtGetAxisHomePos( 'B1')
            local Speed = EMT.S * EgtIf( EMT.HEAD ~= 'H12' and EMT.HEAD ~= 'H16', 1, 1.65)
            if EMT.ZMAX or ( EMT.FLAG == 2 and EMT.FLAG2 == 1) then
               -- se fresa o lama
               if EMT.HEAD ~= 'H13' then
                  local CurrZ1 = EMT.L3pp or MyMaxZ1
                  local CurrB1 = PrevR2
                  local CurrC1 = PrevR1
                  -- muovo asse X
                  local bXSpec = EgtIf( BD.RIGHT_LOAD, ( EMT.L2 > -DeltaTabY), ( EMT.L2 < -DeltaTabY))
                  -- se bisogna scrivere tutti gli assi
                  if WriteAllCoordsOnFirstM101 then
                     EmitMoveDataHead( 1, { X=EgtIf( bXSpec, -DeltaTabY, EMT.L2), Z=CurrZ1, B=CurrB1, C=CurrC1, S=Speed})
                  end
                  -- in caso di rotazione della lama lontano dalla posizione di home degli assi rotanti
                  if ( EMT.HEAD == 'H12' or EMT.HEAD == 'H16') and ( abs( PrevR1 - EMT.R1) > 1 or abs( PrevR2 - EMT.R2) > 1) and ( abs( HomeC1 - EMT.R1) > 30.1 or abs( HomeB1 - EMT.R2) > 30.1) then
                     -- se non bisogna scrivere tutti gli assi, comunque ribadisco X in caso di lama. Altrimenti ho già scritto prima
                     if not WriteAllCoordsOnFirstM101 then
                        EmitMoveDataHead( 1, { X=EgtIf( bXSpec, -DeltaTabY, EMT.L2), S=Speed})
                     end
                     EmitMoveDataHead( 1, { Z=MyMaxZ1, B=0, S=Speed})
                     EmitMoveDataHead( 1, { C=EMT.R1, S=Speed})
                     EmitMoveDataHead( 1, { X=EMT.L2, S=Speed})
                     -- 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)
                        EmitMoveDataHead( 1, { Z=EMT.L3, B=dBref, S=Speed})
                        EmitMoveDataHead( 1, { B=EMT.R2, S=Speed})
                     else
                        EmitMoveDataHead( 1, { Z=EMT.L3, B=EMT.R2, S=Speed})
                     end
                  -- caso standard
                  else
                     -- se ero in posizione speciale, prima ruoto poi scendo
                     if bXSpec then
                        local dZPos = max( HomeZ1, EMT.L3)
                        EmitMoveDataHead( 1, { Z=dZPos, S=Speed})
                        EmitMoveDataHead( 1, { B=EMT.R2, C=EMT.R1, S=Speed})
                        EmitMoveDataHead( 1, { X=EMT.L2, S=Speed})
                        if abs( dZPos - EMT.L3) > 1 then
                           EmitMoveDataHead( 1, { Z=EMT.L3, S=Speed})
                        end
                     else
                        EmitMoveDataHead( 1, { Z=EMT.L3, S=Speed})
                        EmitMoveDataHead( 1, { B=EMT.R2, S=Speed})
                        EmitMoveDataHead( 1, { C=EMT.R1, S=Speed})
                     end
                  end
               -- altrimenti sega a catena
               else
                  -- se bisogna scrivere tutti gli assi
                  local dSafeZ1 = EgtIf( EMT.TTOTLEN < MinLengthLongCSaw, ParkCSawZ1, ParkLongCSawZ1)
                  if WriteAllCoordsOnFirstM101 then
                     EmitMoveDataHead( 1, { X=-ParkCSawX1, Z=dSafeZ1, B=HomeB1, C=HomeC1, TRad=dTRad, TLen=dTLen, S=Speed})
                  end
                  -- Porto la Z alla giusta quota
                  if EMT.L3 > dSafeZ1 + 1 and abs( EMT.R2) > 89.9 then
                     EmitMoveDataHead( 1, { X=EMT.L2, S=Speed})
                     EmitMoveDataHead( 1, { Z=dSafeZ1, S=Speed})
                  end
                  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
                     EmitMoveDataHead( 1, { Z=dSafeZ1, B=EMT.R2, S=Speed})
                     EmitMoveDataHead( 1, { X=EMT.L2, C=EMT.R1, S=Speed})
                     EmitMoveDataHead( 1, { Z=EMT.L3, S=Speed})
                  else
                     EmitMoveDataHead( 1, { Z=EMT.L3, B=EMT.R2, S=Speed})
                  end
                  -- se motosega molto lunga, ruoto prima di muovermi in X
                  if EMT.TTOTLEN >= MinLengthLongCSaw then
                     EmitMoveDataHead( 1, { C=EMT.R1, S=Speed})
                  end
               end
            end
            EmitMoveDataHead( 1, { X=EMT.L2, Z=EMT.L3, B=EMT.R2, C=EMT.R1, S=Speed})
            EmitMoveStartHead( 1)
            -- se lavorazione in doppio
            if EMT.DOU_TYPE == 2 then
               local dC2 = EMT.R1
               local dB2 = EMT.R2
               if EMT.ZMAX or ( EMT.FLAG == 2 and EMT.FLAG2 == 1) then
                  local dSafeZ2 = EgtGetAxisHomePos( 'Z2')
                  -- se bisogna scrivere tutti gli assi
                  if WriteAllCoordsOnFirstM101 then
                     EmitMoveDataHead( 2, { X=SafeX2, Z=MinZ2, B=ParkB2, C=ParkC2, TPos=EMT.DOU_TPOS, S=EMT.DOU_SPEED})
                  else
                     EmitMoveDataHead( 2, { X=SafeX2, TPos=EMT.DOU_TPOS, S=EMT.DOU_SPEED})
                  end

                  EmitMoveDataHead( 2, { Z=-dSafeZ2, TPos=EMT.DOU_TPOS, S=EMT.DOU_SPEED})
                  EmitMoveDataHead( 2, { B=dB2, C=dC2, TPos=EMT.DOU_TPOS, S=EMT.DOU_SPEED})
               else
                  local dX2 = Delta2TabY + EMT.DOU_TLEN - ( -DeltaTabY - EMT.L2 + EMT.TLEN + EgtIf( BD.RIGHT_LOAD, -EMT.HB, EMT.HB))
                  local dZ2 = EgtClamp( -Head2Z + MillOffs + Mill2Offs + EMT.L3, MinZ2, MaxZ2)
                  EmitMoveDataHead( 2, { X=dX2, Z=-dZ2, B=dB2, C=dC2, TPos=EMT.DOU_TPOS, S=EMT.DOU_SPEED})
               end
               EmitMoveStartHead( 2)
               -- aspetto esecuzione movimento testa 2
               EmitMoveWaitHead( 2)
            elseif EMT.DOU_TYPE == 3 then
               local dX2 = -Head2Y - EMT.L2
               local dZ2 = Delta2TabZ - EMT.DOU_TLEN - ( -DeltaTabZ + EMT.L3 - EMT.TLEN - EMT.SB)
               dZ2 = max( dZ2, MinZ2)
               local dC2 = EMT.R1
               local dB2 = EMT.R2
               if EMT.ZMAX then
                  local dSafeZ2 = EgtGetAxisHomePos( 'Z2')
                  -- se bisogna scrivere tutti gli assi
                  if WriteAllCoordsOnFirstM101 then
                     EmitMoveDataHead( 2, { X=SafeX2, Z=-dSafeZ2, B=ParkB2, C=ParkC2, TPos=EMT.DOU_TPOS, S=EMT.DOU_SPEED})
                  else
                     EmitMoveDataHead( 2, { Z=-dSafeZ2, TPos=EMT.DOU_TPOS, S=EMT.DOU_SPEED})
                  end
                  EmitMoveDataHead( 2, { X=dX2, TPos=EMT.DOU_TPOS, S=EMT.DOU_SPEED})
               end
               EmitMoveDataHead( 2, { X=dX2, Z=-dZ2, B=dB2, C=dC2, TPos=EMT.DOU_TPOS, S=EMT.DOU_SPEED})
               EmitMoveStartHead( 2)
               -- aspetto esecuzione movimento testa 2
               EmitMoveWaitHead( 2)
            else
               -- eventuale preselezione successiva testa 2
               PreselectNextDiffHead( EMT.MCHID, EMT.HEAD)
            end
            -- aspetto esecuzione movimento testa 1
            EmitMoveWaitHead( 1)
         -- altrimenti testa 2
         elseif nHSet == 2 then
            local MyMinZ2 = EgtGetAxisMin( 'Z2')
            local CurrZ2 = EMT.L3pp or MyMinZ2
            local CurrB2 = PrevR2
            local CurrC2 = PrevR1
            local dSafeZ2 = EgtGetAxisHomePos( 'Z2')
            local Speed = EMT.S * EgtIf( EMT.HEAD ~= 'H22', 1, 1.65)
            if not EMT.L2pp then EMT.L2pp = EgtGetAxisHomePos( 'X2') end
            -- 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
                        EmitMoveDataHead( 2, { X=ParkSawX2, Z=-dSafeZ2, B=0, C=CurrC2, S=Speed})
                        EMT.L2pp = ParkSawX2
                     else
                        EmitMoveDataHead( 2, { X=SafeX2, Z=-dSafeZ2, B=CurrB2, C=CurrC2, S=Speed})
                        EMT.L2pp = SafeX2
                     end
                  else
                     if EMT.HEAD == 'H22' then
                        EmitMoveDataHead( 2, { X=ParkSawX2, S=Speed})
                        EMT.L2pp = ParkSawX2
                     else
                        EmitMoveDataHead( 2, { X=SafeX2, S=Speed})
                        EMT.L2pp = SafeX2
                     end
                  end
                  -- scrivo solo se gli assi rotanti sono cambiati
                  EmitMoveDataHead( 2, { Z=-dSafeZ2, S=Speed})
                  if EMT.L2pp > SafeX2RotAxis and ( CurrB2 ~= EMT.R2  or  CurrC2 ~= EMT.R1) then
                     EmitMoveDataHead( 2, { X=SafeX2RotAxis, Z=-dSafeZ2, B=CurrB2, C=CurrC2, S=Speed}) 
                  end
               else
                  -- scrivo solo se gli assi rotanti sono cambiati
                  if EMT.L2pp > SafeX2RotAxis and ( CurrB2 ~= EMT.R2  or  CurrC2 ~= EMT.R1) then
                     EmitMoveDataHead( 2, { X=SafeX2RotAxis, Z=-dSafeZ2, B=CurrB2, C=CurrC2, S=Speed}) 
                  end
               end
            -- se stesso utensile vado già alla X di lavoro
            else
               -- scrivo solo se gli assi rotanti sono cambiati
               if EMT.ZMAX and EMT.L2pp > SafeX2RotAxis and ( CurrB2 ~= EMT.R2  or  CurrC2 ~= EMT.R1) then
                  EmitMoveDataHead( 2, { X=SafeX2RotAxis, Z=-dSafeZ2, B=CurrB2, C=CurrC2, S=Speed}) 
               end
            end
            -- scrivo solo se gli assi rotanti sono cambiati
            if EMT.HEAD == 'H22' and ( CurrB2 ~= EMT.R2  or  CurrC2 ~= EMT.R1) then
               EmitMoveDataHead( 2, { B=EMT.R2, C=EMT.R1, S=Speed})
            end    
            if EMT.ZMAX then
               EmitMoveDataHead( 2, { X=EMT.L2, B=EMT.R2, C=EMT.R1, S=Speed})            
            else
               EmitMoveDataHead( 2, { X=EMT.L2, Z=EMT.L3, B=EMT.R2, C=EMT.R1, S=Speed})            
            end
            EmitMoveStartHead( 2)
            -- eventuale preselezione successiva testa 1
            PreselectNextDiffHead( EMT.MCHID, EMT.HEAD)
            -- aspetto esecuzione movimento testa 2
            EmitMoveWaitHead( 2)
         -- altrimenti testa 3
         else
            -- eventuale preselezione successiva testa 2
            PreselectNextDiffHead( EMT.MCHID, EMT.HEAD)
            -- selezione testa (posso muovere X solo a Zmax)
            local MyMaxZ3 = EgtGetAxisMax( 'Z3')
            local dSafeZ3 = EgtGetAxisHomePos( 'Z3')
            local HomeC3 = EgtGetAxisHomePos( 'C3')
            local HomeB3 = EgtGetAxisHomePos( 'B3')
            local Speed = EMT.S
            -- se è macchina a 3 teste con gruppo truciolatore 4 assi
            if EgtGetHeadId( 'H39') then
               if EMT.ZMAX or ( EMT.FLAG == 2 and EMT.FLAG2 == 1) then
                  local CurrB3 = PrevR2
                  local CurrC3 = PrevR1
                  -- se bisogna scrivere tutti gli assi
                  if WriteAllCoordsOnFirstM101 then
                     EmitMoveDataHead( 3, { X=EMT.L2, Z=MyMaxZ3, B=CurrB3, C=CurrC3, S=Speed})
                  else
                     EmitMoveDataHead( 3, { X=EMT.L2, Z=MyMaxZ3, S=Speed})
                  end

                  EmitMoveDataHead( 3, { Z=EMT.L3, B=EMT.R2, C=EMT.R1, S=Speed})
               else
                  EmitMoveDataHead( 3, { X=EMT.L2, Z=EMT.L3, B=EMT.R2, C=EMT.R1, S=Speed})
               end
            -- se gruppo lama dedicato, senza cambio utensile
            elseif EgtGetHeadId( 'H38') then
               if EMT.ZMAX or ( EMT.FLAG == 2 and EMT.FLAG2 == 1) then
                  local B3Home = EgtGetAxisHomePos( 'B3')
                  local C3Home = EgtGetAxisHomePos( 'C3')
                  EmitMoveDataHead( 3, { X=-SafeX3RotAxis, S=Speed})
                  -- 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)
                     EmitMoveDataHead( 3, { Z=SafeZ3RotAxis, S=Speed})
                     EmitMoveDataHead( 3, { B=EMT.R2, C=EMT.R1, S=Speed})
                     if ( EMT.L3 - SafeZ3RotAxis) > 100 * GEO.EPS_SMALL then
                        EmitMoveDataHead( 3, { Z=EMT.L3, S=Speed})
                     end
                     EmitMoveDataHead( 3, { X=EMT.L2, S=Speed})
                  end
               end
               -- muovo in posizione finale
               EmitMoveDataHead( 3, { X=EMT.L2, Z=EMT.L3, B=EMT.R2, C=EMT.R1, S=Speed})
            else
               EmtSetLastError( 1212, "HEAD not managed")
            end
            
            EmitMoveStartHead( 3)
            -- aspetto esecuzione movimento testa 3
            EmitMoveWaitHead( 3)
         end
         -- dati aggancio a trave
         local BhData = { T=EMT.L1}
         if EMT.Y1DELTA then
            BhData.Y1 = EMT.L1 + EMT.Y1DELTA - ( EMT.X_OFF or 0)
            BhData.ViseY1 = 1
         else
            BhData.Y1 = ( EMT.Y1POS or ParkY1)
            BhData.ViseY1 = 2
         end
         if bSplitCut and EMT.Y2DELTA then
            BhData.ViseY1 = 3
         end
         if EMT.Y2DELTA then
            BhData.Y2 = EMT.L1 + EMT.Y2DELTA - ( EMT.X_OFF or 0)
            BhData.ViseY2 = 1
         else
            BhData.Y2 = ParkY2
            BhData.ViseY2 = 2
         end
         if GetV1ToCloseTPA() then
            BhData.V1 = EMT.V1NEXTPOS ; EMT.V1POS = BhData.V1
            BhData.StatV1 = -2
         elseif abs( EMT.V1POS - ParkV1) > 0.1 then
            BhData.V1 = ParkV1 ; EMT.V1POS = BhData.V1
            BhData.StatV1 = 1
         end
         if GetV2ToCloseTPA() then
            BhData.V2 = EMT.V2NEXTPOS ; EMT.V2POS = BhData.V2
            BhData.StatV2 = -2
         elseif abs( EMT.V2POS - ParkV2) > 0.1 then
            BhData.V2 = ParkV2 ; EMT.V2POS = BhData.V2
            BhData.StatV2 = 1
         end
         EmitBeamHeadData( BhData)
         EMT.POSTROT = false
         EMT.RELOAD = false
         EMT.RELOAD2 = nil
      end
      EMT.REFLOC = 0
      EMT.MCHFIRST = false
      EMT.ZMAX = false
   -- se standard
   elseif EMT.FLAG == 0 or EMT.FLAG == 1 or EMT.FLAG == 2 then
      -- se necessario, impostazione riferimento locale
      if EMT.REFLOC == 0 then
         -- se lavorazione in doppio
         if EMT.DOU_TYPE == 2 then
            -- aggiusto assi
            EMT.L2 = -EMT.L2
            -- emetto movimenti in Zero macchina
            local Speed = EMT.S
            if EMT.HEAD == 'H12' or EMT.HEAD == 'H16' or EMT.HEAD == 'H22' then Speed = 1.65 * EMT.S end
            EmitMoveDataHead( 1, { X=EMT.L2, Z=EMT.L3, B=EMT.R2, C=EMT.R1, S=Speed})
            EmitMoveStartHead( 1)
            local dX2 = Delta2TabY + EMT.DOU_TLEN - ( -DeltaTabY - EMT.L2 + EMT.TLEN + EgtIf( BD.RIGHT_LOAD, -EMT.HB, EMT.HB))
            local dZ2 = -Head2Z + MillOffs + Mill2Offs + EMT.L3
            -- dato che la testa 2 è slave, bisogna controllare eventuale extra-corsa da post
            if dZ2 > MaxZ2 then
               EmtSetLastError( 1220, EMT.MCHNAME..'  ==>  Out of Stroke: Z2 : '.. EgtNumToString( dZ2-MaxZ2, 2))
            end
            local dC2 = EMT.R1
            local dB2 = EMT.R2
            EmitMoveDataHead( 2, { Z=-dZ2, B=dB2, C=dC2, TPos=EMT.DOU_TPOS, S=EMT.DOU_SPEED})
            EmitMoveDataHead( 2, { X=dX2, TPos=EMT.DOU_TPOS, S=EMT.DOU_SPEED})
            EmitMoveStartHead( 2)
            -- aspetto esecuzione movimento testa 2 e 1
            EmitMoveWaitHead( 2)
            EmitMoveWaitHead( 1)
            -- ripristino assi
            EMT.L2 = -EMT.L2
         elseif EMT.DOU_TYPE == 3 then
            -- aggiusto assi
            EMT.L2 = -EMT.L2
            -- emetto movimenti in Zero macchina
            local Speed = EMT.S
            if EMT.HEAD == 'H12' or EMT.HEAD == 'H16' or EMT.HEAD == 'H22' then Speed = 1.65 * EMT.S end
            EmitMoveDataHead( 1, { X=EMT.L2, Z=EMT.L3, B=EMT.R2, C=EMT.R1, S=Speed})
            EmitMoveStartHead( 1)
            local dX2 = -Head2Y - EMT.L2
            local dZ2 = Delta2TabZ - EMT.DOU_TLEN - ( -DeltaTabZ + EMT.L3 - EMT.TLEN - EMT.SB)
            local dC2 = EMT.R1
            local dB2 = EMT.R2
            EmitMoveDataHead( 2, { X=dX2, Z=-dZ2, B=dB2, C=dC2, TPos=EMT.DOU_TPOS, S=EMT.DOU_SPEED})
            EmitMoveStartHead( 2)
            -- aspetto esecuzione movimento testa 2 e 1
            EmitMoveWaitHead( 2)
            EmitMoveWaitHead( 1)
            -- ripristino assi
            EMT.L2 = -EMT.L2
         end
         -- calcolo per piano generico
         CalcInterpPlane()
         EMT.REFLOC = 1
         -- trasformo i punti nel piano
         MyAdjustLinearAxes()
         EmtAdjustRotaryAxes()
         EMT.L1p = nil
         EMT.L2p = nil
         EMT.L3p = nil
         MyOutput( 'M105 P1=' .. GetFaceCode() .. ' P2='..GetHeadSetCode()..' P3='..EgtIf( EMT.DOU_TYPE, 2, 0))
         MyOutput( 'M98')
         MyOutput( 'M6 ' .. GetHeadToolCode() .. AdjustTcPos( true))
         MyOutput( 'G24' .. EMT.IPLGLSTR)
         EMT.PLANEACTIVE = true
         -- forzo successiva emissione assi rotanti
         EMT.R1p = nil
         EMT.R2p = nil
      else
         -- se lavorazione in doppio controllo le corse
         if EMT.DOU_TYPE == 2 then
            local dZ2 = -Head2Z + MillOffs + Mill2Offs + EMT.L3
            -- dato che la testa 2 è slave, bisogna controllare eventuale extra-corsa da post
            if dZ2 > MaxZ2 then
               EmtSetLastError( 1220, EMT.MCHNAME..'  ==>  Out of Stroke: Z2 : '.. EgtNumToString( dZ2-MaxZ2, 2))
            end
         end
         MyAdjustLinearAxes()
         EmtAdjustRotaryAxes()
      end
      -- emissione movimento
      local sAxes = EmtGetAxis('L1')..EmtGetAxis('L2')..EmtGetAxis('L3')..
                    EmtGetAxis('R2')..EmtGetAxis('R1')
      if #sAxes > 0 then
         if EmitRapidInG1 then
            local sFeed = ' F30000'
            MyOutput( 'G1' .. sAxes .. sFeed)
         else
            MyOutput( 'G0' .. sAxes)
         end
      end
   -- se altrimenti risalita a Z max a fine lavorazione
   elseif EMT.FLAG == 3 then
      EMT.REFLOC = nil
      EMT.IPLGL = false
      MyAdjustLinearAxes()
      EmtAdjustRotaryAxes()
      -- se Split lascio agganciata solo la pinza Y2 alla fine dei movimenti
      local bSplitCut = ( EMT.MCHUSERNOTES:find( 'Split', 1, true) ~= nil)
      -- porto in home la testa corrente se lavorazione successiva sopra con testa diversa oppure se punta lunga
      local nNextTopHSet = GetNextTopHSet( EMT.MCHID)
      -- ricavo prossimo utensile
      local sNextTool = GetNextTool( EMT.MCHID)
      local bTopGoHome = ( IsLastPath( EMT.PATHID) and not sNextTool and nHSet == 3)  or EMT.HEAD == 'H38' or ( nHSet ~= 2 and nNextTopHSet ~= 0 and nHSet ~= nNextTopHSet) or ( nHSet == 1 and EMT.TTOTLEN > LongTool)
      EmitZmax( true, true, EMT.R1p, EMT.R2p, bSplitCut, bTopGoHome, false)
      -- aggiorno quota finale trave dopo Zmax
      EMT.L1o = EMT.TPOS
      EMT.ZMAX = true
   -- se altrimenti movimento in Home
   elseif EMT.FLAG == 4 then
      -- non previsto
   -- altrimenti errore
   else
      EmtSetLastError( 1212, "Unknown Rapid flag")
   end
   -- aggiorno valori come precedenti
   EMT.TPOS = EMT.L1o
   EmtUpdatePrev()
end

---------------------------------------------------------------------
function OnLinear()
   -- se necessario, impostazione riferimento locale
   if EMT.REFLOC == 0 then
      -- calcolo per piano generico
      CalcInterpPlane()
      EMT.REFLOC = 1
      -- salvo posizione attuale
      local OldL1 = EMT.L1
      local OldL2 = EMT.L2
      local OldL3 = EMT.L3
      -- imposto posizione precedente (se presente devo annullare l'offset in X per sovramateriale di testa)
      EMT.L1 = EMT.L1o
      EMT.L2 = EMT.L2o
      EMT.L3 = EMT.L3o
      if EMT.X_OFF then EMT.L1 = EMT.L1 - EMT.X_OFF end
      -- trasformo i punti nel piano
      MyAdjustLinearAxes()
      EmtAdjustRotaryAxes()
      EMT.L1p = nil
      EMT.L2p = nil
      EMT.L3p = nil
      MyOutput( 'M105 P1=' .. GetFaceCode() .. ' P2='..GetHeadSetCode()..' P3='..EgtIf( EMT.DOU_TYPE, 2, 0))
      MyOutput( 'M98')
      MyOutput( 'M6 ' .. GetHeadToolCode() .. AdjustTcPos( true))
      MyOutput( 'G24' .. EMT.IPLGLSTR)
      EMT.PLANEACTIVE = true
      -- emissione movimento
      EMT.R1p = nil
      EMT.R2p = nil
      if EmitRapidInG1 then
         local sFeed = ' F30000'
         sOut = 'G1'..EmtGetAxis('L1')..EmtGetAxis('L2')..EmtGetAxis('L3')..
                      EmtGetAxis('R2')..EmtGetAxis('R1')..sFeed
      else
         sOut = 'G0'..EmtGetAxis('L1')..EmtGetAxis('L2')..EmtGetAxis('L3')..
                      EmtGetAxis('R2')..EmtGetAxis('R1')
      end

      MyOutput( sOut)
      -- aggiorno precedenti
      EMT.MOVE = 0
      EmtUpdatePrev()
      EMT.MOVE = 1
      -- ripristino posizione attuale
      EMT.L1 = OldL1
      EMT.L2 = OldL2
      EMT.L3 = OldL3
   end

   -- se primo movimento in feed della lavorazione, dichiaro inizio lavorazione
   if EMT.MCHFIRSTFEED then
      EMT.MCHFIRSTFEED = nil
      MyOutput( 'M97')
   end

   -- aggiustamento valori
   MyAdjustLinearAxes()
   EmtAdjustRotaryAxes()

   -- se lavorazione in doppio e fondo foro, inverto movimento punta doppia
   local bDouHoleBott = ( EMT.MCHTYPE == MCH_MY.DRILLING and ( EMT.DOU_TYPE == 2 or EMT.DOU_TYPE == 3) and EMT.FLAG == 101)
   if bDouHoleBott then
      MyOutput( "M106 P1=1")
   end

   -- valori degli assi
   local sAxes = EmtGetAxis('L1')..EmtGetAxis('L2')..EmtGetAxis('L3')..
                 EmtGetAxis('R2')..EmtGetAxis('R1')
   -- feed
   local sFeed = EmtGetFeed()
   -- se da emettere
   if #sAxes > 0 then
      MyOutput( "G1" .. sAxes .. sFeed)
   end

   -- se lavorazione in doppio e fondo foro, muovo punta 2 e poi ripristino movimento standard punta doppia
   if bDouHoleBott then
      if EMT.DOU_TYPE == 2 or EMT.DOU_TYPE == 3 then
         local dZ = 2 * EMT.L3p - EMT.L3
         MyOutput( "G1 Z" .. EgtNumToString( dZ, 3)) 
         MyOutput( "G1 Z" .. EgtNumToString( EMT.L3p, 3))
      end
      MyOutput( "M106 P1=2")
   end

   -- aggiorno valori come precedenti
   EMT.TPOS = EMT.L1o
   EmtUpdatePrev()
end

---------------------------------------------------------------------
function OnArc()
   -- se necessario, impostazione riferimento locale
   if EMT.REFLOC == 0 then
      -- calcolo per piano generico
      CalcInterpPlane()
      EMT.REFLOC = 1
      -- salvo posizione attuale
      local OldL1 = EMT.L1
      local OldL2 = EMT.L2
      local OldL3 = EMT.L3
      -- imposto posizione precedente (se presente devo annullare l'offset in X per sovramateriale di testa)
      EMT.L1 = EMT.L1o
      EMT.L2 = EMT.L2o
      EMT.L3 = EMT.L3o
      if EMT.X_OFF then EMT.L1 = EMT.L1 - EMT.X_OFF end
      -- trasformo i punti nel piano
      MyAdjustLinearAxes()
      EmtAdjustRotaryAxes()
      EMT.L1p = nil
      EMT.L2p = nil
      EMT.L3p = nil
      MyOutput( 'M105 P1=' .. GetFaceCode() .. ' P2='..GetHeadSetCode()..' P3='..EgtIf( EMT.DOU_TYPE, 2, 0))
      MyOutput( 'M98')
      MyOutput( 'M6 ' .. GetHeadToolCode() .. AdjustTcPos( true))
      MyOutput( 'G24' .. EMT.IPLGLSTR)
      EMT.PLANEACTIVE = true
      -- emissione movimento
      EMT.R1p = nil
      EMT.R2p = nil
      if EmitRapidInG1 then
         local sFeed = ' F30000'
         sOut = 'G1'..EmtGetAxis('L1')..EmtGetAxis('L2')..EmtGetAxis('L3')..
                      EmtGetAxis('R2')..EmtGetAxis('R1')..sFeed
      else
         sOut = 'G0'..EmtGetAxis('L1')..EmtGetAxis('L2')..EmtGetAxis('L3')..
                      EmtGetAxis('R2')..EmtGetAxis('R1')
      end

      MyOutput( sOut)
      -- aggiorno precedenti
      local nOriginalMove = EMT.MOVE
      EMT.MOVE = 0
      EmtUpdatePrev()
      EMT.MOVE = nOriginalMove
      -- ripristino posizione attuale
      EMT.L1 = OldL1
      EMT.L2 = OldL2
      EMT.L3 = OldL3
   end

   -- se primo movimento in feed della lavorazione, dichiaro inizio lavorazione
   if EMT.MCHFIRSTFEED then
      EMT.MCHFIRSTFEED = nil
      MyOutput( 'M97')
   end

   -- non modale su archi
   EmtResetPrevLinear()
   -- aggiustamento valori
   MyAdjustLinearAxes()
   EmtAdjustRotaryAxes()

   -- valori degli assi
   local sAxes = EmtGetAxis('L1')..EmtGetAxis('L2')..EmtGetAxis('L3')..
                 EmtGetAxis('R2')..EmtGetAxis('R1')
   -- se nulla da emettere, esco
   if string.len( sAxes) == 0 then
      return
   end
   -- valori delle coordinate del centro
   EmtAdjustCenterAxes()
   -- coordinate centro (per ora solo archi nel piano XY)
   --local sCen = ' ' .. EMT.C1t .. EmtLenToString(EMT.C1,3) ..
   --             ' ' .. EMT.C2t .. EmtLenToString(EMT.C2,3)
   -- raggio
   local sRad = ' ' .. EMT.RRt .. EmtLenToString( EMT.RR, EMT.DECNUM)
   -- aggiungo feed
   local sFeed = EmtGetFeed()
   -- tipo arco
   local sArc = 'G' .. EgtNumToString(EMT.MOVE,0)
   -- emetto arco
   MyOutput( sArc..sAxes..sRad..sFeed)

   -- aggiorno valori come precedenti
   EMT.TPOS = EMT.L1o
   EmtUpdatePrev()
end

---------------------------------------------------------------------
function CalcForzaPinze( dH, dS, dL, dT)
   local MassaRef = 1500                              -- [Kg]
   local dMinJoin = BD.GetMinJoin( dS, dH, dT)
   if dMinJoin < 0.9 * BD.MIN_JOIN_L then
      return MinForzaPinze
   else
      local KgMtCubo= WoodDensity or 550              -- densità legno [Kg / metro cubo]
      local Massa = ( dH * dS * dL * KgMtCubo ) / 1e9 -- massa [Kg]
      local Forza = Massa / MassaRef * MaxForzaPinze
      return EgtClamp( Forza, MinForzaPinze, MaxForzaPinze)
   end
end

---------------------------------------------------------------------
function CalcCharStatus( sCmd)
   -- aperto
   if sCmd == '0' then
      return '1'
   -- chiuso
   else
      return '-1'
   end
end

---------------------------------------------------------------------
function CalcCharStatusN( sCmd)
   -- aperto
   if sCmd == '0' then
      return 1
   -- chiuso
   elseif sCmd == '1' then
      return -1
   end
end

---------------------------------------------------------------------
function PrepareLoad( sCmd, nInd, bStart)
   local Cmd = EgtSplitString( sCmd)
   if Cmd[1] == '0' then
      -- se inizio
      if nInd == 1 then
         -- imposto stato pinze
         EMT.ISY1 = 1
         EMT.FSY1 = EMT.ISY1  -- ??? -1
         EMT.ISY2 = 1
         EMT.FSY2 = EMT.ISY2
         EMT.BV = 0
      end
      -- se dichiarazione inizio riposizionamento carrelli
      if Cmd[2] == 'CARR_MOVE' then
         if bStart then
            EMT.RELOAD = false
         else
            EMT.RELOAD2 = true
         end
         for i = 1, #EMT.MDCHAR do
            EMT.MDCHAR[i].MovType = 3
         end
      end
   elseif Cmd[1] == '1' then
      if Cmd[2] ~= 'Z' then
         local MDChar = { IniStatY1=EMT.ISY1, FinStatY1=EMT.FSY1, IniStatY2=EMT.ISY2, FinStatY2=EMT.FSY2, BeamVise=EMT.BV}
         local MoveY1 = GetCmdAxMove( Cmd, 'Y1')
         local MoveY2 = GetCmdAxMove( Cmd, 'Y2')
         if MoveY1 then MDChar.Y1 = MoveY1 end
         if MoveY2 then MDChar.Y2 = MoveY2 end
         if EMT.ROLL_IN then
            MDChar.V1 = MinV1 ; EMT.V1POS = MDChar.V1
            MDChar.V2 = MaxV2 ; EMT.V2POS = MDChar.V2
            EMT.ROLL_IN = nil
         end
         table.insert( EMT.MDCHAR, MDChar)
         EMT.Y1POS = MoveY1 or EMT.Y1POS
         EMT.TPOS = GetCmdAxMove( Cmd, 'T') or EMT.TPOS
      end
   elseif Cmd[1] == '2' then
      -- se in riposizionamento carrelli
      if #EMT.MDCHAR > 0 and EMT.MDCHAR[1].MovType == 3 then
         local MDChar = { IniStatY1=EMT.ISY1, FinStatY1=EMT.FSY1, IniStatY2=EMT.ISY2, FinStatY2=EMT.FSY2, BeamVise=EMT.BV}
         local MoveY1 = GetCmdAxMove( Cmd, 'Y1')
         local MoveY2 = GetCmdAxMove( Cmd, 'Y2')
         if MoveY1 then MDChar.Y1 = MoveY1 end
         if MoveY2 then MDChar.Y2 = MoveY2 end
         if EMT.ROLL_IN then
            MDChar.V1 = MinV1 ; EMT.V1POS = MDChar.V1
            MDChar.V2 = MaxV2 ; EMT.V2POS = MDChar.V2
            EMT.ROLL_IN = nil
         end
         table.insert( EMT.MDCHAR, MDChar)
      elseif Cmd[2] == 'Y1' then
         local MoveY1 = tonumber( Cmd[3]) + TurnerOffs
         local MDChar = { Y1=MoveY1, V1=ParkV1, IniStatY1=-75, FinStatY1=-1, BeamVise=0, MovType=1}
         table.insert( EMT.MDCHAR, MDChar)
         SetStartValue( 4, MoveY1)
         SetStartValue( 5, 110)
         EMT.V1POS = ParkV1
      elseif Cmd[2] == 'T' then
         local sDeltaY1 = 'Delta=' .. EgtNumToString( tonumber(Cmd[5]) - tonumber(Cmd[3]), 1)
         local MDChar = { Y1=tonumber(Cmd[3]), IniStatY1=-76, FinStatY1=EMT.FSY1, FinStatV1=-1, BeamVise=0, MovType=1, Remark=sDeltaY1}
         table.insert( EMT.MDCHAR, MDChar)
      end
      EMT.TPOS = GetCmdAxMove( Cmd, 'T') or EMT.TPOS
   elseif Cmd[1] == '3' then
      local MDChar = { IniStatY1=EMT.ISY1, FinStatY1=EMT.FSY1, IniStatY2=EMT.ISY2, FinStatY2=EMT.FSY2, BeamVise=EMT.BV}
      local MoveY1 = GetCmdAxMove( Cmd, 'Y1')
      local MoveY2 = GetCmdAxMove( Cmd, 'Y2')
      if MoveY1 then MDChar.Y1 = MoveY1 end
      if MoveY2 then MDChar.Y2 = MoveY2 end
      if EMT.ROLL_IN then
         MDChar.V1 = MinV1 ; EMT.V1POS = MDChar.V1
         MDChar.V2 = MaxV2 ; EMT.V2POS = MDChar.V2
         EMT.ROLL_IN = nil
      end
      table.insert( EMT.MDCHAR, MDChar)
      EMT.TPOS = GetCmdAxMove( Cmd, 'T') or EMT.TPOS
   elseif Cmd[1] == '4' then
      -- richiesta movimento a ZMAX già gestita
      if Cmd[2] == '1' then
         EMT.ROLL_IN = true
      end
   elseif Cmd[1] == '11' then
      EMT.ISY1 = CalcCharStatusN( Cmd[2])
      EMT.FSY1 = EMT.ISY1
      EMT.BV = EgtIf( Cmd[2] ~= '0', 1, 2)
      if #EMT.MDCHAR > 0 then EMT.MDCHAR[#EMT.MDCHAR].FinStatY1 = EMT.FSY1 end
   elseif Cmd[1] == '12' then
      EMT.ISY2 = CalcCharStatusN( Cmd[2])
      EMT.FSY2 = EMT.ISY2
      EMT.BV = EgtIf( Cmd[2] ~= '0', 2, 1)
      if #EMT.MDCHAR > 0 then EMT.MDCHAR[#EMT.MDCHAR].FinStatY2 = EMT.FSY2 end
   elseif Cmd[1] == '21' then
      local nY1Delta = tonumber( Cmd[2])
      local nY2Delta = tonumber( Cmd[3])
      if nY1Delta > 0 and nY2Delta > 0 then
         EMT.Y1DELTA = nY1Delta
         EMT.Y2DELTA = nY2Delta
      elseif nY1Delta > 0 then
         EMT.Y1DELTA = nY1Delta
         EMT.Y2DELTA = nil
      elseif nY2Delta > 0 then
         EMT.Y1DELTA = nil
         EMT.Y2DELTA = nY2Delta
      end
      if not FindReadHeadWithLaser( EMT.MDCHAR) then
         local MDChar = { Y1=LoadT, IniStatY1=-76, FinStatY1=EMT.FSY1, FinStatV1=-1, BeamVise=0, MovType=1}
         table.insert( EMT.MDCHAR, MDChar)
      end
   elseif Cmd[1] == '22' then
      EMT.V1NEXTPOS = tonumber( Cmd[2])
      EMT.V2NEXTPOS = tonumber( Cmd[3])
   end
end

---------------------------------------------------------------------
function PrepareMoveChar( sCmd, nInd)
   local Cmd = EgtSplitString( sCmd)
   if Cmd[1] == '0' then
      -- se inizio
      if nInd == 1 then
         -- imposto stato pinze
         EMT.ISY1 = EgtIf( EMT.Y1DELTA, -1, 1)
         EMT.FSY1 = EMT.ISY1
         EMT.ISY2 = EgtIf( EMT.Y2DELTA, -1, 1)
         EMT.FSY2 = EMT.ISY2
         EMT.BV = EgtIf( EMT.Y1DELTA, 1, 2)
         EMT.ROLL_IN = nil
         EMT.ROLL_OPEN = true
      end
   elseif Cmd[1] == '1' then
      if Cmd[2] ~= 'Z' then
         local MDChar = { IniStatY1=EMT.ISY1, FinStatY1=EMT.FSY1, IniStatY2=EMT.ISY2, FinStatY2=EMT.FSY2, BeamVise=EMT.BV}
         local MoveY1 = GetCmdAxMove( Cmd, 'Y1')
         local MoveY2 = GetCmdAxMove( Cmd, 'Y2')
         if MoveY1 then MDChar.Y1 = MoveY1 end
         if MoveY2 then MDChar.Y2 = MoveY2 end
         if EMT.ROLL_IN then
            MDChar.V1 = MinV1 ; EMT.V1POS = MDChar.V1
            MDChar.V2 = MaxV2 ; EMT.V2POS = MDChar.V2
            EMT.ROLL_IN = nil
         else
            if MoveY1 and EMT.V1POS > MoveY1 - MinDeltaYV - 0.1 then
               MDChar.V1 = min( MoveY1 - MinDeltaYV, ParkV1) ; EMT.V1POS = MDChar.V1
            end
            if MoveY2 and EMT.V2POS < MoveY2 + MinDeltaYV + 0.1 then
               MDChar.V2 = max( MoveY2 + MinDeltaYV, ParkV2) ; EMT.V2POS = MDChar.V2
            end
         end
         if EMT.ROLL_OPEN then
            MDChar.IniStatV1 = 1
            MDChar.IniStatV2 = 1
            EMT.ROLL_OPEN = nil
         end
         table.insert( EMT.MDCHAR, MDChar)
         EMT.Y1POS = MoveY1 or EMT.Y1POS
         EMT.TPOS = GetCmdAxMove( Cmd, 'T') or EMT.TPOS
      end
   elseif Cmd[1] == '2' then
      local MDChar = { IniStatY1=EMT.ISY1, FinStatY1=EMT.FSY1, IniStatY2=EMT.ISY2, FinStatY2=EMT.FSY2, BeamVise=EMT.BV}
      local MoveY1 = GetCmdAxMove( Cmd, 'Y1')
      local MoveY2 = GetCmdAxMove( Cmd, 'Y2')
      if MoveY1 then MDChar.Y1 = MoveY1 end
      if MoveY2 then MDChar.Y2 = MoveY2 end
      if EMT.ROLL_IN then
         MDChar.V1 = MinV1 ; EMT.V1POS = MDChar.V1
         MDChar.V2 = MaxV2 ; EMT.V2POS = MDChar.V2
         EMT.ROLL_IN = nil
      else
         if MoveY1 and EMT.V1POS > MoveY1 - MinDeltaYV - 0.1 then
            MDChar.V1 = min( MoveY1 - MinDeltaYV, ParkV1) ; EMT.V1POS = MDChar.V1
         end
         if MoveY2 and EMT.V2POS < MoveY2 + MinDeltaYV + 0.1 then
            MDChar.V2 = max( MoveY2 + MinDeltaYV, ParkV2) ; EMT.V2POS = MDChar.V2
         end
      end
      if EMT.ROLL_OPEN then
         MDChar.IniStatV1 = 1
         MDChar.IniStatV2 = 1
         EMT.ROLL_OPEN = nil
      end
      table.insert( EMT.MDCHAR, MDChar)
      EMT.TPOS = GetCmdAxMove( Cmd, 'T') or EMT.TPOS
   elseif Cmd[1] == '3' then
      local MDChar = { IniStatY1=EMT.ISY1, FinStatY1=EMT.FSY1, IniStatY2=EMT.ISY2, FinStatY2=EMT.FSY2, BeamVise=EMT.BV}
      local MoveY1 = GetCmdAxMove( Cmd, 'Y1')
      local MoveY2 = GetCmdAxMove( Cmd, 'Y2')
      if MoveY1 then MDChar.Y1 = MoveY1 end
      if MoveY2 then MDChar.Y2 = MoveY2 end
      if EMT.ROLL_IN then
         MDChar.V1 = MinV1 ; EMT.V1POS = MDChar.V1
         MDChar.V2 = MaxV2 ; EMT.V2POS = MDChar.V2
         EMT.ROLL_IN = nil
      else
         if MoveY1 and EMT.V1POS > MoveY1 - MinDeltaYV - 0.1 then
            MDChar.V1 = min( MoveY1 - MinDeltaYV, ParkV1) ; EMT.V1POS = MDChar.V1
         end
         if MoveY2 and EMT.V2POS < MoveY2 + MinDeltaYV + 0.1 then
            MDChar.V2 = max( MoveY2 + MinDeltaYV, ParkV2) ; EMT.V2POS = MDChar.V2
         end
      end
      if EMT.ROLL_OPEN then
         MDChar.IniStatV1 = 1
         MDChar.IniStatV2 = 1
         EMT.ROLL_OPEN = nil
      end
      table.insert( EMT.MDCHAR, MDChar)
      EMT.TPOS = GetCmdAxMove( Cmd, 'T') or EMT.TPOS
   elseif Cmd[1] == '4' then
      -- richiesta movimento a ZMAX già gestita
      if Cmd[2] == '1' then
         EMT.ROLL_IN = true
      end
   elseif Cmd[1] == '11' then
      EMT.ISY1 = CalcCharStatusN( Cmd[2])
      EMT.FSY1 = EMT.ISY1
      -- se pinza due in presa, non attivo la 1
      if EMT.BV ~= 2 then
         EMT.BV = EgtIf( Cmd[2] ~= '0', 1, 2)
      end
      if #EMT.MDCHAR > 0 then EMT.MDCHAR[#EMT.MDCHAR].FinStatY1 = EMT.FSY1 end
   elseif Cmd[1] == '12' then
      EMT.ISY2 = CalcCharStatusN( Cmd[2])
      EMT.FSY2 = EMT.ISY2
      EMT.BV = EgtIf( Cmd[2] ~= '0', 2, 1)
      if #EMT.MDCHAR > 0 then EMT.MDCHAR[#EMT.MDCHAR].FinStatY2 = EMT.FSY2 end
   elseif Cmd[1] == '21' then
      local nY1Delta = tonumber( Cmd[2])
      local nY2Delta = tonumber( Cmd[3])
      if nY1Delta > 0 and nY2Delta > 0 then
         EMT.Y1DELTA = nY1Delta
         EMT.Y2DELTA = nY2Delta
      elseif nY1Delta > 0 then
         EMT.Y1DELTA = nY1Delta
         EMT.Y2DELTA = nil
      elseif nY2Delta > 0 then
         EMT.Y1DELTA = nil
         EMT.Y2DELTA = nY2Delta
      end
   elseif Cmd[1] == '22' then
      EMT.V1NEXTPOS = tonumber( Cmd[2])
      EMT.V2NEXTPOS = tonumber( Cmd[3])
   end
end

---------------------------------------------------------------------
function PrepareResidue( sCmd, nInd)
   local Cmd = EgtSplitString( sCmd)
   if Cmd[1] == '0' then
      -- se non è scarico
      if Cmd[2] ~= 'Unloading' then
         -- se inizio o subito dopo, imposto stato iniziale pinze
         if nInd <= 2 then
            EMT.ISY1 = EgtIf( EMT.Y1DELTA, -1, 1)
            EMT.FSY1 = EMT.ISY1
            EMT.ISY2 = EgtIf( EMT.Y2DELTA, -1, 1)
            EMT.FSY2 = EMT.ISY2
            EMT.BV = EgtIf( EMT.Y1DELTA, 1, 2)
            EMT.ROLL_IN = nil
         end
      else
         EMT.UNL = true
      end
      -- se è scarico al carico
      if Cmd[2] == 'Unload-On-Load' then
         EMT.UNL2 = true
      end
   elseif Cmd[1] == '1' then
      if Cmd[2] ~= 'Z' then
         local MDChar
         if not EMT.UNL then
            MDChar = { [Cmd[2]]=tonumber(Cmd[3]), IniStatY1=EMT.ISY1, FinStatY1=EMT.FSY1, IniStatY2=EMT.ISY2, FinStatY2=EMT.FSY2, BeamVise=EMT.BV}
         else
            MDChar = { [Cmd[2]]=tonumber(Cmd[3]), IniStatY2=1, FinStatY2=-84, BeamVise=0}
         end
         if EMT.ROLL_IN then
            MDChar.V1 = MinV1 ; EMT.V1POS = MDChar.V1
            MDChar.V2 = MaxV2 ; EMT.V2POS = MDChar.V2
            EMT.ROLL_IN = nil
         end
         -- se scarico al carico parcheggio per sicurezza morsa e paratia lato scarico
         if EMT.UNL2 then
            MDChar.V1 = ParkV1 ; EMT.V1POS = MDChar.V1
            MDChar.V2 = ParkV2 ; EMT.V2POS = MDChar.V2
         end
         table.insert( EMT.MDCHAR, MDChar)
      end
   elseif Cmd[1] == '2' then
      local MDChar = { IniStatY1=EMT.ISY1, FinStatY1=EMT.FSY1, IniStatY2=EMT.ISY2, FinStatY2=EMT.FSY2, IniStatV2=1, BeamVise=EMT.BV}
      if Cmd[2] ~= 'T' then MDChar[Cmd[2]] = tonumber( Cmd[3]) end
      if Cmd[4] ~= 'T' then MDChar[Cmd[4]] = tonumber( Cmd[5]) end
      if EMT.ROLL_IN then
         MDChar.V1 = MinV1 ; EMT.V1POS = MDChar.V1
         MDChar.V2 = MaxV2 ; EMT.V2POS = MDChar.V2
         EMT.ROLL_IN = nil
      end
      table.insert( EMT.MDCHAR, MDChar)
   elseif Cmd[1] == '3' then
      local MDChar = { IniStatY1=EMT.ISY1, FinStatY1=EMT.FSY1, IniStatY2=EMT.ISY2, FinStatY2=EMT.FSY2, IniStatV1=1, IniStatV2=1, BeamVise=EMT.BV}
      if Cmd[2] ~= 'T' then MDChar[Cmd[2]] = tonumber( Cmd[3]) end
      if Cmd[4] ~= 'T' then MDChar[Cmd[4]] = tonumber( Cmd[5]) end
      if Cmd[6] ~= 'T' then MDChar[Cmd[6]] = tonumber( Cmd[7]) end
      if EMT.ROLL_IN then
         MDChar.V1 = MinV1 ; EMT.V1POS = MDChar.V1
         MDChar.V2 = MaxV2 ; EMT.V2POS = MDChar.V2
         EMT.ROLL_IN = nil
      end
      table.insert( EMT.MDCHAR, MDChar)
   elseif Cmd[1] == '4' then
      EMT.TO_ZMAX = true
      if Cmd[2] == '1' then
         EMT.ROLL_IN = true
      end
   elseif Cmd[1] == '11' then
      EMT.ISY1 = CalcCharStatusN( Cmd[2])
      EMT.FSY1 = EMT.ISY1
      EMT.BV = EgtIf( Cmd[2] ~= '0', 1, 2)
      if #EMT.MDCHAR > 0 then EMT.MDCHAR[#EMT.MDCHAR].FinStatY1 = EMT.FSY1 end
   elseif Cmd[1] == '12' then
      EMT.ISY2 = CalcCharStatusN( Cmd[2])
      EMT.FSY2 = EMT.ISY2
      EMT.BV = EgtIf( Cmd[2] ~= '0', 2, 1)
      if #EMT.MDCHAR > 0 then EMT.MDCHAR[#EMT.MDCHAR].FinStatY2 = EMT.FSY2 end
   elseif Cmd[1] == '21' then
      local nY1Delta = tonumber( Cmd[2])
      local nY2Delta = tonumber( Cmd[3])
      if nY1Delta > 0 and nY2Delta > 0 then
         EMT.Y1DELTA = nY1Delta
         EMT.Y2DELTA = nY2Delta
      elseif nY1Delta > 0 then
         EMT.Y1DELTA = nY1Delta
         EMT.Y2DELTA = nil
      elseif nY2Delta > 0 then
         EMT.Y1DELTA = nil
         EMT.Y2DELTA = nY2Delta
      end
   elseif Cmd[1] == '22' then
      EMT.V1NEXTPOS = tonumber( Cmd[2])
      EMT.V2NEXTPOS = tonumber( Cmd[3])
   end
end

---------------------------------------------------------------------
function PrepareSplit( sCmd, nInd)
   local Cmd = EgtSplitString( sCmd)
   if Cmd[1] == '0' then
      -- non interessa
   elseif Cmd[1] == '1' then
      if Cmd[2] == 'Y1' then
         local MDChar = { Y1=tonumber(Cmd[3]), V1=ParkV1, IniStatY1=-1, FinStatY1=-1, BeamVise=2, MovType=1}
         table.insert( EMT.MDCHAR, MDChar)
         EMT.Y1POS = tonumber( Cmd[3])
         EMT.V1POS = ParkV1
      end   
   elseif Cmd[1] == '21' then
      local nY1Delta = tonumber( Cmd[2])
      local nY2Delta = tonumber( Cmd[3])
      if nY1Delta > 0 and nY2Delta > 0 then
         EMT.Y1DELTA = nY1Delta
         EMT.Y2DELTA = nY2Delta
      elseif nY1Delta > 0 then
         EMT.Y1DELTA = nY1Delta
         EMT.Y2DELTA = nil
      elseif nY2Delta > 0 then
         EMT.Y1DELTA = nil
         EMT.Y2DELTA = nY2Delta
      end
   elseif Cmd[1] == '22' then
      EMT.V1NEXTPOS = tonumber( Cmd[2])
      EMT.V2NEXTPOS = tonumber( Cmd[3])
   end
end

---------------------------------------------------------------------
function PrepareUnload( sCmd, nInd)

   local Cmd = EgtSplitString( sCmd)
   if Cmd[1] == '0' then
      -- non interessa
   elseif Cmd[1] == '1' then
      if Cmd[2] == 'Y2' then
         local bBarOnY = ( EMT.PHASE < EgtGetPhaseCount() and not IsEnd2Phase( EMT.PHASE))
         -- Se scarico in linea con abbassamento piano ferma pezzo allo scarico
         if Cmd[4] == 'UnBrakeBeam' then
            -- Muove carrello aperto e lo mantiene aperto
            local MDChar = { Y2=tonumber(Cmd[3]), V2=ParkV2, IniStatY2=1, FinStatY2=1, BeamVise=EgtIf( bBarOnY, 1, 0), MovType=EgtIf( EMT.CHY_ON, 3, 2)}
            table.insert( EMT.MDCHAR, MDChar)
            -- Abbassa ferma pezzo
            local MDStopBeam = { StopBeamStat=2}
            table.insert( EMT.MDCHAR, MDStopBeam)
            -- Dopo che si è abbassato il ferma pezzo chiude il pezzo in morsa per evitare che esso possa essere pinzato inclinato dal pistone
            MDChar = { Y2=tonumber(Cmd[3]), V2=ParkV2, IniStatY2=1, FinStatY2=-1, BeamVise=EgtIf( bBarOnY, 1, 0), MovType=EgtIf( EMT.CHY_ON, 3, 2)}
            table.insert( EMT.MDCHAR, MDChar)
         -- Se non è ultima fase e non è fase successiva a scarico su carico c'è una barra sulla pinza Y1
         else
            local nFinStatY2 = EgtIf( Cmd[4] == 'NoWaitUnload', -84, -1)
            local MDChar = { Y2=tonumber(Cmd[3]), V2=ParkV2, IniStatY2=1, FinStatY2=nFinStatY2, BeamVise=EgtIf( bBarOnY, 1, 0), MovType=EgtIf( EMT.CHY_ON, 3, 2)}
            table.insert( EMT.MDCHAR, MDChar)
         end
      end
   elseif Cmd[1] == '2' then
      if Cmd[4] == 'Y2' then
         local bBarOnY = ( EMT.PHASE < EgtGetPhaseCount() and not IsEnd2Phase( EMT.PHASE))
         -- Se scarico in linea con alzata piano ferma pezzo allo scarico
         if Cmd[6] == 'BrakeBeam' then
            -- Muove carrello e lo mantiene in presa
            local MDChar = { Y2=tonumber(Cmd[5]), IniStatY1=EgtIf( bBarOnY, -1, 1), FinStatY1=EgtIf( bBarOnY, -1, 1), IniStatY2=-1, FinStatY2=-1, IniStatV2=1, BeamVise=2, MovType=EgtIf( EMT.CHY_ON, 3, 2)}
            table.insert( EMT.MDCHAR, MDChar)
            -- Alza ferma pezzo
            local MDStopBeam = { StopBeamStat=1}
            table.insert( EMT.MDCHAR, MDStopBeam)
            -- Ribadisce le quote del carrello appena mosso e Apre la pinza
            MDChar = { Y2=tonumber(Cmd[5]), IniStatY1=EgtIf( bBarOnY, -1, 1), FinStatY1=EgtIf( bBarOnY, -1, 1), IniStatY2=-1, FinStatY2=1, IniStatV2=1, BeamVise=2, MovType=EgtIf( EMT.CHY_ON, 3, 2)}
            table.insert( EMT.MDCHAR, MDChar)
         -- se non è ultima fase c'è una barra sulla pinza Y1
         else
            local MDChar = { Y2=tonumber(Cmd[5]), IniStatY1=EgtIf( bBarOnY, -1, 1), FinStatY1=EgtIf( bBarOnY, -1, 1), IniStatY2=-1, FinStatY2=1, IniStatV2=1, BeamVise=2, MovType=EgtIf( EMT.CHY_ON, 3, 2)}
            table.insert( EMT.MDCHAR, MDChar)
         end
      end
   elseif Cmd[1] == '3' then
      -- non interessa
   elseif Cmd[1] == '4' then
      -- non interessa
   elseif Cmd[1] == '11' then
      -- non interessa
   elseif Cmd[1] == '12' then
      -- non interessa
   elseif Cmd[1] == '21' then
      -- non interessa
   elseif Cmd[1] == '22' then
      -- non interessa ??
   end
end

---------------------------------------------------------------------
function PreparePreRotation( sCmd, nInd)
   local Cmd = EgtSplitString( sCmd)
   if Cmd[1] == '0' then
      -- se inizio o subito dopo, imposto stato iniziale pinze
      if nInd <= 2 then
         EMT.ISY1 = EgtIf( EMT.Y1DELTA, -1, 1)
         EMT.FSY1 = EMT.ISY1
         EMT.ISY2 = EgtIf( EMT.Y2DELTA, -1, 1)
         EMT.FSY2 = EMT.ISY2
         EMT.BV = EgtIf( EMT.Y1DELTA, 1, 2)
      end
      -- se è pre-rotazione
      if Cmd[2] == 'Pre-Rotation' then
         EMT.PREROT = true
      elseif Cmd[2] == 'SplitRot' then
         EMT.SPLITROT = true
      -- se è scarico al carico
      elseif Cmd[2] == 'Unload-On-Load' then
         EMT.UNL2 = true
      end
   elseif Cmd[1] == '1' then
      if Cmd[2] ~= 'Z' then
         local MDChar
         if not EMT.PREROT then
            MDChar = { [Cmd[2]]=tonumber(Cmd[3]), IniStatY1=EMT.ISY1, FinStatY1=EMT.FSY1, IniStatY2=EMT.ISY2, FinStatY2=EMT.FSY2, BeamVise=EMT.BV}
            if EMT.UNL2 then
               MDChar.V1 = ParkV1 ; EMT.V1POS = MDChar.V1
               MDChar.V2 = ParkV2 ; EMT.V2POS = MDChar.V2
            end
         else
            MDChar = { [Cmd[2]]=tonumber(Cmd[3]), IniStatY1=86, FinStatY1=87, IniStatY2=1, BeamVise=0}
         end
         table.insert( EMT.MDCHAR, MDChar)
      end
   elseif Cmd[1] == '2' then
      local MDChar = { IniStatY1=EMT.ISY1, FinStatY1=EMT.FSY1, IniStatY2=EMT.ISY2, FinStatY2=EMT.FSY2, IniStatV2=1, BeamVise=EMT.BV}
      if Cmd[2] ~= 'T' then MDChar[Cmd[2]] = tonumber( Cmd[3]) end
      if Cmd[4] ~= 'T' then MDChar[Cmd[4]] = tonumber( Cmd[5]) end
      if EMT.ROLL_IN then
         MDChar.V1 = MinV1 ; EMT.V1POS = MDChar.V1
         MDChar.V2 = MaxV2 ; EMT.V2POS = MDChar.V2
         EMT.ROLL_IN = nil
      end
      table.insert( EMT.MDCHAR, MDChar)
   elseif Cmd[1] == '3' then
      local MDChar = { IniStatY1=EMT.ISY1, FinStatY1=EMT.FSY1, IniStatY2=EMT.ISY2, FinStatY2=EMT.FSY2, IniStatV1=1, IniStatV2=1, BeamVise=EMT.BV}
      if Cmd[2] ~= 'T' then MDChar[Cmd[2]] = tonumber( Cmd[3]) end
      if Cmd[4] ~= 'T' then MDChar[Cmd[4]] = tonumber( Cmd[5]) end
      if Cmd[6] ~= 'T' then MDChar[Cmd[6]] = tonumber( Cmd[7]) end
      if EMT.ROLL_IN then
         MDChar.V1 = MinV1 ; EMT.V1POS = MDChar.V1
         MDChar.V2 = MaxV2 ; EMT.V2POS = MDChar.V2
         EMT.ROLL_IN = nil
      end
      table.insert( EMT.MDCHAR, MDChar)
   elseif Cmd[1] == '4' then
      EMT.TO_ZMAX = true
      if Cmd[2] == '1' then
         EMT.ROLL_IN = true
      end
   elseif Cmd[1] == '11' then
      if EMT.SPLITROT then
         EMT.ISY1 = EgtIf( Cmd[2] ~= '0', CalcCharStatusN( Cmd[2]), 86)
         EMT.FSY1 = EMT.ISY1
         EMT.BV = EgtIf( Cmd[2] ~= '0', 1, 2)
      else
         EMT.ISY1 = CalcCharStatusN( Cmd[2])
         EMT.FSY1 = EMT.ISY1
         EMT.BV = EgtIf( Cmd[2] ~= '0', 1, 2)
      end
      if #EMT.MDCHAR > 0 then EMT.MDCHAR[#EMT.MDCHAR].FinStatY1 = CalcCharStatusN( Cmd[2]) end
   elseif Cmd[1] == '12' then
      EMT.ISY2 = CalcCharStatusN( Cmd[2])
      EMT.FSY2 = EMT.ISY2
      EMT.BV = EgtIf( Cmd[2] ~= '0', 2, 1)
      if #EMT.MDCHAR > 0 then EMT.MDCHAR[#EMT.MDCHAR].FinStatY2 = EMT.FSY2 end
   elseif Cmd[1] == '21' then
      local nY1Delta = tonumber( Cmd[2])
      local nY2Delta = tonumber( Cmd[3])
      if nY1Delta > 0 and nY2Delta > 0 then
         EMT.Y1DELTA = nY1Delta
         EMT.Y2DELTA = nY2Delta
      elseif nY1Delta > 0 then
         EMT.Y1DELTA = nY1Delta
         EMT.Y2DELTA = nil
      elseif nY2Delta > 0 then
         EMT.Y1DELTA = nil
         EMT.Y2DELTA = nY2Delta
      end
   elseif Cmd[1] == '22' then
      EMT.V1NEXTPOS = tonumber( Cmd[2])
      EMT.V2NEXTPOS = tonumber( Cmd[3])
   end
end

---------------------------------------------------------------------
function PreparePostRotation( sCmd, nInd)
   -- è sostanzialmente un carico
   local Cmd = EgtSplitString( sCmd)
   if Cmd[1] == '0' then
      -- se inizio
      if nInd == 1 then
         -- imposto stato pinze
         EMT.FSY1 = -1
      end
      -- se dichiarazione inizio riposizionamento carrelli
      if Cmd[2] == 'CARR_MOVE' then
         EMT.POSTROT = false
         for i = 1, #EMT.MDCHAR do
            EMT.MDCHAR[i].MovType = 3
         end
      end
   elseif Cmd[1] == '1' then
      -- non interessa
   elseif Cmd[1] == '2' then
      if Cmd[2] == 'Y1' then
         -- tolgo eventuale sovramateriale presente sul pezzo in posizione iniziale (la misura laser è ora col finito)
         local MDChar = { Y1=tonumber(Cmd[3])-EMT.HOVM, IniStatY2=-75, FinStatY2=-1, BeamVise=0, MovType=1}
         table.insert( EMT.MDCHAR, MDChar)
      end
   elseif Cmd[1] == '3' then
      -- non interessa
   elseif Cmd[1] == '11' then
      EMT.FSY1 = CalcCharStatusN( Cmd[2])
   elseif Cmd[1] == '12' then
      -- non interessa
   elseif Cmd[1] == '21' then
      local nY1Delta = tonumber( Cmd[2])
      local nY2Delta = tonumber( Cmd[3])
      if nY1Delta > 0 and nY2Delta > 0 then
         EMT.Y1DELTA = nY1Delta
         EMT.Y2DELTA = nY2Delta
      elseif nY1Delta > 0 then
         EMT.Y1DELTA = nY1Delta
         EMT.Y2DELTA = nil
      elseif nY2Delta > 0 then
         EMT.Y1DELTA = nil
         EMT.Y2DELTA = nY2Delta
      end
      local MDChar = { Y1=LoadT, IniStatY1=-76, FinStatY1=EMT.FSY1, BeamVise=0, MovType=1}
      table.insert( EMT.MDCHAR, MDChar)
   elseif Cmd[1] == '22' then
      EMT.V1NEXTPOS = tonumber( Cmd[2])
      EMT.V2NEXTPOS = tonumber( Cmd[3])
   end
end

---------------------------------------------------------------------
function GetV1ToCloseTPA()
   local dMaxHeadPos = EMT.MAXMAX[1] + ( EMT.HOVM or 0) - ( EMT.X_OFF or 0)
   local dMinTailPos = EMT.MAXMIN[1] + EMT.LB + EgtIf( IsStartOrMidPhase( EMT.PHASE), 0, ( EMT.HOVM or 0)) - ( EMT.X_OFF or 0)
   --MyOutput( string.format( 'V1 MaxHeadPos=%.3f MinTailPos=%.3f BarLen=%.3f HeadOvm=%.3f V1NextPos=%.3f', dMaxHeadPos, dMinTailPos, EMT.LB, ( EMT.HOVM or 0), EMT.V1NEXTPOS))
   return ( dMaxHeadPos <= EMT.V1NEXTPOS + RollCageMin and dMinTailPos >= EMT.V1NEXTPOS + RollCageMax)
end

---------------------------------------------------------------------
function GetV2ToCloseTPA()
   local dMaxHeadPos = EMT.MAXMAX[1] + ( EMT.HOVM or 0) - ( EMT.X_OFF or 0)
   local dMinTailPos = EMT.MAXMIN[1] + EMT.LB + EgtIf( IsStartOrMidPhase( EMT.PHASE), 0, ( EMT.HOVM or 0)) - ( EMT.X_OFF or 0)
   --MyOutput( string.format( 'V2 MaxHeadPos=%.3f MinTailPos=%.3f BarLen=%.3f HeadOvm=%.3f', dMaxHeadPos, dMinTailPos, EMT.LB, ( EMT.HOVM or 0)))
   return ( dMaxHeadPos <= EMT.V2NEXTPOS - RollCageMax and dMinTailPos >= EMT.V2NEXTPOS - RollCageMin)
end

---------------------------------------------------------------------
function MyAdjustLinearAxes()
   local MyL1o = EMT.L1
   local MyL2o = EMT.L2
   local MyL3o = EMT.L3
   local nHSet = GetHeadSet( EMT.HEAD)
   if EMT.REFLOC then
      local vtE
      -- se testa 1
      if nHSet == 1 then
         if EMT.HEAD == 'H13' then
            local Len = ( EMT.TDIST or ChSawLen) + MillOffs
            local LenRef = MillOffs
            vtE = Vector3d( EMT.ADIR) * Len - Z_AX() * LenRef
         else
            local dAddLen = 0
            if EMT.HEAD == 'H12' then
               dAddLen = -SawOffsZ
            elseif EMT.HEAD == 'H16' then
               dAddLen = -Saw2OffsZ
            end
            local Len = EMT.TLEN + dAddLen + MillOffs
            local LenRef = MillOffs
            vtE = Vector3d( EMT.TDIR) * Len - Z_AX() * LenRef
         end
      -- se testa 2
      elseif nHSet == 2 then
         local dAddLen = 0
         if EMT.HEAD == 'H22' then
            dAddLen = SawUnderOffsZ
         end
         local Len = EMT.TLEN + dAddLen + Mill2Offs
         local LenRef = Mill2Offs
         vtE = Vector3d( EMT.TDIR) * Len + Z_AX() * LenRef
      -- se testa 3
      else
         local Len = EMT.TLEN + Mill3Offs
         local LenRef = Mill3Offs
         vtE = Vector3d( EMT.TDIR) * Len - Z_AX() * LenRef
      end

      EMT.L1 = EMT.L1 - vtE:getX()
      EMT.L2 = EMT.L2 - vtE:getY()
      EMT.L3 = EMT.L3 - vtE:getZ()
   end

   if EMT.X_OFF then EMT.L1 = EMT.L1 + EMT.X_OFF end

   EmtAdjustLinearAxes()
   
   EMT.L1o = MyL1o
   EMT.L2o = MyL2o
   EMT.L3o = MyL3o
   
   if not EMT.REFLOC then
      if EMT.HEAD ~= 'H21' and  EMT.HEAD ~= 'H22' then
         EMT.L2 = -EMT.L2
      else
         EMT.L3 = -EMT.L3
      end
      EMT.L1t = 'Y'
      EMT.L2t = 'X'
   else
      EMT.L1t = 'X'
      EMT.L2t = 'Y'
   end
end

---------------------------------------------------------------------
function AdjustTcPos( bLen3, sTcPos, dAxR3)
   if not sTcPos then sTcPos = EMT.TCPOS end
   local sPos = sTcPos:gsub( 'T', '')
   if bLen3 then
      if #sPos == 1 then
         sPos = '00' .. sPos
      elseif #sPos == 2 then
         sPos = '0' .. sPos
      end
   end
   if sPos == '101' then
      if not dAxR3 then dAxR3 = EMT.R3 end
      -- controllo che il valore esista. Altrimenti è una testa senza asse ausiliario
      if dAxR3 then
         if abs( dAxR3 - 0) < 0.1 then
            sPos = '101'
         elseif abs( dAxR3 - 270) < 0.1 then
            sPos = '102'
         elseif abs( dAxR3 - 180) < 0.1 then
            sPos = '103'
         elseif abs( dAxR3 - 90) < 0.1 then
            sPos = '104'
         else
            EmtSetLastError( 1210, 'Tool T101 orientation not allowed')
         end
      end
   end
   if sPos == '111' then
      if not dAxR3 then dAxR3 = EMT.R3 or 0 end
      -- controllo che il valore esista. Altrimenti è una testa senza asse ausiliario
      if dAxR3 then
         if abs( dAxR3 - 0) < 0.1 then
            sPos = '111'
         elseif abs( dAxR3 - 270) < 0.1 then
            sPos = '112'
         elseif abs( dAxR3 - 180) < 0.1 then
            sPos = '113'
         elseif abs( dAxR3 - 90) < 0.1 then
            sPos = '114'
         else
            EmtSetLastError( 1210, 'Tool T111 orientation not allowed')
         end
      end
   end
   if sPos == '121' then
      if not dAxR3 then dAxR3 = EMT.R3 or 0 end
      -- controllo che il valore esista. Altrimenti è una testa senza asse ausiliario
      if dAxR3 then
         if abs( dAxR3 - 0) < 0.1 then
            sPos = '121'
         elseif abs( dAxR3 - 270) < 0.1 then
            sPos = '122'
         elseif abs( dAxR3 - 180) < 0.1 then
            sPos = '123'
         elseif abs( dAxR3 - 90) < 0.1 then
            sPos = '124'
         else
            EmtSetLastError( 1210, 'Tool T121 orientation not allowed')
         end
      end
   end
   if sPos == '201' then
      if not dAxR3 then dAxR3 = EMT.R3 end
      if abs( dAxR3 - 0) < 0.1 then
         sPos = '201'
      elseif abs( dAxR3 - 90) < 0.1 then
         sPos = '204'
      -- Lama su aggregato da sotto non può essere presa in queste posizioni per problemi di collisione durante parcheggio
      --elseif abs( dAxR3 - 270) < 0.1 then
      --   sPos = '202'
      --elseif abs( dAxR3 - 180) < 0.1 then
      --   sPos = '203'
      else
         EmtSetLastError( 1210, 'Saw orientation not allowed')
      end
   end
   return sPos
end

---------------------------------------------------------------------
function CalcInterpPlane()
   -- origine del piano
   local xS = EMT.START[1] + LoadT
   if EMT.X_OFF then xS = xS + EMT.X_OFF end
   local ptS = Point3d( xS, 0, 0)
   local nHSet = GetHeadSet( EMT.HEAD)
   if nHSet == 1 then
      ptS = ptS + Vector3d( 0, DeltaTabY, DeltaTabZ)
   elseif nHSet == 2 then
      ptS = ptS + Vector3d( 0, Delta2TabY, Delta2TabZ)
   else
      ptS = ptS + Vector3d( 0, Delta3TabY, Delta3TabZ)
   end

   -- calcolo per piano generico
   local vtE
   if EMT.HEAD ~= 'H13' then
      if EMT.MCHTYPE ~= MCH_OY.GENMACHINING then
         vtE = Vector3d( EMT.EXTR)
      else
         vtE = Z_AX()
      end
   else
      vtE = EgtGetCalcAuxDirFromAngles( EMT.R1, EMT.R2, EMT.R3)
   end
   EMT.IPLGLFR = Frame3d( ptS, vtE)
   --MyOutput( 'IPLGLFR='..tostring(EMT.IPLGLFR))
   local vtX = EMT.IPLGLFR:getVersX()
   local vtY = EMT.IPLGLFR:getVersY()
   local vtZ = EMT.IPLGLFR:getVersZ()
   local nHSet = GetHeadSet( EMT.HEAD)
   if nHSet ~= 2 then
      EMT.IPLGLSTR = ' X0 Y' .. EmtLenToString( xS, EMT.DECNUM) .. ' Z0' ..
                     ' A' .. EgtNumToString( -vtX:getY(), 6) .. ' B' .. EgtNumToString( vtX:getX(), 6) .. ' C' .. EgtNumToString( vtX:getZ(), 6) ..
                     ' I' .. EgtNumToString( -vtY:getY(), 6) .. ' J' .. EgtNumToString( vtY:getX(), 6) .. ' K' .. EgtNumToString( vtY:getZ(), 6) ..
                     ' P' .. EgtNumToString( -vtZ:getY(), 6) .. ' Q' .. EgtNumToString( vtZ:getX(), 6) .. ' R' .. EgtNumToString( vtZ:getZ(), 6)
   else
      EMT.IPLGLSTR = ' X0 Y' .. EmtLenToString( xS, EMT.DECNUM) .. ' Z0' ..
                     ' A' .. EgtNumToString( vtX:getY(), 6) .. ' B' .. EgtNumToString( vtX:getX(), 6) .. ' C' .. EgtNumToString( -vtX:getZ(), 6) ..
                     ' I' .. EgtNumToString( vtY:getY(), 6) .. ' J' .. EgtNumToString( vtY:getX(), 6) .. ' K' .. EgtNumToString( -vtY:getZ(), 6) ..
                     ' P' .. EgtNumToString( vtZ:getY(), 6) .. ' Q' .. EgtNumToString( vtZ:getX(), 6) .. ' R' .. EgtNumToString( -vtZ:getZ(), 6)
   end
   EMT.IPLGL = true
end

---------------------------------------------------------------------
function GetFaceCode()
   if EMT.Y1DELTA and EMT.Y2DELTA then
      return '3'
   elseif EMT.Y1DELTA then
      return '6'
   else
      return '5'
   end
end

---------------------------------------------------------------------
function GetHeadSetCode()
   local nSetHead = GetHeadSet( EMT.HEAD)
   if nSetHead == 1 then
      return '1'
   elseif nSetHead == 2 then
      return '2'
   elseif nSetHead == 3 then
      return '3'
   else
      EmtSetLastError( 1211, 'Error Unknown Head Set')
   end
end

---------------------------------------------------------------------
function GetHeadToolCode()
   local nSetHead = GetHeadSet( EMT.HEAD)
   if nSetHead == 1 then
      return 'T101'
   elseif nSetHead == 2 then
      return 'T202'
   elseif nSetHead == 3 then
      return 'T303'
   else
      EmtSetLastError( 1211, 'Error Unknown Head Set')
   end
end

---------------------------------------------------------------------
function EmitZmax( bReset, bEnd, PrevR1, PrevR2, bSplitCut, bTopGoHome, bUsePrevDelta)
   if bReset then
      EmitResetMachining()
   end
   local nSetHead = GetHeadSet( EMT.HEAD)
   local dXPos = EgtIf( bUsePrevDelta, EMT.L2pp or EMT.L2o, EMT.L2o)
   -- se testa 1
   if nSetHead == 1 then
      -- posizioni sicure
      local dMaxZ1 = EgtGetAxisMax( 'Z1')
      local dHomeX1 = EgtGetAxisHomePos( 'X1')
      local dHomeZ1 = EgtGetAxisHomePos( 'Z1')
      local dHomeB1 = EgtGetAxisHomePos( 'B1')
      local dHomeC1 = EgtGetAxisHomePos( 'C1')
      -- se necessario allargo le cabine
      if RollerParkingNeeded( EMT.HEAD, PrevR1, PrevR2, dHomeC1, dHomeB1) then
         local dPosT = EgtIf( bEnd, EMT.L1o, EMT.TPOS or EMT.L1op)
         EmitParkRoller( dPosT, bSplitCut)
      end
      -- se fresa o lama
      if EMT.HEAD ~= 'H13' then
         -- eseguo la salita a Z1max (oriento solo se assi rotanti non già a posto)
         if abs( dHomeC1 - PrevR1) > 0.1 or abs( dHomeB1 - PrevR2) > 0.1 then
            local dZref = dHomeZ1 + GetZExtra( EMT.HEAD, PrevR2)
            dZref = min( dZref, dMaxZ1)
            local MovH = { Z = EgtIf( dZref > EMT.L3o, dZref, EMT.L3o)}
            MovH.X = -dXPos ; MovH.B = EgtClamp( PrevR2, -90, 90) ;  MovH.C = PrevR1 ;
            EmitMoveDataHead( 1, MovH)
            if ( EMT.HEAD == 'H12' or EMT.HEAD == 'H16') and ( abs( dHomeC1 - PrevR1) > 30.1 or abs( dHomeB1 - PrevR2) > 30.1) then
               EmitMoveDataHead( 1, { Z=dMaxZ1, B=0, Fmt=1})
            end
            if EgtIf( BD.RIGHT_LOAD, ( -dXPos > -DeltaTabY), ( -dXPos < -DeltaTabY)) then
               EmitMoveDataHead( 1, { X=-DeltaTabY, Fmt=1})
            end
            EmitMoveDataHead( 1, { C=dHomeC1, Fmt=1})
            EmitMoveDataHead( 1, { B=dHomeB1, Fmt=1})
         end
         EmitMoveDataHead( 1, { Z=dMaxZ1, Fmt=1})
         if bTopGoHome then
            EmitMoveDataHead( 1, { X=-ParkX1, Fmt=1})
         end
         EmitMoveStartHead( 1)
         -- se lavorazione in doppio
         if EMT.DOU_TYPE == 2 or EMT.DOU_TYPE == 3 then
            EmitDouZMax( EMT.DOU_TYPE)
         end
         -- attesa fine movimento testa 1
         EmitMoveWaitHead( 1)
      -- altrimenti sega a catena
      else
         -- salgo in Z sicurezza raddrizzando la B
         local dSafeZ1 = EgtIf( EMT.TTOTLEN < MinLengthLongCSaw, ParkCSawZ1, ParkLongCSawZ1)
         EmitMoveDataHead( 1, { Z=dSafeZ1, B=dHomeB1, Fmt=1})
         -- lascio la motosega a parcheggio. In caso di utilizzo della testa 3, la motosega viene scaricata automaticamente dalla macro.
         EmitMoveDataHead( 1, { X=-ParkCSawX1, C=dHomeC1, Fmt=1})
         EmitMoveStartHead( 1)
         EmitMoveWaitHead( 1)
      end
      -- se ho degli aggregati e la prossima è la testa 3, scarico e prendo utensile di default
      local nNextTopHSet = GetNextTopHSet( EMT.MCHID)
      if IsHeadExisting( 3) and nNextTopHSet == 3 and ( EMT.TCPOS == 'T101' or EMT.TCPOS == 'T111' or EMT.TCPOS == 'T121') then
         -- lascio la motosega a parcheggio. In caso di utilizzo della testa 3, la motosega viene scaricata automaticamente dalla macro.
         local sDefToolToLoad = AdjustTcPos( false, DefTcPos1)
         EmitMoveDataHead( 1, { X=-ParkX1, TPos=sDefToolToLoad, Fmt=1})
         EmitMoveStartHead( 1)
         EmitMoveWaitHead( 1)
      end
   -- se altrimenti testa 2
   elseif nSetHead == 2 then
      -- posizioni sicure
      local dHomeX2 = EgtGetAxisHomePos( 'X2')
      local dHomeZ2 = EgtGetAxisHomePos( 'Z2')
      local dHomeB2 = EgtGetAxisHomePos( 'B2')
      local dHomeC2 = EgtGetAxisHomePos( 'C2')
      -- se necessario allargo le cabine
      if RollerParkingNeeded( EMT.HEAD, PrevR1, PrevR2, dHomeC2, dHomeB2) then
         local dPosT = EgtIf( bEnd, EMT.L1o, EMT.TPOS or EMT.L1op)
         EmitParkRoller( dPosT, bSplitCut)
      end
      -- eseguo la discesa a Z2max
      EmitMoveDataHead( 2, { Z=-dHomeZ2, Fmt=1})
      EmitMoveDataHead( 2, { B=dHomeB2, C=dHomeC2, Fmt=1})
      if EMT.HEAD == 'H22' then
         EmitMoveDataHead( 2, { X=SafeX2, Fmt=1})
      else
         EmitMoveDataHead( 2, { X=dHomeX2, Fmt=1})
      end
      -- solo se è macchina a 3 teste con gruppo truciolatore 4 assi, devo stare più alto
      if EgtGetHeadId( 'H39') then
         EmitMoveDataHead( 2, { Z=-ParkInLavZ2, Fmt=1})
      end
      EmitMoveStartHead( 2)
      EmitMoveWaitHead( 2)
   -- altrimenti testa 3
   else
      -- posizioni sicure
      local dMaxZ3 = EgtGetAxisMax( 'Z3')
      local dHomeX3 = EgtGetAxisHomePos( 'X3')
      local dHomeB3 = EgtGetAxisHomePos( 'B3')

      -- se è macchina a 3 teste con gruppo truciolatore 4 assi
      if EgtGetHeadId( 'H39') then
         -- eseguo la salita a Z3max
         EmitMoveDataHead( 3, { Z=dMaxZ3, Fmt=1})
         if bTopGoHome then
            EmitMoveDataHead( 3, { X=-dHomeX3, B=dHomeB3, Fmt=1})
         end
         -- se gruppo lama dedicato, senza cambio utensile
      elseif EgtGetHeadId( 'H38') then
         local dHomeC3 = EgtGetAxisHomePos( 'C3')
         if RollerParkingNeeded( EMT.HEAD, PrevR1, PrevR2, dHomeC3, dHomeB3) then
            local dPosT = EgtIf( bEnd, EMT.L1o, EMT.TPOS or EMT.L1op)
            EmitParkRoller( dPosT, bSplitCut)
         end
         -- se non sono esattamente in home, ruoto dove sono
         if abs( dHomeC3 - PrevR1) > 1 or abs( dHomeB3 - PrevR2) > 1 then
            --EmitMoveDataHead( 3, { X=-SafeX3RotAxis, Fmt=1})
            EmitMoveDataHead( 3, { Z=SafeZ3RotAxis, Fmt=1})
            EmitMoveDataHead( 3, { B=dHomeB3, C=dHomeC3, Fmt=1})
         end
         -- eseguo la salita a Z3max
         EmitMoveDataHead( 3, { Z=dMaxZ3, Fmt=1})
         if bTopGoHome then
            EmitMoveDataHead( 3, { X=-dHomeX3, Fmt=1})
         end
      else
         EmtSetLastError( 1212, "HEAD not managed")
      end

      EmitMoveStartHead( 3)
      EmitMoveWaitHead( 3)
   end
   -- resetto posizione precedente assi
   EMT.L2pp = nil
   EMT.L3pp = nil
   EMT.R1pp = nil
   EMT.R2pp = nil
end

---------------------------------------------------------------------
function EmitDouZMax( nDouType)
   -- posizioni sicure
   local dHomeX2 = EgtGetAxisHomePos( 'X2')
   local dHomeZ2 = EgtGetAxisHomePos( 'Z2')
   local dHomeB2 = EgtGetAxisHomePos( 'B2')
   local dHomeC2 = EgtGetAxisHomePos( 'C2')
   -- se lavorazione in doppio in Y (prima mi allontano in Y)
   if nDouType == 2 then
      EmitMoveDataHead( 2, { X=SafeX2, TPos=EMT.DOU_TPOS, Fmt=1})
   end
   -- scendo in Z, sistemo B e C porto X in home
   EmitMoveDataHead( 2, { Z=-dHomeZ2, TPos=EMT.DOU_TPOS, Fmt=1})
   EmitMoveDataHead( 2, { B=dHomeB2, C=dHomeC2, TPos=EMT.DOU_TPOS, Fmt=1})
   EmitMoveDataHead( 2, { X=dHomeX2, TPos=EMT.DOU_TPOS, Fmt=1})
   EmitMoveStartHead( 2)
   EmitMoveWaitHead( 2)
   -- dichiaro eseguito
   EMT.DOU_TO_ZMAX = nil
end

---------------------------------------------------------------------
function EmitResetMachining()
   if EMT.PLANEACTIVE then
      MyOutput( 'G27')
      if EMT.PREFALLCUT then
         MyOutput( 'M29')
         EMT.PREFALLCUT = nil
      end
      local sP12 = ''
      if EMT.TOOL ~= GetNextTool( EMT.MCHID) then sP12 = EgtIf( EMT.HEAD ~= 'H21', ' P1=0'..EgtIf( EMT.DOU_TYPE, ' P2=0', ''), ' P2=0') end
      local sP4 = EgtIf( EMT.MCHUSERNOTES:find( 'Split', 1, true), ' P4=1', '')
      MyOutput( 'M99'..sP12..sP4)
      -- piano non più attivo
      EMT.PLANEACTIVE = false
   end
end

---------------------------------------------------------------------
function EmitParkRoller( dPosT, bSplitCut)
   -- se entrambe già parcheggiate, non devo fare alcunché
   if ( EMT.V1POS - ParkV1) > -0.1 and ( EMT.V2POS - ParkV2) < 0.1 then return end
   -- Parcheggi pinze
   local bAgg = EgtExistsInfo( EMT.PATHID, 'CNT')
   local MyParkY1 = ParkY1 + EgtIf( bAgg, AggLoad, 0)
   local MyParkY2 = ParkY2
   -- le parcheggio
   local MDChar = {}
   if EMT.Y1DELTA and EMT.Y2DELTA then
      local dPosY1 = dPosT + EMT.Y1DELTA
      local dPosY2 = dPosT + EMT.Y2DELTA
      local DiffY1 = MyParkY1 - dPosY1
      local DiffY2 = MyParkY2 - dPosY2
      --MyOutput( string.format( 'PosT=%.3f DiffY1=%.3f DiffY2=%.3f', dPosT, DiffY1, DiffY2))
      if bSplitCut then
         if DiffY1 > 0.1  then
            MDChar.Y1 = dPosY1 + DiffY1
         end
         if DiffY2 < -0.1 then
            MDChar.Y2 = dPosY2 + DiffY2
            dPosT = dPosT + DiffY2
         end
      elseif DiffY1 > 0.1 and DiffY2 < -0.1 then
         EmtSetLastError( 1201, 'Error Collision in ParkRoller')
      elseif DiffY1 > 0.1  then
         if dPosY2 + DiffY1 > MyParkY2 + 0.1 then
            EmtSetLastError( 1201, 'Error Collision Y2 in ParkRoller')
         else
            MDChar.Y1 = dPosY1 + DiffY1
            MDChar.Y2 = dPosY2 + DiffY1
            dPosT = dPosT + DiffY1
         end
      elseif DiffY2 < -0.1 then
         if dPosY1 + DiffY2 < MyParkY1 - 0.1 then
            EmtSetLastError( 1201, 'Error Collision Y1 in ParkRoller')
         else
            MDChar.Y1 = dPosY1 + DiffY2
            MDChar.Y2 = dPosY2 + DiffY2
            dPosT = dPosT + DiffY2
         end
      end
      MDChar.MovType = 3
      MDChar.BeamVise = ( EMT.BV or 1)
   elseif EMT.Y1DELTA then
      local dMoveV1 = ParkV1 - EMT.V1POS
      local dPosY1 = dPosT + EMT.Y1DELTA
      local DiffY1 = MyParkY1 - dPosY1
      local dMoveY1 = EgtIf( DiffY1 > 0.1, dMoveV1, 0)
      local dTryMoveY1 = min( ParkV1 - dPosT - EgtIf( bSplitCut or EMT.FALL, EMT.LT, 0), MaxY1 - dPosY1 - 10)
      if ( dPosT > ParkV2 - ExtraParkV and dPosT < ParkV1 and dPosY1 + dTryMoveY1 < MaxY1) then dMoveY1 = max( dMoveY1, dTryMoveY1) end
      --MyOutput( string.format( 'PosT=%.3f PosY1=%.3f V1POS=%.3f Split=%s', dPosT, dPosY1, EMT.V1POS, EgtIf( bSplitCut, '1', '0')))
      MDChar.Y1 = dPosY1 + dMoveY1
      MDChar.MovType = 1
      MDChar.BeamVise = 1
      dPosT = dPosT + dMoveY1
   elseif EMT.Y2DELTA then
      local dMoveV2 = ParkV2 - EMT.V2POS
      local dPosY2 = dPosT + EMT.Y2DELTA
      local DiffY2 = MyParkY2 - dPosY2
      local dMoveY2 = EgtIf( DiffY2 < -0.1, dMoveV2, 0)
      local dTryMoveY2 = max( ParkV2 - dPosT - EMT.LT - EMT.HOVM, MinY2 - dPosY2 + 10)
      if ( dPosT + EMT.LT + EMT.HOVM < ParkV1 + ExtraParkV and dPosT + EMT.LT + EMT.HOVM > ParkV2 and dPosY2 + dTryMoveY2 > MinY2) then dMoveY2 = min( dMoveY2, dTryMoveY2) end
      --MyOutput( string.format( 'PosT=%.3f LT=%.3f PosY2=%.3f V2POS=%.3f', dPosT, EMT.LT, dPosY2, EMT.V2POS))
      MDChar.Y2 = dPosY2 + dMoveY2
      MDChar.MovType = 2
      MDChar.BeamVise = 2
      dPosT = dPosT + dMoveY2
   end
   if abs( ParkV1 - EMT.V1POS) > 0.1 then
      MDChar.V1 = ParkV1
      MDChar.IniStatV1 = 1
   end
   if abs( ParkV2 - EMT.V2POS) > 0.1 then
      MDChar.V2 = ParkV2
      MDChar.IniStatV2 = 1
   end
   EmitMoveDataChars( MDChar)
   EmitMoveStartChars( MDChar.MovType)
   EmitMoveWaitChars( MDChar.MovType)
   EMT.V1POS = ParkV1
   EMT.V2POS = ParkV2
   EMT.TPOS = dPosT
end

---------------------------------------------------------------------
function GetNextTool( nMchId)
   -- inizializzo prossimo utensile
   local sNextTool
   -- recupero lavorazione successiva
   local nNextMchId = EgtGetNextActiveOperation( nMchId)
   while nNextMchId do
      if EgtGetOperationType( nNextMchId) ~= MCH_OY.DISP then
         break
      end
      nNextMchId = EgtGetNextActiveOperation( nNextMchId)
   end
   -- se trovata
   if nNextMchId and EgtSetCurrMachining( nNextMchId) then
      sNextTool = EgtGetMachiningParam( MCH_MP.TOOL)
   end
   -- ripristino stato corrente
   EgtSetCurrMachining( EMT.MCHID)
   EgtTdbSetCurrTool( EMT.TOOL)
   return sNextTool
end

---------------------------------------------------------------------
function PreselectNextDiffHead( nMchId, sHead)
   -- recupero lavorazione successiva, se non esiste esco subito
   local nNextMchId = EgtGetNextActiveOperation( nMchId)
   while nNextMchId do
      if EgtGetOperationType( nNextMchId) ~= MCH_OY.DISP then break end
      nNextMchId = EgtGetNextActiveOperation( nNextMchId)
   end
   if not nNextMchId then return end

   -- se esiste lavorazione successiva
   if EgtSetCurrMachining( nNextMchId) then
      local sNextTool = EgtGetMachiningParam( MCH_MP.TOOL)
      if EgtTdbSetCurrTool( sNextTool) then
         local nHSet = GetHeadSet( sHead)
         local sNextHead = EgtTdbGetCurrToolParam( MCH_TP.HEAD)
         local nNextHSet = GetHeadSet( sNextHead)
         -- se utensile su altra testa
         if nNextHSet ~= nHSet then
            local sNextTcPos = EgtTdbGetCurrToolParam( MCH_TP.TCPOS)
            local dNextTotLen = EgtTdbGetCurrToolParam( MCH_TP.TOTLEN)

            -- se sto lavorando con testa 1
            if nHSet == 1 then
               -- se preselezione testa 2
               if nNextHSet == 2 then
                  -- se agregato lama speciale non preseleziono mai
                  if sNextHead == 'H22' or EMT.PREVHEAD_H2 == 'H22' then return end
                  -- se la Z di lavoro è più bassa del cambio della minima Z
                  if EMT.MAXMIN[3] < MinZ1ToChangeH2 then
                     -- se diametro utensile più del truciolatore standard grande, si rischia di collidere anche con il carro X testa sotto (quindi indipendentemente dalla Y della lavorazione)
                     if EMT.TTOTDIAM > 201 then return end
                     -- verifico quota del TC
                     if BD.RIGHT_LOAD and EMT.MAXMAX[2] > ( DeltaTabY + MaxHoOpen + 200) then return end
                     if not BD.RIGHT_LOAD and EMT.MAXMAX[2] > ( DeltaTabY + 200) then return end
                  end
               -- se preselezione testa 3
               else
                  ; -- non devo fare nulla
               end
            -- se sto lavorando con testa 2
            elseif nHSet == 2 then
               -- se preselezione testa 1
               if nNextHSet == 1 then
                  -- se sopra Z oltre la faccia sotto della trave, controllo il cambio utensile
                  if EMT.MAXMIN[3] > Delta2TabZ then
                     local nNextTc = GetTcForTopHeadTool( sNextTcPos)   -- utensile da caricare
                     local nPrevTc = GetTcForTopHeadTool( EMT.PREVTCPOS_H1) -- utensile da depositare
                     -- se deposito e carico da diversi TC non faccio preselezione
                     if nNextTc ~= nPrevTc and nPrevTc ~= 0 then return
                     -- altrimenti se operazione su stesso TC
                     else
                        -- TC su montante destro oppure non so cosa c'è montato
                        if nNextTc == 1  or nPrevTc == 0 then
                           if BD.RIGHT_LOAD and EMT.MAXMIN[2] < ( Delta2TabY - 200) then return end
                           if not BD.RIGHT_LOAD and EMT.MAXMIN[2] < ( Delta2TabY - MaxHoOpen - 200) then return end
                        end
                        -- TC su montante sinistro oppure non so cosa c'è montato
                        if nNextTc == 2  or nPrevTc == 0 then
                           if BD.RIGHT_LOAD and EMT.MAXMAX[2] > ( Delta2TabY + MaxHoOpen + 200) then return end
                           if not BD.RIGHT_LOAD and EMT.MAXMAX[2] > ( Delta2TabY + 200) then return end
                        end
                     end
                  end
               -- se preselezione testa 3
               else
                  ; -- non devo fare nulla
               end
            -- altrimenti (testa 3)
            else
               -- se è macchina a 3 teste con gruppo truciolatore 4 assi
               if EgtGetHeadId( 'H39') then
                  -- se preselezione testa 1
                  if nNextHSet == 1 then
                     local nNextTc = GetTcForTopHeadTool( sNextTcPos)   -- utensile da caricare
                     local nPrevTc = GetTcForTopHeadTool( EMT.PREVTCPOS_H1) -- utensile da depositare
                     -- se devo lasciare o prelevare da TC1 oppure non so cosa c'è montato, non posso preselezionare
                     if nNextTc == 1 or nPrevTc == 1 or nPrevTc == 0 then return
                     -- TC su montante sinistro
                     else
                        if BD.RIGHT_LOAD and EMT.MAXMAX[2] > ( DeltaTabY + MaxHoOpen + 200) then return end
                        if not BD.RIGHT_LOAD and EMT.MAXMAX[2] > DeltaTabY then return end
                     end
                  -- se preselezione testa 2
                  else
                     -- se agregato lama speciale non preseleziono mai
                     if sNextHead == 'H22' or EMT.PREVHEAD_H2 == 'H22' then return end
                     -- se la Z di lavoro è più bassa del cambio utensili, verifico quota X (la nostra Y)
                     if EMT.MAXMIN[3] < MinZ1ToChangeH2 then
                        if BD.RIGHT_LOAD and EMT.MAXMAX[2] > ( DeltaTabY + MaxHoOpen + 200) then return end
                        if not BD.RIGHT_LOAD and EMT.MAXMAX[2] > DeltaTabY then return end
                     end
                  end
               -- se gruppo lama dedicato, senza cambio utensile non si preseleziona mai
               elseif EgtGetHeadId( 'H38') then
                  return
               end
            end

            -- faccio preselezione ( se sono arrivato qui, vuol dire che posso preselezionare)
            if nNextHSet == 1 then
               local dNextX
               -- se sto lavorando con testa 3, la testa 1 preselezionata deve restare in home
               if nHSet == 3 then
                  dNextX = - EgtGetAxisHomePos( 'X1')
               -- se il prossimo utensile su testa 1 è utensile lungo, resta in home
               elseif sNextHead == 'H11' and dNextTotLen > LongTool then
                  dNextX = - EgtGetAxisHomePos( 'X1')
               -- altrimenti recupero quota X1 (nostro L2)
               else
                  dNextX = GetStartMachiningXaxis( nNextMchId)
               end

               -- Emetto preselezione
               if sNextHead == 'H11' then
                  if dNextTotLen < LongTool then
                     EmitMoveDataHead( 1, { X=dNextX, B=ParkB1, C=ParkC1, TPos=AdjustTcPos( false, sNextTcPos), Fmt=2})
                  else
                     local nNextTc = GetTcForTopHeadTool( sNextTcPos)
                     EmitMoveDataHead( 1, { X=dNextX, B=ParkLongB1, C=EgtIf( nNextTc ~= 2, ParkLongTc1C1, ParkLongTc2C1), TPos=AdjustTcPos( false, sNextTcPos), Fmt=2})
                  end
                  EmitMoveStartHead( 1)
               elseif sNextHead == 'H12' or sNextHead == 'H16' then
                  EmitMoveDataHead( 1, { X=dNextX, B=ParkB1, C=ParkC1, TPos=AdjustTcPos( false, sNextTcPos), Fmt=2})
                  EmitMoveStartHead( 1)
               elseif sNextHead == 'H13' then
                  -- recupero il valore dell'asse virtuale bloccato A
                  local dPosA = GetCurrChainSawingVirtualAxis()
                  local MyParkCSawC1 = GetChainSawCHomeFromVirtualAxis( dPosA, EMT.TTOTLEN)
                  local bXSpec = EgtIf( BD.RIGHT_LOAD, ( dNextX > -DeltaTabY), ( dNextX < -DeltaTabY))
                  EmitMoveDataHead( 1, { X=EgtIf( bXSpec, -DeltaTabY, dNextX), B=ParkCSawB1, C=MyParkCSawC1, TPos=AdjustTcPos( false, sNextTcPos, dPosA), Fmt=2})
                  EmitMoveStartHead( 1)
               end
            elseif nNextHSet == 2 then
               EmitMoveDataHead( 2, { B=ParkB2, C=ParkC2, TPos=AdjustTcPos( false, sNextTcPos), Fmt=2})
               EmitMoveStartHead( 2)
            else
               ; -- non si preseleziona testa 3
            end
         end
      end
   end
   -- ripristino stato corrente
   EgtSetCurrMachining( EMT.MCHID)
   EgtTdbSetCurrTool( EMT.TOOL)
end

---------------------------------------------------------------------
function GetNextTopHSet( nMchId)
   local nNextTopHSet = 0
   -- recupero lavorazione successiva
   local nNextMchId = EgtGetNextActiveOperation( nMchId)
   while nNextMchId do
      if EgtGetOperationType( nNextMchId) ~= MCH_OY.DISP then
         if EgtSetCurrMachining( nNextMchId) then
            local sNextTool = EgtGetMachiningParam( MCH_MP.TOOL)
            if EgtTdbSetCurrTool( sNextTool) then
               local sNextHead = EgtTdbGetCurrToolParam( MCH_TP.HEAD)
               local nNextHSet = GetHeadSet( sNextHead)
               if nNextHSet ~= 2 then
                  nNextTopHSet = nNextHSet
                  break
               end
            end
         end
      end
      nNextMchId = EgtGetNextActiveOperation( nNextMchId)
   end
   -- ripristino stato corrente
   EgtSetCurrMachining( EMT.MCHID)
   EgtTdbSetCurrTool( EMT.TOOL)
   return nNextTopHSet
end

---------------------------------------------------------------------
function GetFmaxClamp()
   return EmtLenToString( EMT.FMAXPINZE / 1000, 0)
end

---------------------------------------------------------------------
function FindReadHeadWithLaser( vMDChar)
   for i = 1, #vMDChar do
      if vMDChar[i].IniStatY1 == -76 then
         return true
      end
   end
   return false
end

---------------------------------------------------------------------
function FindFallAtEndAux( nPathId)
   for nInd = 1, 100 do
      local sInfo = EgtGetInfo( nPathId, 'AE'..tostring( nInd))
      if sInfo then
         local Cmd = EgtSplitString( sInfo)
         if ( Cmd[1] == '0'  and  Cmd[2] == 'Fall') then
            return true
         end
      else
         return false
      end
   end
   return false
end

---------------------------------------------------------------------
function EmitRemark( sOut)
   sOut = string.gsub( string.gsub( sOut, '%(', '['), '%)', ']')
   MyOutput( '(' .. sOut .. ')')
end

---------------------------------------------------------------------
function EmitMoveDataHead( nHead, MoData)
   local sOut = 'M101'
   local sRealTcPos

   if EMT.TCPOS then
      sRealTcPos = AdjustTcPos( false)
   end
      
   if nHead == 1 then
      sOut = 'M101'
      EMT.PREVTCPOSREAL_H1 = 'T' .. sRealTcPos
   elseif nHead == 2 then
      sOut = 'M102'
      EMT.PREVTCPOSREAL_H2 = 'T' .. sRealTcPos
   elseif nHead == 3 then
      sOut = 'M103'
   end
   sOut = sOut .. ' P1=1'
   if MoData.X then sOut = sOut .. ' P2=' .. EmtLenToString( MoData.X, 3) end
   if MoData.Z then sOut = sOut .. ' P3=' .. EmtLenToString( MoData.Z, 3) end
   if MoData.B then sOut = sOut .. ' P4=' .. EmtLenToString( MoData.B, 3) end
   if MoData.C then sOut = sOut .. ' P5=' .. EmtLenToString( MoData.C, 3) end
   if MoData.TPos then sOut = sOut .. ' P6=' .. MoData.TPos elseif EMT.TCPOS then sOut = sOut .. ' P6=' .. sRealTcPos end
   if MoData.S then sOut = sOut .. ' P7=' .. EgtNumToString( MoData.S, 0) end
   if not MoData.Fmt then 
      if MoData.F then sOut = sOut .. ' P8=' .. EmtLenToString( MoData.F, 0) else sOut = sOut .. ' P8=0' end
      sOut = sOut .. ' P9=0 P10=4 P11=1'
   elseif MoData.Fmt == 1 then
      sOut = sOut .. ' P10=4 P11=1'
   end
   MyOutput( sOut)
end

---------------------------------------------------------------------
function EmitMoveStartHead( nHead)
   local sOut = 'M101'
   if nHead == 2 then
      sOut = 'M102'
   elseif nHead == 3 then
      sOut = 'M103'
   end
   sOut = sOut .. ' P1=2'
   MyOutput( sOut)
end

---------------------------------------------------------------------
function EmitMoveWaitHead( nHead)
   local sOut = 'M101'
   if nHead == 2 then
      sOut = 'M102'
   elseif nHead == 3 then
      sOut = 'M103'
   end
   sOut = sOut .. ' P1=3'
   MyOutput( sOut)
end

---------------------------------------------------------------------
-- Emette i movimenti di esecuzione movimenti trave e alzata o discesa del piano ferma pezzo
function EmitStopBeam( MoData)
   EmitMoveStartChars(2)
   EmitMoveWaitChars(2)
   if MoData.StopBeamStat then sOut = 'M125 P2=' .. EgtNumToString( MoData.StopBeamStat, 0) end
   MyOutput( sOut)
end

---------------------------------------------------------------------
function EmitMoveDataChars( MoData)
   local sOut = 'M111'
   if MoData.MovType == 1 then sOut = sOut .. ' P1=11' elseif MoData.MovType == 2 then sOut = sOut .. ' P1=12' else  sOut = sOut .. ' P1=10' end
   if MoData.Y1 then sOut = sOut .. ' P2=' .. EmtLenToString( MoData.Y1, 3) end
   if MoData.Y2 then sOut = sOut .. ' P3=' .. EmtLenToString( MoData.Y2, 3) end
   if MoData.IniStatY1 then sOut = sOut .. ' P4=' .. EgtNumToString( MoData.IniStatY1, 0) end
   if MoData.FinStatY1 then sOut = sOut .. ' P5=' .. EgtNumToString( MoData.FinStatY1, 0) end
   if MoData.IniStatY2 then sOut = sOut .. ' P6=' .. EgtNumToString( MoData.IniStatY2, 0) end
   if MoData.FinStatY2 then sOut = sOut .. ' P7=' .. EgtNumToString( MoData.FinStatY2, 0) end
   if MoData.BeamVise then sOut = sOut .. ' P8=' .. EgtNumToString( MoData.BeamVise, 0) end
   if MoData.F then sOut = sOut .. ' P9=' .. EmtLenToString( MoData.F, 0) else sOut = sOut .. ' P9=' .. GetFmaxClamp() end
   if MoData.V1 then sOut = sOut .. ' P15=' .. EmtLenToString( MoData.V1, 3) end
   if MoData.V2 then sOut = sOut .. ' P16=' .. EmtLenToString( MoData.V2, 3) end
   if MoData.IniStatV1 then local sVal = EgtNumToString( MoData.IniStatV1, 0) ; sOut = sOut .. ' P17=' .. sVal .. ' P18=' .. sVal end
   if MoData.FinStatV1 then local sVal = EgtNumToString( MoData.FinStatV1, 0) ; sOut = sOut .. ' P19=' .. sVal .. ' P20=' .. sVal end
   if MoData.IniStatV2 then local sVal = EgtNumToString( MoData.IniStatV2, 0) ; sOut = sOut .. ' P21=' .. sVal .. ' P22=' .. sVal end
   if MoData.FinStatV2 then local sVal = EgtNumToString( MoData.FinStatV2, 0) ; sOut = sOut .. ' P23=' .. sVal .. ' P24=' .. sVal end
   if MoData.Remark then sOut = sOut .. ' ('.. MoData.Remark .. ')' end
   MyOutput( sOut)
end

---------------------------------------------------------------------
function EmitMoveStartChars( MovType)
   local sMovType = ' P1=20'
   if MovType == 1 then
      sMovType = ' P1=21'
   elseif MovType == 2 then
      sMovType = ' P1=22'
   end
   MyOutput( 'M111' .. sMovType)
end

---------------------------------------------------------------------
function EmitMoveWaitChars( MovType, bRun)
   local sMovType = ' P1=30'
   if MovType == 1 then
      sMovType = ' P1=31'
   elseif MovType == 2 then
      sMovType = ' P1=32'
   end
   if bRun == nil or bRun then
      MyOutput( 'M111' .. sMovType)
   else
      MyOutput( ';M111' .. sMovType)
   end
end

---------------------------------------------------------------------
function EmitBeamHeadData( BhData)
   local sOut = 'M112'
   if BhData.T then sOut = sOut .. ' P1=' .. EmtLenToString( BhData.T, 3) end
   if BhData.ViseY1 then sOut = sOut .. ' P3=' .. EgtNumToString( BhData.ViseY1, 0) end
   if BhData.ViseY2 then sOut = sOut .. ' P4=' .. EgtNumToString( BhData.ViseY2, 0) end
   if BhData.SetHead then sOut = sOut .. ' P5=' .. EgtNumToString( BhData.SetHead, 0) end
   if BhData.Y1 then sOut = sOut .. ' P6=' .. EmtLenToString( BhData.Y1, 3) end
   if BhData.Y2 then sOut = sOut .. ' P7=' .. EmtLenToString( BhData.Y2, 3) end
   if BhData.F then sOut = sOut .. EgtIf( BhData.F >= 1, ' P9=' .. EmtLenToString( BhData.F, 0), '') else sOut = sOut .. ' P9=' .. GetFmaxClamp() end
   if BhData.StatV1 then sOut = sOut .. ' P10=' .. EgtNumToString( BhData.StatV1, 0) end
   if BhData.V1 then sOut = sOut .. ' P11=' .. EmtLenToString( BhData.V1, 3) end
   if BhData.StatV1 then sOut = sOut .. ' P12=' .. EgtNumToString( BhData.StatV1, 0) end
   if BhData.StatV2 then sOut = sOut .. ' P13=' .. EgtNumToString( BhData.StatV2, 0) end
   if BhData.V2 then sOut = sOut .. ' P14=' .. EmtLenToString( BhData.V2, 3) end
   if BhData.StatV2 then sOut = sOut .. ' P15=' .. EgtNumToString( BhData.StatV2, 0) end
   if BhData.ViseY1 == 3 then sOut = sOut .. ' P16=1' end
   if BhData.ViseY2 == 3 then sOut = sOut .. ' P17=1' end
   MyOutput( sOut)
end

---------------------------------------------------------------------
---------------------------------------------------------------------
local bToPark = true

---------------------------------------------------------------------
function SetToParkLine()
   bToPark = true
end

---------------------------------------------------------------------
function MyOutput( sOut)
   if not bToPark then
      EmtOutput( sOut)
   else
      ParkLine( sOut)
   end
end

---------------------------------------------------------------------
---------------------------------------------------------------------
local ParkedLines = {}

---------------------------------------------------------------------
function ParkLine( sLine)
   table.insert( ParkedLines, sLine)
end

---------------------------------------------------------------------
function EmitParkedLines()
   bToPark = false
   for i = 1, #ParkedLines do
      EmtOutput( ParkedLines[i])
   end
   ParkedLines = {}
end

---------------------------------------------------------------------
---------------------------------------------------------------------
local StartValues = {}

---------------------------------------------------------------------
function SetStartValue( nI, dVal)
   StartValues[nI] = dVal
end

---------------------------------------------------------------------
function EmitStartValues()
   bToPark = false
   local sLine = ''
   for i = 1, 10 do
      sLine = sLine .. string.format( ';V%02d=', i) .. EgtNumToString( StartValues[i] or 0, 3)
   end
   EmtOutput( sLine)
   StartValues = {}
end

---------------------------------------------------------------------
-- *** END GENERATION ***
---------------------------------------------------------------------
