-- Special Operations macchina Essetre-PF1250 by Egalware s.r.l.  2024/04/09
-- Intestazioni

require( 'EmtGenerator')
EgtEnableDebug( false)

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

---------------------------------------------------------------------
-- *** Generic Machinings ***
---------------------------------------------------------------------
require( 'EmtGenMachining')

---------------------------------------------------------------------
-- *** Special GetPrevMachiningOffset ***
---------------------------------------------------------------------
-----------------------------------------------------------------------------------------
function OnSpecialGetPrevMachiningOffset()
   -- Aggiorno posizione della testa della trave a seguito di movimenti delle pinze non previsti tra le fasi
   local function TPosUpdate()
      local nClId = EgtGetFirstNameInGroup( EMC.CURRMCHID, 'CL') -- recupero Id del gruppo CL della lavorazione corrente
      local nPathId = EgtGetFirstInGroup( nClId or GDB_ID.NULL) -- Id del primo gruppo nella lavorazione (P1)
      if not nPathId then
         EMC.ERR = 12
         EMC.MSG = '  Error : CL group path not found'
         return
      end
      local dAuxMoveCount = EgtGetInfo( nPathId, 'AS#', 'd') or 0 -- numero di movimenti ausiliari 
      local dTRepos = nil
      -- controlla ogni gruppo di movimenti ausiliari
      for i = 1, dAuxMoveCount do
         local aAuxMove = EgtGetInfo( nPathId, 'AS' .. tostring( i), 'vs') or {} -- array contenete i parametri di ogni gruppo
         -- controlla solo i movimenti della testa trave e salva l'ultimo
         if aAuxMove[1] == '2' or aAuxMove[1] == '3' then
            for j = 2, #aAuxMove do
               if aAuxMove[j] == 'T' then
                  dTRepos = aAuxMove[j+1]
                  break
               end
            end
         end
      end
      return dTRepos
   end

   -- default
   EMC.ERR = 0
   EMC.PREVOFFSX = 0
   -- se c'è cambio di fase tra le lavorazioni (quindi la precedente è l'ultima della sua fase e la corrente la prima)
   if EMC.PREVPHASE ~= EMC.CURRPHASE then
      -- se la fase corrente è di inizio lavorazione di nuova trave
      if IsStartOrRestPhase( EMC.CURRPHASE) then
         -- recupero la posizione finale della lavorazione precedente
         local vPrevAx = EmtGetFinalAxesPos( EMC.PREVMCHID)
         -- ricava se e quanto la trave viene spostata tra le due fasi dai movimenti ausiliari e corregge l'offset di fine fase
         local dNewTPos = TPosUpdate()
         -- se ci sono dei movimenti della testa trave tra le due fasi ricava il delta tra la vecchia e la nuova posizione
         if dNewTPos then
            EMC.PREVOFFSX = dNewTPos - vPrevAx[1]
         -- oppure la X (L1) di questa corrisponde alla posizione iniziale della nuova trave, se ne deduce l'offset
         else
            EMC.PREVOFFSX = ParkV1 - vPrevAx[1]
         end
      end
   end
end

---------------------------------------------------------------------
-- *** Special Z moves ***
---------------------------------------------------------------------
---------------------- OnSpecialGetMaxZ -----------------------------
local function CalcExtraZ( vtTp, vtT, vMZ)
   -- la tabella deve esistere ed essere non vuota
   if not vMZ or #vMZ == 0 then return 0 end
   -- componente Z di riferimento è la minima
   local vtTz = min( vtTp:getZ(), vtT:getZ())
   -- se oltre il massimo
   if vtTz > vMZ[1].Tz then return vMZ[1].Ez end
   -- interpolo
   for i = 2, #vMZ do
      if vtTz > vMZ[i].Tz then
         local dCoeff = ( vtTz - vMZ[i-1].Tz) / ( vMZ[i].Tz - vMZ[i-1].Tz) 
         return (( 1 - dCoeff) * vMZ[i-1].Ez + dCoeff * vMZ[i].Ez)
      end
   end
   -- sotto il minimo
   return 0
end

---------------------------------------------------------------------
function OnSpecialGetMaxZ()
   -- Inizializzazioni
   EMC.ERR = 0

   -- Gestione speciale per sega a catena
   if EMC.HEAD == 'H13' then
      EMC.MAXZ = EgtGetAxisHomePos( 'Z1')
      return
   end

   -- Sistemazione dati di input
   local vtTp = Vector3d( EMC.TDIRp)
   local bFromZmax = false
   -- recupero il gruppo
   local nSetHead = GetHeadSet( EMC.HEAD)
   if vtTp:isSmall() then
      vtTp = X_AX()
      bFromZmax = true
      if nSetHead == 1 then
         EMC.R1p = ParkC1
         EMC.R2p = ParkB1
      elseif nSetHead == 2 then
         EMC.R1p = ParkC2
         EMC.R2p = ParkB2
      elseif nSetHead == 3 then
         EMC.R1p = ParkC3
         EMC.R2p = ParkB3
      end
   end
   local vtT = Vector3d( EMC.TDIR)
   local vtTpZm = EgtIf( bFromZmax, vtT, vtTp)
   local bBSameSign = (( EMC.R2p < 10 and EMC.R2 < 10) or ( EMC.R2p > -10 and EMC.R2 > -10))

   -- Calcolo in funzione della testa e dei parametri
   if EMC.HEAD == 'H11' then
      if bBSameSign and vtTp:getX() > -0.1 and vtT:getX() > -0.1 then
         EMC.MAXZ = MaxZ1
      else
         local vMZ = {{ Tz=0.85, Ez=390}, { Tz=0.5, Ez=280}, { Tz=-0.01, Ez=160}}
         EMC.MAXZ = ParkZ1 + CalcExtraZ( vtTpZm, vtT, vMZ)
      end
   elseif EMC.HEAD == 'H12' then
      if vtTp:getX() > 0.3 and vtT:getX() > 0.3 then
         if bBSameSign and abs( EMC.R1 - EMC.R1p) < 165 then
            EMC.MAXZ = MaxZ1Blade - EgtIf( abs( EMC.R2) < 90.1 and abs( EMC.R2p) < 90.1, 0, 130)
         elseif vtTp:getZ() > 0.707 or vtT:getZ() > 0.707 then
            EMC.MAXZ = ParkZ1 + 200
         elseif vtTp:getZ() > 0.5 or vtT:getZ() > 0.5 then
            EMC.MAXZ = ParkZ1 + 100
         else
            EMC.MAXZ = ParkZ1 + 1
         end
      elseif bBSameSign and EMC.R1p > 29.9 and EMC.R1p < 180.1 and EMC.R1 > 29.9 and EMC.R1 < 180.1 and EMC.R2p > -10 and EMC.R2 > -10 then
         EMC.MAXZ = MaxZ1Blade - EgtIf( abs( EMC.R2) < 90.1 and abs( EMC.R2p) < 90.1, 0, 130)
      elseif bBSameSign and EMC.R1p > -0.1 and EMC.R1p < 180.1 and EMC.R1 > -0.1 and EMC.R1 < 180.1 and EMC.R2p > -10 and EMC.R2 > -10 then
         local vMZ = {{ Tz=0.85, Ez=440}, { Tz=0.5, Ez=200}, { Tz=-0.01, Ez=5}, { Tz=-0.5, Ez=1}}
         EMC.MAXZ = ParkZ1 + CalcExtraZ( vtTpZm, vtT, vMZ)
      else
         local vMZ = {{ Tz=0.85, Ez=400}, { Tz=0.7, Ez=300}, { Tz=0.5, Ez=195}, { Tz=0.15, Ez=60}, { Tz=-0.01, Ez=5}, { Tz=-0.5, Ez=1}}
         EMC.MAXZ = ParkZ1 + CalcExtraZ( vtTpZm, vtT, vMZ)
      end
   elseif EMC.HEAD == 'H16' then
      if vtTp:getX() > 0.3 and vtT:getX() > 0.3 then
         if bBSameSign and abs( EMC.R1 - EMC.R1p) < 165 then
            EMC.MAXZ = MaxZ1Blade - EgtIf( abs( EMC.R2) < 90.1 and abs( EMC.R2p) < 90.1, 0, 130)
         elseif vtTp:getZ() > 0.707 or vtT:getZ() > 0.707 then
            EMC.MAXZ = ParkZ1 + 200
         elseif vtTp:getZ() > 0.5 or vtT:getZ() > 0.5 then
            EMC.MAXZ = ParkZ1 + 100
         else
            EMC.MAXZ = ParkZ1 + 1
         end
      elseif bBSameSign and EMC.R1p > 29.9 and EMC.R1p < 180.1 and EMC.R1 > 29.9 and EMC.R1 < 180.1 and EMC.R2p > -10 and EMC.R2 > -10 then
         EMC.MAXZ = MaxZ1Blade - EgtIf( abs( EMC.R2) < 90.1 and abs( EMC.R2p) < 90.1, 0, 130)
      elseif bBSameSign and EMC.R1p > -0.1 and EMC.R1p < 180.1 and EMC.R1 > -0.1 and EMC.R1 < 180.1 and EMC.R2p > -10 and EMC.R2 > -10 then
         local vMZ = {{ Tz=0.85, Ez=440}, { Tz=0.5, Ez=200}, { Tz=-0.01, Ez=5}, { Tz=-0.5, Ez=1}}
         EMC.MAXZ = ParkZ1 + CalcExtraZ( vtTpZm, vtT, vMZ)
      else
         local vMZ = {{ Tz=0.85, Ez=400}, { Tz=0.7, Ez=300}, { Tz=0.5, Ez=195}, { Tz=0.15, Ez=60}, { Tz=-0.01, Ez=5}, { Tz=-0.5, Ez=1}}
         EMC.MAXZ = ParkZ1 + CalcExtraZ( vtTpZm, vtT, vMZ)
      end
   elseif EMC.HEAD == 'H21' then
      if bBSameSign and vtTp:getX() > -0.1 and vtT:getX() > -0.1 then
         EMC.MAXZ = MaxZ2
      else
         local vMZ = {{ Tz=0.85, Ez=390}, { Tz=0.5, Ez=280}, { Tz=-0.01, Ez=160}}
         EMC.MAXZ = ParkZ2 + CalcExtraZ( vtTpZm, vtT, vMZ)
      end
   elseif EMC.HEAD == 'H38' then
      EMC.MAXZ = SafeZ3RotAxis
   end
end

---------------------- OnSpecialMoveZup -----------------------------
function OnSpecialMoveZup()

   --EgtOutLog( 'OnSpecialMoveZup : ' .. EMC.HEAD .. '.' .. tostring( EMC.EXIT))

   -- Inizializzazioni
   EMC.ERR = 0
   EMC.MODIF = false

   -- Direzione utensile
   local vtT = Vector3d( EMC.TDIR)

   -- recupero Z1 home
   local nHeadSet = GetHeadSet( EMC.HEAD)
   local dZmax = EgtGetAxisHomePos( EgtIf( nHeadSet ~= 2, 'Z1', 'Z2'))


   -- se fresa su testa1 o testa 2
   if EMC.HEAD == 'H11' or EMC.HEAD == 'H21' or EMC.HEAD == 'H22' then
      ;
   -- se lama
   elseif EMC.HEAD == 'H12' or EMC.HEAD == 'H16' then
      -- riporto i valori tra -179 e + 180
      local dCheckAxR1 = EMC.R1
      if dCheckAxR1 < -180 then
         dCheckAxR1 = dCheckAxR1 + 360
      elseif dCheckAxR1 > 180 then
         dCheckAxR1 = dCheckAxR1 - 360
      end
      -- se lama su testa1
      if EMC.HEAD == 'H12' then
         -- se inclinata oltre 90 gradi e interferisce con la trave
         if ( EMC.R2 > 87.9 and ( dCheckAxR1 < 15.0 or dCheckAxR1 > 180.0)) or ( EMC.R2 < -87.9 and ( dCheckAxR1 > -15.0 and dCheckAxR1 < 180.0)) then
            local dZref = dZmax + min( 330 * vtT:getZ(), 0) + 260 * ( 1 - sqrt( 1- vtT:getZ() * vtT:getZ()))
            -- se troppo in alto
            if EMC.L3 > dZref + 1 then
               ---- sistemo asse rotante
               --EMC.R2 = EgtIf( ( EMC.R2 > 0), 90, -90)
               ---- ricalcolo versore utensile
               --EMC.TDIR = EgtGetCalcToolDirFromAngles( EMC.R1, EMC.R2)
               ---- porto alla giusta quota
               --EMC.L3 = dZmax
               EMC.L3 = dZref
               -- dichiaro modificato
               EMC.MODIF = true
            end
         end
      -- se seconda lama su testa1
      elseif EMC.HEAD == 'H16' then
         -- se inclinata oltre 90 gradi e interferisce con la trave
         if ( EMC.R2 > 87.9 and ( dCheckAxR1 < 15.0 or dCheckAxR1 > 180.0)) or ( EMC.R2 < -87.9 and ( dCheckAxR1 > -15.0 and dCheckAxR1< 180.0)) then
            local dZref = dZmax + min( 330 * vtT:getZ(), 0) + 260 * ( 1 - sqrt( 1- vtT:getZ() * vtT:getZ()))
            -- se troppo in alto
            if EMC.L3 > dZref + 1 then
               ---- sistemo asse rotante
               --EMC.R2 = EgtIf( ( EMC.R2 > 0), 90, -90)
               ---- ricalcolo versore utensile
               --EMC.TDIR = EgtGetCalcToolDirFromAngles( EMC.R1, EMC.R2)
               ---- porto alla giusta quota
               --EMC.L3 = dZmax
               EMC.L3 = dZref
               -- dichiaro modificato
               EMC.MODIF = true
            end
         end
      end
   elseif EMC.HEAD == 'H38' then
      EMC.L3 = SafeZ3RotAxis
      -- dichiaro modificato
      EMC.MODIF = true
   else
      ; -- non si fa nulla
   end
end

---------------------------------------------------------------------
-- *** OnSpecialApplyDisposition & OnPostApplyMachining ***
---------------------------------------------------------------------
----------------------- Costanti ------------------------------------
local DELTA_SIC = 1
local MinJoin = BD.MIN_JOIN_S
local MinOther = abs( MinY1) + abs( MaxY2) + MinJoin
local SIC_V = 50                   -- sicurezza da testa
local MIN_ENG_V = 115              -- ingombro asse Z
local MIN_ENG_RACK_V = 161         -- ingombro asse Z con cremagliera
local MIN_ENG_XZ2_V = 180          -- ingombro assi XZ sotto
local MIN_Z2_FOR_ENG_XZ2_V = 250   -- quota Z2 da cui considerare ingombro assi XZ sotto
local AGG_V = MinDeltaYV           -- ingombro rulli pressori + sicurezza
local MaxLenSmT = 1500             -- massima lunghezza pezzo scaricato con nastri verdi
local DIST_Y1MAX_LOAD = 100        -- distanza carrello Y1 da massimo asse al carico

----------------------- Variabili -----------------------------------
local Test = false
local SPLIT

---------------------------------------------------------------------
local function PrepareClGroup( nParentId)

   local nClId = EgtGetFirstNameInGroup( nParentId, 'CL')
   -- se non c'è, lo aggiungo
   if not nClId then
      nClId = EgtGroup( EMC.DISPID)
      if not nClId then
         return nil
      end
      EgtSetName( nClId, 'CL')
      -- altrimenti lo svuoto
   else
      EgtEmptyGroup( nClId)
   end

   return nClId
end

---------------------------------------------------------------------
local function IsStartOrRestPhase( nPhase)
   local sVal = EgtGetInfo( EgtGetPhaseDisposition( nPhase) or GDB_ID.NULL, 'TYPE')
   return ( sVal == 'START' or sVal == 'REST')
end

---------------------------------------------------------------------
local function IsMidPhase( nPhase)
   local sVal = EgtGetInfo( EgtGetPhaseDisposition( nPhase) or GDB_ID.NULL, 'TYPE')
   return ( sVal == 'MID')
end

---------------------------------------------------------------------
local function IsEndPhase( nPhase)
   local sVal = EgtGetInfo( EgtGetPhaseDisposition( nPhase) or GDB_ID.NULL, 'TYPE')
   return ( sVal == 'END')
end

---------------------------------------------------------------------
local function GetNextStartOrRestPhase( nPhase)
   local nNextPhase = nPhase + 1
   while nNextPhase <= EgtGetPhaseCount() do
      if IsStartOrRestPhase( nNextPhase) then
         break ;
      end
      nNextPhase = nNextPhase + 1
   end
   return nNextPhase
end

---------------------------------------------------------------------
local function IsFirstMachiningOfStart( nMchId)
   -- se non è fase inizio di pezzo, ritorno risultato negativo
   if not IsStartOrRestPhase( EMC.PHASE) then
      return false
   end
   -- recupero la precedente operazione attiva
   local nPrevOperId = EgtGetPrevActiveOperation( nMchId)
   -- se non esiste o non è una disposizione, ritorno risultato negativo
   if not nPrevOperId or EgtGetOperationType( nPrevOperId) ~= MCH_OY.DISP then
      return false
   end
   -- è la prima
   return true
end

---------------------------------------------------------------------
local function UpdateMinJoin()
   MinJoin = BD.GetMinJoin( EMC.SB, EMC.HB, EMC.LB)
   MinOther = abs( MinY1) + abs( MaxY2) + MinJoin
end

---------------------------------------------------------------------
local function GetCUTID()
   -- recupero CUTID del pezzo in lavoro
   local nOrd = GetPhaseOrd( EMC.PHASE)
   local nPartRawId
   local nRawId = EgtGetFirstRawPart()
   while nRawId do
      local nRawOrd = EgtGetInfo( nRawId, 'ORD', 'i')
      if nRawOrd == nOrd then
         nPartRawId = nRawId
         break
      end
      nRawId = EgtGetNextRawPart( nRawId)
   end
   local CutID = EgtGetInfo( EgtGetFirstPartInRawPart( nPartRawId or GDB_ID.NULL) or GDB_ID.NULL, 'CUTID', 'i') or 0
   return CutID
end

---------------------------------------------------------------------
local function GetNextRawInOrd( nCurrRawId)
   local nCurrOrd = EgtGetInfo( nCurrRawId, 'ORD', 'i')
   if not nCurrOrd then return end
   local nRawId = EgtGetFirstRawPart()
   while nRawId do
      local nRawOrd = EgtGetInfo( nRawId, 'ORD', 'i')
      if nRawOrd == nCurrOrd + 1 then
         return nRawId
      end
      nRawId = EgtGetNextRawPart( nRawId)
   end
