-- Special Operations macchina Saomad-KAIROS by EgalWare s.r.l.  2023/12/01

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

-- Carico i dati globali
local sBaseDir = EgtGetCurrMachineDir()
local BD = dofile( sBaseDir .. '\\Beam\\BeamData.lua')

---------------------------------------------------------------------
-- *** Generic Machinings ***
---------------------------------------------------------------------
pcall( require, 'EmtGenMachining')  -- si fa una require con PCALL perchè la libreria è opzionale

---------------------------------------------------------------------
-- *** Special Link moves ***
---------------------------------------------------------------------

-----------------------------------------------------------------------------------------
function OnSpecialLink()

   -- se fresa su testa 1
   if EMC.HEAD == 'H1' then
      -- se inizio lavorazione con prelievo utensile
      if EMC.LINKTYPE == 1 then
      -- se fine lavorazione con deposito utensile
      elseif EMC.LINKTYPE == 2 then
      -- altrimenti collegamento tra due lavorazioni (3)
      else
         local nClId = EgtGetFirstNameInGroup( EMC.NEXTMCHID, 'CL')   -- recupero Id del gruppo CL della lavorazione corrente
         local nPathId = EgtGetFirstInGroup( nClId or GDB_ID.NULL)    -- Id del primo gruppo nella lavorazione (P1)
         local dAuxMoveCount = EgtGetInfo( nPathId, 'AS#', 'd') or 0  -- numero di movimenti ausiliari
         -- se ci sono stati movimenti di pinze vado a una quota sicura
         if dAuxMoveCount > 3 then
            -- recupero quota massima di collegamento
            local dZSecRotAx = 500
            -- retrazione
            EmtRemoveRise( EMC.PREVMCHID)
            EmtAddRise( EMC.PREVMCHID, { EMC.L1p, EMC.L2p, dZSecRotAx, EMC.R1p, EMC.R2p}, 30, 3, 2, 'UniqueRise=1;')
            -- approccio
            EmtRemoveClimb( EMC.NEXTMCHID)
            EmtAddClimb( EMC.NEXTMCHID, { EMC.L1, EMC.L2, dZSecRotAx, EMC.R1, EMC.R2}, 31, 2, 2, 'FirstClimb=1;')
            EmtAddClimb( EMC.NEXTMCHID, { EMC.L1, EMC.L2, EMC.L3, EMC.R1, EMC.R2}, 30, 2, 2, 'LastClimb=1;')
         end
      end
   -- se motosega
   elseif EMC.HEAD == 'H3' then
   end
   EMC.ERR = 0
end

---------- OnSpecialApplyDisposition & OnPostApplyMachining ---------
----------------------- Costanti ------------------------------------
local AGG_LOAD = 0
local MaxLenSmT = 1500             -- massima lunghezza pezzo scaricato con nastri verdi

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

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

   local nClId = EgtGetFirstNameInGroup( nParentId, 'CL')
   -- se non c'è, lo aggiunge
   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 IsStartPhase( nPhase)
   local sVal = EgtGetInfo( EgtGetPhaseDisposition( nPhase) or GDB_ID.NULL, 'TYPE')
   return ( sVal == 'START')
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 IsMid2Phase( nPhase)
   local sVal = EgtGetInfo( EgtGetPhaseDisposition( nPhase) or GDB_ID.NULL, 'TYPE')
   return ( sVal == 'MID2')
end

---------------------------------------------------------------------
local function IsEnd2Phase( nPhase)
   local sVal = EgtGetInfo( EgtGetPhaseDisposition( nPhase) or GDB_ID.NULL, 'TYPE')
   return ( sVal == 'END2')
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 GetPhaseRot( nPhase)
   return ( EgtGetInfo( EgtGetPhaseDisposition( nPhase) or GDB_ID.NULL, 'ROT', 'i') or 0)
end

---------------------------------------------------------------------
local function IsLastOperationBeforeRotation( nOperId)
   -- se ultima fase o ultima fase del pezzo, ritorno risultato negativo
   if EMC.PHASE == EgtGetPhaseCount() or IsEndPhase( EMC.PHASE) or IsEnd2Phase( EMC.PHASE) then
      return false
   end
   -- recupero la successiva operazione attiva
   local nNextOperId = EgtGetNextActiveOperation( nOperId)
   -- se non esiste o non è una disposizione, ritorno risultato negativo
   if not nNextOperId or EgtGetOperationType( nNextOperId) ~= MCH_OY.DISP then
      return false
   end
   -- recupero le rotazioni della fase corrente e della prossima fase
   local nRot = GetPhaseRot( EMC.PHASE)
   local nNextRot = GetPhaseRot( EMC.PHASE + 1)
   -- ritorno se sono diverse
   return ( nRot ~= nNextRot)
end

