-- Special Operations macchina Essetre-FAST by EgalWare s.r.l. 2024/03/28 -- Intestazioni require( 'EmtGenerator') EgtEnableDebug( false) -- carico librerie local BD = require( 'BeamData') ---------------------- OnSpecialMoveZup ----------------------------- --------------------------------------------------------------------- function OnSpecialMoveZup() --EgtOutLog( 'OnSpecialMoveZup : ' .. EMC.HEAD .. '.' .. tostring( EMC.EXIT)) -- Inizializzazioni EMC.ERR = 0 EMC.MODIF = false -- se sega a catena if EMC.HEAD == 'H3' then end end ---------- OnSpecialApplyDisposition & OnPostApplyMachining --------- ----------------------- Costanti ------------------------------------ local DELTA_TOL_V = 110 local DELTA_TOL_S = 210 local DELTA_TOL_L = 410 local DELTA_TOL_FIXED = 50 local DeltaTol = DELTA_TOL_S local DELTA_SIC = 1 local AGG_LOAD = 50 local MIN_JOIN_VV = EgtClamp( MinJoinVV or 75, 60, 150) local MIN_JOIN_SS = EgtClamp( MinJoinSS or 100, 80, 200) local MIN_JOIN_LS = EgtClamp( MinJoinLS or 290, 250, 400) local MIN_JOIN_SL = EgtClamp( MinJoinSL or 100, 80, 200) local MIN_JOIN_LL = EgtClamp( MinJoinLL or 400, 300, 600) local MinJoin = MIN_JOIN_SS local MinOther = abs( MinY) + abs( MaxV) + MinJoin ----------------------- 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 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 UpdateMinJoinDeltaTol() local L_SMALL = 800 local H_V = 90 local H_S = 200 local H_L = 400 local W_V = 90 local W_S = 150 local W_L = 250 if EMC.SB <= W_V and EMC.HB <= H_V then MinJoin = MIN_JOIN_VV DeltaTol = DELTA_TOL_V elseif EMC.LB <= L_SMALL then MinJoin = MIN_JOIN_SS DeltaTol = DELTA_TOL_S else local dMinJoinS local dMinJoinL if EMC.SB <= W_S then dMinJoinS = MIN_JOIN_SS dMinJoinL = MIN_JOIN_LS elseif EMC.SB <= W_L then local Coeff = ( EMC.SB - W_S) / ( W_L - W_S) dMinJoinS = ( 1 - Coeff) * MIN_JOIN_SS + Coeff * MIN_JOIN_SL dMinJoinL = ( 1 - Coeff) * MIN_JOIN_LS + Coeff * MIN_JOIN_LL else dMinJoinS = MIN_JOIN_SL dMinJoinL = MIN_JOIN_LL end if EMC.HB <= H_S then MinJoin = dMinJoinS DeltaTol = DELTA_TOL_S elseif EMC.HB <= H_L then local Coeff = ( EMC.HB - H_S) / ( H_L - H_S) MinJoin = ( 1 - Coeff) * dMinJoinS + Coeff * dMinJoinL DeltaTol = ( 1 - Coeff) * DELTA_TOL_S + Coeff * DELTA_TOL_L else MinJoin = dMinJoinL DeltaTol = DELTA_TOL_L end if EMC.SB < W_V then DeltaTol = DELTA_TOL_V end end MinOther = abs( MinY) + abs( MaxV) + MinJoin end --------------------------------------------------------------------- local function GetCUTID() -- recupero CUTID del pezzo in lavoro local nOrd = GetPhaseOrd( EMC.PHASE) local nPartRawId local nRawId = EgtGetFirstRawPart() while nRawId do local nRawOrd = EgtGetInfo( nRawId, 'ORD', 'i') if nRawOrd == nOrd then nPartRawId = nRawId break end nRawId = EgtGetNextRawPart( nRawId) end local CutID = EgtGetInfo( EgtGetFirstPartInRawPart( nPartRawId or GDB_ID.NULL) or GDB_ID.NULL, 'CUTID', 'i') or 0 return CutID end --------------------------------------------------------------------- local function EnsureZmax( bZmaxOk, vCmd) if not bZmaxOk then table.insert( vCmd, { 1, 'Z', EgtGetAxisHomePos( 'Z')}) end return true end --------------------------------------------------------------------- local function EmitComment( vCmd, sOut) EgtOutLog( ' ' .. sOut, 1) if Test then table.insert( vCmd, { 0, sOut}) end end --------------------------------------------------------------------- function OnSpecialApplyDisposition() EgtOutLog( ' *** Fase : ' .. EgtNumToString( EMC.PHASE, 0) .. ' ***', 1) -- Inizializzo codice di errore EMC.ERR = 0 -- Campi obbligatori ma non usati EMC.HEAD = "" EMC.EXIT = 1 EMC.TCPOS = "" EMC.SHIFTS = 0 EMC.SBH = false -- Se disposizione da saltare non devo fare alcunché if EgtExistsInfo( EMC.DISPID, 'SKIP') then return end -- Assegno flag di pezzo separato dal resto del grezzo 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 dPosY = EgtGetInfo( EMC.DISPID, 'YPOS', 'd') EMC.TPOS = dPosT EMC.YDELTA = dPosY - dPosT EMC.VDELTA = nil EMC.CNT = 1 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.YDELTA = EgtGetInfo( nLastPathId, 'YDELTA', 'd') EMC.VDELTA = EgtGetInfo( nLastPathId, 'VDELTA', 'd') EMC.CNT = EgtGetInfo( nLastPathId, 'CNT', 'i') 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.LB = 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.YDELTA = EgtGetInfo( nLastPathId, 'YDELTA', 'd') EMC.VDELTA = EgtGetInfo( nLastPathId, 'VDELTA', 'd') EMC.CNT = EgtGetInfo( nLastPathId, 'CNT', 'i') -- Determinazione delle dimensioni del grezzo in lavoro local b3Raw = BBox3d() local nRawId = EgtGetFirstRawPart() while nRawId do if EgtVerifyRawPartPhase( nRawId, EMC.PHASE) and not EgtVerifyRawPartPhase( nRawId, EMC.PHASE + 1) then b3Raw = EgtGetRawPartBBox( nRawId) break end nRawId = EgtGetNextRawPart( nRawId) end EMC.LB = b3Raw:getDimX() + 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 -- Recupero il grezzo in lavoro local nCurrRawId = GDB_ID.NULL local nRawId = EgtGetFirstRawPart() while nRawId do if EgtVerifyRawPartPhase( nRawId, EMC.PHASE) and not EgtVerifyRawPartPhase( nRawId, EMC.PHASE + 1) then 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.LB = b3Raw:getDimX() + 10 * GEO.EPS_SMALL EMC.SB = b3Raw:getDimY() EMC.HB = b3Raw:getDimZ() EMC.ZMIN = b3Raw:getMin():getZ() -- Aggiorno limiti di presa e tolleranza UpdateMinJoinDeltaTol() -- 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 pezzo o il grezzo rimasto -- Posizione trave local dPosT -- Se fase 1 o dopo rotazione eseguo carico con carrello Y local vCmd = {} if EMC.PHASE == 1 or IsEnd2Phase( EMC.PHASE) then dPosT = LoadT if IsEnd2Phase( EMC.PHASE) then dPosT = dPosT + TurnerOffs - EMC.HOVM end vCmd = SpecCalcLoad( dPosT, 0, min( EMC.LB - MinOther - AGG_LOAD - EMC.HOVM, MaxY - 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 dPosY = EgtGetInfo( EMC.DISPID, 'YPOS', 'd') -- se carrello agganciato if dPosY then -- confermo i nuovi parametri di aggancio table.insert( vCmd, { 21, dPosY - 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) EMC.CNT = EgtGetInfo( nLastPathId, 'CNT', 'i') -- altrimenti è grezzo scaricato al carico e devo ricaricarlo else vCmd = SpecCalcLoad( dPosT, 0, min( EMC.LB - MinOther - AGG_LOAD, MaxY - dPosT)) end -- altrimenti fase successiva pari else local dPosV = EgtGetInfo( EMC.DISPID, 'VPOS', 'd') EMC.VDELTA = dPosV end -- Se fase inizio o rimanenza, eseguo scambio per avere solo pinza V local vCmd2 = {} if IsStartOrRestPhase( EMC.PHASE) or IsEnd2Phase( EMC.PHASE) then EMC.TPOS = dPosT SpecSetCarrPosFromCmds( vCmd) local dDistFront = EgtIf( EMC.LB < abs( MinV - UnloadT), MinJoin, EMC.LB - abs( MinV - UnloadT) + MinJoin + DeltaTol) + EMC.HOVM vCmd2 = SpecCalcCarriages( dDistFront, 0) 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() 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.YDELTA = nil EMC.VDELTA = nil EMC.HCING_IGNORE = true EMC.CNT = 1 elseif IsStartOrRestPhase( EMC.PHASE) then -- carico le posizioni local dPosT = EgtGetInfo( nPrevOpeId, 'TPOS', 'd') local dPosY = EgtGetInfo( nPrevOpeId, 'YPOS', 'd') -- se carrello agganciato if dPosY then EMC.TPOS = dPosT EMC.YDELTA = dPosY - dPosT EMC.VDELTA = nil -- altrimenti è grezzo scaricato al carico e devo ricaricarlo else EMC.TPOS = nil EMC.YDELTA = nil EMC.VDELTA = nil end EMC.HCING_IGNORE = true EMC.CNT = nil 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.YDELTA = EgtGetInfo( nLastPathId, 'YDELTA', 'd') EMC.VDELTA = EgtGetInfo( nLastPathId, 'VDELTA', 'd') EMC.CNT = nil 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.YDELTA = EgtGetInfo( nLastPathId, 'YDELTA', 'd') EMC.VDELTA = EgtGetInfo( nLastPathId, 'VDELTA', 'd') EMC.CNT = EgtGetInfo( nLastPathId, 'CNT', 'i') 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 OriYDelta = EMC.YDELTA local OriVDelta = EMC.VDELTA local OriCnt = EMC.CNT -- 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.YDELTA = OriYDelta EMC.VDELTA = OriVDelta EMC.CNT = OriCnt -- 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) -- Assegno flag di pezzo in separazione local bFixedDelta = ( bPreSplit or bSplitting or bPreCut or bCutting) -- Se separazione o taglio del grezzo finale, verifico se precedente era una preparazione local bFixedPos = false if bPreSplit or bSplitting or bPreCut or bCutting then local nPrevOpeId = EgtGetPrevActiveOperation( EMC.MCHID) bFixedPos = EgtExistsInfo( nPrevOpeId or GDB_ID.NULL, 'IS_PRE') end -- 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.LB = EgtIf( bSplit, b3Raw:getDimX(), b3Tot:getDimX()) + 10 * GEO.EPS_SMALL EMC.SB = b3Tot:getDimY() EMC.HB = b3Tot:getDimZ() EMC.LR = b3Raw:getDimX() + 10 * GEO.EPS_SMALL EMC.YMIN = b3Raw:getMin():getY() EMC.ZMIN = b3Raw:getMin():getZ() -- Aggiorno limiti di presa e tolleranza UpdateMinJoinDeltaTol() -- Recupero sovramateriale di testa e ingombro tagli di testa e di coda EMC.HOVM = EgtGetInfo( nCurrRawId, 'HOVM', 'd') or 0 EMC.HCING = EgtGetInfo( nCurrRawId, 'HCING', 'd') or 0 EMC.TCING = EgtGetInfo( nCurrRawId, 'TCING', 'd') or 0 -- Calcolo dell'ingombro della lavorazione local dDistFront, dDistBack = SpecialCalcMachiningEncumbrance( EMC.MCHID, bPreCut) if not dDistFront or not dDistBack then return end if bPreSplit or bSplitting then local dDistF = SpecialCalcPhaseEncumbrance( 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 EgtOutLog( 'DistBack='..EgtNumToString( dDistBack)..' OtherBack='..EgtNumToString( dBackOther), 3) dDistBack = min( dDistBack, dBackOther) elseif bPreCut or bCutting then local dDistF = SpecialCalcPhaseEncumbrance( EMC.PHASE + 1) dDistFront = min( dDistFront, dDistF) dDistBack = 0.0 end -- Verifico lunghezza pezzo if not bSplit and not VerifyPartLength() then return end -- 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 = SpecCalcLoad( dPosT, dDistFront, max( dDistBack, MinJoin)) local vCmd2 = SpecCalcCarriages( dDistFront, dDistBack, bFixedDelta, bFixedPos) if vCmd2 and #vCmd2 > 1 then table.insert( vCmd, { 0, 'CARR_MOVE'}) end EgtJoinTables( vCmd, vCmd2) SpecOutputCmds( vCmd) -- Se altrimenti carri entrambi diponibili, eseguo calcoli per carrelli elseif not IsEndPhase( EMC.PHASE) then local vCmd = SpecCalcCarriages( dDistFront, dDistBack, bFixedDelta, bFixedPos) -- Se non ci sono spostamenti, confermo i parametri di aggancio if SpecTestOnlyRemarkInCmds( vCmd) then table.insert( vCmd, { 21, EgtIf( EMC.YDELTA, EMC.YDELTA, 0), EgtIf( EMC.VDELTA, EMC.VDELTA, 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') -- verifico se separazione con caduta if not EMC.VDELTA 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.YDELTA or not EMC.VDELTA then EMC.ERR = 19 EMC.MSG = ' Error SPLIT : Y or V not clamped' return false end -- eseguo la separazione ( standard o di pezzo ruotato) if not IsMid2Phase( EMC.PHASE + 1) then vCmd = SpecCalcSplit( b3Raw:getDimX()) else vCmd = SpecCalcSplitRot( b3Raw:getDimX()) end end -- Se taglio finale di grezzo a perdere if bCutting then -- salvo distanza carrello V da inizio grezzo rimasto nella disposizione della prossima fase local NextDispId = EgtGetPhaseDisposition( EMC.PHASE + 1) if NextDispId then EgtSetInfo( NextDispId, 'VPOS', EMC.VDELTA) end end -- Se previsto scarico, lo eseguo if bUnload then EMC.LB = 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 SpecialCalcMachiningEncumbrance( nMchId, bPreCut) -- gruppi della lavorazione local nClId = EgtGetFirstNameInGroup( nMchId, 'CL') local nPathId = EgtGetFirstInGroup( nClId or GDB_ID.NULL) if not nPathId then EMC.ERR = 12 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 -- 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 bAngTrasm = ( sHead == 'H5' or sHead == 'H6') local dTLen = EgtTdbGetCurrToolParam( MCH_TP.LEN) local dTDiam = EgtTdbGetCurrToolParam( MCH_TP.DIAM) local dThLen = EgtTdbGetCurrToolThLength() -- 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 vtAux = vtTool else vtAux = vtTool ^ vtAux end end -- Se rinvio da sotto, devo correggere il versore Aux per farlo coincidere con la direzione del braccio C if bAngTrasm then local nExit = EgtTdbGetCurrToolParam( MCH_TP.EXIT) vtAux = vtTool ^ vtAux if nExit == 2 then vtAux = -vtAux end end -- Calcolo limiti derivanti dalla lavorazione local dDistFront, dDistBack = SpecCalcEncumbrance( vtTool, vtAux, ptMin, ptMax, bSaw, bChain, dTLen, dTDiam, dThLen) return dDistFront, dDistBack end --------------------------------------------------------------------- function SpecialCalcPhaseEncumbrance( nPhase) -- Deve essere la fase finale di lavorazione di un pezzo (già staccato dal resto della trave) local dDistFront = EMC.LB local dDistBack = EMC.LB -- 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 = SpecialCalcMachiningEncumbrance( 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 --------------------------------------------------------------------- function SpecCalcEncumbrance( vtTool, vtArm, ptMin, ptMax, bSaw, bChain, dTLen, dTDiam, dThLen) -- 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 riferimento pezzo in Y e Z) local ptHeadMin = ptMin + vtTool * dTLen - Vector3d( 0, EMC.YMIN + EMC.SB, EMC.ZMIN) local ptHeadMax = ptMax + vtTool * dTLen - Vector3d( 0, EMC.YMIN + EMC.SB, EMC.ZMIN) -- Ingombro a sinistra local dDistBack = EMC.LB + 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 = 450 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.HB) 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.06 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 = 450 else dHeadBack = 660 end else dHeadBack = 50 + 0.5 * dTDiam * sqrt( 1 - vtTool:getX() * vtTool:getX()) end 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 = EgtIf( EMC.CNT == 1, 180, 130) elseif ( vtTool:getX() > -0.1 and vtArm:getX() > -0.95) then dHeadBack = 180 elseif ( vtTool:getX() < -0.8) then dHeadBack = 675 elseif ( vtTool:getX() < -0.75) then dHeadBack = 650 elseif ( vtTool:getX() < -0.5) then dHeadBack = 450 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( EgtIf( EMC.CNT == 1, 180, 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 = 510 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 = 500 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 Y+/- 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.SB - 80)) then dHeadBack = EgtIf( EMC.CNT == 1, 180, 130) end -- per interferenza con triangolo di rinforzo sulla pinza Y if ( vtTool:getZ() < 0.25 and vtTool:getX() < 0.10 and vtTool:getY() > 0 and vtArm:getX() < -0.707 and not BD.RIGHT_LOAD) or ( vtTool:getZ() < 0.25 and vtTool:getX() < 0.10 and vtTool:getY() < 0 and vtArm:getX() < -0.707 and BD.RIGHT_LOAD)then dHeadBack = dHeadBack + 90 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 = 450 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.HB) 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.06 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 = 660 end else dHeadFront = 50 + 0.5 * dTDiam * sqrt( 1 - vtTool:getX() * vtTool:getX()) end 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.8) then dHeadFront = 675 elseif ( vtTool:getX() > 0.75) then dHeadFront = 650 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 = 510 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 = 500 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 Y+/- 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.SB - 80)) then dHeadFront = 130 end -- per forature di coda abbastanza lontane dai bordi pezzo in Y if BD.ENABLE_TOOL_BETWEEN_VICES and not bChain and vtTool:getX() < -0.999 and ptHeadMax:getY() + dTDiam / 2 < -15 and ptHeadMin:getY() - dTDiam / 2 > -EMC.SB + 15 then dHeadFront = - ( dTLen - dThLen - 15) end -- per interferenza con triangolo di rinforzo sulla pinza V if ( vtTool:getZ() < 0.25 and vtTool:getX() > -0.10 and vtTool:getY() > 0 and vtArm:getX() > 0.707 and not BD.RIGHT_LOAD) or ( vtTool:getZ() < 0.25 and vtTool:getX() > -0.10 and vtTool:getY() < 0 and vtArm:getX() > 0.707 and BD.RIGHT_LOAD) then dHeadFront = dHeadFront + 90 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 return ( dDistFront - dHeadFront), ( dDistBack - dHeadBack) end --------------------------------------------------------------------- function VerifyPartLength() -- Verifico lunghezza pezzo if EMC.LB < 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 SpecCalcLoad( dPosT, dDistFront, dDistBack) --[L] local dMinDistBack= max( dDistBack, MinJoin + EgtIf( IsMid2Phase( EMC.PHASE) or IsEnd2Phase( EMC.PHASE), EMC.TCING, 0)) local dNewYDelta = max( EMC.LB - dMinDistBack, MinOther + AGG_LOAD + EMC.HCING + EMC.HOVM) local dNewVDelta = nil local dNewY = dPosT + TurnerOffs + dNewYDelta local vCmd = {} EgtOutLog( ' *[L]', 1) -- [L-1] if dNewY - MaxY > 0 then dNewYDelta = min( EMC.LB - MinJoin + AGG_LOAD, MaxY - dPosT - TurnerOffs) EgtOutLog( ' *[L1]', 1) end --[L-2] if EMC.LB - dNewYDelta < MinJoin then dNewYDelta = min( EMC.LB - MinJoin + AGG_LOAD, MaxY - dPosT - TurnerOffs) EgtOutLog( ' *[L2]', 1) end -- Commento table.insert( vCmd, { 0, 'Loading'}) -- risalita testa a Zmax local bZmaxOk = EnsureZmax( bZmaxOk, vCmd) -- Apro entrambe le morse table.insert( vCmd, { 11, 0}) table.insert( vCmd, { 12, 0}) -- Sposto il carrello Y per il carico table.insert( vCmd, { 2, 'Y', dPosT + dNewYDelta, 'V', ParkV}) -- Chiudo morsa Y table.insert( vCmd, { 11, EgtIf( EMC.LB - dNewYDelta < LenToPress, 1, 2)}) -- confermo i nuovi parametri di aggancio table.insert( vCmd, { 21, dNewYDelta, 0}) -- Inizializzo contatore globale EMC.CNT = 1 SpecOutputCNT() -- Assegno stato corrente EMC.TPOS = dPosT EMC.YDELTA = dNewYDelta EMC.VDELTA = 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 SpecCalcCarriages( dDistFront, dDistBack, bFixedDelta, bFixedPos) local MinFrontJoin = MinJoin + EMC.HCING + EMC.HOVM local MinBackJoin = MinJoin + EgtIf( IsMid2Phase( EMC.PHASE) or IsEnd2Phase( EMC.PHASE), EMC.TCING, 0) local MyMinOther = MinOther + EgtIf( EMC.CNT == 1, AGG_LOAD, 0) local dDistFrontEff = min( dDistFront, EMC.LB - MyMinOther - EMC.TCING) if bFixedDelta and IsMid2Phase( EMC.PHASE + 1) then dDistFrontEff = min( dDistFrontEff, EMC.LR - MinOther - EMC.TCING) end local dDistBackEff = min( dDistBack, EMC.LB - MyMinOther - EMC.HCING - EMC.HOVM) EgtOutLog( ' Dist/Min : Back=' .. EgtNumToString( dDistBackEff, 1) .. '/' .. EgtNumToString( MinBackJoin, 1) .. ' Front=' .. EgtNumToString( dDistFrontEff, 1) .. '/' .. EgtNumToString( MinFrontJoin, 1) .. ' Fixed : Delta=' .. EgtIf( bFixedDelta, 'T', 'F') .. ' Pos=' .. EgtIf( bFixedPos, 'T', 'F'), 3) local dPosT = EMC.TPOS local dYDelta = EMC.YDELTA local dVDelta = EMC.VDELTA local dNewPosT = GetMachiningStartingPoint( EMC.PATHID) local dNewYDelta = nil local dNewVDelta = nil -- [A] se posso mettere solo carrello Y if dDistFrontEff < MinFrontJoin and dDistBackEff > MinBackJoin - GEO.EPS_SMALL then dNewYDelta = EMC.LB - dDistBackEff dNewVDelta = nil -- [B] se altrimenti posso mettere entrambi i carrelli Y e V elseif dDistBackEff > MinBackJoin - GEO.EPS_SMALL and dDistFrontEff > MinFrontJoin - GEO.EPS_SMALL then dNewYDelta = EMC.LB - dDistBackEff dNewVDelta = dDistFrontEff -- [C] se altrimenti posso mettere solo carrello V elseif dDistBackEff < MinBackJoin and dDistFrontEff > MinFrontJoin - GEO.EPS_SMALL then dNewYDelta = nil dNewVDelta = dDistFrontEff -- 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 {} end -- salvo valori finali EMC.YDELTANEXT = dNewYDelta EMC.VDELTANEXT = dNewVDelta local vCmd = {} SpecAdjustCarriages( vCmd, dPosT, dYDelta, dVDelta, dNewPosT, dNewYDelta, dNewVDelta, bFixedDelta, bFixedPos) return vCmd end --------------------------------------------------------------------- function SpecCalcSplit( dLenRaw) local vCmd = {} EgtOutLog( ' *[S]', 1) table.insert( vCmd, { 0, EgtIf( EMC.VDELTA, 'Split', 'Fall')}) -- determino i grezzi da agganciare al carrello Y (sono quelli presenti nella fase successiva dispari) local nNextOddPhase = GetNextStartOrRestPhase( EMC.PHASE) local nRawId = EgtGetFirstRawPart() while nRawId do if EgtVerifyRawPartPhase( nRawId, nNextOddPhase) then table.insert( vCmd, { 31, nRawId, 'Y'}) end nRawId = EgtGetNextRawPart( nRawId) end -- riporto il carrello Y al carico con il resto della trave local dLDelta = EMC.YDELTA - dLenRaw table.insert( vCmd, { 1, 'Y', LoadT + dLDelta}) table.insert( vCmd, { 21, 0, EMC.VDELTA or 0}) -- imposto subito Y non più attaccato al trave in lavoro EMC.YDELTA = nil -- salvo posizione carrello Y in disposizione del pezzo dopo split local PostDispId = EgtGetPhaseDisposition( EMC.PHASE + 1) if PostDispId then EgtSetInfo( PostDispId, 'YPOS', LoadT + dLDelta) end -- salvo posizione grezzo rimasto e posizione carrello Y nella disposizione iniziale del pezzo succ (prossima fase dispari) local NextDispId = EgtGetPhaseDisposition( nNextOddPhase) if NextDispId then EgtSetInfo( NextDispId, 'TPOS', LoadT) EgtSetInfo( NextDispId, 'YPOS', LoadT + 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 Y (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, 'Y'}) end -- riporto il carrello Y al carico con il resto della trave local dLDelta = EMC.YDELTA - dLenRaw table.insert( vCmd, { 1, 'Y', LoadT + TurnerOffs + dLDelta}) table.insert( vCmd, { 21, 0, EMC.VDELTA}) -- imposto subito Y non più attaccato alla trave in lavoro EMC.YDELTA = nil -- apro il carrello Y table.insert( vCmd, { 11, 0}) -- sgancio i grezzi dal carrello Y for _, nId in ipairs( vRaw) do table.insert( vCmd, { 31, nId, ''}) end -- lo porto in parcheggio table.insert( vCmd, { 1, 'Y', ParkY}) -- 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 Y chiusa , devo effettuare uno scambio if EMC.YDELTA then -- determino posizione testa trave local nLastEntId = EgtGetLastInGroup( EMC.PATHID) local vAxes = EmtGetAxesPos( nLastEntId) if #vAxes > 0 then EMC.TPOS = vAxes[1] end -- imposto quote aggancio per avere solo pinza V local dDistFront = EMC.LB - MinOther - EMC.TCING - 10 * GEO.EPS_SMALL local dDistBack = 0 -- effettuo scambio vCmdPre = SpecCalcCarriages( dDistFront, dDistBack) 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.LB - EMC.HOVM < MaxUnloadLen + 1) -- Commento table.insert( vCmd, { 0, 'Unloading', EgtIf( bStdUl, 'Unloading', 'Manual Unloading')}) -- risalita testa a Zmax local bZmaxOk = EnsureZmax( bZmaxOk, vCmd) -- Se pinza Y chiusa, la apro if EMC.YDELTA 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 = UnloadT - EMC.LB local dFinV = dFinT + EMC.VDELTA table.insert( vCmd, { 2, 'T', dFinT, 'V', dFinV}) else table.insert( vCmd, { 1, 'V', MaxV}) end -- apro la morsa table.insert( vCmd, { 12, 0}) -- riporto il carrello in home table.insert( vCmd, { 1, 'V', ParkV}) -- eventuale unione tabelle if #vCmdPre > 0 then vCmd = EgtJoinTables( vCmdPre, vCmd) end return vCmd end --------------------------------------------------------------------- function SpecCalcPreRot() local vCmdPre = {} EgtOutLog( ' *[PR]', 1) -- Se pinza V chiusa , devo effettuare uno scambio if EMC.VDELTA then -- imposto quote aggancio per avere solo pinza Y local dDistFront = 0 local dDistBack = EMC.LB - MinOther - EMC.HOVM + 10 * GEO.EPS_SMALL -- effettuo scambio vCmdPre = SpecCalcCarriages( dDistFront, dDistBack) -- recupero nuova posizione carrelli SpecSetCarrPosFromCmds( vCmdPre) EgtOutLog( ' *[PR1]', 1) end -- porto il pezzo alla zona di rotazione con il carro Y local vCmd = {} -- Commento table.insert( vCmd, { 0, 'Pre-Rotation'}) -- risalita testa a Zmax local bZmaxOk = EnsureZmax( bZmaxOk, vCmd) -- Se pinza V chiusa, la apro if EMC.VDELTA then table.insert( vCmd, { 12, 0}) end -- riporto la trave al carico local RotT = LoadT + TurnerOffs - EMC.HOVM table.insert( vCmd, { 2, 'Y', RotT + EMC.YDELTA, 'T', RotT}) -- apro la morsa table.insert( vCmd, { 11, 0}) -- riporto il carrello in home table.insert( vCmd, { 1, 'Y', ParkY}) -- eventuale unione tabelle if #vCmdPre > 0 then vCmd = EgtJoinTables( vCmdPre, vCmd) end return vCmd end --------------------------------------------------------------------- function SpecAdjustCarriages( vCmd, dTPosI, dYDeltaI, dVDeltaI, dTPosF, dYDeltaF, dVDeltaF, bFixedDelta, bFixedPos) local dYPosA, dVPosA, dTPosA -- verifico che le morse non sono in posizione if ( dYDeltaF ~= dYDeltaI or dVDeltaF ~= dVDeltaI) then if #vCmd == 0 then table.insert( vCmd, { 0, 'Clamp repositioning'}) end -- calcolo delta spostamento trave local dDeltaBeam = dTPosI - dTPosF -- se positivo, la trave si sposta dal carico allo scarico -- se pinza non in presa, setto offset minimo in base all'altra che sta pinzando if not dYDeltaI then dYDeltaI = dVDeltaI + ( MinY - MaxV) end if not dVDeltaI then dVDeltaI = dYDeltaI - ( MinY - MaxV) end if not dYDeltaF then dYDeltaF = dVDeltaF + ( MinY - MaxV) end if not dVDeltaF then dVDeltaF = dYDeltaF - ( MinY - MaxV) end -- scambio diretto if ( ( dYDeltaI - dVDeltaF) >= MinY - MaxV + DELTA_SIC and dDeltaBeam > 0) or ( ( dVDeltaI - dYDeltaF) >= MinY - MaxV + DELTA_SIC and dDeltaBeam < 0) then if EMC.YDELTA then if not EMC.VDELTA then table.insert( vCmd, { 12, 0}) end dTPosA = MaxV - dVDeltaF dVPosA = MaxV dYPosA = dTPosA - dYDeltaI table.insert( vCmd, { 3, 'Y', dYPosA , 'T', dTPosA, 'V', dVPosA}) table.insert( vCmd, { 12, 1}) table.insert( vCmd, { 11, 0}) dTPosA = dTPosF dYPosA = dVDeltaF + dTPosF dVPosA = dYDeltaF + dTPosF table.insert( vCmd, { 3, 'Y', dYPosA , 'T', dTPosA, 'V', dVPosA}) dTPosI, dYDeltaI, dVDeltaI = dTPosF, dYDeltaF, dVDeltaF if EMC.YDELTANEXT then table.insert( vCmd, { 11, 1}) end if not EMC.VDELTANEXT then table.insert( vCmd, { 12, 0}) end else dTPosA = MinY - dYDeltaF dVPosA = dTPosA - dVDeltaI dYPosA = MinY table.insert( vCmd, { 3, 'Y', dYPosA , 'T', dTPosA, 'V', dVPosA}) table.insert( vCmd, { 11, 1}) table.insert( vCmd, { 12, 0}) dTPosA = dTPosF dYPosA = dYDeltaF + dTPosF dVPosA = dVDeltaF + dTPosF table.insert( vCmd, { 3, 'Y', dYPosA , 'T', dTPosA, 'V', dVPosA}) dTPosI, dYDeltaI, dVDeltaI = dTPosF, dYDeltaF, dVDeltaF if EMC.VDELTANEXT then table.insert( vCmd, { 12, 1}) end if not EMC.YDELTANEXT then table.insert( vCmd, { 11, 0}) end end -- se devo recuperare corsa con passo del pellegrino else -- compatto al centro if not EMC.YDELTA then dTPosA = MaxV - dVDeltaI dVPosA = MaxV dYPosA = MinY table.insert( vCmd, { 3, 'Y', dYPosA , 'T', dTPosA, 'V', dVPosA}) table.insert( vCmd, { 11, 1}) EMC.YDELTA = true dYDeltaI = dVDeltaI - MaxV + MinY dTPosI = dTPosA end if not EMC.VDELTA then dTPosA = MinY - dYDeltaI dVPosA = MaxV dYPosA = MinY table.insert( vCmd, { 3, 'Y', dYPosA , 'T', dTPosA, 'V', dVPosA}) table.insert( vCmd, { 12, 1}) EMC.VDELTA = true dVDeltaI = dYDeltaI + MaxV - MinY dTPosI = dTPosA end -- aggiorno delta mancante dDeltaBeam = dTPosI - dTPosF -- a questo punto entrambe le pinze sono in presa sul pezzo. Il trascinatore si decide in base alla direzione di movimento trave -- trascino con morsa allo scarico if dDeltaBeam > 0 then -- apro morsa al carico table.insert( vCmd, { 11, 0}) -- devo recuperare più del doppio delle corse if abs( dYDeltaI - dYDeltaF) > ( MinY + MaxY - MinV - MaxV) then -- calcolo nuova posizione della morsa al carico dopo riposizionamento dYDeltaI = dYDeltaI + ( MaxY - MinY - MinV + MaxV) dYPosA = MaxY dVPosA = MinV dTPosA = dTPosI - MaxV + MinV table.insert( vCmd, { 3, 'Y', dYPosA , 'T', dTPosA, 'V', dVPosA}) table.insert( vCmd, { 11, 1}) table.insert( vCmd, { 12, 0}) dTPosI = dTPosA EMC.YDELTA = true EMC.VDELTA = false -- mi serve riposizonare di nuovo, quindi richiamo funzione in modo ricorsivo SpecAdjustCarriages( vCmd, dTPosI, dYDeltaI, dVDeltaI, dTPosF, dYDeltaF, dVDeltaF, bFixedDelta, bFixedPos) -- recupero una corsa massima e l'altra solo di quello che serve elseif abs( dYDeltaI - dYDeltaF) > MinY + MaxY then -- aggiorno nuova posizione della morsa al carico dopo riposizionamento dYDeltaI = dYDeltaF dVPosA = MinV dTPosA = dTPosI - MaxV + MinV dYPosA = - ( dYDeltaF + dTPosA) table.insert( vCmd, { 3, 'Y', dYPosA , 'T', dTPosA, 'V', dVPosA}) table.insert( vCmd, { 11, 1}) table.insert( vCmd, { 12, 0}) dTPosI = dTPosA EMC.YDELTA = true EMC.VDELTA = false -- mi serve riposizonare di nuovo, quindi richiamo funzione in modo ricorsivo SpecAdjustCarriages( vCmd, dTPosI, dYDeltaI, dVDeltaI, dTPosF, dYDeltaF, dVDeltaF, bFixedDelta, bFixedPos) -- non serve recuperare al massimo della corsa, suddivido movimento in due else -- suddivido il movimento su entrambe le morse nTotMove = abs( dYDeltaI - dYDeltaF) / 2 ; dYPosA = dTPosI + dYDeltaI + nTotMove dVPosA = dTPosI + dVDeltaI - nTotMove dTPosA = dTPosI - nTotMove table.insert( vCmd, { 3, 'Y', dYPosA , 'T', dTPosA, 'V', dVPosA}) table.insert( vCmd, { 11, 1}) table.insert( vCmd, { 12, 0}) -- aggiorno nuova posizione della morsa al carico dopo riposizionamento dYDeltaI = dYDeltaF ; dYPosA = dYPosA - nTotMove dVPosA = dVPosA + nTotMove dTPosA = dTPosA - nTotMove table.insert( vCmd, { 3, 'Y', dYPosA , 'T', dTPosA, 'V', dVPosA}) dTPosI, dYDeltaI, dVDeltaI = dTPosF, dYDeltaF, dVDeltaF -- aggiorno nuova posizione della morsa allo scarico dopo riposizionamento dVDeltaI = dVDeltaF -- sistemo apertura morse if EMC.VDELTANEXT then table.insert( vCmd, { 12, 1}) end if not EMC.YDELTANEXT then table.insert( vCmd, { 11, 0}) table.insert( vCmd, { 1, 'Y', MinY}) -- sposto il carrello Y in parcheggio table.insert( vCmd, { 1, 'Y', ParkY}) -- imposto i nuovi parametri di aggancio table.insert( vCmd, { 21, 0, dVDeltaF}) end end end end end end --------------------------------------------------------------------- function SpecOutputCNT() if EMC.CNT == 1 then EgtSetInfo( EMC.PATHID, 'CNT', 1) else EgtRemoveInfo( EMC.PATHID, 'CNT') end 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 Y elseif Cmd[1] == 11 then local sInfo = '11,'..EgtNumToString( Cmd[2],0) EgtSetInfo( EMC.PATHID, sKey, sInfo) if Cmd[2] == 0 then EMC.YDELTA = nil end -- apertura/chiusura morsa V elseif Cmd[1] == 12 then local sInfo = '12,'..EgtNumToString( Cmd[2],0) EgtSetInfo( EMC.PATHID, sKey, sInfo) if Cmd[2] == 0 then EMC.VDELTA = 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.YDELTA = Cmd[2] EMC.VDELTA = Cmd[3] elseif Cmd[2] > 0 then EMC.YDELTA = Cmd[2] EMC.VDELTA = nil elseif Cmd[3] > 0 then EMC.YDELTA = nil EMC.VDELTA = 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 -- Salvo i nuovi delta dei carrelli if EMC.YDELTA then EgtSetInfo( EMC.PATHID, 'YDELTA', EMC.YDELTA) else EgtRemoveInfo( EMC.PATHID, 'YDELTA') end if EMC.VDELTA then EgtSetInfo( EMC.PATHID, 'VDELTA', EMC.VDELTA) else EgtRemoveInfo( EMC.PATHID, 'VDELTA') 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.YDELTA = Cmd[2] EMC.VDELTA = Cmd[3] elseif Cmd[2] > 0 then EMC.YDELTA = Cmd[2] EMC.VDELTA = nil elseif Cmd[3] > 0 then EMC.YDELTA = nil EMC.VDELTA = 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