end

-----------------------------------------------------------------------
local function EnsureZmax( bCloseV, vCmd)
   -- verifico non sia già inserito nella lista dei comandi
   for i = 1, #vCmd do
      if vCmd[i][1] == 4 then
         -- se richiesta chiusura, eventuale inserimento se non già presente
         if bCloseV and vCmd[i][2] == 0 then
            vCmd[i][2] = 1
         end
         return true
      end
   end
   -- posizionamento sicuro teste e rulli
   table.insert( vCmd, { 4, EgtIf( bCloseV, 1, 0)})
   return true
end

-----------------------------------------------------------------------
local function EmitComment( vCmd, sOut)
   EgtOutLog( ' ' .. sOut, 1)
   if Test then
      table.insert( vCmd, { 0, sOut})  
   end
end

---------------------------------------------------------------------
function OnSpecialApplyDisposition()

   EgtOutLog( ' *** Fase : ' .. EgtNumToString( EMC.PHASE, 0) .. ' ***', 1)

   -- Inizializzo codice di errore
   EMC.ERR = 0

   -- Campi obbligatori ma non usati
   EMC.HEAD = ""
   EMC.EXIT = 1
   EMC.TCPOS = ""
   EMC.SHIFTS = 0
   EMC.SBH = false

   -- Se disposizione da saltare non devo fare alcunché
   if EgtExistsInfo( EMC.DISPID, 'SKIP') then return end

   -- Assegno flag di pezzo separato dal resto del grezzo
   SPLIT = IsEndPhase( EMC.PHASE)

   -- Recupero il tipo dell'operazione successiva
   local nNextOpeType = EgtGetOperationType( EgtGetNextActiveOperation( EMC.DISPID) or GDB_ID.NULL)

   -- Se ci sono lavorazioni successive non devo fare alcunché
   if nNextOpeType ~= MCH_OY.NONE  and  nNextOpeType ~= MCH_OY.DISP then return end

   -- Imposto gruppo e path di movimento
   local nClId = PrepareClGroup( EMC.DISPID)
   if not nClId then
      EMC.ERR = 3
      EMC.MSG = '  Error : creation CL group failed'
      return
   end
   local nPathId = EgtGroup( nClId)
   if not nPathId then
      EMC.ERR = 6
      EMC.MSG = '  Error : creation path in CL group failed'
      return
   end
   EgtSetName( nPathId, 'Empty')
   EMC.PATHID = nPathId
   EMC.SHIFTS = -1

   -- Se la lavorazione successiva è ancora una disposizione, devo scaricare il pezzo
   if nNextOpeType == MCH_OY.DISP  and  SPLIT then
      -- aggiornamento posizioni
      local nPrevOpeId = EgtGetPrevActiveOperation( EMC.DISPID)
      local nLastPathId = EgtGetLastInGroup( EgtGetFirstNameInGroup( nPrevOpeId, 'CL') or GDB_ID.NULL)
      local nLastEntId = EgtGetLastInGroup( nLastPathId)
      local vAxes = EmtGetAxesPos( nLastEntId)
      if #vAxes > 0 then EMC.TPOS = vAxes[1] end
      EMC.Y1DELTA = EgtGetInfo( nLastPathId, 'Y1DELTA', 'd')
      EMC.Y2DELTA = EgtGetInfo( nLastPathId, 'Y2DELTA', 'd')
      EMC.V1POS = EgtGetInfo( nLastPathId, 'V1POS', 'd') or ParkV1
      EMC.V2POS = EgtGetInfo( nLastPathId, 'V2POS', 'd') or ParkV2
      EMC.CNT = SpecGetCNT( EMC.DISPID)
      -- Determinazione delle dimensioni del grezzo in lavoro
      local b3Raw = BBox3d()
      local nRawId = EgtGetFirstRawPart()
      while nRawId do
         if EgtVerifyRawPartPhase( nRawId, EMC.PHASE)  and  not EgtVerifyRawPartPhase( nRawId, EMC.PHASE + 1) then
            b3Raw = EgtGetRawPartBBox( nRawId)
            break
         end
         nRawId = EgtGetNextRawPart( nRawId)
      end
      EMC.LB = b3Raw:getDimX()
      EMC.HOVM = EgtGetInfo( nRawId or GDB_ID.NULL, 'HOVM', 'd') or 0
      -- Eseguo scarico
      local vCmd = SpecCalcUnload()
      SpecOutputCmds( vCmd, true)
      return
   end

   -- Verifico ci sia un solo grezzo nella fase corrente
   local nRawCount = 0
   local nCurrRawId = GDB_ID.NULL
   local nRawId = EgtGetFirstRawPart()
   while nRawId do
      if EgtVerifyRawPartPhase( nRawId, EMC.PHASE) then
         nRawCount = nRawCount + 1
         nCurrRawId = nRawId
      end
      nRawId = EgtGetNextRawPart( nRawId)
   end
   if nRawCount > 1 then
      EMC.ERR = 10
      EMC.MSG = '  Error : too many Raw Parts in current Phase'
      return
   end

   -- Determinazione delle sue dimensioni
   local b3Raw = EgtGetRawPartBBox( nCurrRawId)
   if not b3Raw or b3Raw:isEmpty() then
      EMC.ERR = 11
      EMC.MSG = '  Error : null Raw Part'
      return
   end
   EMC.LB = b3Raw:getDimX()
   EMC.SB = b3Raw:getDimY()
   EMC.HB = b3Raw:getDimZ()

   -- Aggiorno limiti di presa
   UpdateMinJoin()

   -- Assegno sovramateriale di testa e ingombro tagli di testa e di coda
   EMC.HOVM = 0
   EMC.HCING = 0
   EMC.TCING = 0

   -- Devo scaricare il grezzo rimasto (deve essere unico)
   -- Posizione trave
   local dPosT

   -- Se fase 1 eseguo carico con carrello Y
   local vCmd  = {}
   if EMC.PHASE == 1 then
      dPosT = LoadT
      vCmd = SpecCalcLoad( dPosT, 0, 0)
   -- se altrimenti fase successiva alla prima di tipo inizio o rimanenza
   elseif IsStartOrRestPhase( EMC.PHASE) then
      -- recupero posizione trave e quota di aggancio carrello
      dPosT = EgtGetInfo( EMC.DISPID, 'TPOS', 'd')
      local dPosY1 = EgtGetInfo( EMC.DISPID, 'Y1POS', 'd')
      if not dPosY1 then
         EMC.ERR = 14
         EMC.MSG = '  Error : Y1 non clamped (start or rest)'
         return
      end
      -- confermo i nuovi parametri di aggancio
      table.insert( vCmd, { 21, dPosY1 - dPosT, 0})
      -- recupero posizione rulli
      EMC.V1POS = ParkV1
      EMC.V2POS = ParkV2
      -- recupero contatore pinza 1
      EMC.CNT = SpecGetCNT( EMC.DISPID)
   -- altrimenti fase successiva pari
   else
      EMC.Y2DELTA = EgtGetInfo( EMC.DISPID, 'Y2POS', 'd')
      EMC.V1POS = EgtGetInfo( EMC.DISPID, 'V1POS', 'd')
      EMC.V2POS = EgtGetInfo( EMC.DISPID, 'V2POS', 'd')
      -- recupero contatore pinza 1
      EMC.CNT = SpecGetCNT( EMC.DISPID)
   end
   -- Se fase inizio o rimanenza, eseguo scambio per avere solo pinza V
   local vCmd2 = {}
   if IsStartOrRestPhase( EMC.PHASE) then
      EMC.TPOS = dPosT
      SpecSetCarrPosFromCmds( vCmd)
      local dDistFront = EMC.LB - MinJoin + 10 * GEO.EPS_SMALL
      local dDistBack = 0
      local dY2DeltaMinUL = EMC.LB + MinY2 - EgtIf( EMC.LB < MaxLenSmT, UnloadSmT, UnloadT)
      if EMC.PHASE == 1 then EMC.LOAD = true end
      vCmd2 = SpecCalcCarriages( dDistFront, dDistBack, -EMC.V2POS, EMC.V1POS, nil, dY2DeltaMinUL)
      EMC.LOAD = nil
      if vCmd and #vCmd > 1 and vCmd2 and #vCmd2 > 1 and SpecTestSomeMoveInCmds( vCmd2) then
         table.insert( vCmd, { 0, 'CARR_MOVE'})
      end
   end
   -- eseguo scarico
   SpecSetCarrPosFromCmds( vCmd2)
   local vCmd3 = SpecCalcUnload()
   -- unisco ed emetto i comandi
   vCmd = EgtJoinTables( vCmd, vCmd2)
   vCmd = EgtJoinTables( vCmd, vCmd3)
   SpecOutputCmds( vCmd, true)
end

---------------------------------------------------------------------
function OnPostApplyMachining()

   EgtOutLog( ' Lavorazione : ' .. EgtGetName( EMC.MCHID) .. ' (' .. tostring( EMC.MCHID) .. ')')

   -- Inizializzo codice di errore
   EMC.ERR = 0

   -- Recupero la posizione della trave e dei carrelli al termine della precedente operazione
   local nPrevOpeId = EgtGetPrevActiveOperation( EMC.MCHID)
   -- se precedente operazione non esiste, errore
   if not nPrevOpeId then
      EMC.ERR = 1
      EMC.MSG = '  Error : previous Disposition not found'
      return
   -- se precedente operazione è disposizione
   elseif  EgtGetOperationType( nPrevOpeId) == MCH_OY.DISP then
      if EMC.PHASE == 1 then
         -- posizioni home
         EMC.TPOS = nil
         EMC.Y1DELTA = nil
         EMC.Y2DELTA = nil
         EMC.V1POS = ParkV1
         EMC.V2POS = ParkV2
         EMC.CNT = nil
      elseif IsStartOrRestPhase( EMC.PHASE) then
         -- carico le posizioni
         local dPosT = EgtGetInfo( nPrevOpeId, 'TPOS', 'd')
         local dPosY = EgtGetInfo( nPrevOpeId, 'Y1POS', 'd')
         EMC.TPOS = dPosT
         EMC.Y1DELTA = dPosY - dPosT
         EMC.Y2DELTA = nil
         EMC.V1POS = ParkV1
         EMC.V2POS = ParkV2
         EMC.CNT = SpecGetCNT( EMC.MCHID)
      else
         -- aggiornamento posizioni (da lavorazione precedente a disposizione)
         local nPrev2OpeId = EgtGetPrevActiveOperation( nPrevOpeId)
         if not nPrev2OpeId then
            EMC.ERR = 1
            EMC.MSG = '  Error : previous Disposition not found'
            return
         end
         local nLastPathId = EgtGetLastInGroup( EgtGetFirstNameInGroup( nPrev2OpeId, 'CL') or GDB_ID.NULL)
         local nLastEntId = EgtGetLastInGroup( nLastPathId)
         local vAxes = EmtGetAxesPos( nLastEntId)
         if #vAxes > 0 then EMC.TPOS = vAxes[1] end
         EMC.Y1DELTA = EgtGetInfo( nLastPathId, 'Y1DELTA', 'd')
         EMC.Y2DELTA = EgtGetInfo( nLastPathId, 'Y2DELTA', 'd')
         EMC.V1POS = EgtGetInfo( nLastPathId, 'V1POS', 'd') or ParkV1
         EMC.V2POS = EgtGetInfo( nLastPathId, 'V2POS', 'd') or ParkV2
         EMC.CNT = SpecGetCNT( EMC.MCHID)
      end
   -- altrimenti precedente operazione è lavorazione
   else
      -- aggiornamento posizioni
      local nLastPathId = EgtGetLastInGroup( EgtGetFirstNameInGroup( nPrevOpeId, 'CL') or GDB_ID.NULL)
      local nLastEntId = EgtGetLastInGroup( nLastPathId)
      local bAtZMax = ( EgtGetClEntMove( nLastEntId) == 0 and EgtGetClEntFlag( nLastEntId) == 3)
      local vAxes = EmtGetAxesPos( nLastEntId)
      if #vAxes > 0 then EMC.TPOS = vAxes[1] end
      EMC.Y1DELTA = EgtGetInfo( nLastPathId, 'Y1DELTA', 'd')
      EMC.Y2DELTA = EgtGetInfo( nLastPathId, 'Y2DELTA', 'd')
      EMC.V1POS = EgtIf( bAtZMax, ParkV1, EgtGetInfo( nLastPathId, 'V1POS', 'd') or ParkV1)
      EMC.V2POS = EgtIf( bAtZMax, ParkV2, EgtGetInfo( nLastPathId, 'V2POS', 'd') or ParkV2)
      EMC.CNT = SpecGetCNT( EMC.MCHID)
   end

   -- Verifico se ultima lavorazione della fase
   local nNextOpeId = EgtGetNextActiveOperation( EMC.MCHID)
   local bMchLast = ( not nNextOpeId  or  EgtGetOperationPhase( nNextOpeId) ~= EMC.PHASE)

   -- Verifico flag di separazione e fase di scarico
   local sNotes = EgtGetMachiningParam( MCH_MP.USERNOTES)

   -- bPreSplit sempre a falso per obbligare a pinzare il pezzo con 1 morsa anche se iniziato il taglio che dividerà grezzo e finito (in genere per sezioni grandi con 2 tagli di lama)
   -- Tra un taglio e l'altro infatti c'e' inversione del braccio e quindi un'apertura delle rulliere. Se pinzano entrambe le pinze, potrebbero esserci problemi di collisone rulli-morse
   local bPreSplit = false --( sNotes:find( 'Presplit', 1, true) ~= nil)

   local bSplitting = ( sNotes:find( 'Split', 1, true) ~= nil)
   local bPreCut = ( sNotes:find( 'Precut', 1, true) ~= nil)
   local bCutting = ( sNotes:find( 'Cut', 1, true) ~= nil)
   local bUnload = IsEndPhase( EMC.PHASE)

   -- Agisco sui diversi percorsi della lavorazione
   local nPathId = EgtGetFirstInGroup( EgtGetFirstNameInGroup( EMC.MCHID, 'CL') or GDB_ID.NULL)
   while nPathId do
      -- assegno id percorso da elaborare
      EMC.PATHID = nPathId
      -- recupero id del successivo
      nPathId = EgtGetNext( nPathId)
      -- verifico se ultimo percorso di ultima lavorazione della fase
      local bLast = ( bMchLast and ( not nPathId))
      -- se ultimo, elimino ritorno in home
      if bLast then EgtRemoveOperationHome( EMC.MCHID) end
      -- salvo lo stato dei carrelli
      local OriTPos = EMC.TPOS
      local OriY1Delta = EMC.Y1DELTA
      local OriY2Delta = EMC.Y2DELTA
      local OriV1Pos = EMC.V1POS
      local OriV2Pos = EMC.V2POS
      local OriCnt = EMC.CNT 
      -- eseguo le elaborazioni
      SpecApplyPath( bPreSplit, bSplitting, bPreCut, bCutting, bLast and bUnload)
      -- se separazione, verifico il risultato
      if bSplitting then
         -- recupero CUTID del pezzo in lavoro
         local CutID = GetCUTID()
         -- in caso di errore mancato pinzaggio uscita riprovo dopo aver disabilitato le lavorazioni finali
         if EMC.ERR == 18 then
            -- segnalazione warning
            EMC.ERR = -101
            EMC.MSG = 'Warning : skipped final processes (WRN=101,CUTID='..tostring( CutID)..')'
            -- ripristino lo stato originale dei carrelli
            EMC.TPOS = OriTPos
            EMC.Y1DELTA = OriY1Delta
            EMC.Y2DELTA = OriY2Delta
            EMC.V1POS = OriV1Pos
            EMC.V2POS = OriV2Pos
            EMC.CNT = OriCnt
            -- eseguo le elaborazioni
            SpecApplyPath( bPreSplit, bSplitting, bPreCut, bCutting, bLast and bUnload)
            -- pinzaggio ancora impossibile, pezzo a caduta
            if EMC.ERR == 18 then
               -- segnalazione warning
               EMC.ERR = -102
               EMC.MSG = 'Warning : skipped final processes and unload by fall (WRN=102,CUTID='..tostring( CutID)..')'
            end
         -- scarico standard
         elseif EMC.ERR == 0 then
            -- segnalazione warning
            EMC.ERR = -100
            EMC.MSG = 'Warning : standard unload (WRN=100,CUTID='..tostring( CutID)..')'
         end
      -- se taglio del residuo finale, scarico standard
      elseif bCutting then
         -- recupero CUTID del pezzo in lavoro
         local CutID = GetCUTID()
         -- se non ci sono errori, segnalazione warning
         if EMC.ERR == 0 then
            EMC.ERR = -100
            EMC.MSG = 'Warning : standard unload (WRN=100,CUTID='..tostring( CutID)..')'
         end
      end
      if EMC.ERR > 0 then return end
      -- determino la posizione finale della trave
      local nLastEntId = EgtGetLastInGroup( EMC.PATHID)
      local vAxes = EmtGetAxesPos( nLastEntId)
      if #vAxes > 0 then EMC.TPOS = vAxes[1] end
   end

end