---------------------------------------------------------------------
local function IsFirstMachiningAfterRotation( nMchId)
   -- se prima fase o prima fase del pezzo, ritorno risultato negativo
   if EMC.PHASE == 1 or 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
   -- recupero le rotazioni della fase corrente e della fase precedente
   local nRot = GetPhaseRot( EMC.PHASE)
   local nPrevRot = GetPhaseRot( EMC.PHASE - 1)
   -- ritorno se sono diverse
   return ( nRot ~= nPrevRot)
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 AddZmaxMove( vCmd)
   table.insert( vCmd, { 1, 'Z', EgtGetAxisHomePos( 'Z')})
   return vCmd
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
   local bSplit = IsEndPhase( EMC.PHASE) or IsMid2Phase( EMC.PHASE) or IsEnd2Phase( 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
      return
   end
   local nPathId = EgtGroup( nClId)
   if not nPathId then
      EMC.ERR = 6
      return
   end
   EgtSetName( nPathId, 'Empty')
   EMC.PATHID = nPathId
   EMC.SHIFTS = -1

   -- Se l'operazione successiva è una disposizione con rotazione, devo preparare il pezzo alla rotazione
   if IsLastOperationBeforeRotation( EMC.DISPID) then
      -- aggiornamento posizioni
      if IsStartPhase( EMC.PHASE) then
         -- carico le posizioni
         local dPosT = EgtGetInfo( EMC.DISPID, 'TPOS', 'd')
         local dPosX1 = EgtGetInfo( EMC.DISPID, 'X1POS', 'd')
         EMC.TPOS = dPosT
         EMC.X1DELTA = dPosX1 - dPosT
         EMC.X2DELTA = nil
      else
         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.X1DELTA = EgtGetInfo( nLastPathId, 'X1DELTA', 'd')
         EMC.X2DELTA = EgtGetInfo( nLastPathId, 'X2DELTA', 'd')
         EMC.DISTFRONT = EgtGetInfo( nLastPathId, 'DISTFRONT', 'd') or 0
         EMC.DISTBACK = EgtGetInfo( nLastPathId, 'DISTBACK', 'd') or 0
      end
      -- Determinazione delle dimensioni del grezzo in lavoro
      local b3Raw = BBox3d()
      local nRawId = EgtGetFirstRawPart()
      while nRawId do
         if EgtVerifyRawPartPhase( nRawId, EMC.PHASE) then
            b3Raw = EgtGetRawPartBBox( nRawId)
            break
         end
         nRawId = EgtGetNextRawPart( nRawId)
      end
      EMC.LENGTHBEAM = b3Raw:getDimX() + 10 * GEO.EPS_SMALL
      -- Assegno sovramateriale di testa e ingombro tagli di testa e di coda
      EMC.HOVM = EgtGetInfo( nRawId, 'HOVM', 'd') or 0
      EMC.HCING = 0
      EMC.TCING = EgtGetInfo( nRawId, 'TCING', 'd') or 0
      -- Eseguo preparazione alla rotazione
      local vCmd = SpecCalcPreRot()
      SpecOutputCmds( vCmd, true)
      return
   end

   -- Se l'operazione successiva è ancora una disposizione, devo scaricare il pezzo
   if nNextOpeType == MCH_OY.DISP  and  bSplit  and  not IsEnd2Phase( EMC.PHASE) 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.X1DELTA = EgtGetInfo( nLastPathId, 'X1DELTA', 'd')
      EMC.X2DELTA = EgtGetInfo( nLastPathId, 'X2DELTA', 'd')
      EMC.DISTFRONT = EgtGetInfo( nLastPathId, 'DISTFRONT', 'd') or 0
      EMC.DISTBACK = EgtGetInfo( nLastPathId, 'DISTBACK', 'd') or 0
      -- 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.LENGTHBEAM = b3Raw:getDimX() + 10 * GEO.EPS_SMALL
      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

   -- Determinazione delle sue dimensioni
   local b3Raw = EgtGetRawPartBBox( nCurrRawId)
   if not b3Raw or b3Raw:isEmpty() then
      EMC.ERR = 11
      return
   end
   EMC.LENGTHBEAM = b3Raw:getDimX() + 10 * GEO.EPS_SMALL
   EMC.WIDTHBEAM = b3Raw:getDimY()
   EMC.HEIGHTBEAM = b3Raw:getDimZ()
   EMC.ZMIN = b3Raw:getMin():getZ()

   -- Aggiorno limiti di presa e tolleranza
   UpdateMinJoinDeltaTol( EMC.WIDTHBEAM, EMC.HEIGHTBEAM, EMC.LENGTHBEAM)

   -- TODO per il momento si ha solo area proibita in testa e coda, ma servirebbe dinamico che cambi ad ogni lavorazione eseguita.
   -- Assegno 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

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

   -- Se fase 1 o dopo rotazione eseguo carico con carrello X1
   local vCmd  = {}
   if EMC.PHASE == 1 or IsEnd2Phase( EMC.PHASE) then
      dPosT = LoadT
      if IsEnd2Phase( EMC.PHASE) then dPosT = dPosT + TurnerOffs end
      vCmd = GetLoadCmd( dPosT, 0, min( EMC.LENGTHBEAM - MinOther - AGG_LOAD, MaxX1 - dPosT))
   -- 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 dPosX1 = EgtGetInfo( EMC.DISPID, 'X1POS', 'd')
      -- se carrello agganciato
      if dPosX1 then
         -- confermo i nuovi parametri di aggancio
         table.insert( vCmd, { 21, dPosX1 - dPosT, 0})
         -- recupero CNT
         local nPrevOpeId = EgtGetPrevActiveOperation( EMC.DISPID)
         if EgtGetOperationType( nPrevOpeId) == MCH_OY.DISP and EgtExistsInfo( nPrevOpeId, 'SKIP') then
            nPrevOpeId = EgtGetPrevActiveOperation( nPrevOpeId)
         end
         local nLastPathId = EgtGetLastInGroup( EgtGetFirstNameInGroup( nPrevOpeId, 'CL') or GDB_ID.NULL)
      -- altrimenti è grezzo scaricato al carico e devo ricaricarlo
      else
         vCmd = GetLoadCmd( dPosT, 0, min( EMC.LENGTHBEAM - MinOther - AGG_LOAD, MaxX1 - dPosT))
      end
   -- altrimenti fase successiva pari
   else
      local dPosX2 = EgtGetInfo( EMC.DISPID, 'X2POS', 'd')
      EMC.X2DELTA = dPosX2
   end
   -- Se fase inizio o rimanenza, eseguo scambio per avere solo pinza X2
   local vCmd2 = {}
   if IsStartOrRestPhase( EMC.PHASE) or IsEnd2Phase( EMC.PHASE) then
      EMC.TPOS = dPosT
      SpecSetCarrPosFromCmds( vCmd)
      local CurrUnloadT = EgtIf( EMC.LENGTHBEAM < MaxLenSmT, UnloadSmT, UnloadT)
      local dDistFront = EgtIf( EMC.LENGTHBEAM < abs( MinX2 - CurrUnloadT), MinJoin, EMC.LENGTHBEAM - abs( MinX2 - CurrUnloadT) + MinJoin + DeltaTol)
      local dDistBack = 0
      vCmd2 = GetCarriagesCmdFromMachEncumbrance( dDistFront, dDistBack)
      if vCmd and #vCmd > 1 and vCmd2 and #vCmd2 > 1 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()
   -- Inizializzo codice di errore
   EMC.ERR = 0
   -- Verifico se ultima lavorazione della fase
   local nNextOpeId = EgtGetNextActiveOperation( EMC.MCHID)
   local bMchLast = ( not nNextOpeId  or  EgtGetOperationPhase( nNextOpeId) ~= EMC.PHASE)   -- Agisco sui diversi percorsi della lavorazione
   local nPathId = EgtGetFirstInGroup( EgtGetFirstNameInGroup( EMC.MCHID, 'CL') or GDB_ID.NULL)
   while nPathId do
      -- 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
   end
end

---------------------------------------------------------------------
function OnSpecialApplyMachining()

   EgtOutLog( ' Lavorazione : ' .. EgtGetName( EMC.MCHID), 1)

   -- 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
      return
   -- se precedente operazione è disposizione
   elseif  EgtGetOperationType( nPrevOpeId) == MCH_OY.DISP then
      if EMC.PHASE == 1 or IsFirstMachiningAfterRotation( EMC.MCHID) then
         -- posizioni home
         EMC.TPOS = nil
         EMC.X1DELTA = nil
         EMC.X2DELTA = nil
         EMC.HCING_IGNORE = true
      elseif IsStartOrRestPhase( EMC.PHASE) then
         -- carico le posizioni
         local dPosT = EgtGetInfo( nPrevOpeId, 'TPOS', 'd')
         local dPosX1 = EgtGetInfo( nPrevOpeId, 'X1POS', 'd')
         -- se carrello agganciato
         if dPosX1 then
            EMC.TPOS = dPosT
            EMC.X1DELTA = dPosX1 - dPosT
            EMC.X2DELTA = nil
         -- altrimenti è grezzo scaricato al carico e devo ricaricarlo
         else
            EMC.TPOS = nil
            EMC.X1DELTA = nil
            EMC.X2DELTA = nil
         end
         EMC.HCING_IGNORE = true
      else
         -- aggiornamento posizioni (da lavorazione precedente a disposizione)
         local nPrev2OpeId = EgtGetPrevActiveOperation( nPrevOpeId)
         if not nPrev2OpeId then
            EMC.ERR = 2
            return
         end
         local nLastPathId = EgtGetLastInGroup( EgtGetFirstNameInGroup( nPrev2OpeId, 'CL') or GDB_ID.NULL)
         local nLastEntId = EgtGetLastInGroup( nLastPathId)
         local vAxes = EmtGetAxesPos( nLastEntId or GDB_ID.NULL)
         if not vAxes then
            EMC.ERR = 3
            return
         end
         if #vAxes > 0 then EMC.TPOS = vAxes[1] end
         EMC.X1DELTA = EgtGetInfo( nLastPathId, 'X1DELTA', 'd')
         EMC.X2DELTA = EgtGetInfo( nLastPathId, 'X2DELTA', 'd')
         EMC.DISTFRONT = EgtGetInfo( nLastPathId, 'DISTFRONT', 'd') or 0
         EMC.DISTBACK = EgtGetInfo( nLastPathId, 'DISTBACK', 'd') or 0
      end
   -- altrimenti precedente operazione è lavorazione
   else
      -- aggiornamento posizioni
      local nLastPathId = EgtGetLastInGroup( EgtGetFirstNameInGroup( nPrevOpeId, 'CL') or GDB_ID.NULL)
      local nLastEntId = EgtGetLastInGroup( nLastPathId)
      local vAxes = EmtGetAxesPos( nLastEntId or GDB_ID.NULL)
      if not vAxes then
         EMC.ERR = 4
         return
      end
      if #vAxes > 0 then EMC.TPOS = vAxes[1] end
      EMC.X1DELTA = EgtGetInfo( nLastPathId, 'X1DELTA', 'd')
      EMC.X2DELTA = EgtGetInfo( nLastPathId, 'X2DELTA', 'd')
      EMC.DISTFRONT = EgtGetInfo( nLastPathId, 'DISTFRONT', 'd') or 0
      EMC.DISTBACK = EgtGetInfo( nLastPathId, 'DISTBACK', 'd') or 0
   end

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

   -- Verifico se ultima lavorazione prima di una rotazione
   local bPreRotMch = IsLastOperationBeforeRotation( EMC.MCHID)

   -- Verifico flag di separazione e fase di scarico
   local sNotes = EgtGetMachiningParam( MCH_MP.USERNOTES)
   local bPreSplit = ( sNotes:find( 'Presplit') ~= nil)
   local bSplitting = ( sNotes:find( 'Split') ~= nil)
   local bPreCut = ( sNotes:find( 'Precut') ~= nil)
   local bCutting = ( sNotes:find( 'Cut') ~= nil)
   local bUnload = IsEndPhase( EMC.PHASE) or IsEnd2Phase( EMC.PHASE)
   if bPreSplit or bPreCut then
      EgtSetInfo( EMC.MCHID, 'IS_PRE', '1')
   else
      EgtRemoveInfo( EMC.MCHID, 'IS_PRE')
   end

   -- 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 di trave e carrelli
      local OriTPos = EMC.TPOS
      local OriX1Delta = EMC.X1DELTA
      local OriX2Delta = EMC.X2DELTA
      -- eseguo le elaborazioni
      SpecApplyPath( bPreSplit, bSplitting, bPreCut, bCutting, bLast and bUnload, bLast and bPreRotMch)
      -- 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 di trave e carrelli
            EMC.TPOS = OriTPos
            EMC.X1DELTA = OriX1Delta
            EMC.X2DELTA = OriX2Delta
            -- eseguo le elaborazioni
            SpecApplyPath( bPreSplit, bSplitting, bPreCut, bCutting, bLast and bUnload, bLast and bPreRotMch)
            -- 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, bPreRotMch)

   -- Assegno flag di pezzo separato dal resto del grezzo
   local bSplit = IsEndPhase( EMC.PHASE) or IsMid2Phase( EMC.PHASE) or IsEnd2Phase( 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
      return
   end
   EMC.LENGTHBEAM = EgtIf( bSplit, b3Raw:getDimX(), b3Tot:getDimX()) + 10 * GEO.EPS_SMALL
   EMC.WIDTHBEAM = b3Tot:getDimY()
   EMC.HEIGHTBEAM = b3Tot:getDimZ()
   EMC.LENGTHRAW = b3Raw:getDimX() + 10 * GEO.EPS_SMALL
   EMC.YMIN = b3Raw:getMin():getY()
   EMC.ZMIN = b3Raw:getMin():getZ()

   -- Aggiorno limiti di presa e tolleranza
   UpdateMinJoinDeltaTol( EMC.WIDTHBEAM, EMC.HEIGHTBEAM, EMC.LENGTHBEAM)

   -- Recupero sovramateriale di testa e ingombro tagli di testa e di coda dal pezzo
   EMC.HOVM = EgtGetInfo( nCurrRawId, 'HOVM', 'd') or 0 -- head over material
   EMC.HCING = EgtGetInfo( nCurrRawId, 'HCING', 'd') or 0 -- head cutting
   EMC.TCING = EgtGetInfo( nCurrRawId, 'TCING', 'd') or 0 -- tail cutting
   EMC.XMAX = b3Raw:getMax():getX() - EMC.HOVM

   local idDisp = EgtGetPhaseDisposition( EMC.PHASE)
   local dHCING = EgtGetInfo( idDisp, 'HCING', 'd') or 0
   if dHCING then
      EMC.HCING = dHCING
   end
   local dTCING = EgtGetInfo( idDisp, 'TCING', 'd') or 0
   if dTCING then
      EMC.TCING = dTCING
   end

   local dDistFront, dDistBack
   local bClampsSamePosition = EgtGetInfo( EMC.MCHID, 'ClampFIX', 'd') == 1 or false
   -- se la posizione è già stata calcolata a partire da una lavorazione precedente
   if bClampsSamePosition and EMC.DISTFRONT and EMC.DISTBACK then
      dDistFront = EMC.DISTFRONT
      dDistBack = EMC.DISTBACK
   -- altrimenti si ricalcola la posizione a partire dalla lavorazione attuale 
   else
      -- se sono stati calcolati gli ingombri
      local bEntirePhaseCalculated = false

      -- ingombro lavorazione attuale
      dDistFront, dDistBack = GetMachiningEncumbrance( EMC.MCHID, bPreCut)

      if not dDistFront or not dDistBack then return end
      if bPreSplit  or  bSplitting then
         local dDistF, dDistB = GetPhaseEncumbrance( EMC.PHASE + 1)
         dDistFront = min( dDistFront, dDistF)
         local dNextHOVM = EgtGetInfo( EgtGetNextRawPart( nCurrRawId) or GDB_ID.NULL, 'HOVM', 'd') or 0
         local dBackOther = b3Tot:getDimX() - b3Raw:getDimX() - MinOther - dNextHOVM
         local dDistBackStartPhase = GetStartPhaseEncumbrance( EMC.PHASE + 2)
         if dDistBackStartPhase then
            dDistBack = min( dDistBack, dDistBackStartPhase)
         end
         EgtOutLog( 'DistBack='..EgtNumToString( dDistBack)..' OtherBack='..EgtNumToString( dBackOther), 3)
         dDistBack = min( dDistBack, dBackOther)
         bEntirePhaseCalculated = true
      elseif bPreCut  or  bCutting then
         local dDistF = GetPhaseEncumbrance( EMC.PHASE + 1)
         dDistFront = min( dDistFront, dDistF)
         dDistBack = 0.0
         bEntirePhaseCalculated = true
      end

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

      -- posizione pinze in base a ingombro lavorazione attuale
      local _, dX1DeltaCurrMach, dX2DeltaCurrMach = CalcCarriagesNewPositions( dDistFront, dDistBack)

      -- se è già stato calcolata per tutta la fase successiva, si controlla che il pinzaggio sia valido
      if bEntirePhaseCalculated then
         if dX1DeltaCurrMach or dX2DeltaCurrMach then
            -- se la posizione è calcolata sull'intera fase successiva, si setta nota su tutte le lavorazioni di quella fase
            local nNextMchId = EgtGetNextActiveOperation( EMC.MCHID)
            while nNextMchId and EgtGetOperationPhase( nNextMchId) <= EMC.PHASE + 1 do
               if EgtGetOperationType( nNextMchId) ~= MCH_OY.DISP then
                  EgtSetInfo( nNextMchId, 'ClampFIX', 1)
               end
               -- prossima lavorazione
               nNextMchId = EgtGetNextActiveOperation( nNextMchId)
            end
         end
      -- altrimenti significa che è solo una lavorazione normale, si prende ingombro lavorazioni successive
      else
         -- controllo se schema cambia lo schema di pinzaggio
         local function IsConsistentClamping( dPrev, dNext)
            return ( dPrev == nil and dNext == nil) or ( dPrev ~= nil and dNext ~= nil) 
         end

         -- se sono in una fase di start e X1DELTA è già calcolato e è più lontano del corrente, dico che X1DELTA è il corrente
         -- si entra in questo caso quando la separazione precedente occupa di più delle lavorazioni di testa del pezzo successivo
         if IsStartPhase( EMC.PHASE) and EMC.X1DELTA and dX1DeltaCurrMach and not EMC.X2DELTA then
            if EMC.X1DELTA > dX1DeltaCurrMach then
               dX1DeltaCurrMach = EMC.X1DELTA
            end
         end

         -- in caso di pinzaggio singolo, mi salvo intervallo minimo e massimo
         local dX1DeltaMin, dX1DeltaMax = dX1DeltaCurrMach, dX1DeltaCurrMach
         local dX2DeltaMin, dX2DeltaMax = dX2DeltaCurrMach, dX2DeltaCurrMach
         local nNextMchId = EgtGetNextActiveOperation( EMC.MCHID)
         local bUseSameClampingConfig = true
         while nNextMchId and EgtGetOperationType( nNextMchId) ~= MCH_OY.DISP and bUseSameClampingConfig do
            local dNextDistFront, dNextDistBack, InfoUserNotes = GetMaxEncumbranceOtherMachining( nNextMchId)
            if dNextDistFront and dNextDistBack then
               -- se la lavorazione che sto controllando è una di separazione, allora si controlla tutta la fase successiva
               if InfoUserNotes.bPreSplit  or  InfoUserNotes.bSplitting then
                  local dDistF, dDistB = GetPhaseEncumbrance( EMC.PHASE + 1)
                  dNextDistFront = min( dNextDistFront, dDistF)
                  local dNextHOVM = EgtGetInfo( EgtGetNextRawPart( nCurrRawId) or GDB_ID.NULL, 'HOVM', 'd') or 0
                  local dBackOther = b3Tot:getDimX() - b3Raw:getDimX() - MinOther - dNextHOVM
                  local dDistBackStartPhase = GetStartPhaseEncumbrance( EMC.PHASE + 2)
                  if dDistBackStartPhase then
                     dNextDistBack = min( dNextDistBack, dDistBackStartPhase)
                  end
                  EgtOutLog( 'DistBack='..EgtNumToString( dNextDistBack)..' OtherBack='..EgtNumToString( dBackOther), 3)
                  dNextDistBack = min( dNextDistBack, dBackOther)
                  bEntirePhaseCalculated = true
               elseif InfoUserNotes.bPreCut  or  InfoUserNotes.bCutting then
                  -- possono esserci altri tagli di precut, scorro tutte fino a fine fase
                  local nLastMchId = EgtGetNextActiveOperation( nNextMchId)
                  while nLastMchId and EgtGetOperationType( nLastMchId) ~= MCH_OY.DISP do
                     local dLastDistFront = GetMaxEncumbranceOtherMachining( nLastMchId)
                     dNextDistFront = min( dNextDistFront, dLastDistFront)
                     nLastMchId = EgtGetNextActiveOperation( nLastMchId)
                  end
                  local dDistF = GetPhaseEncumbrance( EMC.PHASE + 1)
                  dNextDistFront = min( dNextDistFront, dDistF)
                  dNextDistBack = 0.0
                  bEntirePhaseCalculated = true
               end
            end

            local _, dNextNewX1Delta, dNextNewX2Delta = CalcCarriagesNewPositions( dNextDistFront, dNextDistBack)

            local bUnloadIsOk = true
            if dNextNewX2Delta and dX2DeltaCurrMach and ( InfoUserNotes.bPreSplit or InfoUserNotes.bSplitting) then
               local dLastX2Delta = min( dX2DeltaCurrMach, dNextNewX2Delta)
               -- controllo che si riesca a scaricare restando nelle corse
               if ( b3Raw:getDimX() - dLastX2Delta - UnloadT) + MinX2 > -100 then
                  bUseSameClampingConfig = false
                  bUnloadIsOk = false
               end
            end

            if IsConsistentClamping( dX1DeltaCurrMach, dNextNewX1Delta) and IsConsistentClamping( dX2DeltaCurrMach, dNextNewX2Delta) and bUnloadIsOk then
               -- se entrambe le morse in presa
               if dX1DeltaCurrMach and dX2DeltaCurrMach then
                  if ( ( max( dX1DeltaCurrMach, dNextNewX1Delta) - min( dX2DeltaCurrMach, dNextNewX2Delta)) < CLAMP_MAXDIST_2CLAMP) then
                     if dNextNewX1Delta > dX1DeltaCurrMach then dX1DeltaCurrMach = dNextNewX1Delta end
                     if dNextNewX2Delta < dX2DeltaCurrMach then dX2DeltaCurrMach = dNextNewX2Delta end
                     dDistFront = min( dDistFront, dNextDistFront)
                     dDistBack = min( dDistBack, dNextDistBack)
                     bUseSameClampingConfig = true
                  else
                     bUseSameClampingConfig = false
                  end
               -- se in presa solo pinza 1
               elseif dX1DeltaCurrMach then
                  -- se la posizione richiesta della pinza non è oltre la posizione attuale e non sono troppo distante, prendo valori vecchi
                  if ( dX1DeltaMax - dNextNewX1Delta) < CLAMP_MAXDIST_1CLAMP  and  ( dNextNewX1Delta - dX1DeltaMin) < CLAMP_MAXDIST_1CLAMP then
                     dX1DeltaMax = max( dX1DeltaMax, dNextNewX1Delta)
                     dX1DeltaMin = min( dX1DeltaMin, dNextNewX1Delta)
                     dDistBack = min( dDistBack, dNextDistBack)
                     bUseSameClampingConfig = true
                  else
                     bUseSameClampingConfig = false
                  end
               -- se in presa solo pinza 2
               elseif dX2DeltaCurrMach then
                  -- se la posizione richiesta della pinza non è oltre la posizione attuale e non sono troppo distante, prendo valori vecchi
                  if ( dX2DeltaMax - dNextNewX2Delta) < CLAMP_MAXDIST_1CLAMP  and  ( dNextNewX2Delta - dX2DeltaMin) < CLAMP_MAXDIST_1CLAMP then
                     dX2DeltaMax = max( dX2DeltaMax, dNextNewX2Delta)
                     dX2DeltaMin = min( dX2DeltaMin, dNextNewX2Delta)
                     dDistFront = min( dDistFront, dNextDistFront)
                     bUseSameClampingConfig = true
                  else
                     bUseSameClampingConfig = false
                  end
               end
            else
               bUseSameClampingConfig = false
            end

            -- se posso usare la stessa configurazione, allora si setta che la posizione è già calcolata
            if bUseSameClampingConfig then
               EgtSetInfo( nNextMchId, 'ClampFIX', 1)
               -- se la posizione è calcolata sull'intera fase successiva, si setta nota su tutte le lavorazioni di quella fase
               if bEntirePhaseCalculated then
                  -- prossima lavorazione
                  nNextMchId = EgtGetNextActiveOperation( nNextMchId)
                  while nNextMchId and EgtGetOperationPhase( nNextMchId) <= EMC.PHASE + 1 do
                     if EgtGetOperationType( nNextMchId) ~= MCH_OY.DISP then
                        EgtSetInfo( nNextMchId, 'ClampFIX', 1)
                     end
                     -- prossima lavorazione
                     nNextMchId = EgtGetNextActiveOperation( nNextMchId)
                  end
               end
            -- altrimenti si rimuove info in caso fosse stata settata in un calolo precedente
            else
               EgtRemoveInfo( nNextMchId, 'ClampFIX')
            end

            -- se non è già all'ultima lavorazione, chiedo la successiva
            if nNextMchId and EgtGetOperationPhase( nNextMchId) <= EMC.PHASE + 1 then
               nNextMchId = EgtGetNextActiveOperation( nNextMchId)
            end
         end
      end
   end

   -- si salvano i valori
   EMC.DISTFRONT = dDistFront
   EMC.DISTBACK = dDistBack

   -- Se inizio o appena dopo rotazione, eseguo il carico
   if not EMC.TPOS then
      local dPosT = LoadT
      if IsFirstMachiningAfterRotation( EMC.MCHID) then dPosT = dPosT + TurnerOffs end
      local vCmd = GetLoadCmd( dPosT, dDistFront, max( dDistBack, MinJoin))
      local vCmd2 = GetCarriagesCmdFromMachEncumbrance( dDistFront, dDistBack)
      if vCmd2 and #vCmd2 > 1 then
         table.insert( vCmd, { 0, 'CARR_MOVE'})
      end
      EgtJoinTables( vCmd, vCmd2)
      table.insert( vCmd, { 0, 'Move_End'})
      SpecOutputCmds( vCmd)

   -- Se altrimenti carri entrambi diponibili, eseguo calcoli per carrelli
   elseif not IsEndPhase( EMC.PHASE) then
      local vCmd = GetCarriagesCmdFromMachEncumbrance( dDistFront, dDistBack)
      -- Se non ci sono spostamenti, confermo i parametri di aggancio
      if SpecTestOnlyRemarkInCmds( vCmd) then
         table.insert( vCmd, { 21, EgtIf( EMC.X1DELTA, EMC.X1DELTA, 0), EgtIf( EMC.X2DELTA, EMC.X2DELTA, 0)})
      end
      SpecOutputCmds( vCmd)

   -- Altrimenti, non muovo i carrelli rispetto alla trave
   else
      local vCmd = {}
      SpecOutputCmds( vCmd)
   end
   EMC.HCING_IGNORE = nil

   -- 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')
      EMC.MAXLENLEFT = EgtGetInfo( idDisp, 'MAXLENLEFT', 'd')
      -- verifico se separazione con caduta
      if  not EMC.X2DELTA  then
         EgtOutLog( '  Warning SPLITTING -> separazione con caduta pezzo')
         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
         EMC.ERR = 18
      -- verifico che la barra sia agganciata ad entrambi i carrelli
      elseif  not EMC.X1DELTA  or  not EMC.X2DELTA  then
         EMC.ERR = 19
         EMC.MSG = '  Error SPLIT : X1 or X2 not clamped'
         return false
      end
      if not IsMid2Phase( EMC.PHASE + 1) then
         vCmd = SpecCalcSplit( b3Raw:getDimX())
      else
         vCmd = SpecCalcSplitRot( b3Raw:getDimX())
         EMC.DISTBACK = nil
      end
   end
   -- Se taglio finale di grezzo a perdere
   if bCutting then
      -- salvo distanza carrello X2 da inizio grezzo rimasto nella disposizione della prossima fase
      local NextDispId = EgtGetPhaseDisposition( EMC.PHASE + 1)
      if NextDispId then
         EgtSetInfo( NextDispId, 'X2POS', EMC.X2DELTA)
      end
   end   

   -- Se previsto scarico, lo eseguo
   if bUnload then
      EMC.LENGTHBEAM = b3Raw:getDimX() + 10 * GEO.EPS_SMALL
      local vCmdTmp = SpecCalcUnload()
      vCmd = EgtJoinTables( vCmd, vCmdTmp)
   end

   -- Se ritorno al carico per rotazione
   if bPreRotMch then
      -- determino posizione testa trave
      local nLastEntId = EgtGetLastInGroup( EMC.PATHID)
      local vAxes = EmtGetAxesPos( nLastEntId)
      if #vAxes > 0 then EMC.TPOS = vAxes[1] end
      -- eseguo movimento prima di rotazione
      local vCmdTmp = SpecCalcPreRot()
      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 GetMachiningEncumbrance( 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
      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
      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 del vettore estrusione (coincide con il vettore utensile)
   local vtTool = EgtGetInfo( nPathId, 'EXTR', 'v')
   if not vtTool then
      EMC.ERR = 14
      return
   end
   -- Recupero testa
   local sHead = EgtTdbGetCurrToolParam( MCH_TP.HEAD)
   -- Calcolo del vettore ausiliario
   local vAxes = EmtGetAxesPos( EgtGetFirstInGroup( nPathId))
   if not vAxes  or  #vAxes < 5 or ( sHead == 'H3' and #vAxes < 6) then
      EMC.ERR = 15
      return
   end
   local vtAux = EgtGetCalcAuxDirFromAngles( vAxes[4], vAxes[5], vAxes[6])
   if not vtAux then
      EMC.ERR = 16
      return
   end
   local vtArm = vtAux
   -- Recupero dei dati dell'utensile
   local nToolType = EgtTdbGetCurrToolParam( MCH_TP.TYPE)
   local bSaw = ( nToolType == MCH_TY.SAW_STD or nToolType == MCH_TY.SAW_FLAT)
   local bChain = ( nToolType == MCH_TY.MORTISE_STD)
   local dTLen = EgtTdbGetCurrToolParam( MCH_TP.LEN)
   local dTDiam = EgtTdbGetCurrToolParam( MCH_TP.DIAM)
   local dTDist = EgtIf( EgtTdbGetCurrToolParam( MCH_TP.DIST) > 1, EgtTdbGetCurrToolParam( MCH_TP.DIST), ChSawLen)
   -- Se sega a catena, devo correggere il versore Aux per farlo coincidere con la direzione del braccio C
   if bChain then
      if abs( vAxes[6] or 0) < 1 then
         vtArm = vtTool
      else
         vtArm = vtTool ^ vtAux
      end
   end
   -- Calcolo limiti derivanti dalla lavorazione
   local dDistFront, dDistBack = SpecCalcEncumbrance( vtTool, vtArm, vtAux, ptMin, ptMax, bSaw, bChain, dTLen, dTDiam, dTDist)
   return dDistFront, dDistBack
end

---------------------------------------------------------------------
function GetMaxEncumbranceOtherMachining( nMchId)
   -- Salvo lavorazione e utensile correnti, per ripristinarli alla fine
   local nOrigMchId = EgtGetCurrMachining()
   local sOrigTool = EgtTdbGetCurrToolParam( MCH_TP.NAME)
   local sOrigHead = EgtTdbGetCurrToolParam( MCH_TP.HEAD)
   local nOrigExit = EgtTdbGetCurrToolParam( MCH_TP.EXIT)

   -- imposto lavorazione e utensile correnti
   EgtSetCurrMachining( nMchId)
   local sTool = EgtGetMachiningParam( MCH_MP.TOOL)
   if not EgtTdbSetCurrTool( sTool) then
      local sTuuid = EgtGetMachiningParam( MCH_MP.TUUID)
      sTool = EgtTdbGetToolFromUUID( sTuuid)
      EgtTdbSetCurrTool( sTool)
   end
   local sHead = EgtTdbGetCurrToolParam( MCH_TP.HEAD)
   local nExit = EgtTdbGetCurrToolParam( MCH_TP.EXIT)
   if sTool and sHead and nExit then EgtSetCalcTool( sTool, sHead, nExit) end
   -- calcolo ingombri
   local dDistF, dDistB = GetMachiningEncumbrance( nMchId)
   local InfoNotes = {}
   -- Verifico flag di separazione e fase di scarico
   InfoNotes.sNotes = EgtGetMachiningParam( MCH_MP.USERNOTES)
   InfoNotes.bPreSplit = ( InfoNotes.sNotes:find( 'Presplit') ~= nil)
   InfoNotes.bSplitting = ( InfoNotes.sNotes:find( 'Split') ~= nil)
   InfoNotes.bPreCut = ( InfoNotes.sNotes:find( 'Precut') ~= nil)
   InfoNotes.bCutting = ( InfoNotes.sNotes:find( 'Cut') ~= nil)

   -- Ripristino lavorazione e utensile correnti
   if nOrigMchId then EgtSetCurrMachining( nOrigMchId) end
   if sOrigTool then EgtTdbSetCurrTool( sOrigTool) end
   if sOrigTool and sOrigHead and nOrigExit then EgtSetCalcTool( sOrigTool, sOrigHead, nOrigExit) end
   -- Restituisco gli ingombri trovati
   return dDistF, dDistB, InfoNotes
end

---------------------------------------------------------------------
function GetStartPhaseEncumbrance( nPhase)
   -- si pre-calcola solo se è una fase di start dopo uno split
   if not IsStartPhase( nPhase) then
      return nil
   end

   local dDistBack = nil
   local dX1DeltaStartPhaseMin = nil
   local dX1DeltaStartPhaseMax = nil
   local nMchId = EgtGetNextActiveOperation( EgtGetPhaseDisposition( nPhase) or GDB_ID.NULL)

   if nMchId then
      local bGetNext = true
      while bGetNext do
         local dDistFrontStartPhase, dDistBackStartPhase = GetMachiningEncumbrance( nMchId, false)
         local _, dX1DeltaStartPhaseCurr, dX2DeltaStartPhaseCurr = CalcCarriagesNewPositions( dDistFrontStartPhase, dDistBackStartPhase)
         -- se in presa anche pinza 2, esco subito, non sarà possibile posizionamento diretto
         if not dX1DeltaStartPhaseCurr or dX2DeltaStartPhaseCurr then
            break
         else
            -- se non ancora settato, minimo e massimo assumono il valore attuale
            if not dX1DeltaStartPhaseMin  and not dX1DeltaStartPhaseMax then
               dX1DeltaStartPhaseMin = dX1DeltaStartPhaseCurr
               dX1DeltaStartPhaseMax = dX1DeltaStartPhaseCurr
               dDistBack = dDistBackStartPhase
            else
               -- se la posizione richiesta della pinza non è oltre la posizione attuale e non sono troppo distante, prendo valori vecchi
               if ( dX1DeltaStartPhaseMax - dX1DeltaStartPhaseCurr) < CLAMP_MAXDIST_1CLAMP  and  ( dX1DeltaStartPhaseCurr - dX1DeltaStartPhaseMin) < CLAMP_MAXDIST_1CLAMP then
                  dX1DeltaStartPhaseMax = max( dX1DeltaStartPhaseMax, dX1DeltaStartPhaseCurr)
                  dX1DeltaStartPhaseMin = min( dX1DeltaStartPhaseMin, dX1DeltaStartPhaseCurr)
                  dDistBack = min( dDistBack, dDistBackStartPhase)
               else
                  break
               end
            end
         end
         
         nMchId = EgtGetNextActiveOperation( nMchId)
         if EgtGetOperationPhase( nMchId) ~= nPhase then
            bGetNext = false
         end
      end
      -- se ho trovato un valore, allora si corregge con lunghezza del pezzo attuale perchè sto guardando al pezzo successivo,
      -- ma le misure ecc sono calcolate con pezzo attuale ancora presente
      if dDistBack then
         dDistBack = dDistBack - EMC.LENGTHRAW
      end
   end

   return dDistBack
end

---------------------------------------------------------------------
function GetPhaseEncumbrance( nPhase)
   -- Deve essere la fase finale di lavorazione di un pezzo (già staccato dal resto della trave)
   local dDistFront = EMC.LENGTHBEAM
   local dDistBack = EMC.LENGTHBEAM
   -- Salvo lavorazione e utensile correnti, per ripristinarli alla fine
   local nOrigMchId = EgtGetCurrMachining()
   local sOrigTool = EgtTdbGetCurrToolParam( MCH_TP.NAME)
   local sOrigHead = EgtTdbGetCurrToolParam( MCH_TP.HEAD)
   local nOrigExit = EgtTdbGetCurrToolParam( MCH_TP.EXIT)
   -- 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 EgtTdbSetCurrTool( sTool) then
         local sTuuid = EgtGetMachiningParam( MCH_MP.TUUID)
         sTool = EgtTdbGetToolFromUUID( sTuuid)
         EgtTdbSetCurrTool( sTool)
      end
      local sHead = EgtTdbGetCurrToolParam( MCH_TP.HEAD)
      local nExit = EgtTdbGetCurrToolParam( MCH_TP.EXIT)
      if sTool and sHead and nExit then EgtSetCalcTool( sTool, sHead, nExit) end
      -- calcolo ingombri
      local dDistF, dDistB = GetMachiningEncumbrance( nMchId)
      if dDistF and dDistB then
         dDistFront = min( dDistFront, dDistF)
         dDistBack = min( dDistBack, dDistB)
      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 and nOrigExit then EgtSetCalcTool( sOrigTool, sOrigHead, nOrigExit) end
   -- Restituisco gli ingombri trovati
   return dDistFront, dDistBack
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

---------------------------------------------------------------------
-- TODO FUNZIONE DA RIVEDERE
-- il calcolo ingombro non deve essere a casi, ma calcolato preciso con intersezione di solidi
function SpecCalcEncumbrance( vtTool, vtArm, vtAux, ptMin, ptMax, bSaw, bChain, dTLen, dTDiam, dTDist)
   -- Quota in Z dal punto di inclinazione dei carrelli
   local dCompZ = sqrt( 1 - vtTool:getZ() * vtTool:getZ())
   local dZup = ptMin:getZ() - 0.5 * dCompZ * dTDiam - ( EMC.ZMIN + 130)
   -- Posizione min e max del naso mandrino (rispetto a testa pezzo in X e riferimento pezzo in Y e Z)
   local ptHeadMin = ptMin + vtTool * dTLen - Vector3d( EMC.XMAX, EMC.YMIN + EMC.WIDTHBEAM, EMC.ZMIN)
   local ptHeadMax = ptMax + vtTool * dTLen - Vector3d( EMC.XMAX, EMC.YMIN + EMC.WIDTHBEAM, EMC.ZMIN)
   -- Ingombro a sinistra
   local dDistBack = EMC.LENGTHBEAM + ptMin:getX() + LoadT
   local dHeadBack = 350
   if bSaw then
      if vtTool:getX() > 0 and abs( vtTool:getY()) < 0.088 and abs( vtTool:getZ()) < 0.088 then
         dHeadBack = 50 + 0.5 * dTDiam * sqrt( 1 - vtTool:getX() * vtTool:getX())
      elseif abs( vtTool:getZ()) < 0.26 and abs( vtTool:getX()) < 0.35 then
         dHeadBack = EgtIf( vtArm:getX() < 0, 540, 350)
      elseif abs( vtTool:getZ()) < 0.26 and abs( vtTool:getX()) < 0.71 then
         if vtArm:getX() < 0 then
            dHeadBack = 460
         else
            dHeadBack = EgtIf( vtTool:getX() > 0, 50, 90) + 0.5 * dTDiam * sqrt( 1 - vtTool:getX() * vtTool:getX())
         end
      elseif ( vtTool:getX() > 0.7 and abs( vtTool:getY()) < 0.2 and ptMax:getZ() > EMC.ZMIN + 0.9 * EMC.HEIGHTBEAM) then
         if vtTool:getZ() > 0 then
            dHeadBack = max( 50, 90 - 0.5 * dTDiam * sqrt( 1 - vtTool:getX() * vtTool:getX()))
         else
            dHeadBack = max( 50, 40 + 0.5 * dTDiam * sqrt( 1 - vtTool:getX() * vtTool:getX()))
         end
         if ptMax:getZ() < EMC.ZMIN + BD.VICE_MINH then
            dHeadBack = dHeadBack + BD.VICE_MINH
         end
      elseif ( vtTool:getX() > 0.2 and abs( vtTool:getZ()) < 0.5) then
         dHeadBack = max( 90, 40 + 0.5 * dTDiam * sqrt( 1 - vtTool:getX() * vtTool:getX()))
      elseif abs( vtTool:getZ()) < 0.93 then
         if vtTool:getX() > 0 and abs( vtTool:getY()) < 0.2 and ptMax:getZ() > EMC.ZMIN + BD.VICE_MINH then
            dHeadBack = 180
         elseif vtTool:getX() > -0.05 then
            dHeadBack = 50 + 0.5 * dTDiam * sqrt( 1 - vtTool:getX() * vtTool:getX())
         elseif vtTool:getX() > -0.3 then
            dHeadBack = 250
         elseif vtTool:getX() > -0.707 then
            dHeadBack = 350
         elseif vtTool:getX() > -0.8667 then
            dHeadBack = 400
         else
            dHeadBack = 520
         end
      else
         dHeadBack = 50 + 0.5 * dTDiam * sqrt( 1 - vtTool:getX() * vtTool:getX())
      end
      -- per limiti corsa asse X1
      dHeadBack = max( dHeadBack, MinX1 + 1 - vtTool:getX() * ( MillOffs + dTLen))
   else
      if ( vtTool:getX() > -0.1 and vtArm:getX() > -0.1) or
         ( abs( vtTool:getX()) < 0.1 and abs( vtTool:getZ()) < 0.1) then
         dHeadBack = 130
      elseif ( vtTool:getX() > -0.1  and  vtArm:getX() > -0.95) then
         dHeadBack = 180
      elseif ( vtTool:getX() < -0.8) then
         dHeadBack = 375
      elseif ( vtTool:getX() < -0.75) then
         dHeadBack = 350
      elseif ( vtTool:getX() < -0.5) then
         dHeadBack = 350
      end
      if vtTool:getX() < -0.25 then
         dHeadBack = dHeadBack + max( dTLen - 130, 0) * abs( vtTool:getX())
      elseif vtTool:getX() < 0 then 
         dHeadBack = dHeadBack + ( dTLen + 180) * abs( vtTool:getX())
      end
      if vtTool:getX() > 0.866 then
         dHeadBack = 50
      elseif vtTool:getX() >= 0 and dZup > 0 then
         dHeadBack = max( 130, dHeadBack - dZup)
      end
      if abs( vtTool:getX()) < 0.5 and abs( vtTool:getZ()) > 0.259 and dZup < 0 then
         if vtArm:getX() < -0.259 then
            dHeadBack = 410
         else
            dHeadBack = EgtIf(  vtTool:getZ() > 0.966, 160, 280)
         end
      end
      -- per fresature longitudinali con utensile di fianco
      if abs( vtTool:getX()) < 0.1 and vtTool:getZ() < 0.707 and vtArm:getX() < -0.5 then
         dHeadBack = 400
      end
      -- per sega a catena di fianco
      if bChain and vtTool:getX() < 0.5 and vtTool:getZ() < 0.5 and vtArm:getX() < -0.5 then
         dHeadBack = max( dHeadBack, 510)
      end
      -- per fresa diretta quasi esattamente come X1+/- e con la testa non troppo nel pezzo
      if not bChain and abs( vtTool:getX()) < 0.017 and abs( vtTool:getZ()) < 0.017 and
         (( vtTool:getY() > 0 and ptHeadMin:getY() > 80) or ( vtTool:getY() < 0 and ptHeadMax:getY() < -EMC.WIDTHBEAM - 80)) then
         dHeadBack = 130
      end
      -- per limiti corsa asse X1
      if not bChain then
         dHeadBack = max( dHeadBack, MinX1 + 1 - vtTool:getX() * ( MillOffs + dTLen))
      else
         dHeadBack = max( dHeadBack, MinX1 + 1 - vtAux:getX() * ( MillOffs + dTDist) - vtTool:getX() * dTLen)
      end
   end
   -- Ingombro a destra
   local dDistFront = - ptMax:getX() - LoadT
   local dHeadFront = 350
   if bSaw then
      if vtTool:getX() < 0 and abs( vtTool:getY()) < 0.088 and abs( vtTool:getZ()) < 0.088 then
         dHeadFront = 50 + 0.5 * dTDiam * sqrt( 1 - vtTool:getX() * vtTool:getX())
      elseif abs( vtTool:getZ()) < 0.26 and abs( vtTool:getX()) < 0.35 then
         dHeadFront = EgtIf( vtArm:getX() > 0, 540, 350)
      elseif abs( vtTool:getZ()) < 0.26 and abs( vtTool:getX()) < 0.71 then
         if vtArm:getX() > 0 then
            dHeadFront = 350
         else
            dHeadFront = EgtIf( vtTool:getX() < 0, 50, 90) + 0.5 * dTDiam * sqrt( 1 - vtTool:getX() * vtTool:getX())
         end
      elseif ( vtTool:getX() < - 0.7 and abs( vtTool:getY()) < 0.2 and ptMax:getZ() > EMC.ZMIN + 0.9 * EMC.HEIGHTBEAM) then
         if vtTool:getZ() > 0 then
            dHeadFront = max( 50, 90 - 0.5 * dTDiam * sqrt( 1 - vtTool:getX() * vtTool:getX()))
         else
            dHeadFront = max( 50, 40 + 0.5 * dTDiam * sqrt( 1 - vtTool:getX() * vtTool:getX()))
         end
         if ptMax:getZ() < EMC.ZMIN + BD.VICE_MINH then
            dHeadFront = dHeadFront + BD.VICE_MINH
         end
      elseif ( vtTool:getX() < -0.2 and abs( vtTool:getZ()) < 0.5) then
         dHeadFront = max( 90, 40 + 0.5 * dTDiam * sqrt( vtTool:getY() * vtTool:getY() + vtTool:getZ() * vtTool:getZ()))
      elseif abs( vtTool:getZ()) < 0.93 then
         if vtTool:getX() < 0 and abs( vtTool:getY()) < 0.2 and ptMax:getZ() > EMC.ZMIN + BD.VICE_MINH then
            dHeadFront = 180
         elseif vtTool:getX() < 0.05 then
            dHeadFront = 50 + 0.5 * dTDiam * sqrt( 1 - vtTool:getX() * vtTool:getX())
         elseif vtTool:getX() < 0.3 then
            dHeadFront = 250
         elseif vtTool:getX() < 0.707 then
            dHeadFront = 350
         elseif vtTool:getX() < 0.8667 then
            dHeadFront = 450
         else
            dHeadFront = 480
         end
      else
         dHeadFront = 50 + 0.5 * dTDiam * sqrt( 1 - vtTool:getX() * vtTool:getX())
      end
      -- per limiti corsa asse X2
      dHeadFront = max( dHeadFront, -MaxX2 + 1 + vtTool:getX() * ( MillOffs + dTLen))
   else
      if ( vtTool:getX() < -0.5 and vtArm:getX() < 0.1) then
         dHeadFront = max( 50, dTDiam / 2 * abs( vtTool:getZ()) + 20)
      elseif ( vtTool:getX() < 0.1 and vtArm:getX() < 0.1) or
         ( abs( vtTool:getX()) < 0.1 and abs( vtTool:getZ()) < 0.1) then
         dHeadFront = 130
      elseif ( vtTool:getX() < 0.1  and  vtArm:getX() < 0.95) then
         dHeadFront = 180
      elseif ( vtTool:getX() > 0.5) then
         dHeadFront = 450
      end
      if vtTool:getX() > 0.25 then 
         dHeadFront = dHeadFront + max( dTLen - 130, 0) * vtTool:getX()
      elseif vtTool:getX() > 0 then 
         dHeadFront = dHeadFront + ( dTLen + 180) * vtTool:getX()
      end
      if vtTool:getX() < -0.866 then
         dHeadFront = 50
      elseif vtTool:getX() <= 0 and dZup > 0 then
         dHeadFront = max( 130, dHeadFront - dZup)
      end
      if abs( vtTool:getX()) < 0.5 and abs( vtTool:getZ()) > 0.259 and dZup < 0 then
         if vtArm:getX() > 0.259 then
            dHeadFront = 410
         else
            dHeadFront = EgtIf(  vtTool:getZ() > 0.966, 160, 280)
         end
      end
      -- per fresature longitudinali con utensile di fianco
      if abs( vtTool:getX()) < 0.1 and vtTool:getZ() < 0.707 and vtArm:getX() > 0.5 then
         dHeadFront = 400
      end
      -- per sega a catena di fianco
      if bChain and vtTool:getX() > -0.5 and vtTool:getZ() < 0.5 and vtArm:getX() > 0.5 then
         dHeadFront = max( dHeadFront, 510)
      end
      -- per fresa diretta quasi esattamente come X1+/- e con la testa non troppo nel pezzo
      if not bChain and abs( vtTool:getX()) < 0.017 and abs( vtTool:getZ()) < 0.017 and
         (( vtTool:getY() > 0 and ptHeadMin:getY() > 80) or ( vtTool:getY() < 0 and ptHeadMax:getY() < -EMC.WIDTHBEAM - 80)) then
         dHeadFront = 130
      end
      -- per limiti corsa asse X2
      if not bChain then
         dHeadFront = max( dHeadFront, -MaxX2 + 1 + vtTool:getX() * ( MillOffs + dTLen))
      else
         dHeadFront = max( dHeadFront, -MaxX2 + 1 + vtAux:getX() * ( MillOffs + dTDist) + vtTool:getX() * dTLen)
      end
   end
   -- Stampe debug
   EgtOutLog( '  Tdir=' .. tostring( vtTool) .. ' Adir=' .. tostring( vtArm) .. ' Zup=' .. EgtNumToString( dZup), 3)
   EgtOutLog( '  DistFront=' .. EgtNumToString( dDistFront) .. ' DistBack=' .. EgtNumToString( dDistBack) ..
              ' HeadFront=' .. EgtNumToString( dHeadFront) .. ' HeadBack=' .. EgtNumToString( dHeadBack), 3)
   -- Restituisco ingombri effettivi
   dHeadFront = dHeadFront + 30
   dHeadBack = dHeadBack + 30
   return ( dDistFront - dHeadFront), ( dDistBack - dHeadBack)
end

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

   -- Verifico lunghezza pezzo
   if EMC.LENGTHBEAM < MinJoin + MinOther + AGG_LOAD + EMC.HCING + EMC.HOVM then
      EgtOutLog( '  Error CLAMP -> pezzo troppo corto')
      EMC.ERR = 17
      return false
   end

   return true
end

---------------------------------------------------------------------
function GetLoadCmd( dPosT, dDistFront, dDistBack)
   --[L]
   local dMinDistBack= max( dDistBack, MinJoin + EgtIf( IsMid2Phase( EMC.PHASE) or IsEnd2Phase( EMC.PHASE), EMC.TCING, 0))
   local dNewX1Delta = max( EMC.LENGTHBEAM - dMinDistBack, MinOther + AGG_LOAD + EMC.HCING + EMC.HOVM)
   local dNewX2Delta = nil
   local dNewX1 = dPosT + TurnerOffs + dNewX1Delta
   local vCmd = {}
   EgtOutLog( '  *[L]', 1)
   -- [L-1]
   if dNewX1 - MaxX1 > 0 then
      dNewX1Delta = min( EMC.LENGTHBEAM - MinJoin + AGG_LOAD, MaxX1 - dPosT - TurnerOffs)
      EgtOutLog( '  *[L1]', 1)
   end
   --[L-2]
   if EMC.LENGTHBEAM - dNewX1Delta < MinJoin then
      dNewX1Delta = min( EMC.LENGTHBEAM - MinJoin + AGG_LOAD, MaxX1 - dPosT - TurnerOffs)
      EgtOutLog( '  *[L2]', 1)
   end
   -- Commento
   table.insert( vCmd, { 0, 'Loading'})
   -- risalita testa a Zmax
   vCmd = AddZmaxMove( vCmd)
   -- Apro entrambe le morse
   table.insert( vCmd, { 11, 0})
   table.insert( vCmd, { 12, 0})
   -- Sposto il carrello X1 per il carico
   table.insert( vCmd, { 2, 'X1', dPosT + dNewX1Delta, 'X2', ParkX2})
   -- Chiudo morsa X1
   table.insert( vCmd, { 11, EgtIf( EMC.LENGTHBEAM - dNewX1Delta < LenToPress, 1, 2)})
   -- confermo i nuovi parametri di aggancio
   table.insert( vCmd, { 21, dNewX1Delta, 0})
   -- Assegno stato corrente
   EMC.TPOS = dPosT
   EMC.X1DELTA = dNewX1Delta
   EMC.X2DELTA = nil
   -- Restituisco i comandi
   return vCmd
end -- SpecAdjustLoad [L]

---------------------------------------------------------------------
function GetMachiningStartingPoint( idPath)
   local idFirstObj = EgtGetFirstInGroup( idPath)
   -- se esiste l'oggetto, recupero la coordinata del primo punto
   if idFirstObj then
      local vAxes = EmtGetAxesPos( idFirstObj)
      if #vAxes > 0 then 
         return vAxes[1] 
      end
   end
   return nil
end

---------------------------------------------------------------------
function CalcCarriagesNewPositions( dDistFront, dDistBack)
   local MinFrontJoin = MinJoin + EMC.HCING + EMC.HOVM
   local MinBackJoin = MinJoin + EgtIf( IsMid2Phase( EMC.PHASE) or IsEnd2Phase( EMC.PHASE), EMC.TCING, 0)

   local dDistFrontEff = min( dDistFront, EMC.LENGTHBEAM - MinOther - EMC.TCING)
   local dDistBackEff = min( dDistBack, EMC.LENGTHBEAM - MinOther - EMC.HCING - EMC.HOVM)

   local dNewX1Delta = nil
   local dNewX2Delta = nil

   -- [A] se posso mettere solo carrello X1
   if dDistFrontEff < MinFrontJoin  and  dDistBackEff > MinBackJoin - GEO.EPS_SMALL then
      dNewX1Delta = EMC.LENGTHBEAM - dDistBackEff
      dNewX2Delta = nil
   -- [B] se altrimenti posso mettere entrambi i carrelli X1 e X2
   elseif dDistBackEff > MinBackJoin - GEO.EPS_SMALL  and dDistFrontEff > MinFrontJoin - GEO.EPS_SMALL then
      dNewX1Delta = EMC.LENGTHBEAM - dDistBackEff
      dNewX2Delta = dDistFrontEff
   -- [C] se altrimenti posso mettere solo carrello X2
   elseif dDistBackEff < MinBackJoin  and  dDistFrontEff > MinFrontJoin - GEO.EPS_SMALL then
      dNewX2Delta = dDistFrontEff
      dNewX1Delta = nil
   -- altrimenti errore
   else
      if EgtGetDebugLevel() < 3 then
         EgtOutLog( '  Dist/Min : Back=' .. EgtNumToString( dDistBackEff, 1) .. '/' .. EgtNumToString( MinBackJoin, 1) ..
                    ' Front=' .. EgtNumToString( dDistFrontEff, 1) .. '/' .. EgtNumToString( MinFrontJoin, 1))
      end
      EgtOutLog( '  Error CLAMP impossible')
      EMC.ERR = 18
      return nil
   end
   return true, dNewX1Delta, dNewX2Delta
end

---------------------------------------------------------------------
function GetCarriagesCmdFromMachEncumbrance( dDistFront, dDistBack)

   local bOk, dNewX1Delta, dNewX2Delta = CalcCarriagesNewPositions( dDistFront, dDistBack)

   if not bOk then
      return {}
   end

   local dPosT = EMC.TPOS
   local dX1Delta = EMC.X1DELTA
   local dX2Delta = EMC.X2DELTA
   local dNewPosT
   -- se sono già settati, non calcolo posizione finale
   if not EMC.X1DELTANEXT and not EMC.X2DELTANEXT then
      dNewPosT = GetMachiningStartingPoint( EMC.PATHID)
   end

   -- salvo valori finali
   EMC.X1DELTANEXT = dNewX1Delta
   EMC.X2DELTANEXT = dNewX2Delta

   -- se la posizione è già corretta, no nserve riposizionare
   local bCarriagesPosIsOK = true

   local vCmd = {}
   local bSpecialStartCarrInPos = false
   if IsStartPhase( EMC.PHASE) and EMC.X1DELTA and not EMC.X2DELTA then
      if EMC.X1DELTANEXT and EMC.X1DELTA > EMC.X1DELTANEXT and EMC.X2DELTA == nil and EMC.X2DELTANEXT == nil then
         bSpecialStartCarrInPos = true
      end
   end

   if not bSpecialStartCarrInPos then
      if ( EMC.X1DELTA == nil and EMC.X1DELTANEXT) or ( EMC.X1DELTA and EMC.X1DELTANEXT == nil) then
         bCarriagesPosIsOK = bCarriagesPosIsOK and false
      end
      if ( EMC.X1DELTA and EMC.X1DELTANEXT and abs( EMC.X1DELTA - EMC.X1DELTANEXT) > 1000 * GEO.EPS_SMALL) then
         bCarriagesPosIsOK = bCarriagesPosIsOK and false
      end
      if ( EMC.X2DELTA == nil and EMC.X2DELTANEXT) or ( EMC.X2DELTA and EMC.X2DELTANEXT == nil) then
         bCarriagesPosIsOK = bCarriagesPosIsOK and false
      end
      if ( EMC.X2DELTA and EMC.X2DELTANEXT and abs( EMC.X2DELTA - EMC.X2DELTANEXT) > 1000 * GEO.EPS_SMALL) then
         bCarriagesPosIsOK = bCarriagesPosIsOK and false
      end
   end


   if not bCarriagesPosIsOK then
      GetCarriagesRepositioningCmd( vCmd, dPosT, dX1Delta, dX2Delta, dNewPosT, dNewX1Delta, dNewX2Delta)
   end

   return vCmd

end

---------------------------------------------------------------------
function SpecCalcSplit( dLenRaw)
   local vCmd = {}
   EgtOutLog( '  *[S]', 1)
   local bSplit = ( EMC.X2DELTA ~= nil)
   local ParkT = LoadT
   table.insert( vCmd, { 0, EgtIf( EMC.X2DELTA, 'Split', 'Fall')})
   -- determino i grezzi da agganciare al carrello X1 (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, 'X1'})
      end
      nRawId = EgtGetNextRawPart( nRawId)
   end
   -- riporto il carrello X1 al carico con il resto della trave
   local dLDelta = EMC.X1DELTA - dLenRaw
   table.insert( vCmd, { 1, 'X1', ParkT + dLDelta})
   table.insert( vCmd, { 21, 0, EMC.X2DELTA or 0})
   -- imposto subito X1 non più attaccato al trave in lavoro
   EMC.X1DELTA = nil
   -- salvo posizione carrello X1 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, 'X1POS', ParkT + dLDelta)
   end
   -- salvo posizione grezzo rimasto e posizione carrello X1 nella disposizione iniziale del pezzo succ (prossima fase dispari)
   local NextDispId = EgtGetPhaseDisposition( nNextOddPhase)
   if NextDispId then
      EgtSetInfo( NextDispId, 'TPOS', ParkT)
      EgtSetInfo( NextDispId, 'X1POS', ParkT + dLDelta)
   end
   return vCmd