---------------------------------------------------------------------
function SpecApplyPath( bPreSplit, bSplitting, bPreCut, bCutting, bUnload)

   -- Verifico se lavorazione pareti
   local bWall = ( EgtGetInfo( EgtGetCurrMachGroup() or GDB_ID.NULL, 'Wall', 'd') == 1)
   
   -- mi salvo info lavorazione su una lista
   local AuxInfoMach = {}
   AuxInfoMach.bPreSplit = bPreSplit
   AuxInfoMach.bSplitting = bSplitting
   AuxInfoMach.bPreCut = bPreCut
   AuxInfoMach.bCutting = bCutting
   AuxInfoMach.bUnload = bUnload
   
   -- Assegno flag di pezzo separato dal resto del grezzo
   SPLIT = IsEndPhase( EMC.PHASE)

   -- Determinazione delle dimensioni totali dei grezzi e del grezzo in lavoro
   local b3Tot = BBox3d()
   local b3Raw = BBox3d()
   local nNextOddPhase = GetNextStartOrRestPhase( EMC.PHASE)
   local nRawId = EgtGetFirstRawPart()
   local nCurrRawId = GDB_ID.NULL
   while nRawId do
      if EgtVerifyRawPartPhase( nRawId, EMC.PHASE) then
         local b3Tmp = EgtGetRawPartBBox( nRawId)
         b3Tot:Add( b3Tmp)
         if EgtGetPartInRawPartCount( nRawId) > 0  and  not EgtVerifyRawPartPhase( nRawId, nNextOddPhase) then
            b3Raw = b3Tmp
            nCurrRawId = nRawId
         end
      end
      nRawId = EgtGetNextRawPart( nRawId)
   end
   if b3Tot:isEmpty() then
      EMC.ERR = 11
      EMC.MSG = '  Error : null Raw Parts'
      return
   end
   EMC.LB = EgtIf( SPLIT, b3Raw:getDimX(), b3Tot:getDimX())
   EMC.SB = b3Tot:getDimY()
   EMC.HB = b3Tot:getDimZ()
   EMC.LT = b3Raw:getDimX()
   EgtOutLog( '  BarLen='..EgtNumToString( EMC.LB, 1), 3)

   -- Aggiorno limiti di presa
   UpdateMinJoin()

   -- Recupero sovramateriale di testa e ingombro tagli di testa e di coda
   EMC.HOVM = EgtGetInfo( nCurrRawId, 'HOVM', 'd') or 0
   EMC.HCING = EgtGetInfo( nCurrRawId, 'HCING', 'd') or 0
   EMC.TCING = EgtGetInfo( nCurrRawId, 'TCING', 'd') or 0
   -- Recupero ingombro dei tagli di testa del prossimo pezzo (se esiste)
   EMC.NEXT_HCING = 0
   local nNextRawId = GetNextRawInOrd( nCurrRawId)
   if nNextRawId then
      EMC.NEXT_HCING = EgtGetInfo( nNextRawId, 'HCING', 'd') or 0
   end

   -- Calcolo dell'ingombro della lavorazione
   local dDistFront, dDistBack, dRollFront, dRollBack = SpecialCalcMachiningEncumbrance( EMC.MCHID, bPreCut)
   if not dDistFront then return end
   local dMaxLenLeft = 0
   if bPreSplit  or  bSplitting then
      local dDistF, dDistB, dRollF, dRollB, dMaxLF = SpecialCalcPhaseEncumbrance( EMC.PHASE + 1)
      dDistFront = min( dDistFront, dDistF)
      dDistBack = dDistBack
      if bSplitting then dMaxLenLeft = dMaxLF end
   elseif bPreCut  or  bCutting then
      local dDistF, dDistB, dRollF, dRollB = SpecialCalcPhaseEncumbrance( EMC.PHASE + 1)
      dDistFront = min( dDistFront, dDistF)
      dDistBack = min( dDistBack, dDistB)
   end

   -- Verifico lunghezza pezzo
   if not SPLIT and not VerifyPartLength() then
      return
   end

   -- Calcolo quali carrelli sono necessari
   local nChar
   if bPreSplit or bSplitting then
      nChar = 2
   elseif bPreCut or bCutting then
      nChar = 3
   end
   local dY1DeltaMaxSP
   if nChar == 2 then
      dY1DeltaMaxSP = MaxY1 - LoadT + EMC.LT
   end
   local dY2DeltaMinUL
   if ( nChar == 2 or nChar == 3) and not bWall then
      dY2DeltaMinUL = EMC.LT + MinY2 - EgtIf( EMC.LT < MaxLenSmT, UnloadSmT, UnloadT)
   end

   -- Se inizio o appena dopo rotazione, eseguo il carico
   if not EMC.TPOS then
      local dPosT = LoadT
      local vCmd = SpecCalcLoad( dPosT, dDistFront, max( dDistBack, MinJoin, EMC.LB - ( MaxY1 - MinY1) + 6))
      EMC.LOAD = true
      local vCmd2 = SpecCalcCarriages( dDistFront, dDistBack, dRollFront, dRollBack, dY1DeltaMaxSP, dY2DeltaMinUL, nChar, AuxInfoMach)
      EMC.LOAD = nil
      if bSplitting and EMC.ERR == 18 then
         table.insert( vCmd, { 22, dRollBack, -dRollFront})
      end
      if vCmd2 and #vCmd2 > 1 and SpecTestSomeMoveInCmds( vCmd2) then
         table.insert( vCmd, { 0, 'CARR_MOVE'})
      end
      EgtJoinTables( vCmd, vCmd2)
      SpecOutputCmds( vCmd)

   -- Se altrimenti non eseguito SPLIT, eseguo calcoli per carrelli
   elseif not SPLIT then
      local vCmd = SpecCalcCarriages( dDistFront, dDistBack, dRollFront, dRollBack, dY1DeltaMaxSP, dY2DeltaMinUL, nChar, AuxInfoMach)
      -- Se non ci sono spostamenti, confermo i parametri di aggancio e di posizione roller
      if SpecTestOnlyRemarkInCmds( vCmd) then
         table.insert( vCmd, { 21, EgtIf( EMC.Y1DELTA, EMC.Y1DELTA, 0), EgtIf( EMC.Y2DELTA, EMC.Y2DELTA, 0)})
         table.insert( vCmd, { 22, dRollBack, -dRollFront})
      end
      SpecOutputCmds( vCmd)

   -- Altrimenti, non muovo i carrelli rispetto alla trave ma impongo la posizione dei rulli
   else
      SpecOutputCNT()
      local vCmd = {}
      table.insert( vCmd, { 21, EgtIf( EMC.Y1DELTA, EMC.Y1DELTA, 0), EgtIf( EMC.Y2DELTA, EMC.Y2DELTA, 0)})
      table.insert( vCmd, { 22, dRollBack, -dRollFront})
      SpecOutputCmds( vCmd)
   end

   -- Se taglio di separazione
   local vCmd = {}
   if bSplitting then
      -- rimuovo eventuale vecchia info di Skip
      local NextDispId = EgtGetPhaseDisposition( EMC.PHASE + 1) or GDB_ID.NULL
      EgtRemoveInfo( NextDispId, 'SKIP')
      -- verifico se separazione con caduta
      if  not EMC.Y2DELTA  then
         EgtOutLog( '  Warning SPLITTING -> separazione con caduta pezzo')
         SpecOutputCNT()
         if IsEndPhase( EMC.PHASE + 1) then
            EgtSetInfo( NextDispId, 'SKIP', '1')
            local NextOpeId = EgtGetNextOperation( NextDispId) 
            while NextOpeId and EgtGetOperationPhase( NextOpeId) == EMC.PHASE + 1 do
               EgtSetOperationMode( NextOpeId, false)
               NextOpeId = EgtGetNextOperation( NextOpeId)
            end
         end
         -- forzo l'errore 18
         EMC.ERR = 18
      -- verifico che la barra sia agganciata anche al primo carrello
      elseif  not EMC.Y1DELTA  then
         EMC.ERR = 19
         EMC.MSG = '  Error SPLIT : Y1 or Y2 not clamped'
         return false
      end
      -- eseguo la separazione
      EgtOutLog( 'MaxLenLeft=' .. EgtNumToString( dMaxLenLeft, 1), 1)
      vCmd = SpecCalcSplit( b3Raw:getDimX(), dMaxLenLeft)
   end
   -- Se taglio finale di grezzo a perdere
   if bCutting then
      -- salvo distanza carrello Y2 da inizio grezzo rimasto nella disposizione della prossima fase
      local NextDispId = EgtGetPhaseDisposition( EMC.PHASE + 1)
      if NextDispId then
         EgtSetInfo( NextDispId, 'Y2POS', EMC.Y2DELTA)
         EgtSetInfo( NextDispId, 'V1POS', EMC.V1POS)
         EgtSetInfo( NextDispId, 'V2POS', EMC.V2POS)
      end
   end   

   -- Se previsto scarico, lo eseguo
   if bUnload then
      EMC.LB = b3Raw:getDimX()
      local vCmdTmp = SpecCalcUnload()
      vCmd = EgtJoinTables( vCmd, vCmdTmp)
   end

   -- Emetto eventuali comandi di separazione e/o scarico
   if #vCmd > 0 then
      SpecOutputCmds( vCmd, true)
   end

end  --SpecApplyPath( bLast)

---------------------------------------------------------------------
function SpecialCalcMachiningEncumbrance( nMchId, bPreCut)
   -- gruppi della lavorazione
   local nClId = EgtGetFirstNameInGroup( nMchId, 'CL')
   local nPathId = EgtGetFirstInGroup( nClId or GDB_ID.NULL)
   if not nPathId then
      EMC.ERR = 12
      EMC.MSG = '  Error : CL group path not found'
      return
   end
   -- Recupero ptMin ptMax della lavorazione
   local ptMin = EgtGetInfo( nClId, 'MMIN', 'p')
   local ptMax = EgtGetInfo( nClId, 'MMAX', 'p')
   if not ptMin or not ptMax then
      EMC.ERR = 13
      EMC.MSG = '  Error : machining Min or Max not found'
      return
   end
   -- se pre-taglio, aggiorno ptMax con quello del taglio finale
   if bPreCut then
      local ptFinMax = GetFinalCutPmax( nMchId)
      if ptFinMax then
         ptMax = ptFinMax
      end   
   end
   -- Recupero testa
   local sHead = EgtTdbGetCurrToolParam( MCH_TP.HEAD)
   -- Recupero minimi e massimi degli assi macchina della lavorazione
   local vAxMin = EgtGetInfo( nClId, 'MAXMIN', 'vd')
   local vAxMax = EgtGetInfo( nClId, 'MAXMAX', 'vd')
   if not vAxMin or not vAxMax or #vAxMin < 5 or ( sHead == 'H13' and #vAxMin < 6) then
      EMC.ERR = 15
      EMC.MSG = '  Error : machine axes Min or Max not found'
      return
   end
   local vAxMid = {}
   for i = 1, #vAxMin do
      vAxMid[i] = ( vAxMin[i] + vAxMax[i]) / 2
   end
   -- recupero il gruppo
   local nSetHead = GetHeadSet( sHead)
   -- Calcolo dell'ingombro della testa rispetto allo Zero Macchina (assi rotanti fissi, C=4, B=5, A=6)
   local b3Enc
   if nSetHead == 1 then
      EgtSetAxisPos( 'C1', vAxMid[4])
      EgtSetAxisPos( 'B1', vAxMid[5])
      if sHead == 'H13' then
         EgtSetAxisPos( 'A1', vAxMid[6])
      end
      b3Enc = EgtGetBBoxGlob( EgtGetAxisId( 'C1'), GDB_BB.ONLY_VISIBLE)
   elseif nSetHead == 2 then
      EgtSetAxisPos( 'C2', vAxMid[4])
      EgtSetAxisPos( 'B2', vAxMid[5])
      if sHead == 'H22' then
         EgtSetAxisPos( 'A2', vAxMid[6])
      end
      b3Enc = EgtGetBBoxGlob( EgtGetAxisId( 'C2'), GDB_BB.ONLY_VISIBLE)
   else         -- testa 3
      EgtSetAxisPos( 'C3', vAxMid[4])
      EgtSetAxisPos( 'B3', vAxMid[5])
      b3Enc = EgtGetBBoxGlob( EgtGetAxisId( 'C3'), GDB_BB.ONLY_VISIBLE)
   end

   -- Forzo la distanza di sicurezza da utensile
   local nSecRollerDist = EgtGetValInNotes( EgtTdbGetCurrToolParam( MCH_TP.USERNOTES), 'SECDIST', 'i') or SIC_V

   local dMinFrontEng = EgtIf( nSetHead ~= 2, MIN_ENG_RACK_V, EgtIf( vAxMax[3] < MIN_Z2_FOR_ENG_XZ2_V, MIN_ENG_V, MIN_ENG_XZ2_V))
   local dRollFront = max( b3Enc:getMax():getX(), dMinFrontEng) + nSecRollerDist
   local dMinBackEng = EgtIf( nSetHead ~= 2, MIN_ENG_V, EgtIf( vAxMax[3] < MIN_Z2_FOR_ENG_XZ2_V, MIN_ENG_V, MIN_ENG_XZ2_V))
   local dRollBack = max( -b3Enc:getMin():getX(), dMinBackEng) + nSecRollerDist
   EgtOutLog( '  RollFront = ' .. EgtNumToString( dRollFront, 1) .. ' RollBack = ' .. EgtNumToString( dRollBack, 1), 3)
   -- Calcolo della posizione della Punta Utensile rispetto allo Zero Macchina
   local ptTip
   ptTip = EgtGetCalcTipFromPositions( 0, 0, 0, vAxMid[4], vAxMid[5], vAxMid[6] or 0, false)
   EgtOutLog( '  ToolTip = ' .. tostring( ptTip), 5)
   -- Calcolo dell'ingombro della testa rispetto alla Punta Utensile
   local dHeadFront = dRollFront + AGG_V - ptTip:getX()
   local dHeadBack = dRollBack + AGG_V + ptTip:getX()
   EgtOutLog( '  HeadFront = ' .. EgtNumToString( dHeadFront, 1) .. ' HeadBack = ' .. EgtNumToString( dHeadBack, 1), 5)
   -- Distanze limiti della pinza di destra da testa trave e della pinza di sinistra da coda trave
   local dDistFront = - ptMax:getX() - LoadT - dHeadFront
   local dDistBack = ptMin:getX() + LoadT + EMC.LB - dHeadBack
   EgtOutLog( '  DistFront = ' .. EgtNumToString( dDistFront, 1) .. ' DistBack = ' .. EgtNumToString( dDistBack, 1), 3)
   return dDistFront, dDistBack, dRollFront, dRollBack
end

---------------------------------------------------------------------
function SpecialCalcPhaseEncumbrance( nPhase)
   -- Deve essere la fase finale di lavorazione di un pezzo (già staccato dal resto della trave)
   local dDistFront = EMC.LB
   local dDistBack = EMC.LB
   local dRollFront = 0
   local dRollBack = 0
   local dMaxLenLeft = 0
   -- Salvo lavorazione e utensile correnti, per ripristinarli alla fine
   local nOrigMchId = EgtGetCurrMachining()
   local sOrigTool = EgtTdbGetCurrToolParam( MCH_TP.NAME)
   local sOrigHead = EgtTdbGetCurrToolParam( MCH_TP.HEAD)
   -- Ciclo sulle lavorazioni
   local nMchId = EgtGetNextActiveOperation( EgtGetPhaseDisposition( nPhase) or GDB_ID.NULL)
   while nMchId and EgtGetOperationPhase( nMchId) == nPhase do
      -- imposto lavorazione e utensile correnti
      EgtSetCurrMachining( nMchId)
      local sTool = EgtGetMachiningParam( MCH_MP.TOOL)
      if not sTool or not EgtTdbSetCurrTool( sTool) then
         local sTuuid = EgtGetMachiningParam( MCH_MP.TUUID)
         sTool = EgtTdbGetToolFromUUID( sTuuid) or ''
         EgtTdbSetCurrTool( sTool)
      end
      local sHead = EgtTdbGetCurrToolParam( MCH_TP.HEAD)
      EgtSetCalcTool( sTool, sHead, 1)
      -- calcolo ingombri
      local dDistF, dDistB, dRollF, dRollB = SpecialCalcMachiningEncumbrance( nMchId)
      if dDistF and dDistB then
         dDistFront = min( dDistFront, dDistF)
         dDistBack = min( dDistBack, dDistB)
         dRollFront = max( dRollFront, dRollF)
         dRollBack = max( dRollBack, dRollB)
         local dMaxLenL = EMC.LT - dDistF - AGG_V - dRollF
         EgtOutLog( '  MaxLenLeft = ' .. EgtNumToString( dMaxLenL, 1), 3)
         dMaxLenLeft = max( dMaxLenLeft, dMaxLenL)
      end
      nMchId = EgtGetNextActiveOperation( nMchId)
   end
   -- Ripristino lavorazione e utensile correnti
   if nOrigMchId then EgtSetCurrMachining( nOrigMchId) end
   if sOrigTool then EgtTdbSetCurrTool( sOrigTool) end
   if sOrigTool and sOrigHead then EgtSetCalcTool( sOrigTool, sOrigHead, 1) end
   -- Restituisco gli ingombri trovati
   return dDistFront, dDistBack, dRollFront, dRollBack, dMaxLenLeft
end

---------------------------------------------------------------------
function GetFinalCutPmax( nMchId)
   local nFinalCutId
   local nId = EgtGetNextActiveOperation( nMchId)
   while nId  and  EgtGetOperationPhase( nId) == EMC.PHASE do
      nFinalCutId = nId
      nId = EgtGetNextActiveOperation( nId)
   end
   if not nFinalCutId then return end
   local nCLId = EgtGetFirstNameInGroup( nFinalCutId, 'CL')
   if not nCLId then return end
   return EgtGetInfo( nCLId, 'MMAX', 'p')
end

---------------------------------------------------------------------
function VerifyPartLength()

   -- Verifico lunghezza pezzo
   if EMC.LB < MinJoin + MinOther + AggLoad + EMC.HCING + EMC.HOVM then
      EMC.ERR = 17
      EMC.MSG = '  Error CLAMP : part too short'
      return false
   end

   return true
end

---------------------------------------------------------------------
function SpecCalcLoad( dPosT, dDistFront, dDistBack)
   --[L]
   local dNewY1min = max( EMC.LB - dDistBack, MinOther + AggLoad + EMC.HCING + EMC.HOVM)
   local dNewY1max = min( ( MaxY1 - MinY1) - 5, EMC.LB - MinJoin)
   if dNewY1min > dNewY1max then return nil end
    -- minimo posizionamento pinza 1 per permettere scarico del restante
   local dMinLengthRestToUnload = abs( MinY1) + abs( MinV1) + abs( MaxV2) + abs( MaxY2) + MinJoin + AggLoad + EMC.LT + 10
   -- calcolo posizionamento con i coefficienti, ma verifico che sia tra il minimo e il massimo pinzabile
   local dNewY1DeltaMax = EgtClamp( 0.25 * dNewY1min + 0.75 * dNewY1max, dMinLengthRestToUnload, dNewY1max)
   local dNewY1Delta = EgtClamp( BD.CHAR_LOAD_DIST or 3000, 0.75 * dNewY1min + 0.25 * dNewY1max, dNewY1DeltaMax)
   local dNewY2Delta = nil
   local dNewY1 = dPosT + dNewY1Delta
   local vCmd = {}
   EgtOutLog( ' *[L]', 1)
   -- [L-1]
   if dNewY1 + TurnerOffs + DIST_Y1MAX_LOAD - MaxY1 > 0 then
      dNewY1Delta = min( EMC.LB - MinJoin, MaxY1 - dPosT - TurnerOffs - DIST_Y1MAX_LOAD)
      EgtOutLog( ' *[L1]', 1)
   end --[L-2]
   if EMC.LB - dNewY1Delta < MinJoin then
      dNewY1Delta = min( EMC.LB - MinJoin, MaxY1 - dPosT - TurnerOffs - DIST_Y1MAX_LOAD)
      EgtOutLog( ' *[L2]', 1)
   end
   -- Commento
   table.insert( vCmd, { 0, 'Loading'})
   -- posizionamento sicuro teste e rulli
   EnsureZmax( false, vCmd)
   -- Apro entrambe le morse
   table.insert( vCmd, { 11, 0})
   EMC.CNT = nil
   table.insert( vCmd, { 12, 0})
   -- Sposto il carrello Y per il carico
   table.insert( vCmd, { 2, 'Y1', dPosT + dNewY1Delta, 'Y2', ParkY2})
   -- Chiudo morsa Y
   table.insert( vCmd, { 11, 1})
   -- Sposto il carrello Y con la testa trave al parcheggio V1
   table.insert( vCmd, { 2, 'T', ParkV1, 'Y1', ParkV1 + dNewY1Delta})
   -- confermo i nuovi parametri di aggancio
   table.insert( vCmd, { 21, dNewY1Delta, 0})
   -- Inizializzo contatore globale
   EMC.CNT = 1
   SpecOutputCNT()
   -- Assegno stato corrente
   EMC.TPOS = ParkV1
   EMC.Y1DELTA = dNewY1Delta
   EMC.Y2DELTA = nil
   EMC.V1POS = ParkV1
   EMC.V2POS = ParkV2
   -- Restituisco i comandi
   return vCmd
end -- SpecAdjustLoad [L]

---------------------------------------------------------------------
function SpecCalcCarriages( dDistFront, dDistBack, dRollFront, dRollBack, dY1DeltaMaxSP, dY2DeltaMinUL, nChar, AuxInfoMach)

   local MinFrontJoin = MinJoin + EMC.HCING + EMC.HOVM
   local MyMinOther = MinOther + EgtIf( EMC.CNT == 1, AggLoad, 0)
   local dMyDistBack = dDistBack - EgtIf( EMC.CNT == 1, AggLoad, 0)

   local sMaxSplit = ''
   if dY1DeltaMaxSP then sMaxSplit = '/'..EgtNumToString( dY1DeltaMaxSP, 1)..'(MaxSplit)' end
   local sMinUnload = ''
   if dY2DeltaMinUL then sMinUnload = '/'..EgtNumToString( dY2DeltaMinUL, 1)..'(MinUnload)' end
   EgtOutLog( '  Dist/Min : Back=' .. EgtNumToString( dMyDistBack, 1) .. '/' .. EgtNumToString( MinJoin, 1) .. sMaxSplit ..
              ' Front=' .. EgtNumToString( dDistFront, 1) .. '/' .. EgtNumToString( MinFrontJoin, 1) .. sMinUnload, 3)

   local bBackOk = ( dMyDistBack > MinJoin - GEO.EPS_SMALL)
   local bFrontOk = ( dDistFront > MinFrontJoin - GEO.EPS_SMALL)

   -- [A] se non richiesto solo carrello Y2 e posso mettere solo carrello Y1
   if ( nChar ~= 2 and nChar ~= 3 and bBackOk) or
      ( nChar == 2 and bBackOk and not bFrontOk) then

      local WorkTab = {}
      WorkTab.dTPosI = EMC.TPOS
      WorkTab.dY1DeltaI = EMC.Y1DELTA
      WorkTab.dY2DeltaI = EMC.Y2DELTA
      WorkTab.dV1PosI = EMC.V1POS
      WorkTab.bV1CloseI = false
      WorkTab.dV2PosI = EMC.V2POS
      WorkTab.bV2CloseI = false

      WorkTab.dTPosF = nil
      WorkTab.dY1DeltaMinF = max( EMC.LB - dMyDistBack, MyMinOther + EMC.HCING + EMC.HOVM)
      WorkTab.dY1DeltaMaxF = min( EMC.LB - MinJoin, MaxY1 + dDistFront + dRollFront + AGG_V)
      WorkTab.dY2DeltaMinF = nil
      WorkTab.dY2DeltaMaxF = nil
      if dY1DeltaMaxSP then WorkTab.dY1DeltaMaxF = min( WorkTab.dY1DeltaMaxF, dY1DeltaMaxSP) end
      WorkTab.dV1PosF = dRollBack
      WorkTab.bV1CloseF = false
      WorkTab.dV2PosF = -dRollFront
      WorkTab.bV2CloseF = false

      -- dopo che si è calcolato il minimo e massimo dell'intervallo, verifico che in testa rimanga almeno il minimo per poter fare passaggio pinze e scaricare
      if AuxInfoMach and ( AuxInfoMach.bSplitting or AuxInfoMach.bCutting) then
         if WorkTab.dY1DeltaMaxF - WorkTab.dY1DeltaMinF > 1.5 * BD.CHAR_EXTRA_DIST and WorkTab.dY1DeltaMaxF > EMC.LT + BD.MINRAW_S + BD.CHAR_EXTRA_DIST then
            WorkTab.dY1DeltaMinF = max( min( EMC.LT + BD.MINRAW_S, WorkTab.dY1DeltaMaxF - 1), WorkTab.dY1DeltaMinF + EMC.LT)
         end
      end

      -- verifico validità intervallo ammesso per Y1
      if WorkTab.dY1DeltaMinF > WorkTab.dY1DeltaMaxF + 10 * GEO.EPS_SMALL then
         EMC.ERR = 18
         EMC.MSG = '  Error : CLAMP Y1 impossible'
         return {}
      end

      -- al carico o con prima lavorazione di un pezzo si deve ignorare l'ingombro delle lavorazioni di testa per il pinzaggio
      if EMC.LOAD or IsFirstMachiningOfStart( EMC.MCHID) then EMC.HCING = 0 end

      return SpecAdjustCarriages( WorkTab)

   -- [B] se richiesti entrambi e posso metterli
   elseif nChar == 2 and bBackOk and bFrontOk then

      local WorkTab = {}
      WorkTab.dTPosI = EMC.TPOS
      WorkTab.dY1DeltaI = EMC.Y1DELTA
      WorkTab.dY2DeltaI = EMC.Y2DELTA
      WorkTab.dV1PosI = EMC.V1POS
      WorkTab.bV1CloseI = false
      WorkTab.dV2PosI = EMC.V2POS
      WorkTab.bV2CloseI = false

      WorkTab.dTPosF = nil
      WorkTab.dY1DeltaMinF = max( EMC.LB - dMyDistBack, MyMinOther + EMC.HCING + EMC.HOVM, EMC.LT + MyMinOther + EMC.NEXT_HCING)
      WorkTab.dY1DeltaMaxF = min( EMC.LB - MinJoin, MaxY1 + dDistFront + dRollFront + AGG_V)
      WorkTab.dY2DeltaMinF = max( MinJoin + EMC.HCING + EMC.HOVM, EMC.LB + MinY2 - dMyDistBack - AGG_V - dRollBack)
      WorkTab.dY2DeltaMaxF = min( dDistFront, EMC.LB - MyMinOther)   --  - EMC.TCING
      if dY1DeltaMaxSP then WorkTab.dY1DeltaMaxF = min( WorkTab.dY1DeltaMaxF, dY1DeltaMaxSP) end
      if dY2DeltaMinUL then WorkTab.dY2DeltaMinF = max( WorkTab.dY2DeltaMinF, dY2DeltaMinUL) end
      WorkTab.dV1PosF = dRollBack
      WorkTab.bV1CloseF = false
      WorkTab.dV2PosF = -dRollFront
      WorkTab.bV2CloseF = false

      -- dopo che si è calcolato il minimo e massimo dell'intervallo, verifico che in testa rimanga almeno il minimo per poter fare passaggio pinze e scaricare
      if AuxInfoMach and ( AuxInfoMach.bSplitting or AuxInfoMach.bCutting) then
         if WorkTab.dY1DeltaMaxF - WorkTab.dY1DeltaMinF > 1.5 * BD.CHAR_EXTRA_DIST and WorkTab.dY1DeltaMaxF > EMC.LT + BD.MINRAW_S + BD.CHAR_EXTRA_DIST then
            WorkTab.dY1DeltaMinF = max( min( EMC.LT + BD.MINRAW_S, WorkTab.dY1DeltaMaxF - 1), WorkTab.dY1DeltaMinF)
         end
      end

      -- verifico validità intervallo ammesso per Y1
      if WorkTab.dY1DeltaMinF > WorkTab.dY1DeltaMaxF + 10 * GEO.EPS_SMALL then
         EMC.ERR = 18
         EMC.MSG = '  Error : CLAMP Y1 impossible'
         return {}
      end

      -- verifico validità intervallo ammesso per Y2
      if WorkTab.dY2DeltaMinF > WorkTab.dY2DeltaMaxF + 10 * GEO.EPS_SMALL then
         EMC.ERR = 18
         EMC.MSG = '  Error : CLAMP Y2 impossible'
         return {}
      end

      return SpecAdjustCarriages( WorkTab)

   -- [C] se non richiesti entrambi e posso mettere carrello Y2
   elseif bFrontOk then

      local WorkTab = {}
      WorkTab.dTPosI = EMC.TPOS
      WorkTab.dY1DeltaI = EMC.Y1DELTA
      WorkTab.dY2DeltaI = EMC.Y2DELTA
      WorkTab.dV1PosI = EMC.V1POS
      WorkTab.bV1CloseI = false
      WorkTab.dV2PosI = EMC.V2POS
      WorkTab.bV2CloseI = false

      WorkTab.dTPosF = nil
      WorkTab.dY1DeltaMinF = nil
      WorkTab.dY1DeltaMaxF = nil
      WorkTab.dY2DeltaMinF = max( MinJoin + EMC.HCING + EMC.HOVM, EMC.LB + MinY2 - dMyDistBack - AGG_V - dRollBack)
      WorkTab.dY2DeltaMaxF = min( dDistFront, EMC.LB - MyMinOther)   --  - EMC.TCING
      if dY2DeltaMinUL then WorkTab.dY2DeltaMinF = max( WorkTab.dY2DeltaMinF, dY2DeltaMinUL) end
      WorkTab.dV1PosF = dRollBack
      WorkTab.bV1CloseF = false
      WorkTab.dV2PosF = -dRollFront
      WorkTab.bV2CloseF = false

      -- verifico validità intervallo ammesso per Y2
      if WorkTab.dY2DeltaMinF > WorkTab.dY2DeltaMaxF + 10 * GEO.EPS_SMALL then
         EMC.ERR = 18
         EMC.MSG = '  Error : CLAMP Y2 impossible'
         return {}
      end

      return SpecAdjustCarriages( WorkTab)

   -- altrimenti errore
   else
      if EgtGetDebugLevel() < 3 then
         EgtOutLog( '  DistFront=' .. EgtNumToString( dDistFront) .. ' DistBack=' .. EgtNumToString( dMyDistBack) ..
                    ' MinJoin=' .. EgtNumToString( MinJoin) .. ' MinFrontJoin=' .. EgtNumToString( MinFrontJoin))
      end
      EMC.ERR = 18
      EMC.MSG = '  Error : CLAMP impossible'
      return {}
   end
end

---------------------------------------------------------------------
function SpecCalcSplit( dLenRaw, dMaxLenLeft)
   local vCmd = {}
   EgtOutLog( '  *[S]', 1)
   local bSplit = ( EMC.Y2DELTA ~= nil)
   local ParkT = ParkV1
   if bSplit then
      if dMaxLenLeft + 100 > LoadT then
         ParkT = dMaxLenLeft + 300
      elseif dMaxLenLeft + 100 > ParkV1 then
         ParkT = LoadT
      end
   end
   table.insert( vCmd, { 0, EgtIf( bSplit, 'Split', 'Fall')})
   if not bSplit then EnsureZmax( false, vCmd) end
   -- determino i grezzi da agganciare al carrello Y (sono quelli presenti nella fase successiva dispari)
   local nNextOddPhase = GetNextStartOrRestPhase( EMC.PHASE)
   local nRawId = EgtGetFirstRawPart()
   while nRawId do
      if EgtVerifyRawPartPhase( nRawId, nNextOddPhase) then
         table.insert( vCmd, { 31, nRawId, 'Y1'})
      end
      nRawId = EgtGetNextRawPart( nRawId)
   end
   -- riporto il carrello Y al carico con il resto della trave
   local dLDelta = EMC.Y1DELTA - dLenRaw
   table.insert( vCmd, { 1, 'Y1', ParkT + dLDelta})
   table.insert( vCmd, { 21, 0, EMC.Y2DELTA or 0})
   -- imposto subito Y non più attaccato alla trave in lavoro
   EMC.Y1DELTA = nil
   -- salvo posizione carrello Y in disposizione del pezzo dopo split
   local PostDispId = EgtGetPhaseDisposition( EMC.PHASE + 1)
   if PostDispId then
      if not bSplit then
         EgtSetInfo( PostDispId, 'TPOS', ParkT)
      else
         EgtRemoveInfo( PostDispId, 'TPOS')
         EgtSetInfo( PostDispId, 'TPARK', ParkT)
      end
      EgtSetInfo( PostDispId, 'Y1POS', ParkT + dLDelta)
   end
   -- salvo posizione grezzo rimasto e posizione carrello Y nella disposizione iniziale del pezzo succ (prossima fase dispari)
   local NextDispId = EgtGetPhaseDisposition( nNextOddPhase)
   if NextDispId then
      EgtSetInfo( NextDispId, 'TPOS', ParkT)
      EgtSetInfo( NextDispId, 'Y1POS', ParkT + dLDelta)
   end

   return vCmd
end

---------------------------------------------------------------------
function SpecCalcUnload()
   local vCmdPre = {}
   EgtOutLog( '  *[U]', 1)
   if not EMC.Y1DELTA and not EMC.Y2DELTA then
      EMC.ERR = 21
      EMC.MSG = '  Error : Y1 and Y2 not clamped for unloading'
      return {}
   end
   local dY2DeltaMinUL = EMC.LB + MinY2 - EgtIf( EMC.LB < MaxLenSmT, UnloadSmT, UnloadT)
   -- Se pinza Y1 chiusa o Y2 troppo a destra, devo effettuare uno scambio
   if EMC.Y1DELTA or EMC.Y2DELTA < dY2DeltaMinUL then
      -- determino posizione testa trave
      local nLastEntId = EgtGetLastInGroup( EMC.PATHID)
      if not nLastEntId then
         local nPrevOpeId = EgtGetPrevActiveOperation( EMC.DISPID)
         local nPathId = EgtGetFirstInGroup( EgtGetFirstNameInGroup( nPrevOpeId, 'CL'))
         nLastEntId = EgtGetLastInGroup( nPathId)
      end
      local vAxes = EmtGetAxesPos( nLastEntId)
      if #vAxes > 0 then EMC.TPOS = vAxes[1] end
      -- imposto quote aggancio per avere solo pinza V
      local dDistFront = EMC.LB - MinJoin - EMC.HOVM + 10 * GEO.EPS_SMALL
      local dDistBack = 0
      -- imposto rulli aperti (in ogni caso pezzo sicuramente grande)
      EMC.V1POS = ParkV1
      EMC.V2POS = ParkV2
      -- effettuo scambio
      vCmdPre = SpecCalcCarriages( dDistFront, dDistBack, 0, 0, nil, dY2DeltaMinUL)
      -- recupero nuova posizione carrelli
      SpecSetCarrPosFromCmds( vCmdPre)
      EgtOutLog( '  *[U1]', 1)
   end
   local vCmd = {}
   -- Tipo di scarico
   local bStdUl = ( not MaxUnloadLen or MaxUnloadLen < 1 or EMC.LB - EMC.HOVM < MaxUnloadLen + 1)

   -- Commento
   table.insert( vCmd, { 0, 'Unloading', EgtIf( bStdUl, 'Unloading', 'Manual Unloading')})
   -- posizionamento sicuro teste e rulli (non serve ?)
   --table.insert( vCmd, { 4, 0})
   -- Se pinza Y chiusa, la apro
   if EMC.Y1DELTA then
      table.insert( vCmd, { 11, 0})
      EMC.CNT = nil
   end
   -- Se non supero la lunghezza massima di scarico, sposto il pezzo in posizione di scarico
   if bStdUl then
      local dFinT = EgtIf( EMC.LB < MaxLenSmT, UnloadSmT, UnloadT) - EMC.LB
      local dFinY2 = dFinT + EMC.Y2DELTA
      table.insert( vCmd, { 2, 'T', dFinT, 'Y2', dFinY2})
      EgtOutLog( '  Y2PosF=' .. EgtNumToString( dFinY2), 1)
   else
      table.insert( vCmd, { 1, 'Y2', MaxY2})
   end
   -- apro la morsa
   table.insert( vCmd, { 12, 0})
   -- riporto il carrello in home
   table.insert( vCmd, { 1, 'Y2', ParkY2})

   -- eventuale unione tabelle
   if #vCmdPre > 0 then
      vCmd = EgtJoinTables( vCmdPre, vCmd)
   end

   return vCmd
end

---------------------------------------------------------------------
local function ChangedTool( nMchId)
   local nChanged = 0
   -- Salvo lavorazione e utensile correnti, per ripristinarli alla fine
   local nOrigMchId = EgtGetCurrMachining()
   local sOrigTool = EgtTdbGetCurrToolParam( MCH_TP.NAME)
   local sOrigBlockedAx = EgtGetMachiningParam( MCH_MP.BLOCKEDAXIS)
   -- Recupero l'utensile della lavorazione precedente
   local nPrevMchId = EgtGetPrevActiveOperation( nMchId or GDB_ID.NULL)
   if nPrevMchId and EgtGetOperationType( nPrevMchId) ~= MCH_OY.DISP then
      EgtSetCurrMachining( nPrevMchId)
      local sTool = EgtGetMachiningParam( MCH_MP.TOOL)
      nChanged = EgtIf( sTool ~= sOrigTool, 1 , 0)
      local sBlockedAx = EgtGetMachiningParam( MCH_MP.BLOCKEDAXIS)
      -- se stesso utensile, ma modo caricamento diverso, è come se fosse un cambio utensile. Vale solo se aggregato su lama da sotto.
      if sTool == sOrigTool then 
         local sHead = EgtTdbGetCurrToolParam( MCH_TP.HEAD)
         if sHead == 'H22' and sOrigBlockedAx ~= sBlockedAx then
            nChanged = 2
         end
      end
   end
   -- Ripristino lavorazione e utensile correnti
   if nOrigMchId then EgtSetCurrMachining( nOrigMchId) end
   if sOrigTool then EgtTdbSetCurrTool( sOrigTool) end
   return nChanged