end

---------------------------------------------------------------------
function SpecCalcSplitRot( dLenRaw)
   local vCmd = {}
   EgtOutLog( '  *[SR]', 1)
   table.insert( vCmd, { 0, 'SplitRot'})
   -- determino i grezzi da agganciare al carrello X1 (sono quelli presenti nella fase successiva dispari)
   local vRaw = {}
   local nNextOddPhase = GetNextStartOrRestPhase( EMC.PHASE)
   local nRawId = EgtGetFirstRawPart()
   while nRawId do
      if EgtVerifyRawPartPhase( nRawId, nNextOddPhase) then
         table.insert( vRaw, nRawId)
      end
      nRawId = EgtGetNextRawPart( nRawId)
   end
   for _, nId in ipairs( vRaw) do
      table.insert( vCmd, { 31, nId, 'X1'})
   end
   -- riporto il carrello X1 al carico con il resto della trave
   local dLDelta = EMC.X1DELTA - dLenRaw
   table.insert( vCmd, { 1, 'X1', LoadT + TurnerOffs + dLDelta})
   table.insert( vCmd, { 21, 0, EMC.X2DELTA})
   -- imposto subito X1 non più attaccato alla trave in lavoro
   EMC.X1DELTA = nil
   -- apro il carrello X1
   table.insert( vCmd, { 11, 0})
   -- sgancio i grezzi dal carrello X1
   for _, nId in ipairs( vRaw) do
      table.insert( vCmd, { 31, nId, ''})
   end
   -- lo porto in parcheggio
   table.insert( vCmd, { 1, 'X1', ParkX1})
   -- salvo posizione grezzo rimasto nella disposizione iniziale del pezzo succ (prossima fase dispari)
   local NextDispId = EgtGetPhaseDisposition( nNextOddPhase)
   if NextDispId then
      EgtSetInfo( NextDispId, 'TPOS', LoadT)
   end
   return vCmd
end

---------------------------------------------------------------------
function SpecCalcUnload()
   local vCmdPre = {}
   EgtOutLog( '  *[U]', 1)
   -- Se pinza X1 chiusa , devo effettuare uno scambio
   if EMC.X1DELTA then
      -- determino posizione testa trave
      local nLastEntId = EgtGetLastInGroup( EMC.PATHID)
      local vAxes = EmtGetAxesPos( nLastEntId)
      if #vAxes > 0 then EMC.TPOS = vAxes[1] end
      local dX2CurrPos = EgtIf( EMC.X2DELTA, EMC.X2DELTA, EMC.TPOS + MaxX2)
      local dX2LastPos = max( ( MinX2 + ( EMC.LENGTHBEAM - UnloadT)), EMC.X1DELTA - ( MinX1 - MaxX2))
      EMC.X1DELTANEXT = nil
      EMC.X2DELTANEXT = dX2LastPos
      -- effettuo scambio
      vCmdPre = GetCarriagesRepositioningCmd( vCmdPre, EMC.TPOS, EMC.X1DELTA, dX2CurrPos, nil, nil, dX2LastPos)
      if EMC.ERR ~= 0 then
         return {}
      end
      -- recupero nuova posizione carrelli
      SpecSetCarrPosFromCmds( vCmdPre)
      EgtOutLog( '  *[U1]', 1)
   end
   local vCmd = {}
   -- Tipo di scarico
   local bStdUl = ( MaxUnloadLen < 1 or EMC.LENGTHBEAM - EMC.HOVM < MaxUnloadLen + 1)
   -- Commento
   table.insert( vCmd, { 0, 'Unloading', EgtIf( bStdUl, 'Unloading', 'Manual Unloading')})
   -- risalita testa a Zmax
   -- vCmd = AddZmaxMove( vCmd)
   -- Se pinza X1 chiusa, la apro
   if EMC.X1DELTA then
      table.insert( vCmd, { 11, 0})
   end
   -- Se non supero la lunghezza massima di scarico, sposto il pezzo in posizione di scarico
   if bStdUl then
      local dFinT = EgtIf( EMC.LENGTHBEAM < MaxLenSmT, UnloadSmT, UnloadT) - EMC.LENGTHBEAM
      local dFinX2 = dFinT + EMC.X2DELTA
      table.insert( vCmd, { 2, 'T', dFinT, 'X2', dFinX2})
   else
      table.insert( vCmd, { 1, 'X2', MaxX2})
   end
   -- apro la morsa
   table.insert( vCmd, { 12, 0})
   -- riporto il carrello in home
   table.insert( vCmd, { 1, 'X2', ParkX2})

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

   return vCmd