end

--------------------------------------------------------------------------------------------------------
-- **CHIAMATA DELLE FUNZIONI Spec** 
--------------------------------------------------------------------------------------------------------
function SpecAdjustCarriages( WorkTab)

   --EgtOutLog( '   --->>> CNT=' .. EgtIf( EMC.CNT, '1', '_'))
   local bChangedTool = ChangedTool( EMC.MCHID)
   -- Aggiorno la posizione dei rulli per eventuale cambio utensile
   if bChangedTool ~= 0 then
      WorkTab.dV1PosI = ParkV1
      WorkTab.dV2PosI = ParkV2
   end
   -- Verifico se compattare i rulli
   local bCloseV = false
   if WorkTab.dY1DeltaI then
      local dY1DeltaLim = MinJoin + WorkTab.dV1PosI - WorkTab.dV2PosI + 2 * AGG_V + EMC.HCING + EMC.HOVM + EgtIf( EMC.CNT == 1, AggLoad, 0)
      if WorkTab.dY1DeltaI <= dY1DeltaLim or ( WorkTab.dY1DeltaMaxF and WorkTab.dY1DeltaMaxF <= dY1DeltaLim)  then
         bCloseV = true
      end
   end
   if WorkTab.dY2DeltaI then
      local dY2DeltaLim = MinJoin + WorkTab.dV1PosI - WorkTab.dV2PosI + 2 * AGG_V
      if EMC.LB - WorkTab.dY2DeltaI <= dY2DeltaLim or ( WorkTab.dY2DeltaMinF and EMC.LB - WorkTab.dY2DeltaMinF <= dY2DeltaLim)  then  
         bCloseV = true
      end
   end
   -- se ho cambiato modo di prendere utensile, forzo chiusura
   if bChangedTool == 2 then
      bCloseV = true
   end
   -- se i pezzi del cliente sono molto storti, si forza la chiusura per evitare che vada a sbattere. Se parametro non presente, si chiude solo se necessario
   if ForceToCloseRollersGate then
      bCloseV = true
   end

   WorkTab.bCloseV = bCloseV

   -- |POSIZIONO Y1| **[A]**
   if WorkTab.dY1DeltaMinF and ( not WorkTab.dY2DeltaMinF) then
      -- [A1]/[A2] continuazione da morsa Y1 ( Y1 -> Y1 ) scambio Y2 (Y2 -> Y1)
      if ( WorkTab.dY1DeltaI and ( not WorkTab.dY2DeltaI)) or ( WorkTab.dY2DeltaI and (not WorkTab.dY1DeltaI)) then
         WorkTab.bY1Parked = not WorkTab.dY1DeltaI
         WorkTab.dY1DeltaI = WorkTab.dY1DeltaI or ( ParkY1 - WorkTab.dTPosI)
         WorkTab.bY2Parked = not WorkTab.dY2DeltaI
         WorkTab.dY2DeltaI = WorkTab.dY2DeltaI or ( ParkY2 - WorkTab.dTPosI)
         return SpecAdjustCarrA( WorkTab)
      else
         EgtOutLog( '  Error CLAMP NULL-> Y1 impossibile')
         error( 'Error CLAMP NULL-> Y1 impossibile')
      end 

   -- |POSIZIONO Y1Y2| **[B]**
   elseif WorkTab.dY1DeltaMinF and WorkTab.dY2DeltaMinF then
      -- [B1] passo da carrello Y1 a entrambi
      if WorkTab.dY1DeltaI and ( not WorkTab.dY2DeltaI) then
         WorkTab.dY2DeltaI = WorkTab.dY2DeltaI or ( ParkY2 - WorkTab.dTPosI)
         return SpecAdjustCarrB1( WorkTab)
      -- [B2] passo da carrello Y2 a entrambi
      elseif WorkTab.dY2DeltaI and (not WorkTab.dY1DeltaI) then
         WorkTab.dY1DeltaI = WorkTab.dY1DeltaI or ( ParkY1 - WorkTab.dTPosI)
         return SpecAdjustCarrB2(WorkTab)
      -- [B3] rimango con due carrelli ( per ora accetto solo il caso in cui le posizioni sono già valide)
      elseif WorkTab.dY1DeltaI > WorkTab.dY1DeltaMinF - 10 * GEO.EPS_SMALL and WorkTab.dY1DeltaI < WorkTab.dY1DeltaMaxF + 10 * GEO.EPS_SMALL and
             WorkTab.dY2DeltaI > WorkTab.dY2DeltaMinF - 10 * GEO.EPS_SMALL and WorkTab.dY2DeltaI < WorkTab.dY2DeltaMaxF + 10 * GEO.EPS_SMALL then
         EgtOutLog( '*[B3]', 1)
         return {}
      else
         EgtOutLog( '  Error CLAMP NULL-> Y1+Y2 impossibile')
         error( 'Error CLAMP NULL-> Y1+Y2 impossibile')
      end 

   -- |POSIZIONO Y2| **[C]**
   elseif not WorkTab.dY1DeltaMinF and WorkTab.dY2DeltaMinF then
      -- [C1]/[C2] continuazione da morsa Y2 ( Y2 -> Y2 ) sacmbio Y1 (Y1 -> Y2)
      if ( WorkTab.dY1DeltaI and ( not WorkTab.dY2DeltaI)) or ( WorkTab.dY2DeltaI and (not WorkTab.dY1DeltaI)) then
         WorkTab.bY1Parked = not WorkTab.dY1DeltaI
         WorkTab.dY1DeltaI = WorkTab.dY1DeltaI or ( ParkY1 - WorkTab.dTPosI)
         WorkTab.bY2Parked = not WorkTab.dY2DeltaI
         WorkTab.dY2DeltaI = WorkTab.dY2DeltaI or ( ParkY2 - WorkTab.dTPosI)
         return SpecAdjustCarrC( WorkTab)  
      else
         EgtOutLog( '  Error CLAMP NULL-> Y2 impossibile')
         error( 'Error CLAMP NULL-> Y2 impossibile')
      end
   end 

end

-----------------------------------------------------------------------
-- calcolo lo spostamento di Y1 (aperta) e Y2 (chiusa) in **accentramento**
local function PosForCent1FY1( dY1a, dY2a, dTa, dExtraY1, dCorsaY1c, dCorsaY2Tc) 
   local dY2Delta = dY2a - dTa
   local dDispl = dCorsaY1c - dExtraY1
   if dDispl / 2 <=  dCorsaY2Tc then
      if dDispl / 2 <=  dCorsaY1c then
         dY1a = dY1a - dDispl / 2
         dY2a = dY2a + dDispl / 2
      else
         dY1a = dY1a - dCorsaY1c
         dY2a = dY2a + dDispl - dCorsaY1c
      end
      else
      dY2a = dY2a + dCorsaY2Tc
      dY1a = dY1a - (dDispl - dCorsaY2Tc)
   end
   dTa = dY2a - dY2Delta
   return dY1a, dY2a, dTa
end

-----------------------------------------------------------------------
-- calcolo lo spostamento di Y2 (aperta) e Y1 (chiusa) in **accentramento**
local function PosForCent1FY2( dY1a, dY2a, dTa, dExtraY2, dCorsaY1Tc, dCorsaY2c) 
      local dY1Delta = dY1a - dTa
      local dDispl = dCorsaY2c + dExtraY2
      if dDispl / 2 <=  dCorsaY1Tc then
         if dDispl / 2 <= dCorsaY2c then
            dY1a = dY1a - dDispl / 2
            dY2a = dY2a + dDispl / 2
         else
            dY2a = dY2a + dCorsaY2c
            dY1a = dY1a - min( dDispl - dCorsaY2c, dCorsaY1Tc)
         end
      else
         dY1a = dY1a - dCorsaY1Tc
         dY2a = dY2a + min( dDispl - dCorsaY1Tc, dCorsaY2c)
      end
   dTa = dY1a - dY1Delta
   return dY1a, dY2a, dTa
end

-----------------------------------------------------------------------
-- calcolo lo spostamento di Y1 (aperta) e Y2 (chiusa) in **allontanamento**
local function PosForEnl1FY1( dY1a, dY2a, dTa, dExtra, dCorsaY1e, dCorsaY2Te) 
   local dDispl = MaxY1 - dY1a + dExtra
   -- eseguo il posizionamento di Y1 allontanando Y1 e (Y2+T)
   if dDispl/2 <= dCorsaY2Te then
      if dDispl/2 <= dCorsaY1e   then
         dY1a = dY1a + dDispl / 2
         dY2a = dY2a - dDispl / 2
         dTa  = dTa  - dDispl / 2 
      else
         dY1a = dY1a + dCorsaY1e
         dY2a = dY2a - (dDispl - dCorsaY1e)
         dTa  = dTa  - (dDispl - dCorsaY1e)
      end
   else
      dY2a = dY2a - dCorsaY2Te
      dTa  = dTa  - dCorsaY2Te
      dY1a = dY1a + (dDispl - dCorsaY2Te)
   end
   return dY1a, dY2a, dTa
end

-----------------------------------------------------------------------
-- calcolo lo spostamento di Y2 (aperta) e Y1 (chiusa) in **allontanamento**
local function PosForEnl1FY2( dY1a, dY2a, dTa, dExtra, dCorsaY1Te, dCorsaY2e) 
   local dDispl = dY2a - MinY2 - dExtra
   -- eseguo il posizionamento di Y2 allontanando (Y1+T) e Y2
   if dDispl/2 <= dCorsaY1Te then
      if dDispl/2 <= dCorsaY2e   then
         dY1a = dY1a + dDispl / 2
         dTa  = dTa  + dDispl / 2 
         dY2a = dY2a - dDispl / 2
      else
         dY2a = dY2a - dCorsaY2e
         dY1a = dY1a + (dDispl - dCorsaY2e)
         dTa  = dTa  + (dDispl - dCorsaY2e)
      end
   else
      dY1a = dY1a + dCorsaY1Te
      dTa  = dTa  + dCorsaY1Te
      dY2a = dY2a - (dDispl - dCorsaY1Te)
   end
   return dY1a, dY2a, dTa
end

-----------------------------------------------------------------------
local function MaxDispl( TabI, vCmd, sType)
   local dY1a, dY2a
   local dTa = TabI.dTPosI
   -- **[M1]** = allontanamento dei trascinatori con Y1 in presa
   if sType == 'M1' then
      EmitComment( vCmd, '[M1]')
      -- ev' chiudo Y1, apro Y2
      table.insert( vCmd, { 11, 1})
      table.insert( vCmd, { 12, 0})
     -- allontano (Y1+T) e (Y2)
      dY1a = MaxY1
      dY2a = MinY2
      dTa = ( dY1a - TabI.dY1PosI) + dTa 
   -- **[M2]** = allontanamento dei trascinatori con Y2 in presa
   elseif sType == 'M2'   then
      EmitComment( vCmd, '[M2]')
      -- ev' chiudo Y2, apro Y1
      table.insert( vCmd, { 12, 1})
      table.insert( vCmd, { 11, 0})
      EMC.CNT = nil
      -- allontano (Y2+T) e (Y1)
      dY1a = MaxY1
      dY2a = MinY2
      dTa = ( dY2a - TabI.dY2PosI) + dTa 
   -- **[M3]** = accentramento dei trascinatori con Y1 in presa
   elseif sType == 'M3' then
      EmitComment( vCmd, '[M3]')
      -- ev' chiudo Y1, apro Y2
      table.insert( vCmd, { 11, 1})
      table.insert( vCmd, { 12, 0})
      -- accentro (Y1+T) e (Y2)
      dY1a = TabI.MyMinY1V1
      dY2a = TabI.MaxY2V2
      dTa = ( dY1a - TabI.dY1PosI) + dTa 
   -- **[M4]** = accentramento dei trascinatori con Y2 in presa
   elseif sType == 'M4' then
      EmitComment( vCmd, '[M4]')
      -- ev' chiudo Y2, apro Y1
      table.insert( vCmd, { 12, 1})
      table.insert( vCmd, { 11, 0})
      EMC.CNT = nil
      -- accentro (Y2+T) e (Y1)
      dY1a = TabI.MyMinY1V1
      dY2a = TabI.MaxY2V2
      dTa = ( dY2a - TabI.dY2PosI) + dTa 
   end
   --
   table.insert( vCmd, { 3, 'Y1', dY1a,  'Y2', dY2a, 'T', dTa, EMC.CNT})
   TabI.dY1PosI = dY1a
   TabI.dY2PosI = dY2a
   TabI.dTPosI  = dTa
end   -- MaxDisplForRiXw

-----------------------------------------------------------------------------------------------------------
-- **ALLONTANAMENTO di Y1**
------------------------------------------------------------------------------------------------------------------
local function PosY1FromY2A( TabI, vCmd)
   EmitComment( vCmd, '[PosY1FromY2A]')
   local t = {}
   -- **calcolo i delta dai dati iniziali**
   t.dY1NewDelta = ( TabI.dY1DeltaMaxF + TabI.dY1DeltaMinF)/2
   -- valuto gli estremi degli intervalli, con riferimento alle posizioni attuali 
   t.dExtraY1New = (TabI.dTPosI + t.dY1NewDelta) - MaxY1    --se > 0, la posizione non è raggiunginile
   -- corse disponibili in allontanamento (enlargement) dalle posizioni attuali per Y1 e (Y2+T)
   t.dCorsaY1eI = MaxY1 - TabI.dY1PosI
   t.dCorsaY2TeI= TabI.dY2PosI - MinY2
   -- |RICONOSCO I CASI|      
   -- **[(0)]**  morsa Y1 già entro l'intervallo : non si deve muovere
   t.dY1DeltaI = TabI.dY1PosI-TabI.dTPosI
   if t.dY1DeltaI < TabI.dY1DeltaMaxF + 10 * GEO.EPS_SMALL and t.dY1DeltaI > TabI.dY1DeltaMinF - 10 * GEO.EPS_SMALL then
      EmitComment( vCmd, '[Y1(0)] ')
      --return t.dY1DeltaI
   -- **[ri]** = posY1New non raggiungibile direttamente -> muovo fino a fine corsa
   elseif t.dExtraY1New > t.dCorsaY2TeI then
      EmitComment( vCmd, '[Y1A-ri]')
      if not SpecTestSomeMoveInCmds( vCmd) and not EMC.Y2DELTA then
         table.insert( vCmd, { 3, 'Y1', TabI.dY1PosI, 'Y2', TabI.dY2PosI, 'T', TabI.dTPosI})
      end
      -- questo movimento serve solo a gestire (in modo complesso) il caso [ri]
      MaxDispl( TabI, vCmd, 'M2')
      --t.bXW = true
   else
   -- **[rp]** = raggiungo il punto medio Y1New dell'intervallo
      EmitComment( vCmd, '[Y1A-rp] ')
      if not SpecTestSomeMoveInCmds( vCmd) and not EMC.Y2DELTA then
         table.insert( vCmd, { 3, 'Y1', TabI.dY1PosI, 'Y2', TabI.dY2PosI, 'T', TabI.dTPosI, EMC.CNT})
      end
      -- **1:** calcolo pos. di allontanamento per (Y1) e (Y2+T) (da posizioni iniziali)
      TabI.dY1PosI, TabI.dY2PosI, TabI.dTPosI = PosForEnl1FY1( TabI.dY1PosI, TabI.dY2PosI, TabI.dTPosI, t.dExtraY1New, t.dCorsaY1eI, t.dCorsaY2TeI)
               -- aggiornamento posizioni e dY1Delta dopo <end>
               -- chiusura di Y1, apertura e parcheggio di Y2 a fine funzione
      -- ev' chiudo Y2 e apro Y1 ed eseguo allontanamento
      table.insert( vCmd, { 12, 1})
      table.insert( vCmd, { 11, 0})
      EMC.CNT = nil
      table.insert( vCmd, { 3, 'Y1', TabI.dY1PosI, 'Y2', TabI.dY2PosI, 'T', TabI.dTPosI, EMC.CNT})
   end
   t.dY1DeltaA = TabI.dY1PosI - TabI.dTPosI
   -- chiusura di Y1, ev' apertura e parcheggio di Y2 : in funzioni chiamanti
   return t.dY1DeltaA
end   -- PosY1FromY2A

--------------------------------------------------------------------------------------------------------
-- **ALLONTANAMENTO di Y2**
--------------------------------------------------------------------------------------------------------
local function PosY2FromY1A( TabI, vCmd)
   EmitComment( vCmd, '[PosY2FromY1A]')
   local t = {}
   -- **calcolo i delta dai dati iniziali**
   t.dY2NewDelta = (  TabI.dY2DeltaMaxF +  TabI.dY2DeltaMinF)/2
   -- valuto gli estremi degli intervalli, con riferimento alle posizioni attuali 
   t.dExtraY2New = ( TabI.dTPosI +  t.dY2NewDelta) - MinY2    --se < 0, la posizione non è raggiunginile
   -- corse disponibili in allontanamento (enlargement) dalle posizioni attuali per (Y1+T) e Y2
   t.dCorsaY1Te =  MaxY1 - TabI.dY1PosI
   t.dCorsaY2e =  TabI.dY2PosI - MinY2
   --
   -- |RICONOSCO I CASI|      
   -- **[(0)]**  morsa Y2 già entro l'intervallo : non si deve muovere
   t.dY2DeltaI =  TabI.dY2PosI- TabI.dTPosI
   if t.dY2DeltaI < TabI.dY2DeltaMaxF + 10 * GEO.EPS_SMALL and t.dY2DeltaI > TabI.dY2DeltaMinF - 10 * GEO.EPS_SMALL then
      EmitComment( vCmd, '[Y2(0)] ')
   -- **[ri]** = posY2New non raggiungibile direttamente -> muovo fino a fine corsa
   elseif -t.dExtraY2New >  t.dCorsaY1Te then 
      EmitComment( vCmd, '[Y2A-ri]' )
      if not SpecTestSomeMoveInCmds( vCmd) and not EMC.Y1DELTA then
         table.insert( vCmd, { 3, 'Y1', TabI.dY1PosI, 'Y2', TabI.dY2PosI, 'T', TabI.dTPosI, EMC.CNT})
      end
      -- questo movimento serve solo a gestire (in modo complesso) il caso [ri]
      MaxDispl( TabI, vCmd, 'M1')
   else
   -- **[rp]** = raggiungo il punto medio Y2New dell'intervallo
      EmitComment( vCmd, '[Y2A-rp] ')
      if not SpecTestSomeMoveInCmds( vCmd) and not EMC.Y1DELTA then
         table.insert( vCmd, { 3, 'Y1', TabI.dY1PosI, 'Y2', TabI.dY2PosI, 'T', TabI.dTPosI, EMC.CNT})
      end
      -- **1:** calcolo pos. di allontanamento per (Y1+T) e (Y2) (da posizioni iniziali)
      TabI.dY1PosI,  TabI.dY2PosI,  TabI.dTPosI = PosForEnl1FY2(  TabI.dY1PosI,  TabI.dY2PosI,  TabI.dTPosI,  t.dExtraY2New,  t.dCorsaY1Te,  t.dCorsaY2e)
      -- chiudo Y1 e apro Y2 ed eseguo allontanamento (finale)
      table.insert( vCmd, { 11, 1})
      table.insert( vCmd, { 12, 0})   
      table.insert( vCmd, { 3, 'Y1', TabI.dY1PosI, 'Y2', TabI.dY2PosI, 'T', TabI.dTPosI, EMC.CNT})
   end
   t.dY2DeltaA = TabI.dY2PosI - TabI.dTPosI
   -- chiusura di Y2, ev' apertura e parcheggio di Y1 : in funzioni chiamanti
   return  t.dY2DeltaA