end

---------------------------------------------------------------------
function SpecCalcPreRot()
   local vCmdPre = {}
   EgtOutLog( '  *[PR]', 1)
   -- Se pinza X2 chiusa , devo effettuare uno scambio
   if EMC.X2DELTA  then
      -- determino posizione testa trave
      local nLastEntId = EgtGetLastInGroup( EMC.PATHID)
      local vAxes = EmtGetAxesPos( nLastEntId)
      if #vAxes > 0 then EMC.TPOS = vAxes[1] end
      local dX1CurrPos = EgtIf( EMC.X1DELTA, EMC.X1DELTA, nil)
      local dX1LastPos = min ( MaxX1 - LoadT, EMC.X2DELTA + ( MinX1 - MaxX2))
      EMC.X1DELTANEXT = dX1LastPos
      EMC.X2DELTANEXT = nil
      -- effettuo scambio
      vCmdPre = GetCarriagesRepositioningCmd( vCmdPre, EMC.TPOS, dX1CurrPos, EMC.X2DELTA, nil, dX1LastPos, nil)
      -- recupero nuova posizione carrelli
      SpecSetCarrPosFromCmds( vCmdPre)
      EgtOutLog( '  *[PR1]', 1)
   end
   -- porto il pezzo alla zona di rotazione con il carro X1
   local vCmd = {}
   -- Commento
   table.insert( vCmd, { 0, 'Pre-Rotation'})
   -- risalita testa a Zmax
   vCmd = AddZmaxMove( vCmd)
   -- Se pinza X2 chiusa, la apro
   if EMC.X2DELTA then
      table.insert( vCmd, { 12, 0})
   end
   -- riporto la trave al carico
   local RotT = LoadT + TurnerOffs - EMC.HOVM
   table.insert( vCmd, { 2, 'X1', RotT + EMC.X1DELTA, 'T', RotT})
   -- apro la morsa
   table.insert( vCmd, { 11, 0})
   -- riporto il carrello in home
   table.insert( vCmd, { 1, 'X1', ParkX1})

   -- dichiaro fine movimenti
   table.insert( vCmd, { 0, 'Move_End'})

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

   return vCmd