end   -- PosY2FromY1A

------------------------------------------------------------------------------------------------------------
-- **ACCENTRAMENTO di Y1**
------------------------------------------------------------------------------------------------------------
local function PosY1FromY2B( TabI, vCmd)
   EmitComment( vCmd, '[PosY1FromY2B]')
   local t = {}
   -- **calcolo i delta dai dati iniziali**
   t.dY1NewDelta = ( TabI.dY1DeltaMaxF + TabI.dY1DeltaMinF)/2
   -- valuto gli estremi degli intervalli, con riferimento alle posizioni attuali 
   t.dExtraY1New = (TabI.dTPosI + t.dY1NewDelta) - TabI.MyMinY1V1 -- se < 0, la posizione non è raggiunginile
   -- corse in accentramento disponibili per Y1 e (Y2+T) dalle posizioni iniziali
   t.dCorsaY1cI   = TabI.dY1PosI - TabI.MyMinY1V1
   t.dCorsaY2TcI  = TabI.MaxY2V2 - TabI.dY2PosI           
   --
   -- |RICONOSCO I CASI|
   -- **[(0)]**  morsa Y1 già entro l'intervallo : non si deve muovere
   t.dY1DeltaI = TabI.dY1PosI-TabI.dTPosI
   if t.dY1DeltaI < TabI.dY1DeltaMaxF + 10 * GEO.EPS_SMALL and t.dY1DeltaI > TabI.dY1DeltaMinF - 10 * GEO.EPS_SMALL then
      EmitComment( vCmd, '[Y1(0)] ')
   -- **[ri]** = posY1New non raggiungibile direttamente -> muovo fino a fine corsa
   elseif -t.dExtraY1New > t.dCorsaY2TcI then 
      EmitComment( vCmd, '[Y1B-ri]')
      if not SpecTestSomeMoveInCmds( vCmd) and not EMC.Y2DELTA then
         table.insert( vCmd, { 3, 'Y1', TabI.dY1PosI, 'Y2', TabI.dY2PosI, 'T', TabI.dTPosI, EMC.CNT})
      end
      -- questo movimento serve solo a gestire (in modo complesso) il caso [ri]
      MaxDispl( TabI, vCmd, 'M4')
   else   
   -- **[rp]** = raggiungo il punto medio Y1New dell'intervallo
      EmitComment( vCmd, '[Y1B-rp] ')
      if not SpecTestSomeMoveInCmds( vCmd) and not EMC.Y2DELTA then
         table.insert( vCmd, { 3, 'Y1', TabI.dY1PosI, 'Y2', TabI.dY2PosI, 'T', TabI.dTPosI, EMC.CNT})
      end
      -- **1: calcolo accentramento di (Y1) e (Y2+T)**
      TabI.dY1PosI, TabI.dY2PosI, TabI.dTPosI = PosForCent1FY1( TabI.dY1PosI, TabI.dY2PosI, TabI.dTPosI, t.dExtraY1New, t.dCorsaY1cI, t.dCorsaY2TcI)
      -- posizionamento efettivo e aggiornamento di vCmd e dY1Delta dopo <end>
      -- ch. di Y1 e ap. e parcheggio di Y2 a fine funzione chiamante
      -- chiudo Y2 e apro Y1 ed eseguo accentramento (finale)
      table.insert( vCmd, { 12, 1})
      table.insert( vCmd, { 11, 0})
      EMC.CNT = nil
      table.insert( vCmd, { 3, 'Y1', TabI.dY1PosI,  'Y2', TabI.dY2PosI, 'T', TabI.dTPosI, EMC.CNT})
   end
   t.dY1DeltaA = TabI.dY1PosI - TabI.dTPosI
   -- chiusura di Y1, ev' apertura e parcheggio di Y2 : in funzioni chiamanti
   return t.dY1DeltaA
end   -- PosY1FromY2B

--------------------------------------------------------------------------------------------------------
-- **ACCENTRAMENTO di Y2**
--------------------------------------------------------------------------------------------------------
local function PosY2FromY1B( TabI, vCmd)
   EmitComment( vCmd, '[PosY2FromY1B]')
   local t = {}
   -- **calcolo i delta dai dati iniziali**
   t.dY2NewDelta = ( TabI.dY2DeltaMaxF + TabI.dY2DeltaMinF)/2
   -- valuto gli estremi degli intervalli, con riferimento alle posizioni attuali 
   t.dExtraY2New = (TabI.dTPosI + t.dY2NewDelta) - TabI.MaxY2V2   -- se > 0 la posizione non è raggiunginile
   -- corse in accentramento disponibili per (Y1+T) e (Y2) dalle posizioni iniziali
   t.dCorsaY1TcI = TabI.dY1PosI - TabI.MyMinY1V1
   t.dCorsaY2cI  = TabI.MaxY2V2 - TabI.dY2PosI      
   -- |RICONOSCO I CASI|      
   -- **[(0)]**  morsa Y2 già entro l'intervallo : non si deve muovere
   t.dY2DeltaI = TabI.dY2PosI-TabI.dTPosI
   if t.dY2DeltaI < TabI.dY2DeltaMaxF + 10 * GEO.EPS_SMALL and t.dY2DeltaI > TabI.dY2DeltaMinF - 10 * GEO.EPS_SMALL then
      EmitComment( vCmd, '[Y2(0)] ')
   -- **[ri]** = posY2New non raggiungibile direttamente -> muovo fino a fine corsa
   elseif t.dExtraY2New > t.dCorsaY1TcI then
      EmitComment( vCmd, '[Y2B-ri]' )
      if not SpecTestSomeMoveInCmds( vCmd) and not EMC.Y1DELTA then
         table.insert( vCmd, { 3, 'Y1', TabI.dY1PosI, 'Y2', TabI.dY2PosI, 'T', TabI.dTPosI, EMC.CNT})
      end
      -- questo movimento serve solo a gestire (in modo complesso) il caso [ri]
      MaxDispl( TabI, vCmd, 'M3')
   else
   -- **[rp]** = raggiungo il punto medio Y2New dell'intervallo
      EmitComment( vCmd, '[Y2B-rp] ')
      if not SpecTestSomeMoveInCmds( vCmd) and not EMC.Y1DELTA then
         table.insert( vCmd, { 3, 'Y1', TabI.dY1PosI, 'Y2', TabI.dY2PosI, 'T', TabI.dTPosI, EMC.CNT})
      end
      -- Y1 chiuso, Y2 aperto
      -- **1: calcolo accentramento di (Y1+T) e (Y2)**
      TabI.dY1PosI, TabI.dY2PosI, TabI.dTPosI = PosForCent1FY2( TabI.dY1PosI, TabI.dY2PosI, TabI.dTPosI, t.dExtraY2New, t.dCorsaY1TcI, t.dCorsaY2cI)
      -- ev' chiudo Y1 e apro Y2 ed eseguo movimento accentramento
      table.insert( vCmd, { 11, 1})
      table.insert( vCmd, { 12, 0})   
      table.insert( vCmd, { 3, 'Y1', TabI.dY1PosI,  'Y2', TabI.dY2PosI, 'T', TabI.dTPosI, EMC.CNT})
   end
   t.dY2DeltaA = TabI.dY2PosI - TabI.dTPosI
   -- chiusura di Y2, apertura e parcheggio di Y1 : in funzioni chiamanti
   return t.dY2DeltaA
end   -- PosY2FromY1B

--------------------------------------------------------------------------------------------------------
-- **ALLONTANAMENTO di Y1 per pos. di Y2 o Y2+Y1** - chiamata da [B2] e [C]
--------------------------------------------------------------------------------------------------------
local function  PosY1Y2A( TabI, vCmd)
   EmitComment( vCmd, '[PosY1Y2A]')
   --   |RICONOSCO I CASI|  
   repeat 
      local dY2NewDelta = ( TabI.dY2DeltaMaxF + TabI.dY2DeltaMinF)/2 
      local bXW = true
      -- se intervallo per Y1 raggiungibile in una sola fase
      -- ossia se posso posizionare 'direttamente' Y1 all'interno dell'intervallo I1 <=> ( -ExtraI1MaxF < Corsa Y2Tc) 
      -- e Y2 è in posizione per aggancio su T
      if ( TabI.dTPosI +  TabI.dY1DeltaMinF) - MaxY1 < TabI.dY2PosI - MinY2 and
         TabI.dY2PosI > TabI.dTPosI + MinJoin + EMC.HCING + EMC.HOVM - 10 * GEO.EPS_SMALL then
         -- |POSIZIONO MORSA 1| in **allontanamento**
         PosY1FromY2A( TabI, vCmd)
         -- |POSIZIONO MORSA 2|
         if  (TabI.dTPosI + dY2NewDelta) < TabI.dY2PosI then
            -- in **allontanamento**
            PosY2FromY1A( TabI, vCmd)
         else
            -- in **accentramento**
            PosY2FromY1B( TabI, vCmd)
         end
         bXW = false
      else         
         -- |POSIZIONO MORSA 2| e poi MORSA 1
         -- in **allontanamento**
         if  (TabI.dTPosI + dY2NewDelta) < TabI.dY2PosI then
            -- se intervallo per Y2 raggiungibile in una sola fase
            if - (( TabI.dTPosI + TabI.dY2DeltaMaxF) - MinY2) < MaxY1 - TabI.dY1PosI then
               PosY2FromY1A( TabI, vCmd)
               PosY1FromY2A( TabI, vCmd)
               bXW = false   
            end
         else
         -- in **accentramento**
            -- sempre se intervallo per Y2 raggiungibile in una sola fase
            if ( TabI.dTPosI + TabI.dY2DeltaMinF) - TabI.MaxY2V2 < TabI.dY1PosI - TabI.MyMinY1V1 then
               PosY2FromY1B( TabI, vCmd)
               PosY1FromY2A( TabI, vCmd)
               bXW = false   
            end  
         end          
      end
      if bXW then
         -- Massimo trascinamento della trave in direzione negativa ( casi **[xw]**)
         EmitComment( vCmd, '[Y1A-xw]')
         if TabI.dY1PosI ~= TabI.MyMinY1V1 then
         -- accentramento morse, trascinamento trave con Y1
            MaxDispl( TabI, vCmd, 'M3')
         else   
         -- allontanamento morse, trascinamento trave con Y2
            MaxDispl( TabI, vCmd, 'M2')
         end 
      end  
   until ( not bXW)
      -- chiusura/parcheggio dei trascinatori in funzioni chiamanti
   local dY1Delta = TabI.dY1PosI - TabI.dTPosI
   local dY2Delta = TabI.dY2PosI - TabI.dTPosI
   return dY1Delta, dY2Delta
end   -- PosY1Y2A

--------------------------------------------------------------------------------------------------------
-- **ACCENTRAMENTO di Y1 per pos. di Y2 o Y2+Y1** - chiamata da [B2] e [C]
--------------------------------------------------------------------------------------------------------
local function PosY1Y2B( TabI, vCmd)
   EmitComment( vCmd, '[PosY1Y2B]')
   --   |RICONOSCO I CASI|
   local nCnt = 0
   repeat
      local dY2NewDelta = ( TabI.dY2DeltaMaxF + TabI.dY2DeltaMinF) / 2
      local bXW = true
      -- se intervallo per Y1 raggiungibile in una sola fase
      -- ossia se posso posizionare 'direttamente' Y1 all'interno dell'intervallo I1 <=> ( -ExtraI1MaxF < Corsa Y2Tc) 
      -- e Y2 è in posizione per aggancio su T
      if -(( TabI.dTPosI + TabI.dY1DeltaMaxF) - TabI.MyMinY1V1) < TabI.MaxY2V2 - TabI.dY2PosI and
         TabI.dY2PosI > TabI.dTPosI + MinJoin + EMC.HCING + EMC.HOVM - 10 * GEO.EPS_SMALL then
         -- |POSIZIONO MORSA 1| in **accentramento** 
         PosY1FromY2B( TabI, vCmd)
         -- |POSIZIONO MORSA 2|
         local dY2DeltaI = TabI.dY2PosI - TabI.dTPosI
         -- Se il trascinatore 2 non è già nell'intervallo richiesto
         if dY2DeltaI > TabI.dY2DeltaMaxF + 10 * GEO.EPS_SMALL or dY2DeltaI < TabI.dY2DeltaMinF - 10 * GEO.EPS_SMALL then
            if  ( TabI.dTPosI + dY2NewDelta) < TabI.dY2PosI then
               -- in **allontanamento**
               PosY2FromY1A( TabI, vCmd)
            else
               -- in **accentramento**
               PosY2FromY1B( TabI, vCmd)
            end
         end
         bXW = false      
      -- se posso posizionare 'direttamente' Y2 all'interno dell'intervallo I2 (casi [rp] o [ri])
      else
         -- |POSIZIONO MORSA 2| e poi MORSA 1
         -- in **allontanamento** 
         if ( TabI.dTPosI + dY2NewDelta) < TabI.dY2PosI then
            -- se intervallo per Y2 è raggiungibile in una sola fase e Y1 è in posizione per aggancio su T
            if -(( TabI.dTPosI + TabI.dY2DeltaMaxF) - MinY2) < MaxY1 - TabI.dY1PosI and
               TabI.dY1PosI < TabI.dTPosI + EMC.LB - MinJoin - EgtIf( SPLIT, EMC.TCING, 0) + 10 * GEO.EPS_SMALL then
               PosY2FromY1A( TabI, vCmd)
               PosY1FromY2B( TabI, vCmd)
               bXW = false   
            end
         -- in **accentramento** 
         else
            -- sempre se intervallo per Y2 è raggiungibile in una sola fase e Y1 è in posizione per aggancio su T
            if ( TabI.dTPosI + TabI.dY2DeltaMinF) - TabI.MaxY2V2 < TabI.dY1PosI - TabI.MyMinY1V1 and
               TabI.dY1PosI <= TabI.dTPosI + EMC.LB - MinJoin - EgtIf( SPLIT, EMC.TCING, 0) + 10 * GEO.EPS_SMALL then
               PosY2FromY1B( TabI, vCmd)
               PosY1FromY2B( TabI, vCmd)
               bXW = false   
            end   
         end 
      end
      if bXW then
         -- Massimo trascinamento della trave in direzione positiva (casi  **[xw]** )
         -- se Y1 non è a f.c. e se Y1 può afferrare la trave
         EmitComment( vCmd, '[Y1B-xw]')
         if TabI.dY1PosI ~= MaxY1 and
            TabI.dY1PosI < TabI.dTPosI + EMC.LB - MinJoin - EgtIf( SPLIT, EMC.TCING, 0) + 10 * GEO.EPS_SMALL then
         -- allontanamento morse, trascinamento trave con Y1
            MaxDispl( TabI, vCmd, 'M1')
         else 
         -- accentramento morse, trascinamento trave con Y2
            MaxDispl( TabI, vCmd, 'M4')
         end 
      end
      nCnt = nCnt + 1
      if nCnt > 10 then return end
   until ( not bXW)
      -- chiusura/parcheggio dei trascinatori in funzioni chiamanti
   local dY1Delta = TabI.dY1PosI - TabI.dTPosI
   local dY2Delta = TabI.dY2PosI - TabI.dTPosI
   return dY1Delta, dY2Delta
end   -- PosY1Y2B

--------------------------------------------------------------------------------------------------------
-- **ALLONTANAMENTO di Y2 per pos. di Y1 o Y1+Y2** - chiamata da [A] e [B1]
--------------------------------------------------------------------------------------------------------
local function PosY2Y1A( TabI, vCmd)
   EmitComment( vCmd, '[PosY2Y1A]')
   --   |RICONOSCO I CASI| 
   repeat
      local dY1NewDelta = ( TabI.dY1DeltaMaxF + TabI.dY1DeltaMinF)/2
      local bXW = true
      -- se intervallo per Y2 raggiungibile in una sola fase
      -- ossia se posso posizionare 'direttamente' Y2 all'interno dell'intervallo <=> ( -ExtraI2MaxF < Corsa Y1Te and) 
      -- e Y1 è in posizione per aggancio su T
      if MinY2 - ( TabI.dTPosI +  TabI.dY2DeltaMaxF) < MaxY1 -  TabI.dY1PosI and
         TabI.dY1PosI < TabI.dTPosI + EMC.LB - MinJoin - EgtIf( SPLIT, EMC.TCING, 0) + 10 * GEO.EPS_SMALL then  
        -- |POSIZIONO MORSA 2| in **allontanamento** 
         PosY2FromY1A( TabI, vCmd)
         -- |POSIZIONO MORSA 1|
         if ( TabI.dTPosI + dY1NewDelta) > TabI.dY1PosI then
            -- in **allontanamento** 
            PosY1FromY2A( TabI, vCmd)
         else
            -- in **accentramento** 
            PosY1FromY2B( TabI, vCmd)
         end
         bXW = false
      else
         -- |POSIZIONO MORSA 1| e poi MORSA 2
         -- caso di allontanamento
         if ( TabI.dTPosI + dY1NewDelta) > TabI.dY1PosI then
            -- se l'intervallo per Y1 è raggiungibile in una sola fase
            if ( TabI.dTPosI + TabI.dY1DeltaMinF) - MaxY1 <  TabI.dY2PosI - MinY2 then
               PosY1FromY2A( TabI, vCmd)
               PosY2FromY1A( TabI, vCmd)
               bXW = false
            end
         -- in **accentramento** 
         else
            -- sempre se l'intervallo per Y1 è raggiungibile in una sola fase
            if TabI.MyMinY1V1 - ( TabI.dTPosI + TabI.dY1DeltaMaxF)  < TabI.MaxY2V2 - TabI.dY2PosI then
               PosY1FromY2B( TabI, vCmd)
               PosY2FromY1A( TabI, vCmd)
               bXW = false
            end
         end
      end
     if bXW then
         -- Massimo trascinamento della trave in direzione positiva (casi  **[xw]**)
         EmitComment( vCmd, '[Y2A-xw]')
         -- accentramento morse, trascinamento trave con Y2
         if TabI.dY2PosI ~= TabI.MaxY2V2 then
            MaxDispl( TabI, vCmd, 'M4')
         -- allontanamento morse, trascinamento trave con Y1
         else   
            MaxDispl( TabI, vCmd, 'M1') 
         end 
      end
   until ( not bXW)
      -- chiusura/parcheggio dei trascinatori in funzioni chiamanti
   local dY1Delta = TabI.dY1PosI - TabI.dTPosI
   local dY2Delta = TabI.dY2PosI - TabI.dTPosI
   return dY1Delta,  dY2Delta   
end   -- PosY2Y1A
 
--------------------------------------------------------------------------------------------------------
-- **ACCENTRAMENTO di Y2 per pos. di Y1 o Y1+Y2** - chiamata da [A] e [B1]
--------------------------------------------------------------------------------------------------------
local function PosY2Y1B( TabI, vCmd)
   EmitComment( vCmd, '[PosY2Y1B]')
   --   |RICONOSCO I CASI|
   local nCnt = 0
   repeat
      local dY1NewDelta = ( TabI.dY1DeltaMaxF + TabI.dY1DeltaMinF) / 2
      local bXW = true
      -- se intervallo per Y2 raggiungibile in una sola fase
      -- ossia se posso posizionare 'direttamente' Y2 all'interno dell'intervallo I2 <=> (-ExtraI2MinF < Corsa Y1Tc) 
      -- e Y1 è in posizione per aggancio su T
      if ( TabI.dTPosI + TabI.dY2DeltaMinF) - TabI.MaxY2V2 < TabI.dY1PosI - TabI.MyMinY1V1 and
         TabI.dY1PosI < TabI.dTPosI + EMC.LB - MinJoin - EgtIf( SPLIT, EMC.TCING, 0) + 10 * GEO.EPS_SMALL then
         -- |POSIZIONO MORSA 2| in **accentramento**
         PosY2FromY1B( TabI, vCmd)
         -- |POSIZIONO MORSA 1|
         local dY1DeltaI = TabI.dY1PosI - TabI.dTPosI
         -- Se il trascinatore 1 non è già nell'intervallo richiesto
         if dY1DeltaI > TabI.dY1DeltaMaxF + 10 * GEO.EPS_SMALL or dY1DeltaI < TabI.dY1DeltaMinF - 10 * GEO.EPS_SMALL then
            if ( TabI.dTPosI + dY1NewDelta) > TabI.dY1PosI then
               -- in **allontanamento**
               PosY1FromY2A( TabI, vCmd)
            else
               -- in **accentramento**
               PosY1FromY2B( TabI, vCmd)
            end
         end
         bXW = false
      -- se posso posizionare 'direttamente' Y1 all'interno dell'intervallo I1 (casi [rp] o [ri])
      else      
         -- |POSIZIONO MORSA 1| e poi MORSA 2
         -- in **allontanamento**
         if  ( TabI.dTPosI + dY1NewDelta) > TabI.dY1PosI then
            -- se intervallo per Y1 è raggiungibile in una sola fase e Y2 è in posizione per aggancio su T
            if ( TabI.dTPosI +  TabI.dY1DeltaMinF) - MaxY1 < TabI.dY2PosI - MinY2 and
                  TabI.dY2PosI > TabI.dTPosI + MinJoin + EMC.HCING + EMC.HOVM - 10 * GEO.EPS_SMALL then
               PosY1FromY2A( TabI, vCmd)
               PosY2FromY1B( TabI, vCmd)
               bXW = false
            end
         -- in **accentramento**
         else
            -- se (-ExtraI1MaxF < Corsa Y2Tc) e (Y2 è in posizione per aggancio su T)
            if TabI.MyMinY1V1 - ( TabI.dTPosI + TabI.dY1DeltaMaxF)  < TabI.MaxY2V2 - TabI.dY2PosI and
                    TabI.dY2PosI >= TabI.dTPosI + MinJoin + EMC.HCING + EMC.HOVM - 10 * GEO.EPS_SMALL then
               PosY1FromY2B( TabI, vCmd)
               PosY2FromY1B( TabI, vCmd)
               bXW = false
            end   
         end   
      end
      if bXW then
         -- Massimo trascinamento della trave in direzione negativa (casi x[w])
         EmitComment( vCmd, '[Y2B-xw]')
         -- se Y2 non è a f.c. e se Y2 può afferrare la trave
         if TabI.dY2PosI > MinY2 and
            TabI.dY2PosI > TabI.dTPosI + MinJoin + EMC.HOVM + EMC.HCING - 10 * GEO.EPS_SMALL then
         -- allontanamento morse, trascinamento trave con Y2
            MaxDispl( TabI, vCmd, 'M2')
         else 
         -- accentramento morse, trascinamento trave con Y1
            MaxDispl( TabI, vCmd, 'M3')
         end 
      end
      nCnt = nCnt + 1
      if nCnt > 10 then return end
   until ( not bXW)
   -- la chiusura delle morse è fatta sopra
   local dY1Delta = TabI.dY1PosI - TabI.dTPosI
   local dY2Delta = TabI.dY2PosI - TabI.dTPosI
   return dY1Delta,  dY2Delta   
end   -- PosY2Y1FromY1B