end

---------------------------------------------------------------------
local function CalcCharStatus( sType, dDelta)
   -- se per carrello X1
   if sType == 'X1' then
      return EgtIf( EMC.LENGTHBEAM - dDelta < LenToPress, 1, 2)
   -- altrimenti per carrello X2
   else
      return EgtIf( dDelta < LenToPress, 1, 2)
   end
end

---------------------------------------------------------------------
function GetCarriagesRepositioningCmd( vCmd, dTPosI, dX1DeltaI, dX2DeltaI, dTPosF, dX1DeltaF, dX2DeltaF)
   local dX1PosA, dX2PosA, dTPosA
   local dGainOnReclamping = BD.GAIN_RECLAMPING or 1000

   -- se primo scambio
   local MyMinX1 = MinX1

   -- se pinza non in presa, setto offset minimo in base all'altra che sta pinzando
   if not dX1DeltaI then
      dX1DeltaI = dX2DeltaI + ( MyMinX1 - MaxX2)
   end
   if not dX2DeltaI then
      dX2DeltaI = dX1DeltaI - ( MyMinX1 - MaxX2)
   end
   if not dX1DeltaF then
      dX1DeltaF = dX2DeltaF + ( MyMinX1 - MaxX2)
   end
   if not dX2DeltaF then
      dX2DeltaF = dX1DeltaF - ( MyMinX1 - MaxX2)
   end

   -- se non c'è posizione finale, è lo scarico. Allora setto al minimo della morsa allo scarico.
   if not dTPosF then
      dTPosF = MaxX2 - dX2DeltaF
   end

   -- se il pinzaggio deve essere invertito forzo entrata nei riposizionamenti
   -- altrimenti posizioni iniziali e finali sono coincidenti dato che la pinza non in presa viene posizionata alla distanza minima a quella in presa
   local bInvertClamping = not EMC.PILGRIMSTEP and EMC.X1DELTA ~= EMC.X1DELTANEXT and EMC.X2DELTA ~= EMC.X2DELTANEXT
   -- verifico che le morse non sono in posizione
   if ( abs( dX1DeltaF - dX1DeltaI) > 10 * GEO.EPS_SMALL  or  abs( dX2DeltaF - dX2DeltaI) > 10 * GEO.EPS_SMALL or bInvertClamping) and
      ( EMC.X1DELTA ~= EMC.X1DELTANEXT or EMC.X2DELTA ~= EMC.X2DELTANEXT) then
      if #vCmd == 0 then
         table.insert( vCmd, { 0, 'Clamp repositioning'})
         -- risalita testa a Zmax
        -- vCmd = AddZmaxMove( vCmd)
      end

      -- si ribadisce il pinzaggio
      if EMC.X1DELTA then
         table.insert( vCmd, { 11, 1})
      end
      if EMC.X2DELTA then
         table.insert( vCmd, { 12, 1})
      end

      -- ricavo posizione delle morse nelle corse
      local dX1PosI = max( MyMinX1, dTPosI + dX1DeltaI)
      local dX2PosI = min( MaxX2, dTPosI + dX2DeltaI)

      -- calcolo eventuale massimo riposizionamento con passo del pellegrino.
      --    al primo step si calcolano posizioni attuali delle morse
      --    al secondo step si considerano le corse massime
      local dMaxMovePilgrimStepDoubleClamp, dMaxMovePilgrimStepSingleClampX1, dMaxMovePilgrimStepSingleClampX2
      if not EMC.PILGRIMSTEP then
         dMaxMovePilgrimStepDoubleClamp = MaxX1 - dX1PosI - MinX2 + dX2PosI
         dMaxMovePilgrimStepSingleClampX1 = MaxX1 - dX1PosI
         dMaxMovePilgrimStepSingleClampX2 = - MinX2 + dX2PosI
      else
         dMaxMovePilgrimStepDoubleClamp = MaxX1 - MinX1 - MinX2 + MaxX2
         dMaxMovePilgrimStepSingleClampX1 = MaxX1 - MinX1
         dMaxMovePilgrimStepSingleClampX2 = - MinX2 + MaxX2
      end

      -- calcolo delta spostamento trave
      local dDeltaBeam = dTPosI - dTPosF  -- se positivo, la trave si sposta dal carico allo scarico
      local dBeamMove = 0

      -- PRIMO CASO ->  SCAMBIO DIRETTO : X2 IN POSIZIONE FINALE
      -- X2 può andare in posizione direttamente se :
      --    * rispetta interasse minimo con X1
      --    * deve effettivamente spostarsi
      --    * non si arriva da gestione passo del pellegrino
      --    * se riesce a raggiungere la posizione con le corse a disposizione per come sono posizionate le morse attualmente
      if ( dX1DeltaI - dX2DeltaF) + 10 * GEO.EPS_SMALL >= MyMinX1 - MaxX2 and
         ( abs( dX2DeltaF - dX2DeltaI) > 10 * GEO.EPS_SMALL or not EMC.X2DELTA) and
         not EMC.PILGRIMSTEP and
         ( dX2DeltaF - dX2DeltaI > MinX2 - dX2PosI + MyMinX1 - dX1PosI) then
         table.insert( vCmd, { 0, 'Direct-X2-X1'})
         -- se l'altra morsa non era in presa, vado a pinzare il pezzo
         if not EMC.X1DELTA then
            dX1PosA = MyMinX1
            dTPosA = MyMinX1 - dX1DeltaI
            dX2PosA = dTPosA + dX2DeltaI
            table.insert( vCmd, { 3, 'X1', dX1PosA , 'T', dTPosA, 'X2', dX2PosA})
            dTPosI = dTPosA
            dX2PosI = dX2PosA
            table.insert( vCmd, { 11, 1})
         end

         -- se spostando morsa diretto esco dalle corse, calcolo di quanto devo muovere la trave (cioè l'altra morsa) come minimo
         -- se non era ingaggiata, vado al suo minimo
         if not EMC.X2DELTA then
            dBeamMove = -( MaxX2 - dX2DeltaF - dTPosI)
         -- se spostamento verso lo scarico
         elseif ( dX2PosI + ( dX2DeltaF - dX2DeltaI)) < MinX2 then
            dBeamMove = dX2PosI + ( dX2DeltaF - dX2DeltaI) - MinX2
         -- se spostamento verso il carico
         elseif ( dX2PosI + ( dX2DeltaF - dX2DeltaI)) > MaxX2 then
            dBeamMove =  dX2PosI + ( dX2DeltaF - dX2DeltaI) - MaxX2
         -- se è FASTCLAMPING attivo, si suddivide il movimento 
         elseif BD.FASTCLAMPING then
            -- se la trave si sposta dal carico verso lo scarico
            if dTPosI - dTPosF > 0 then
               dBeamMove = min( -( dTPosF - dTPosI), -( MyMinX1 - dX1PosI), -( dX2DeltaI - dX2DeltaF))
            else
               dBeamMove = max( -( dTPosF - dTPosI), -( MyMinX1 - dX1PosI))
            end
            dBeamMove = dBeamMove / 2
         -- altrimenti la trave resta ferma e la pinza 1 va in presa (si limita lo scivolamento del pezzo)
         else
            ;
         end
         -- se serve un grande spostamento e si sta pinzando poco. X1 trascinatore pinza poco e X2 si sposta per prima per recuperare
         if not BD.FASTCLAMPING and abs( dBeamMove) > 2000 and dDeltaBeam < 0 and EMC.LENGTHBEAM - dX1DeltaI < max( 550, (MinJoin * 2)) then
            table.insert( vCmd, { 12, 0})
            dX2PosA = dX2PosI - dGainOnReclamping
            -- sposto il carrello X2 di 1000mm
            table.insert( vCmd, { 1, 'X2', dX2PosA})
            table.insert( vCmd, { 12, 1})
            -- apro morsa X1 e recupero i 1000mm
            table.insert( vCmd, { 11, 0})
            dX2PosA = dX2PosI
            dX1PosA = dX1PosI 
            dTPosA = dTPosI + dGainOnReclamping
            table.insert( vCmd, { 3, 'X1', dX1PosA , 'T', dTPosA, 'X2', dX2PosA})
            dTPosI = dTPosA
            dX2PosI = dX2PosA
            dX2DeltaI = dX2DeltaI - dGainOnReclamping
            dX1DeltaI = dX1DeltaI - dGainOnReclamping
            table.insert( vCmd, { 11, 1})
            dBeamMove = dBeamMove + dGainOnReclamping
         end

         table.insert( vCmd, { 12, 0})

         dTPosA = dTPosI - dBeamMove
         dX1PosA = dTPosA + dX1DeltaI
         dX2PosA = dTPosA + dX2DeltaF
         table.insert( vCmd, { 3, 'X1', dX1PosA , 'T', dTPosA, 'X2', dX2PosA})

        -- la morsa allo scarico è adesso in posizione
         dX2DeltaI = dX2DeltaF
         dTPosI = dTPosA

        -- blocco la morsa
         table.insert( vCmd, { 12, CalcCharStatus( 'X2', dX2DeltaF)})

         -- imposto i nuovi parametri di aggancio
         if EMC.X1DELTANEXT and EMC.X2DELTANEXT  then
            -- porto assi alla loro posizione finale
            if abs( dX1DeltaF - dX1DeltaI) > 10 * GEO.EPS_SMALL then
               table.insert( vCmd, { 11, 0})
               dTPosA = dTPosF
               dX1PosA = dTPosF + dX1DeltaF
               dX2PosA = dTPosF + dX2DeltaF
               table.insert( vCmd, { 3, 'X1', dX1PosA , 'T', dTPosA, 'X2', dX2PosA})
               table.insert( vCmd, { 11, CalcCharStatus( 'X1', dX1DeltaF)})
               -- la morsa al carico è adesso in posizione
               dX1DeltaI = dX1DeltaF
            end
            table.insert( vCmd, { 21, dX1DeltaF, dX2DeltaF})
         elseif EMC.X1DELTANEXT then
            if abs( dX1DeltaF - dX1DeltaI) > 10 * GEO.EPS_SMALL then
               table.insert( vCmd, { 11, 0})
               dX1PosA = max( MyMinX1, dTPosI + dX1DeltaF)
               dTPosA = dX1PosA - dX1DeltaF
               dX2PosA = dTPosA + dX2DeltaF
               table.insert( vCmd, { 3, 'X1', dX1PosA , 'T', dTPosA, 'X2', dX2PosA})
               table.insert( vCmd, { 11, CalcCharStatus( 'X1', dX1DeltaF)})
               -- la morsa al carico è adesso in posizione
               dX1DeltaI = dX1DeltaF
            end
            table.insert( vCmd, { 12, 0})
            -- sposto il carrello X2 in parcheggio
            table.insert( vCmd, { 1, 'X2', ParkX2})
            table.insert( vCmd, { 21, dX1DeltaF, 0})
         else
            table.insert( vCmd, { 11, 0})
            table.insert( vCmd, { 1, 'X1', ParkX1})
            table.insert( vCmd, { 21, 0, dX2DeltaF})
         end

         -- movimento terminato
         dTPosI, dX1DeltaI, dX2DeltaI = dTPosF, dX1DeltaF, dX2DeltaF
         EMC.X1DELTA = EMC.X1DELTANEXT
         EMC.X2DELTA = EMC.X2DELTANEXT

      -- SECONDO CASO ->  SCAMBIO DIRETTO : X1 IN POSIZIONE FINALE
      -- X1 può andare in posizione direttamente se :
      --    * rispetta interasse minimo con X1
      --    * deve effettivamente spostarsi
      --    * non si arriva da gestione passo del pellegrino
      --    * se riesce a raggiungere la posizione con le corse a disposizione per come sono posizionate le morse attualmente
      elseif ( dX1DeltaF - dX2DeltaI) + 10 * GEO.EPS_SMALL >= MyMinX1 - MaxX2 and
             ( abs( dX1DeltaF - dX1DeltaI) > 10 * GEO.EPS_SMALL or not EMC.X1DELTA) and
             not EMC.PILGRIMSTEP and
             ( dX1DeltaF - dX1DeltaI < MaxX1 - dX1PosI + MaxX2 - dX2PosI) then
         table.insert( vCmd, { 0, 'Direct-X1-X2'})
         -- se l'altra morsa non era in presa, vado a pinzare il pezzo
         if not EMC.X2DELTA then
            dX2PosA = MaxX2
            dTPosA = MaxX2 - dX2DeltaI
            dX1PosA = dTPosA + dX1DeltaI
            table.insert( vCmd, { 3, 'X1', dX1PosA , 'T', dTPosA, 'X2', dX2PosA})
            dTPosI = dTPosA
            dX1PosI = dX1PosA
            table.insert( vCmd, { 12, 1})
         end

         -- se spostando morsa diretto esco dalle corse, calcolo di quanto devo muovere la trave (cioè l'altra morsa) come minimo
         -- se non era ingaggiata, vado al suo minimo
         if not EMC.X1DELTA then
            dBeamMove = - ( dX1DeltaF - MyMinX1 + dTPosI)
         -- se spostamento verso lo scarico
         elseif ( dX1PosI + ( dX1DeltaF - dX1DeltaI)) < MyMinX1 then
            dBeamMove = -( dX1PosI + ( dX1DeltaF - dX1DeltaI) - MyMinX1)
         -- se spostamento verso il carico
         elseif ( dX1PosI + ( dX1DeltaF - dX1DeltaI)) > MaxX1 then
            dBeamMove =  -( dX1PosI + ( dX1DeltaF - dX1DeltaI) - MaxX1)
         -- se è FASTCLAMPING attivo, si suddivide il movimento 
         elseif BD.FASTCLAMPING then
            -- se la trave si sposta dal carico verso lo scarico
            if dTPosI - dTPosF > 0 then
               dBeamMove = max( ( dTPosF - dTPosI), ( MinX2 - dX2PosI), ( dX1DeltaI - dX1DeltaF))
            else
               dBeamMove = min( ( dTPosF - dTPosI), ( MaxX2 - dX2PosI))
            end
            dBeamMove = dBeamMove / 2
         -- altrimenti la trave resta ferma e la pinza 1 va in presa (si limita lo scivolamento del pezzo)
         else
            ;
         end

         -- se serve un grande spostamento e si sta pinzando poco. X2 trascinatore pinza poco e X1 si sposta per prima per recuperare
         if not BD.FASTCLAMPING and abs( dBeamMove) > 2000 and dDeltaBeam > 0 and dX2DeltaI < max( 550, (MinJoin * 2)) then
            table.insert( vCmd, { 11, 0})
            dX1PosA = dX1PosI + dGainOnReclamping
            -- sposto il carrello X2 di 1000mm
            table.insert( vCmd, { 1, 'X1', dX1PosA})
            table.insert( vCmd, { 11, 1})
            -- apro morsa X1 e recupero i 1000mm
            table.insert( vCmd, { 12, 0})
            dX2PosA = dX2PosI
            dX1PosA = dX1PosI 
            dTPosA = dTPosI - dGainOnReclamping
            table.insert( vCmd, { 3, 'X1', dX1PosA , 'T', dTPosA, 'X2', dX2PosA})
            dTPosI = dTPosA
            dX2PosI = dX2PosA
            dX2DeltaI = dX2DeltaI + dGainOnReclamping
            dX1DeltaI = dX1DeltaI + dGainOnReclamping
            table.insert( vCmd, { 12, 1})
            dBeamMove = dBeamMove - dGainOnReclamping
         end

         table.insert( vCmd, { 11, 0})

         dTPosA = dTPosI + dBeamMove
         dX1PosA = dTPosA + dX1DeltaF
         dX2PosA = dTPosA + dX2DeltaI
         table.insert( vCmd, { 3, 'X1', dX1PosA , 'T', dTPosA, 'X2', dX2PosA})

        -- la morsa al carico è adesso in posizione
         dX1DeltaI = dX1DeltaF
         dTPosI = dTPosA

        -- blocco la morsa
         table.insert( vCmd, { 11, CalcCharStatus( 'X1', dX1DeltaF)})

         -- imposto i nuovi parametri di aggancio
         if EMC.X1DELTANEXT and EMC.X2DELTANEXT  then
            if abs( dX2DeltaF - dX2DeltaI) > 10 * GEO.EPS_SMALL then
               table.insert( vCmd, { 12, 0})
               dTPosA = dTPosF
               dX1PosA = dTPosF + dX1DeltaF
               dX2PosA = dTPosF + dX2DeltaF
               table.insert( vCmd, { 3, 'X1', dX1PosA , 'T', dTPosA, 'X2', dX2PosA})
               table.insert( vCmd, { 12, CalcCharStatus( 'X2', dX2DeltaF)})
               -- la morsa al carico è adesso in posizione
               dX2DeltaI = dX2DeltaF
            end
            table.insert( vCmd, { 21, dX1DeltaF, dX2DeltaF})
         elseif EMC.X2DELTANEXT then
            if abs( dX2DeltaF - dX2DeltaI) > 10 * GEO.EPS_SMALL then
               table.insert( vCmd, { 12, 0})
               dX2PosA = min( MaxX2, dTPosI + dX2DeltaF)
               dTPosA = dX2PosA - dX2DeltaF
               dX1PosA = dTPosA + dX1DeltaF
               table.insert( vCmd, { 3, 'X1', dX1PosA , 'T', dTPosA, 'X2', dX2PosA})
               table.insert( vCmd, { 12, CalcCharStatus( 'X2', dX2DeltaF)})
               -- la morsa al carico è adesso in posizione
               dX2DeltaI = dX2DeltaF
            end
            table.insert( vCmd, { 11, 0})
            -- sposto il carrello X1 in parcheggio
            table.insert( vCmd, { 1, 'X1', ParkX1})
            table.insert( vCmd, { 21, 0, dX2DeltaF})
         else
            table.insert( vCmd, { 12, 0})
            -- sposto il carrello X2 in parcheggio
            table.insert( vCmd, { 1, 'X2', ParkX2})
            table.insert( vCmd, { 21, dX1DeltaF, 0})
         end
         -- movimento terminato
         dTPosI, dX1DeltaI, dX2DeltaI = dTPosF, dX1DeltaF, dX2DeltaF
         EMC.X1DELTA = EMC.X1DELTANEXT
         EMC.X2DELTA = EMC.X2DELTANEXT

      -- TERZO CASO -> PASSO DEL PELLEGRINO
      -- non è possibile fare scambio diretto.
      -- le morse vengono compattate e allargate sfruttando tutta la corsa X, massimizzando spostamento trave 
      else
         EMC.PILGRIMSTEP = false
         local bUpdateValues = false
         table.insert( vCmd, { 0, 'Pilgrim Cycle'})
         -- compatto al centro
         if not EMC.X1DELTA then
            table.insert( vCmd, { 0, 'Compact X1'})
            table.insert( vCmd, { 11, 0})
            dTPosA = MaxX2 - dX2DeltaI
            dX2PosA = MaxX2
            dX1PosA = MyMinX1
            table.insert( vCmd, { 3, 'X1', dX1PosA , 'T', dTPosA, 'X2', dX2PosA})
            table.insert( vCmd, { 11, 1})
            dX1DeltaI = dX2DeltaI - MaxX2 + MyMinX1
            EMC.X1DELTA = dX1DeltaI
            bUpdateValues = true
         end
         if not EMC.X2DELTA then
            table.insert( vCmd, { 0, 'Compact X2'})
            table.insert( vCmd, { 12, 0})
            dTPosA = MyMinX1 - dX1DeltaI
            dX2PosA = MaxX2
            dX1PosA = MyMinX1
            table.insert( vCmd, { 3, 'X1', dX1PosA , 'T', dTPosA, 'X2', dX2PosA})
            table.insert( vCmd, { 12, 1})
            dX2DeltaI = dX1DeltaI + MaxX2 - MyMinX1
            EMC.X2DELTA = dX2DeltaI
            bUpdateValues = true
         end

         -- se si sono compattate le morse, bisogna aggiornare le posizioni e i massimi recuperi
         if bUpdateValues then
            dTPosI = dTPosA
            dX1PosI = dX1PosA
            dX2PosI = dX2PosA
            dMaxMovePilgrimStepDoubleClamp = MaxX1 - MinX1 - MinX2 + MaxX2
            dMaxMovePilgrimStepSingleClampX1 = MaxX1 - MinX1
            dMaxMovePilgrimStepSingleClampX2 = - MinX2 + MaxX2
         end

         -- aggiorno delta mancante
         dDeltaBeam = dTPosI - dTPosF

         -- a questo punto entrambe le pinze sono in presa sul pezzo.
         -- trave si muove dal carico allo scarico
         -- Il trascinatore si decide in base alla direzione di movimento trave
         -- trascino con morsa allo scarico. Obiettivo mandare X1 in posizione per prima
         if dDeltaBeam > 0 then
            -- se serve un grande spostamento e si sta pinzando poco. X1 trascinatore pinza poco e X2 si sposta per prima per recuperare
            if not BD.FASTCLAMPING and abs( dDeltaBeam) > 2000 and dX2DeltaI < max( 550, (MinJoin * 2)) then
               table.insert( vCmd, { 11, 0})
               dX1PosA = dX1PosI + dGainOnReclamping
               -- sposto il carrello X2 di 1000mm
               table.insert( vCmd, { 1, 'X1', dX1PosA})
               table.insert( vCmd, { 11, 1})
               -- apro morsa X1 e recupero i 1000mm
               table.insert( vCmd, { 12, 0})
               dX2PosA = dX2PosI
               dX1PosA = dX1PosI 
               dTPosA = dTPosI - dGainOnReclamping
               table.insert( vCmd, { 3, 'X1', dX1PosA , 'T', dTPosA, 'X2', dX2PosA})
               dTPosI = dTPosA
               dX2PosI = dX2PosA
               dX2DeltaI = dX2DeltaI + dGainOnReclamping
               dX1DeltaI = dX1DeltaI + dGainOnReclamping
               table.insert( vCmd, { 12, 1})
            end

            -- apro morsa al carico
            table.insert( vCmd, { 11, 0})
            -- devo recuperare più delle corse disponibili di entrambi i carrelli
            if abs( dX1DeltaI - dX1DeltaF) > dMaxMovePilgrimStepDoubleClamp then
               dX1PosA = MaxX1
               dX2PosA = MinX2
               dTPosA = dTPosI - dX2PosI + MinX2
               table.insert( vCmd, { 3, 'X1', dX1PosA , 'T', dTPosA, 'X2', dX2PosA})
               table.insert( vCmd, { 11, 1})
               table.insert( vCmd, { 12, 0})
               -- nuova posizione della morsa al carico dopo riposizionamento
               dX1DeltaI = MaxX1 - dTPosA
               dTPosI = dTPosA
               EMC.X1DELTA = dX1DeltaI
               EMC.X2DELTA = nil
               EMC.PILGRIMSTEP = true
               -- mi serve riposizonare di nuovo, quindi richiamo funzione in modo ricorsivo
               vCmd = GetCarriagesRepositioningCmd( vCmd, dTPosI, dX1DeltaI, dX2DeltaI, dTPosF, dX1DeltaF, dX2DeltaF)
            -- recupero una corsa massima e l'altra solo di quello che serve
            elseif abs( dX1DeltaI - dX1DeltaF) > dMaxMovePilgrimStepSingleClampX2 then
               -- suddivido il movimento su entrambe le morse
               local dTotMove = ( ( dX1DeltaF + dX2DeltaF) / 2 + ( dX1DeltaI + dX2DeltaI) / 2) / 2
               -- morsa X1 arriva in posizione finale
               dX2PosA = EgtClamp( MaxX2 - dTotMove, MaxX2, MinX2)
               dTPosA = dTPosI - dX2PosI + dX2PosA
               dX1PosA = dX1DeltaF + dTPosA
               table.insert( vCmd, { 3, 'X1', dX1PosA , 'T', dTPosA, 'X2', dX2PosA})
               table.insert( vCmd, { 11, CalcCharStatus( 'X1', dX1DeltaF)})
               table.insert( vCmd, { 12, 0})
               dX1DeltaI = dX1DeltaF
               dTPosI = dTPosA
               EMC.X1DELTA = dX1DeltaF
               EMC.X2DELTA = nil
               -- mi serve riposizonare di nuovo, quindi richiamo funzione in modo ricorsivo
               vCmd = GetCarriagesRepositioningCmd( vCmd, dTPosI, dX1DeltaI, dX2DeltaI, dTPosF, dX1DeltaF, dX2DeltaF)
            -- non serve recuperare al massimo della corsa, suddivido movimento in due
            else
               -- suddivido il movimento su entrambe le morse
               local dTotMove = ( dX1DeltaF - dX1DeltaI) / 2

               if abs( dTotMove) > 10 * GEO.EPS_SMALL then
                  dX1PosA = dTPosI + dX1DeltaI + dTotMove
                  dX2PosA = dTPosI + dX2DeltaI - dTotMove
                  dTPosA = dTPosI - dTotMove
                  table.insert( vCmd, { 3, 'X1', dX1PosA , 'T', dTPosA, 'X2', dX2PosA})
                  table.insert( vCmd, { 11, CalcCharStatus( 'X1', dX1DeltaF)})
                  table.insert( vCmd, { 12, 0})

                  -- aggiorno nuova posizione della morsa al carico dopo riposizionamento
                  dX1DeltaI = dX1DeltaF ;

                  if not EMC.X2DELTANEXT then
                     dTPosA = dTPosF
                     dX1PosA = dTPosF + dX1DeltaF
                     dX2PosA = ParkX2
                  elseif EMC.X1DELTANEXT then
                     dTPosA = dTPosF
                     dX1PosA = dTPosF + dX1DeltaF
                     dX2PosA = dTPosF + dX2DeltaF
                  else
                     dX1PosA = MyMinX1
                     dTPosA = MyMinX1 - dX1DeltaF
                     dX2PosA = dTPosA + dX2DeltaF
                  end
                  table.insert( vCmd, { 3, 'X1', dX1PosA , 'T', dTPosA, 'X2', dX2PosA})
                  dTPosI, dX1DeltaI, dX2DeltaI = dTPosF, dX1DeltaF, dX2DeltaF
                  EMC.X1DELTA = EMC.X1DELTANEXT
                  EMC.X2DELTA = EMC.X2DELTANEXT
               end

               -- imposto i nuovi parametri di aggancio
               if EMC.X1DELTANEXT and EMC.X2DELTANEXT  then
                  table.insert( vCmd, { 12, CalcCharStatus( 'X2', dX2DeltaF)})
                  table.insert( vCmd, { 21, dX1DeltaF, dX2DeltaF})
               elseif EMC.X1DELTANEXT then
                  table.insert( vCmd, { 21, dX1DeltaF, 0})
               else
                  table.insert( vCmd, { 12, CalcCharStatus( 'X2', dX2DeltaF)})
                  table.insert( vCmd, { 11, 0})
                  -- sposto il carrello X1 in parcheggio
                  table.insert( vCmd, { 1, 'X1', ParkX1})
                  table.insert( vCmd, { 21, 0, dX2DeltaF})
               end
            end
         -- trave si muove dallo scarico verso il carico. Obiettivo mandare X2 in posizione per prima
         else
            -- se serve un grande spostamento e si sta pinzando poco. X1 trascinatore pinza poco e X2 si sposta per prima per recuperare
            if not BD.FASTCLAMPING and abs( dDeltaBeam) > 2000 and EMC.LENGTHBEAM - dX1DeltaI < max( 550, (MinJoin * 2)) then
               table.insert( vCmd, { 12, 0})
               dX2PosA = dX2PosI - dGainOnReclamping
               -- sposto il carrello X2 di 1000mm
               table.insert( vCmd, { 1, 'X2', dX2PosA})
               table.insert( vCmd, { 12, 1})
               -- apro morsa X1 e recupero i 1000mm
               table.insert( vCmd, { 11, 0})
               dX2PosA = dX2PosI
               dX1PosA = dX1PosI 
               dTPosA = dTPosI + dGainOnReclamping
               table.insert( vCmd, { 3, 'X1', dX1PosA , 'T', dTPosA, 'X2', dX2PosA})
               dTPosI = dTPosA
               dX2PosI = dX2PosA
               dX2DeltaI = dX2DeltaI - dGainOnReclamping
               dX1DeltaI = dX1DeltaI - dGainOnReclamping
               table.insert( vCmd, { 11, 1})
            end

            -- apro morsa allo scarico
            table.insert( vCmd, { 12, 0})
            -- devo recuperare più del doppio delle corse
            local dMaxMoveFirst
            if abs( dX2DeltaI - dX2DeltaF) > dMaxMovePilgrimStepDoubleClamp then
               dX1PosA = MaxX1
               dX2PosA = MinX2
               dTPosA = dTPosI + ( MaxX1 - dX1PosI)
               table.insert( vCmd, { 3, 'X1', dX1PosA , 'T', dTPosA, 'X2', dX2PosA})
               table.insert( vCmd, { 12, 1})
               table.insert( vCmd, { 11, 0})
               -- nuova posizione della morsa allo scarico dopo riposizionamento
               dX2DeltaI = MinX2 - dTPosA
               dTPosI = dTPosA
               EMC.X1DELTA = nil
               EMC.X2DELTA = dX2DeltaI
               EMC.PILGRIMSTEP = true
               -- mi serve riposizonare di nuovo, quindi richiamo funzione in modo ricorsivo
               vCmd = GetCarriagesRepositioningCmd( vCmd, dTPosI, dX1DeltaI, dX2DeltaI, dTPosF, dX1DeltaF, dX2DeltaF)
            -- recupero una corsa massima e l'altra solo di quello che serve
         elseif abs( dX2DeltaI - dX2DeltaF) > dMaxMovePilgrimStepSingleClampX1 then
               -- suddivido il movimento su entrambe le morse
               local dTotMove = ( ( dX1DeltaF + dX2DeltaF) / 2 + ( dX1DeltaI + dX2DeltaI) / 2) / 2
               -- morsa X2 arriva in posizione finale
               dX1PosA = EgtClamp( MaxX1 + dTotMove, MinX1, MaxX1)
               dTPosA = dTPosI - dX1PosI + dX1PosA
               dX2PosA = dX2DeltaF + dTPosA
               table.insert( vCmd, { 3, 'X1', dX1PosA , 'T', dTPosA, 'X2', dX2PosA})
               table.insert( vCmd, { 12, CalcCharStatus( 'X2', dX2DeltaF)})
               table.insert( vCmd, { 11, 0})
               dX2DeltaI = dX2DeltaF
               dTPosI = dTPosA
               EMC.X1DELTA = nil
               EMC.X2DELTA = dX2DeltaF
               -- mi serve riposizonare di nuovo, quindi richiamo funzione in modo ricorsivo
               vCmd = GetCarriagesRepositioningCmd( vCmd, dTPosI, dX1DeltaI, dX2DeltaI, dTPosF, dX1DeltaF, dX2DeltaF)
            -- non serve recuperare al massimo della corsa, suddivido movimento in due
            else
               -- suddivido il movimento su entrambe le morse
               local dTotMove = ( dX2DeltaI - dX2DeltaF) / 2

               if abs( dTotMove) > 10 * GEO.EPS_SMALL then
                  dX1PosA = dTPosI + dX1DeltaI + dTotMove
                  dX2PosA = dTPosI + dX2DeltaI - dTotMove
                  dTPosA = dTPosI + dTotMove
                  table.insert( vCmd, { 3, 'X1', dX1PosA , 'T', dTPosA, 'X2', dX2PosA})
                  table.insert( vCmd, { 12, CalcCharStatus( 'X2', dX2DeltaF)})
                  table.insert( vCmd, { 11, 0})

                  -- aggiorno nuova posizione della morsa allo scarico dopo riposizionamento
                  dX2DeltaI = dX2DeltaF ;

                  if not EMC.X1DELTANEXT then
                     dTPosA = dTPosF
                     dX2PosA = dTPosF + dX2DeltaF
                     dX1PosA = ParkX1
                  elseif EMC.X2DELTANEXT then
                     dTPosA = dTPosF
                     dX1PosA = dTPosF + dX1DeltaF
                     dX2PosA = dTPosF + dX2DeltaF
                  else
                     dX2PosA = MaxX2
                     dTPosA = MaxX2 - dX2DeltaF
                     dX1PosA = dTPosA + dX1DeltaF
                  end
                  table.insert( vCmd, { 3, 'X1', dX1PosA , 'T', dTPosA, 'X2', dX2PosA})
                  dTPosI, dX1DeltaI, dX2DeltaI = dTPosF, dX1DeltaF, dX2DeltaF
                  EMC.X1DELTA = EMC.X1DELTANEXT
                  EMC.X2DELTA = EMC.X2DELTANEXT
               end

               -- imposto i nuovi parametri di aggancio
               if EMC.X1DELTANEXT and EMC.X2DELTANEXT  then
                  table.insert( vCmd, { 11, CalcCharStatus( 'X1', dX1DeltaF)})
                  table.insert( vCmd, { 21, dX1DeltaF, dX2DeltaF})
               elseif EMC.X1DELTANEXT then
                  table.insert( vCmd, { 11, CalcCharStatus( 'X1', dX1DeltaF)})
                  -- sposto il carrello X1 in parcheggio
                  table.insert( vCmd, { 12, 0})
                  table.insert( vCmd, { 1, 'X2', ParkX2})
                  table.insert( vCmd, { 21, dX1DeltaF, 0})
               else
                  table.insert( vCmd, { 21, 0, dX2DeltaF})
               end
            end
         end
      end
   end
   return vCmd
end

---------------------------------------------------------------------
function SpecOutputCmds( vCmd, bEnd)

   local sRoot = EgtIf( not bEnd, 'AS', 'AE')

   -- Registro il numero di comandi
   if #vCmd > 0 then
      EgtSetInfo( EMC.PATHID, sRoot..'#', #vCmd)
   else
      EgtRemoveInfo( EMC.PATHID, sRoot..'#')
   end
   -- Registro i comandi
   for i = 1, #vCmd do
      local Cmd = vCmd[i]
      local sKey = sRoot..tostring( i)
      -- commento
      if Cmd[1] == 0 then
         local sInfo = '0,'..Cmd[2]
         if Cmd[3] then sInfo = sInfo..','..Cmd[3] end
         EgtSetInfo( EMC.PATHID, sKey, sInfo)
      -- movimento di 1 asse
      elseif Cmd[1] == 1 then
         local sInfo = '1,'..Cmd[2]..','..EgtNumToString( Cmd[3],3)
         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)
         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)
         EgtSetInfo( EMC.PATHID, sKey, sInfo)
      -- apertura/chiusura morsa X1
      elseif Cmd[1] == 11 then
         local sInfo = '11,'..EgtNumToString( Cmd[2],0)
         EgtSetInfo( EMC.PATHID, sKey, sInfo)
         if Cmd[2] == 0 then EMC.X1DELTA = nil end
      -- apertura/chiusura morsa X2
      elseif Cmd[1] == 12 then
         local sInfo = '12,'..EgtNumToString( Cmd[2],0)
         EgtSetInfo( EMC.PATHID, sKey, sInfo)
         if Cmd[2] == 0 then EMC.X2DELTA = nil end
      -- 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.X1DELTA = Cmd[2]
            EMC.X2DELTA = Cmd[3]
         elseif Cmd[2] > 0 then
            EMC.X1DELTA = Cmd[2]
            EMC.X2DELTA = nil
         elseif Cmd[3] > 0 then
            EMC.X1DELTA = nil
            EMC.X2DELTA = Cmd[3]
         end
      -- aggancio grezzo a carrello
      elseif Cmd[1] == 31 then
         local sInfo = '31,'..EgtNumToString( Cmd[2],0)..','..Cmd[3]
         EgtSetInfo( EMC.PATHID, sKey, sInfo)
      end
   end

   -- gli ingombri della lavorazione sono sempre salvati
   if EMC.DISTBACK then
      EgtSetInfo( EMC.PATHID, 'DISTBACK', EMC.DISTBACK)
   else
      EgtRemoveInfo( EMC.PATHID, 'DISTBACK')
   end

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

---------------------------------------------------------------------
function SpecSetCarrPosFromCmds( vCmd)
   -- recupero nuova posizione carrelli
   for i = 1, #vCmd do
      local Cmd = vCmd[i]
      if Cmd[1] == 21 then
         if Cmd[2] > 0 and Cmd[3] > 0 then
            EMC.X1DELTA = Cmd[2]
            EMC.X2DELTA = Cmd[3]
         elseif Cmd[2] > 0 then
            EMC.X1DELTA = Cmd[2]
            EMC.X2DELTA = nil
         elseif Cmd[3] > 0 then
            EMC.X1DELTA = nil
            EMC.X2DELTA = Cmd[3]
         end
      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