---------------------------------------------------------------------
--   **[A] Posizionamento trascinatore Y1 da (Y1 o Y2) <=> Y1/Y2 -> Y1**
---------------------------------------------------------------------
function SpecAdjustCarrA( WorkTab)
   -- Elenco comandi
   local vCmd = {}
   table.insert( vCmd, { 0, 'Y1/Y2 -> Y1'})
   EmitComment( vCmd, '*[A]')
   
   local MinY1V1 = max( WorkTab.dV1PosI + AGG_V, MinY1)
   local MaxY2V2 = min( WorkTab.dV2PosI - AGG_V, MaxY2)
   -- se primo scambio
   local MyMinY1V1 = EgtIf( EMC.CNT == 1, MinY1V1 + AggLoad, MinY1V1)
   -- recupero le posizioni correnti
   local dY1PosI = WorkTab.dTPosI + WorkTab.dY1DeltaI
   local dY2PosI = WorkTab.dTPosI + WorkTab.dY2DeltaI
   local dY1DeltaF
   -- posizioni obiettivo dei carrelli al centro dei rispettivi intervalli di validità
   EgtOutLog( '  Y1DeltaI=' .. EgtNumToString( WorkTab.dY1DeltaI) .. '  Y2DeltaI=' .. EgtNumToString( WorkTab.dY2DeltaI), 1)
   EgtOutLog( '  Y1DeltaMinF=' .. EgtNumToString( WorkTab.dY1DeltaMinF)..' Y1DeltaMaxF=' .. EgtNumToString( WorkTab.dY1DeltaMaxF), 1)

   -- [A(1)] - Se il trascinatore Y1 non è nell'intervallo valido, devo posizionarlo
   if WorkTab.dY1DeltaI < WorkTab.dY1DeltaMinF - GEO.EPS_SMALL or WorkTab.dY1DeltaI > WorkTab.dY1DeltaMaxF + GEO.EPS_SMALL then
      EmitComment( vCmd, '*[A(1)]')
      -- -----------------------------------------
      -- |VERIFICO POSIZIONE RULLI|
      -- ------------------------------------------   
      -- eseguo eventuale posizionamento sicuro di teste e rulli
      if WorkTab.bCloseV then
         EnsureZmax( true, vCmd)
         MyMinY1V1 = EgtIf( EMC.CNT == 1, MinY1 + AggLoad, MinY1)
         MaxY2V2 = MaxY2
      end
      -- ------------------------------------------   
      --  |DEFINISCO INTERVALLO 2|
      -- ------------------------------------------  
      WorkTab.dY2DeltaMaxF = WorkTab.dY1DeltaMinF - ( MyMinY1V1 - MaxY2V2) + ( WorkTab.dY1DeltaMaxF - WorkTab.dY1DeltaMinF)    
      WorkTab.dY2DeltaMinF = WorkTab.dY1DeltaMaxF - ( MaxY1 - MinY2) - ( WorkTab.dY1DeltaMaxF - WorkTab.dY1DeltaMinF)
      -- se Y2 deve allontanarsi assegno il primo punto valido (altrimenti mantengo tutto l'intervallo valido)
      if WorkTab.dY2DeltaI > WorkTab.dY2DeltaMaxF then
         WorkTab.dY2DeltaMinF = max( WorkTab.dY2DeltaMaxF - ( WorkTab.dY1DeltaMaxF - WorkTab.dY1DeltaMinF), MinJoin + EMC.HCING + EMC.HOVM)
      end
      -- se la trave è più 'corta' dell'intervallo per Y2 aggiorno l'intervallo (potrei essere comunque in un punto valido)
      if WorkTab.dY2DeltaMinF < MinJoin + EMC.HCING + EMC.HOVM then      
         WorkTab.dY2DeltaMinF = MinJoin + EMC.HCING + EMC.HOVM
         -- se Y2 deve accentrarsi
         if WorkTab.dY2DeltaI < WorkTab.dY2DeltaMinF then
            WorkTab.dY2DeltaMaxF = 2 * MinJoin + EMC.HCING + EMC.HOVM
         end
      end
      -- posizioni obiettivo dei carrelli al centro dei rispettivi intervalli di validità
      local dY1NewDelta = ( WorkTab.dY1DeltaMinF + WorkTab.dY1DeltaMaxF) / 2
      local dY2NewDelta = ( WorkTab.dY2DeltaMinF + WorkTab.dY2DeltaMaxF) / 2
      --------------------------------------------   
      --  |RICONOSCO I CASI|
      --------------------------------------------   
      TabValI = {dY1PosI=dY1PosI, dY2PosI=dY2PosI, dTPosI=WorkTab.dTPosI, MyMinY1V1=MyMinY1V1, MaxY2V2=MaxY2V2, dY1DeltaMaxF=WorkTab.dY1DeltaMaxF,
                                    dY1DeltaMinF=WorkTab.dY1DeltaMinF, dY2DeltaMaxF=WorkTab.dY2DeltaMaxF, dY2DeltaMinF=WorkTab.dY2DeltaMinF }
      if WorkTab.dY1DeltaI > dY1NewDelta then
         --[A(1)B]  **ACCENTRAMENTO** (dall'origine) del trascinatore Y1
         EmitComment( vCmd, '[A-Y1B]')
         dY1DeltaF = PosY1Y2B( TabValI, vCmd)
      else
         --[A(1)A]  **ALLONTANAMENTO** (dall'origine) del trascinatore Y1
         EmitComment( vCmd, '[A-Y1A]')
         dY1DeltaF = PosY1Y2A( TabValI, vCmd)
      end
      -- chiudo Y1
      table.insert( vCmd, { 11, 1})
      -- dichiaro Y2 da parcheggiare
      WorkTab.bY2Parked = false

   -- [A(0)] - Se Y1 è già nell'intervallo valido, si lascia dove è
   else
      EmitComment( vCmd, '*[A(0)]')
      if not WorkTab.bY2Parked then
         -- dichiaro posizione di Y2 e T
         table.insert( vCmd, { 2, 'T', WorkTab.dTPosI, 'Y2', dY2PosI})
      end
      if WorkTab.bY1Parked then
         -- chiudo Y1
         table.insert( vCmd, { 11, 1})
      end
      dY1DeltaF = WorkTab.dY1DeltaI
   end

   if not WorkTab.bY2Parked then
      -- apro e parcheggio Y2
      table.insert( vCmd, { 12, 0})
      table.insert( vCmd, { 1, 'Y2', ParkY2})
   end

   -- aggiorno e segnalo la posizione finale Y1Delta
   table.insert( vCmd, { 21, dY1DeltaF, 0})
   EgtOutLog( '  Y1DeltaF=' .. EgtNumToString( dY1DeltaF), 1)
   -- segnalo la posizione dei rulli
   table.insert( vCmd, { 22, WorkTab.dV1PosF, WorkTab.dV2PosF})

   SpecOutputCNT()
   return vCmd
end 
-- SpecAdjustCarrAA

--------------------------------------------------------------------
--   **[B1]  da trascinatore Y1 a entrambi <=> Y1 -> Y1+Y2**
--------------------------------------------------------------------
function SpecAdjustCarrB1( WorkTab)
   -- Elenco comandi
   local vCmd = {}
   table.insert( vCmd, { 0, 'Y1 -> Y1+Y2'})
   EmitComment( vCmd, '*[B1]')

   local MinY1V1 = max( WorkTab.dV1PosI + AGG_V, MinY1)
   local MaxY2V2 = min( WorkTab.dV2PosI - AGG_V, MaxY2)
   -- se primo scambio
   local MyMinY1V1 = EgtIf( EMC.CNT == 1, MinY1V1 + AggLoad, MinY1V1)

   -- recupero le posizioni correnti
   local dY1PosI = WorkTab.dTPosI + WorkTab.dY1DeltaI
   local dY2PosI = WorkTab.dTPosI + WorkTab.dY2DeltaI
   local dY1DeltaF, dY2DeltaF
   -- posizioni obiettivo dei carrelli al centro dei rispettivi intervalli di validità
   local dY1NewDelta = ( WorkTab.dY1DeltaMinF + WorkTab.dY1DeltaMaxF) / 2
   local dY2NewDelta = ( WorkTab.dY2DeltaMinF + WorkTab.dY2DeltaMaxF) / 2
   --
   EgtOutLog( '  Y1DeltaI='..EgtNumToString( WorkTab.dY1DeltaI) .. '  Y2DeltaI='..EgtNumToString( WorkTab.dY2DeltaI), 1)
   EgtOutLog( '  Y1DeltaMinF=' .. EgtNumToString( WorkTab.dY1DeltaMinF)..' Y1DeltaMaxF=' .. EgtNumToString( WorkTab.dY1DeltaMaxF), 1)
   EgtOutLog( '  Y2DeltaMinF=' .. EgtNumToString( WorkTab.dY2DeltaMinF)..' Y2DeltaMaxF=' .. EgtNumToString( WorkTab.dY2DeltaMaxF), 1)
  
   -- Il controllo che i trascinatori non siano già nell'intervallo valido è svolto nelle funzioni a valle
   -- -----------------------------------------
   -- |VERIFICO POSIZIONE RULLI|
   -- ------------------------------------------
   if WorkTab.bCloseV then
      EnsureZmax( true, vCmd)
      MyMinY1V1 = EgtIf( EMC.CNT == 1, MinY1 + AggLoad, MinY1)
      MaxY2V2 = MaxY2
   end
   
   -- -----------------------------------------
   --  |RICONOSCO I CASI|
   -- ------------------------------------------
   -- definisco la tabella delle posizioni iniziali
   TabValI = {dY1PosI=dY1PosI, dY2PosI=dY2PosI, dTPosI=WorkTab.dTPosI, MyMinY1V1=MyMinY1V1, MaxY2V2=MaxY2V2, dY1DeltaMaxF=WorkTab.dY1DeltaMaxF,
                                  dY1DeltaMinF=WorkTab.dY1DeltaMinF, dY2DeltaMaxF=WorkTab.dY2DeltaMaxF, dY2DeltaMinF=WorkTab.dY2DeltaMinF }
   -- [B1-Y2A] **ALLONTANAMENTO** del trascinatore **Y2** e (eventuale) posizionamento di Y1
   if WorkTab.dY2DeltaI > dY2NewDelta then
      EmitComment( vCmd, '[B1-Y2A]')
      dY1DeltaF, dY2DeltaF = PosY2Y1A( TabValI, vCmd)
   else
   -- [B1-Y2B] **ACCENTRAMENTO** del trascinatore **Y2** e (eventuale) posizionamento di Y1
      EmitComment( vCmd, '[B1-Y2B] ')
      dY1DeltaF, dY2DeltaF = PosY2Y1B( TabValI, vCmd)
   end
   -- -----------------------------------------
   -- |CHIUSURA MORSE|
   -- ------------------------------------------
   if not SpecTestSomeMoveInCmds( vCmd) then
      table.insert( vCmd, { 3, 'Y1', dY1PosI, 'Y2', dY2PosI, 'T', WorkTab.dTPosI, EMC.CNT})
   end
   table.insert( vCmd, { 12, 1})   -- Chiudo Y2
   table.insert( vCmd, { 11, 1})   -- chiudo Y1
   -- imposto i nuovi parametri di aggancio
   table.insert( vCmd, { 21, dY1DeltaF, dY2DeltaF})
   EgtOutLog( '  Y1DeltaF=' .. EgtNumToString( dY1DeltaF) .. ' Y2DeltaF=' .. EgtNumToString( dY2DeltaF), 1)
   -- segnalo la posizione dei rulli
   table.insert( vCmd, { 22, WorkTab.dV1PosF, WorkTab.dV2PosF})

   SpecOutputCNT()
   return vCmd
end 
-- SpecAdjustCarrB1

---------------------------------------------------------------------
--  **[B2]  da trascinatore Y2 a entrambi <=> Y2 -> Y1+Y2**
--------------------------------------------------------------------
function SpecAdjustCarrB2( WorkTab)
   -- Elenco comandi
   local vCmd = {}
   table.insert( vCmd, { 0, 'Y2 -> Y1+Y2'})
   EmitComment( vCmd, '*[B2]')

   local MinY1V1 = max( WorkTab.dV1PosI + AGG_V, MinY1)
   local MaxY2V2 = min( WorkTab.dV2PosI - AGG_V, MaxY2)
   -- se primo scambio
   local MyMinY1V1 = EgtIf( EMC.CNT == 1, MinY1V1 + AggLoad, MinY1V1)
   
   -- definisco le posizioni **Iniziali** (= recupero le posizioni correnti)
   local dY1PosI = WorkTab.dTPosI + WorkTab.dY1DeltaI
   local dY2PosI = WorkTab.dTPosI + WorkTab.dY2DeltaI
   local dY2DeltaF, dY1DeltaF
   -- posizioni obiettivo dei carrelli al centro dei rispettivi intervalli di validità
   local dY1NewDelta = ( WorkTab.dY1DeltaMinF + WorkTab.dY1DeltaMaxF) / 2
   local dY2NewDelta = ( WorkTab.dY2DeltaMinF + WorkTab.dY2DeltaMaxF) / 2
   -- 
   EgtOutLog( '  Y1DeltaI='..EgtNumToString( WorkTab.dY1DeltaI)..'  Y2DeltaI='..EgtNumToString( WorkTab.dY2DeltaI), 1)
   EgtOutLog( '  Y1DeltaMinF=' .. EgtNumToString( WorkTab.dY1DeltaMinF)..' Y1DeltaMaxF=' .. EgtNumToString( WorkTab.dY1DeltaMaxF), 1)
   EgtOutLog( '  Y2DeltaMinF=' .. EgtNumToString( WorkTab.dY2DeltaMinF)..' Y2DeltaMaxF=' .. EgtNumToString( WorkTab.dY2DeltaMaxF), 1)
  
   -- Il controllo che i trascinatori non siano già nell'intervallo valido è svolto nelle funzioni a valle
   -- -----------------------------------------
   -- |VERIFICO POSIZIONE RULLI|
   -- ------------------------------------------
   -- eseguo eventuale posizionamento sicuro di teste e rulli
   if WorkTab.bCloseV then
      EnsureZmax( true, vCmd)
      MyMinY1V1 = EgtIf( EMC.CNT == 1, MinY1 + AggLoad, MinY1)
      MaxY2V2 = MaxY2
   end
   
   -- -----------------------------------------
   --  |RICONOSCO I CASI|
   -- ------------------------------------------  
   -- definisco la tabella delle posizioni iniziali
   TabValI = {dY1PosI=dY1PosI, dY2PosI=dY2PosI, dTPosI=WorkTab.dTPosI, MyMinY1V1=MyMinY1V1, MaxY2V2=MaxY2V2, dY1DeltaMaxF=WorkTab.dY1DeltaMaxF,
                                  dY1DeltaMinF=WorkTab.dY1DeltaMinF, dY2DeltaMaxF=WorkTab.dY2DeltaMaxF, dY2DeltaMinF=WorkTab.dY2DeltaMinF }
   -- [B2-Y1A] **ALLONTANAMENTO** del trascinatore **Y1** e (eventuale) posizionamento di Y2
   if WorkTab.dY1DeltaI < dY1NewDelta then
      EmitComment( vCmd, '[B2-Y1A]')         
      dY1DeltaF, dY2DeltaF = PosY1Y2A( TabValI, vCmd)
   else
   -- [B2-Y1B] **ACCENTRAMENTO** del trascinatore **Y1** e (eventuale) posizionamento di Y2 
      EmitComment( vCmd, '[B2-Y1B]')
      dY1DeltaF, dY2DeltaF = PosY1Y2B( TabValI, vCmd)
   end         

   -- -----------------------------------------
   -- |CHIUSURA MORSE|
   -- ------------------------------------------     
   if not SpecTestSomeMoveInCmds( vCmd) then
      table.insert( vCmd, { 3, 'Y1', dY1PosI, 'Y2', dY2PosI, 'T', WorkTab.dTPosI, EMC.CNT})
   end
   table.insert( vCmd, { 12, 1})   -- Chiudo Y2
   table.insert( vCmd, { 11, 1})   -- chiudo Y1
   -- imposto i nuovi parametri di aggancio
   table.insert( vCmd, { 21, dY1DeltaF, dY2DeltaF})
   EgtOutLog( '  Y1DeltaF=' .. EgtNumToString( dY1DeltaF) .. '  Y2DeltaF=' .. EgtNumToString( dY2DeltaF), 1)
   -- segnalo la posizione dei rulli
   table.insert( vCmd, { 22, WorkTab.dV1PosF, WorkTab.dV2PosF})

   SpecOutputCNT()
   return vCmd
end 
-- SpecAdjustCarrB2

---------------------------------------------------------------------
--   **[C] ex[C1]/[C2] Posizionamento trascinatore Y2 da (Y1 o Y2) <=> Y1/Y2 -> Y2** 
---------------------------------------------------------------------
function SpecAdjustCarrC( WorkTab)
   -- Elenco comandi
   local vCmd = {}
   table.insert( vCmd, { 0, 'Y1/Y2 -> Y2'})
   EmitComment( vCmd, '*[C]')

   local MinY1V1 = max( WorkTab.dV1PosI + AGG_V, MinY1)
   local MaxY2V2 = min( WorkTab.dV2PosI - AGG_V, MaxY2)
   local MyTCING = EgtIf( SPLIT, EMC.TCING, 0)
   -- se primo scambio
   local MyMinY1V1 = EgtIf( EMC.CNT == 1, MinY1V1 + AggLoad, MinY1V1)
   -- recupero le posizioni correnti
   local dY1PosI = WorkTab.dTPosI + WorkTab.dY1DeltaI
   local dY2PosI = WorkTab.dTPosI + WorkTab.dY2DeltaI
   local dY2DeltaF
   --
   EgtOutLog( '  Y1DeltaI=' .. EgtNumToString( WorkTab.dY1DeltaI) .. ' Y2DeltaI=' .. EgtNumToString( WorkTab.dY2DeltaI), 1)
   EgtOutLog( '  Y2DeltaMinF=' .. EgtNumToString( WorkTab.dY2DeltaMinF)..' Y2DeltaMaxF=' .. EgtNumToString( WorkTab.dY2DeltaMaxF), 1)

   -- [C(1)] - Se il trascinatore Y2 non è nell'intervallo valido, devo posizionarlo
   if WorkTab.dY2DeltaI < WorkTab.dY2DeltaMinF - GEO.EPS_SMALL or WorkTab.dY2DeltaI > WorkTab.dY2DeltaMaxF + GEO.EPS_SMALL then
      EmitComment( vCmd, '*[C(1)]')
      -- -----------------------------------------
      -- |VERIFICO POSIZIONE RULLI|
      -- ------------------------------------------
      -- eseguo eventuale posizionamento sicuro di teste e rulli
      if WorkTab.bCloseV then
         EnsureZmax( true, vCmd)
         MyMinY1V1 = EgtIf( EMC.CNT == 1, MinY1 + AggLoad, MinY1)
         MaxY2V2 = MaxY2
      end
      -- ------------------------------------------   
      --  |DEFINISCO INTERVALLO 1|
      -- ------------------------------------------   
      WorkTab.dY1DeltaMaxF = WorkTab.dY2DeltaMinF + (MaxY1 - MinY2) + (WorkTab.dY2DeltaMaxF - WorkTab.dY2DeltaMinF)    
      WorkTab.dY1DeltaMinF = WorkTab.dY2DeltaMaxF + (MyMinY1V1 - MaxY2V2) - (WorkTab.dY2DeltaMaxF - WorkTab.dY2DeltaMinF)    
      -- se Y1 deve allontanarsi assegno il primo punto valido (altrimenti lascio l'intervallo)
      if WorkTab.dY1DeltaI < WorkTab.dY1DeltaMinF then
         WorkTab.dY1DeltaMaxF = min( WorkTab.dY1DeltaMinF + (WorkTab.dY2DeltaMaxF - WorkTab.dY2DeltaMinF), EMC.LB - MinJoin - MyTCING)
      end
      -- se la trave è più 'corta' dell'intervallo per Y1 aggiorno l'intervallo (potrei essere comunque in un punto valido)
      if WorkTab.dY1DeltaMaxF > EMC.LB - ( MinJoin + MyTCING)  then
         WorkTab.dY1DeltaMaxF = EMC.LB - ( MinJoin + MyTCING)
         -- se Y1 deve accentrarsi 
         if WorkTab.dY1DeltaI > WorkTab.dY1DeltaMaxF then
            WorkTab.dY1DeltaMinF = EMC.LB - 2 * MinJoin - MyTCING
         end
      end
      -- posizioni obiettivo dei carrelli al centro dei rispettivi intervalli di validità
      local dY1NewDelta = ( WorkTab.dY1DeltaMinF + WorkTab.dY1DeltaMaxF) / 2
      local dY2NewDelta = ( WorkTab.dY2DeltaMinF + WorkTab.dY2DeltaMaxF) / 2

      -- ------------------------------------------   
      --  |RICONOSCO I CASI|
      -- ------------------------------------------   
      TabValI = {dY1PosI=dY1PosI, dY2PosI=dY2PosI, dTPosI=WorkTab.dTPosI, MyMinY1V1=MyMinY1V1, MaxY2V2=MaxY2V2, dY1DeltaMaxF=WorkTab.dY1DeltaMaxF,
                                    dY1DeltaMinF=WorkTab.dY1DeltaMinF, dY2DeltaMaxF=WorkTab.dY2DeltaMaxF, dY2DeltaMinF=WorkTab.dY2DeltaMinF}
      if WorkTab.dY2DeltaI < dY2NewDelta then
      -- [C-Y2B]  **ACCENTRAMENTO** (dall'origine) del trascinatore Y2
         EmitComment( vCmd, '[C-Y2B]')
         _, dY2DeltaF = PosY2Y1B( TabValI, vCmd)
      else
      -- [C-Y2A]  **ALLONTANAMENTO** (dall'origine) del trascinatore Y2
         EmitComment( vCmd, '[C-Y2A]')
         _, dY2DeltaF = PosY2Y1A( TabValI, vCmd)
      end
      -- chiudo Y2
      table.insert( vCmd, { 12, 1})
      -- dichiaro Y1 da parcheggiare
      WorkTab.bY1Parked = false

   -- [C(0)] - Se Y2 è già nell'intervallo valido, si lascia dove è
   else
      EmitComment( vCmd, '*[C(0)]')
      if not WorkTab.bY1Parked then
         -- dichiaro posizione di Y1 e T
         table.insert( vCmd, { 2, 'T', WorkTab.dTPosI, 'Y1', dY1PosI, EMC.CNT})
      end
      if WorkTab.bY2Parked then
         -- chiudo Y2
         table.insert( vCmd, { 12, 1})
      end
      dY2DeltaF = WorkTab.dY2DeltaI
   end

   if not WorkTab.bY1Parked then
      -- apro e parcheggio Y1
      table.insert( vCmd, { 11, 0})
      EMC.CNT = nil
      table.insert( vCmd, { 1, 'Y1', ParkY1})
   end

   -- aggiorno e segnalo la posizione finale Y2Delta
   table.insert( vCmd, { 21, 0, dY2DeltaF})
   EgtOutLog( ' Y2DeltaF=' .. EgtNumToString( dY2DeltaF), 1)
   -- segnalo la posizione dei rulli
   table.insert( vCmd, { 22, WorkTab.dV1PosF, WorkTab.dV2PosF})

   SpecOutputCNT()
   return vCmd
end   -- SpecAdjustCarrC2

---------------------------------------------------------------------
function SpecOutputCNT()
   if EMC.CNT == 1 then
      EgtSetInfo( EMC.PATHID, 'CNT', 1)
   else
      EgtRemoveInfo( EMC.PATHID, 'CNT')
   end
end

---------------------------------------------------------------------
function SpecGetCNT( CurrOpeId)
   while CurrOpeId do
      local nPrevOpeId = EgtGetPrevActiveOperation( CurrOpeId)
      if not nPrevOpeId then
         return nil
      end
      local nLastPathId = EgtGetLastInGroup( EgtGetFirstNameInGroup( nPrevOpeId, 'CL') or GDB_ID.NULL)
      if nLastPathId then
         return EgtGetInfo( nLastPathId, 'CNT', 'i')
      end
      CurrOpeId = nPrevOpeId
   end
   return nil
end

---------------------------------------------------------------------
function SpecOutputCmds( vCmd, bEnd)
   local sRoot = EgtIf( not bEnd, 'AS', 'AE')
   -- Registro il numero di comandi
   if vCmd and #vCmd > 0 then
      EgtSetInfo( EMC.PATHID, sRoot..'#', #vCmd)
   else
      EgtRemoveInfo( EMC.PATHID, sRoot..'#')
   end
   -- Registro i comandi
   for i = 1, #(vCmd or {}) do
      local Cmd = vCmd[i]
      local sKey = sRoot .. tostring( i)
      -- commento
      if Cmd[1] == 0 then
         local sInfo = '0,'..Cmd[2]
         EgtSetInfo( EMC.PATHID, sKey, sInfo)
      -- movimento di 1 asse
      elseif Cmd[1] == 1 then
         local sInfo = '1,'..Cmd[2]..','..EgtNumToString( Cmd[3],3)..EgtIf( Cmd[4], ',*', '')
         EgtSetInfo( EMC.PATHID, sKey, sInfo)
      -- movimento di 2 assi
      elseif Cmd[1] == 2 then
         local sInfo = '2,'..Cmd[2]..','..EgtNumToString( Cmd[3],3)..','..Cmd[4]..','..EgtNumToString( Cmd[5],3)..EgtIf( Cmd[6], ',*', '')
         EgtSetInfo( EMC.PATHID, sKey, sInfo)
      -- movimento di 3 assi
      elseif Cmd[1] == 3 then
         local sInfo = '3,'..Cmd[2]..','..EgtNumToString( Cmd[3],3)..','..Cmd[4]..','..EgtNumToString( Cmd[5],3)..','..
                             Cmd[6]..','..EgtNumToString( Cmd[7],3)..EgtIf( Cmd[8], ',*', '')
         EgtSetInfo( EMC.PATHID, sKey, sInfo)
      -- posizione sicura per movimento carrelli
      elseif Cmd[1] == 4 then
         local sInfo = '4,'..EgtNumToString( Cmd[2],0)
         EgtSetInfo( EMC.PATHID, sKey, sInfo)
      -- apertura/chiusura morsa Y
      elseif Cmd[1] == 11 then
         local sInfo = '11,'..EgtNumToString( Cmd[2],0)
         EgtSetInfo( EMC.PATHID, sKey, sInfo)
      -- apertura/chiusura morsa V
      elseif Cmd[1] == 12 then
         local sInfo = '12,'..EgtNumToString( Cmd[2],0)
         EgtSetInfo( EMC.PATHID, sKey, sInfo)
      -- impostazione nuovo stato dei carrelli
      elseif Cmd[1] == 21 then
         local sInfo = '21,'..EgtNumToString( Cmd[2],3)..','..EgtNumToString( Cmd[3],3)
         EgtSetInfo( EMC.PATHID, sKey, sInfo)
         if Cmd[2] > 0 and Cmd[3] > 0 then
            EMC.Y1DELTA = Cmd[2]
            EMC.Y2DELTA = Cmd[3]
         elseif Cmd[2] > 0 then
            EMC.Y1DELTA = Cmd[2]
            EMC.Y2DELTA = nil
         elseif Cmd[3] > 0 then
            EMC.Y1DELTA = nil
            EMC.Y2DELTA = Cmd[3]
         end
      -- impostazione nuova posizione dei rulli
      elseif Cmd[1] == 22 then
         local sInfo = '22,'..EgtNumToString( Cmd[2],3)..','..EgtNumToString( Cmd[3],3)
         EgtSetInfo( EMC.PATHID, sKey, sInfo)
         EMC.V1POS = Cmd[2]
         EMC.V2POS = Cmd[3]
      -- aggancio grezzo a carrello
      elseif Cmd[1] == 31 then
         local sInfo = '31,'..EgtNumToString( Cmd[2],0)..','..Cmd[3]
         EgtSetInfo( EMC.PATHID, sKey, sInfo)
      -- segnalazione errore
      elseif Cmd[1] == 99 then
         local sInfo = '99,' .. Cmd[2] .. ',' .. Cmd[3]
         EgtSetInfo( EMC.PATHID, sKey, sInfo)
      end
   end

   -- Salvo i nuovi delta dei carrelli
   if EMC.Y1DELTA then
      EgtSetInfo( EMC.PATHID, 'Y1DELTA', EMC.Y1DELTA)
   else
      EgtRemoveInfo( EMC.PATHID, 'Y1DELTA')
   end
   if EMC.Y2DELTA then
      EgtSetInfo( EMC.PATHID, 'Y2DELTA', EMC.Y2DELTA)
   else
      EgtRemoveInfo( EMC.PATHID, 'Y2DELTA')
   end

   --Salvo le nuove posizioni dei rulli
   EgtSetInfo( EMC.PATHID, 'V1POS', EMC.V1POS)
   EgtSetInfo( EMC.PATHID, 'V2POS', EMC.V2POS)
   -- Se comandi finali
   if bEnd then
      -- Se operazione successiva è disposizione, salvo anche lì le posizioni dei rulli
      local NextDispId = EgtGetPhaseDisposition( EMC.PHASE + 1)
      if NextDispId and NextDispId == EgtGetNextActiveOperation( EMC.DISPID or EMC.MCHID) then
         EgtSetInfo( NextDispId, 'V1POS', EMC.V1POS)
         EgtSetInfo( NextDispId, 'V2POS', EMC.V2POS)
      end
   end

end

---------------------------------------------------------------------
function SpecSetCarrPosFromCmds( vCmd)
   -- recupero nuova posizione carrelli
   for i = 1, #(vCmd or {}) do
      local Cmd = vCmd[i]
      if Cmd[1] == 21 then
         if Cmd[2] > 0 and Cmd[3] > 0 then
            EMC.Y1DELTA = Cmd[2]
            EMC.Y2DELTA = Cmd[3]
         elseif Cmd[2] > 0 then
            EMC.Y1DELTA = Cmd[2]
            EMC.Y2DELTA = nil
         elseif Cmd[3] > 0 then
            EMC.Y1DELTA = nil
            EMC.Y2DELTA = Cmd[3]
         end
      elseif Cmd[1] == 22 then
         EMC.V1POS = Cmd[2]
         EMC.V2POS = Cmd[3]
      end
   end
end

---------------------------------------------------------------------
function SpecTestOnlyRemarkInCmds( vCmd)
   if not vCmd then return true end
   -- verifico se nella lista dei comandi ci sono solo commenti   
   for i = 1, #vCmd do
      if vCmd[i][1] ~= 0 then
         return false
      end   
   end 
   return true
end

---------------------------------------------------------------------
function SpecTestSomeMoveInCmds( vCmd)
   if not vCmd then return false end
   -- verifico se nella lista dei comandi c'è almeno un movimento
   for i = 1, #vCmd do
      if vCmd[i][1] == 1 or vCmd[i][1] == 2 or vCmd[i][1] == 3 then
         return true
      end   
   end 
   return false
end
