Imports EgtUILib
Imports System.Text.RegularExpressions

Module VacuumCups
   ' Tipo manipolatore con ventosa (0=assente, 1=dietro, 2=laterale)
   Private m_nVacType As Integer = 0
   ' Dati del manipolatore
   Private m_nTempId As Integer = GDB_ID.NULL
   Private m_nVacId As Integer = GDB_ID.NULL
   Private m_nRefId As Integer = GDB_ID.NULL
   Private m_dPreferredRot As Double = 0
   Private m_dPrefVertRotXMinus As Double = 0
   Private m_dPrefVertRotXPlus As Double = 0
   Private m_dPrefVertRotYMinus As Double = 0
   Private m_dPrefVertRotYPlus As Double = 0
   Private m_dDripRefAng As Double = 0
   ' dati carico massimo manipolatore
   Private m_RawDensity As Double = 2500
   Private m_RawWeight As Double = 0
   Private m_MaxWeightSinglePlugger As Double = 250
   Private m_MaxWeightDoublePlugger As Double = 750
   Public bOverWeight As Boolean = False
   ' dati per rotazione ventose vicono al fine corsa Y e X
   Private bRotateVacuumNearExtraStrokeY As Boolean = False
   Private bRotateVacuumNearExtraStrokeX As Boolean = False
   ' dati stroke
   Public bExtraStroke As Boolean = False
   Public ptStartPointLift As Point3d
   Public dDegRotStartAng As Double

   ' Nome del gruppo temporaneo per le ventose
   Private Const VACTMP_GRP As String = "VacTmp"

   '-----------------------------------------------------------------------------------------------
   Friend Class RawMoveData

      Public m_nId As Integer
      Public m_vtRawMove As Vector3d
      Public m_dRawAngRotDeg As Double
      Public m_vtDelta As Vector3d
      Public m_dAngRotDeg As Double
      Public m_sCups As String

      Sub New()
         m_nId = GDB_ID.NULL
         m_vtRawMove = Vector3d.NULL()
         m_dRawAngRotDeg = 0
         m_vtDelta = Vector3d.NULL()
         m_dAngRotDeg = 0
         m_sCups = String.Empty
      End Sub

      Sub New(nId As Integer)
         m_nId = nId
         m_vtRawMove = Vector3d.NULL()
         m_dRawAngRotDeg = 0
         m_vtDelta = Vector3d.NULL()
         m_dAngRotDeg = 0
         m_sCups = String.Empty
      End Sub
   End Class


   '-----------------------------------------------------------------------------------------------
   Friend Function GetVacuumType() As Integer
      Return m_nVacType
   End Function

   ' carico i dati macchina relativi al peso massimo movimentabile
   Friend Sub GetWeightInformation(Density As Double, MaxSingle As Double, MaxDouble As Double)
      m_RawDensity = Density
      m_MaxWeightSinglePlugger = MaxSingle
      m_MaxWeightDoublePlugger = MaxDouble
   End Sub

   Friend Sub GetRotationForExtraStrokeY(Rotate As Boolean)
      bRotateVacuumNearExtraStrokeY = Rotate
   End Sub

   Friend Sub GetRotationForExtraStrokeX(Rotate As Boolean)
      bRotateVacuumNearExtraStrokeX = Rotate
   End Sub
   Friend Function GetVacuumId() As Integer
      Return m_nVacId
   End Function

   Friend Function LoadVacuumCups() As Boolean
      ' Leggo tipo manipolatore con ventosa
      m_nVacType = 0
      EgtGetInfo(EgtGetHeadId(VACUUM_HEAD), KEY_VAC_TYPE, m_nVacType)
      ' Cancello eventuali vecchie ventose
      RemoveVacuumCups()
      ' Identificativo ventose nella macchina
      Dim nLayId As Integer = EgtGetFirstNameInGroup(EgtGetHeadId(VACUUM_HEAD), VACUUM_HEAD_LAYOUT)
      If nLayId = GDB_ID.NULL Then Return False
      ' Identificativo riferimento della testa nella macchina
      Dim nT1Id As Integer = EgtGetFirstNameInGroup(EgtGetHeadId(VACUUM_HEAD), HEAD_FIRST_EXIT)
      If nT1Id = GDB_ID.NULL Then Return False
      ' imposto la l'uscita della ventosa come fosse l'uscita di un utensile
      EgtSetCalcTool("", "H4", 1)
      ' Creo gruppo temporaneo in cui copiarli
      m_nTempId = EgtCreateGroup(GDB_ID.ROOT)
      If m_nTempId = GDB_ID.NULL Then Return False
      EgtSetName(m_nTempId, VACTMP_GRP)
      EgtSetLevel(m_nTempId, GDB_LV.TEMP)
      ' Eseguo copia in globale
      m_nVacId = EgtCopyGlob(nLayId, m_nTempId)
      If m_nVacId = GDB_ID.NULL Then Return False
      ' Angolo di rotazione preferito
      EgtGetInfo( m_nVacId, KEY_VACLAY_PREFROT, m_dPreferredRot)
      ' Angoli di rotazione preferiti per ventosa in verticale (lungo Y) a sinistra e a destra del centro tavola
      EgtGetInfo( m_nVacId, KEY_VACLAY_PREFVROTXMINUS, m_dPrefVertRotXMinus)
      EgtGetInfo(m_nVacId, KEY_VACLAY_PREFVROTXPLUS, m_dPrefVertRotXPlus)
      EgtGetInfo(m_nVacId, KEY_VACLAY_PREFVROTYMINUS, m_dPrefVertRotYMinus)
      EgtGetInfo(m_nVacId, KEY_VACLAY_PREFVROTYPLUS, m_dPrefVertRotYPlus)
      ' Direzione di riferimento per tagli Drip
      EgtGetInfo( m_nVacId, KEY_VACLAY_DRIPREFDIR, m_dDripRefAng)
      ' Nascondo il gruppo ma rendo visibili le curve di contorno delle ventose
      EgtSetStatus(m_nVacId, GDB_ST.OFF)
      Dim nId As Integer = EgtGetFirstInGroup(m_nVacId)
      While nId <> GDB_ID.NULL
         Select Case EgtGetType(nId)
         Case GDB_TY.CRV_LINE, GDB_TY.CRV_ARC, GDB_TY.CRV_BEZ, GDB_TY.CRV_COMPO
            EgtSetStatus(nId, GDB_ST.ON_)
         Case Else
            EgtSetStatus(nId, GDB_ST.OFF)
         End Select
         nId = EgtGetNext(nId)
      End While
      m_nRefId = EgtCopyGlob(nT1Id, m_nVacId)
      If m_nRefId = GDB_ID.NULL Then Return False
      EgtSetStatus(m_nRefId, GDB_ST.ON_)
      Return True
   End Function

   Friend Function RemoveVacuumCups() As Boolean
      ' Cancello eventuali gruppi per ventose usati appena prima
      EgtErase(m_nTempId)
      m_nTempId = GDB_ID.NULL
      m_nVacId = GDB_ID.NULL
      m_nRefId = GDB_ID.NULL
      ' Cancello eventuali vecchi gruppi per ventose
      Dim nId As Integer = EgtGetFirstNameInGroup(GDB_ID.ROOT, VACTMP_GRP)
      While nId <> GDB_ID.NULL
         Dim nNextId As Integer = EgtGetNextName(nId, VACTMP_GRP)
         EgtErase(nId)
         nId = nNextId
      End While
      Return True
   End Function

   Friend Function ResetVacuumCups() As Boolean
      ' Ripristino posizione e rotazione originali
      Dim frOriRef As New Frame3d
      EgtFrame(EgtGetFirstNameInGroup(EgtGetHeadId(VACUUM_HEAD), HEAD_FIRST_EXIT), GDB_ID.ROOT, frOriRef)
      Dim frCurrRef As New Frame3d
      EgtFrame(m_nRefId, GDB_ID.ROOT, frCurrRef)
      EgtMove(m_nVacId, frOriRef.Orig() - frCurrRef.Orig(), GDB_RT.GLOB)
      frCurrRef.ToLoc(frOriRef)
      Dim dLen, dAngVertDeg, dAngOrizzDeg As Double
      frCurrRef.VersX().ToSpherical(dLen, dAngVertDeg, dAngOrizzDeg)
      EgtRotate(m_nVacId, frOriRef.Orig(), frOriRef.VersZ(), -dAngOrizzDeg, GDB_RT.GLOB)
      ' Ripristino visualizzazione originale
      Dim nId As Integer = EgtGetFirstInGroup(m_nVacId)
      While nId <> GDB_ID.NULL
         Select Case EgtGetType(nId)
         Case GDB_TY.CRV_LINE, GDB_TY.CRV_ARC, GDB_TY.CRV_BEZ, GDB_TY.CRV_COMPO
            EgtSetStatus(nId, GDB_ST.ON_)
         Case Else
            EgtSetStatus(nId, GDB_ST.OFF)
         End Select
         nId = EgtGetNext(nId)
      End While
      EgtSetStatus(m_nRefId, GDB_ST.ON_)
      Return True
   End Function

   Friend Function PutVacuumCupsOnRaw(nRawId As Integer, ByRef rmData As RawMoveData) As Boolean
      ' Ripristino posizione originale ventose
      ResetVacuumCups()
      ' Box e baricentro del grezzo (riportato sopra al grezzo)
      Dim b3Raw As New BBox3d
      EgtGetRawPartBBox(nRawId, b3Raw)
      Dim ptRawCen As Point3d
      EgtGetRawPartCenter(nRawId, ptRawCen)
      ptRawCen.z += b3Raw.DimZ() / 2
      ' Se non esiste, creo la regione del kerf del grezzo
      Dim nKerfId As Integer = EgtGetFirstNameInGroup(nRawId, NAME_KERF)
      Dim nRKerfId = EgtGetFirstNameInGroup(nRawId, NAME_KERF_REGION)
      If nRKerfId = GDB_ID.NULL Then
         nRKerfId = EgtCreateSurfFlatRegion(nRawId, nKerfId)
         ' se non sono riuscito a crearla, allora è nulla e posso uscire
         If nRKerfId = GDB_ID.NULL Then Return False
         EgtSetName(nRKerfId, NAME_KERF_REGION)
         EgtSetMode(nRKerfId, GDB_MD.HIDDEN)
      End If
      ' Box e baricentro della regione di kerf
      Dim b3Kerf As New BBox3d
      EgtGetBBoxGlob(nRKerfId, GDB_BB.STANDARD, b3Kerf)
      Dim ptKerfCen As Point3d
      EgtCentroid(nRKerfId, GDB_ID.ROOT, ptKerfCen)

      '-------------------- INIZIO CALCOLO PESO --------------------
      ' recupero l'area del grezzo da muovere
      Dim RawArea As Double = 0
      ' superficie del grezzo senza considerare eventuali fori
      EgtSurfFrGrossArea(nRKerfId, RawArea)
      ' volume calcolato in mmc
      Dim RawVolume As Double = RawArea * b3Raw.DimZ()
      ' peso calolato in kg
      m_RawWeight = RawVolume * m_RawDensity / Math.Pow(10, 9)
      '-------------------- FINE CALCOLO PESO --------------------

      ' Eseguo ricerca
      If FindVacuumCupsOnRaw(nRawId, ptRawCen, b3Kerf, ptKerfCen, nKerfId, nRKerfId, rmData) Then
         Return True
      End If
      ' In caso di fallimento, provo riducendo con offset la regione di kerf
      ' (così si simula la proiezione del centro sul MAT - medial axis transform)
      Dim bOkFind As Boolean = False
      Dim vOffset() As Double = {-450, -350, -250, -150}
      For i As Integer = 0 To vOffset.Length() - 1
         Dim nRKerfOffsId = EgtCopy(nRKerfId, nRKerfId, GDB_POS.AFTER)
         If EgtSurfFrOffset(nRKerfOffsId, vOffset(i), OFF_TYPE.FILLET) AndAlso
            EgtCentroid(nRKerfOffsId, GDB_ID.ROOT, ptKerfCen) Then
            Dim nOutOffsId As Integer = GetRegionOutLoop(nRKerfOffsId, nRawId)
            bOkFind = FindVacuumCupsOnRaw(nRawId, ptRawCen, b3Kerf, ptKerfCen, nOutOffsId, nRKerfId, rmData)
            EgtErase(nOutOffsId)
         End If
         EgtErase(nRKerfOffsId)
         If bOkFind Then Return True
      Next
      Return False
   End Function

   ' utilizzata per gestire la movimentazione dei pezzi per eseguire i drip
   Friend Function PutVacuumCupsOnPart(nPartId As Integer,
                                       ByRef rmData As RawMoveData, ByRef b3Part As BBox3d) As Boolean
      ' Ripristino posizione originale ventose
      ResetVacuumCups()
      ' Verifico sia veramente un pezzo
      If EgtGetRawPartFromPart(nPartId) = GDB_ID.NULL Then Return False
      ' Cerco la direzione del primo taglio attivo da sotto
      Dim nDripLayId As Integer = EgtGetFirstNameInGroup( nPartId, NAME_DRIPCUT)
      Dim nDripLineId As Integer = EgtGetFirstInGroup( nDripLayId)
      While nDripLineId <> GDB_ID.NULL
         Dim nMchId As Integer = EgtGetOperationId( "DripSaw" & nDripLineId.ToString())
         Dim nMode As Integer
         If EgtGetMode( nMchId, nMode) AndAlso nMode = GDB_MD.STD Then Exit While
         nDripLineId = EgtGetNext( nDripLineId)
      End While
      Dim vtDir As New Vector3d
      if EgtStartVector( nDripLineId, GDB_ID.ROOT, vtDir) Then
         Dim dLen, dAngV, dAngH As Double
         vtDir.ToSpherical( dLen, dAngV, dAngH)
         Dim dOffsAng = dAngH - m_dDripRefAng
         While dOffsAng - m_dPreferredRot >= 90 
            dOffsAng -= 180
         End While
         While dOffsAng - m_dPreferredRot <= -90
            dOffsAng += 180
         End While
         m_dPreferredRot = dOffsAng
      End If
      ' Recupero la regione del pezzo
      Dim nGrpRegId As Integer = EgtGetFirstNameInGroup(nPartId, NAME_REGION)
      Dim nRegId As Integer = EgtGetFirstInGroup(nGrpRegId)
      While nRegId <> GDB_ID.NULL
         If EgtGetType(nRegId) = GDB_TY.SRF_FRGN Then Exit While
         nRegId = EgtGetNext(nRegId)
      End While
      If nRegId = GDB_ID.NULL Then Return False
      ' Box e baricentro del pezzo (sempre sopra)
      EgtGetBBoxGlob(nRegId, GDB_BB.STANDARD, b3Part)
      Dim ptPartCen As Point3d
      EgtCentroid(nRegId, GDB_ID.ROOT, ptPartCen)
      ' Calcolo il contorno esterno del pezzo
      Dim nOutId As Integer = GetRegionOutLoop(nRegId, nGrpRegId)
      If nOutId = GDB_ID.NULL Then Return False
      ' Eseguo ricerca
      If FindVacuumCupsOnRaw(nPartId, ptPartCen, b3Part, ptPartCen, nOutId, nRegId, rmData) Then
         EgtErase(nOutId)
         Return True
      Else
         EgtErase(nOutId)
      End If
      ' In caso di fallimento, provo riducendo con offset la regione di kerf
      ' (così si simula la proiezione del centro sul MAT - medial axis transform)
      Dim bOkFind As Boolean = False
      Dim vOffset() As Double = {-450, -350, -250, -150}
      For i As Integer = 0 To vOffset.Length() - 1
         Dim nRegOffsId As Integer = EgtCopy(nRegId, nRegId, GDB_POS.AFTER)
         Dim ptRegOffsCen As Point3d
         If EgtSurfFrOffset(nRegOffsId, vOffset(i), OFF_TYPE.FILLET) AndAlso
            EgtCentroid(nRegOffsId, GDB_ID.ROOT, ptRegOffsCen) Then
            Dim nOutOffsId As Integer = GetRegionOutLoop(nRegOffsId, nGrpRegId)
            bOkFind = FindVacuumCupsOnRaw(nPartId, ptRegOffsCen, b3Part, ptPartCen, nOutOffsId, nRegId, rmData)
            EgtErase(nOutOffsId)
         End If
         EgtErase(nRegOffsId)
         If bOkFind Then Return True
      Next
      Return False
   End Function

   Private Function FindVacuumCupsOnRaw(nRawId As Integer, ptRawCen As Point3d,
                                        b3Kerf As BBox3d, ptKerfCen As Point3d, nOutlineId As Integer, nRKerfId As Integer,
                                        ByRef rmData As RawMoveData) As Boolean
      ' Cerco migliore configurazione di ventose per prendere il grezzo
      Const MAX_SEL As Integer = 20
      For nI As Integer = 1 To MAX_SEL
         ' Recupero la configurazione di ventose nI-esima
         Dim sCups() As String = Nothing
         Dim sCups2() As String = Nothing
         If Not GetVacuumCupSelection(nI, sCups, sCups2) Then Return False
         bExtraStroke = False
         ' Determino validità soluzioni della configurazione
         Dim vtMove As New Vector3d
         Dim ptRotCen As New Point3d
         Dim dRotAngDeg As Double = 0
         Dim vtMove2 As New Vector3d
         Dim ptRotCen2 As New Point3d
         Dim dRotAngDeg2 As Double = 0
         Dim dDist = TestVacuumCupSelection(sCups, b3Kerf, ptKerfCen, nOutlineId, nRKerfId, vtMove, ptRotCen, dRotAngDeg)
         Dim dDist2 = TestVacuumCupSelection(sCups2, b3Kerf, ptKerfCen, nOutlineId, nRKerfId, vtMove2, ptRotCen2, dRotAngDeg2)
         If dDist > INFINITO - 1 And dDist2 > INFINITO - 1 Then Continue For
         If dDist2 < dDist Then
            sCups = sCups2
            vtMove = vtMove2
            ptRotCen = ptRotCen2
            dRotAngDeg = dRotAngDeg2
         End If
         ' Eseguo il movimento
         EgtMove(m_nVacId, vtMove, GDB_RT.GLOB)
         EgtRotate(m_nVacId, ptRotCen, Vector3d.Z_AX(), dRotAngDeg, GDB_RT.GLOB)
         ' Visualizzo le ventose
         For nJ As Integer = 0 To sCups.Length() - 1
            Dim nCupId = EgtGetFirstNameInGroup(m_nVacId, sCups(nJ))
            EgtSetStatus(nCupId, GDB_ST.ON_)
         Next
         ' Calcolo delta posizione
         Dim frCurrRef As New Frame3d
         EgtFrame(m_nRefId, GDB_ID.ROOT, frCurrRef)
         Dim vtDelta As Vector3d = frCurrRef.Orig() - ptRawCen

         ' Assegno dati noti al movimento del grezzo
         rmData.m_nId = nRawId
         rmData.m_vtRawMove = Vector3d.NULL()
         rmData.m_vtDelta = vtDelta
         rmData.m_dAngRotDeg = dRotAngDeg
         Dim sVal As String = String.Empty
         For Each sCup As String In sCups
            If String.IsNullOrEmpty(sVal) Then
               sVal &= sCup
            Else
               sVal &= "," & sCup
            End If
         Next
         rmData.m_sCups = sVal
         Return True
      Next
      Return False
   End Function

   Private Function GetVacuumCupSelection(nInd As Integer, ByRef sCups() As String, ByRef sCups2() As String) As Boolean
      ' Recupero elenco ventose nella soluzione
      Dim sSel As String = KEY_VACLAY_SEL & nInd.ToString()
      Dim sVal As String = String.Empty
      If Not EgtGetInfo(m_nVacId, sSel, sVal) Then Return False
      If sVal.IndexOf("/") >= 0 Then
         Dim sSplit() As String = sVal.Split("/".ToCharArray)
         If sSplit.Length() >= 2 Then
            sCups = sSplit(0).Split(",".ToCharArray)
            sCups2 = sSplit(1).Split(",".ToCharArray)
         ElseIf sSplit.Length() >= 1 Then
            sCups = sSplit(0).Split(",".ToCharArray)
            sCups2 = Nothing
         Else
            sCups = Nothing
            sCups2 = Nothing
         End If
      Else
         sCups = sVal.Split(",".ToCharArray)
         sCups2 = Nothing
      End If
      Return True
   End Function

   Private Function GetVacRotAxisSteps(ByRef vAngRot As List(Of Double)) As Boolean
      ' Recupero l'asse rotante della testa ventosa
      Dim nRotAxId As Integer = EgtGetParent(EgtGetHeadId(VACUUM_HEAD))
      ' Verifico se contiene info con STEPS
      Dim sSteps As String = ""
      If Not EgtGetInfo(nRotAxId, KEY_ROTVAC_STEPS, sSteps) Then Return False
      ' Leggo gli step previsti
      Dim vStep() As String = sSteps.Split(",".ToCharArray)
      For Each sStep As String In vStep
         Dim dStep As Double = 0
         If StringToDouble(sStep, dStep) Then
            vAngRot.Add(dStep)
         End If
      Next
      Return True
   End Function

   Private Function TestVacuumCupSelection(sCups() As String, b3Raw As BBox3d, ptRawCen As Point3d, nOutlineId As Integer, nRawRegId As Integer,
                                           ByRef vtMove As Vector3d, ByRef ptRotCen As Point3d, ByRef dRotAngDeg As Double) As Double
      ' Se definizione mancante, scarto soluzione
      If IsNothing(sCups) Then Return INFINITO
      ' Ne calcolo il box
      Dim b3Vac As New BBox3d
      For nJ As Integer = 0 To sCups.Length() - 1
         Dim nCupId = EgtGetFirstNameInGroup(m_nVacId, sCups(nJ))
         Dim b3Cup As New BBox3d
         If EgtGetBBoxGlob(nCupId, GDB_BB.STANDARD, b3Cup) Then b3Vac.Add(b3Cup)
      Next
      If b3Vac.IsEmpty() Then Return INFINITO
      ' Se box maggiore di quello del pezzo, scarto soluzione
      If b3Vac.Radius() > b3Raw.Radius() Then Return INFINITO
      '-------------------- INIZIO VERIFICA PESO --------------------
      bOverWeight = False
      Select Case GetPluggerFromCameras(sCups)
         Case 2
            ' se peso del grezzo oltre il limite consentito allora scarto la soluzione
            If m_RawWeight > m_MaxWeightDoublePlugger Then
               bOverWeight = True
               Return INFINITO
            End If
         Case 1
            ' se peso del grezzo oltre il limite consentito allora scarto la soluzione
            If m_RawWeight > m_MaxWeightSinglePlugger Then
               bOverWeight = True
               Return INFINITO
            End If
      End Select
      '-------------------- FINE VERIFICA PESO --------------------
      ' Recupero l'area della tavola
      Dim b3Tab As New BBox3d
      EgtGetTableArea(1, b3Tab)
      ' Determino il movimento
      vtMove = ptRawCen - b3Vac.Center()
      b3Vac.Move(vtMove)
      ' Determino la rotazione, allineando il lato lungo delle ventose con quello del grezzo
      ptRotCen = b3Vac.Center()
      Dim frMinRect As New Frame3d
      If EgtCurveMinAreaRectangleXY(nOutlineId, GDB_ID.ROOT, frMinRect) Then
         Dim dLen, dAngVertDeg, dAngOrizzDeg As Double
         frMinRect.VersX().ToSpherical(dLen, dAngVertDeg, dAngOrizzDeg)
         dRotAngDeg = dAngOrizzDeg
         If b3Vac.DimY() > b3Vac.DimX() + EPS_SMALL Then dRotAngDeg -= 90
         Dim dPreferredRot As Double = m_dPreferredRot
         ' -------------------- INIZIO GESTIONE ROTAZIONE ASSE C PER NON ANDARE IN EXTRA-CORSA --------------------
         ' verifico l'orientamento del pezzo
         If bRotateVacuumNearExtraStrokeX And (Math.Abs(dRotAngDeg - 90) < 45 Or Math.Abs(dRotAngDeg - 270) < 45) Then
            ' se l'orientamento è verticale
            If frMinRect.Orig().x < b3Tab.Center().x Then
               dPreferredRot = m_dPrefVertRotXMinus
            Else
               dPreferredRot = m_dPrefVertRotXPlus
            End If
         ElseIf bRotateVacuumNearExtraStrokeY And (Math.Abs(dRotAngDeg - 90) > 45 Or Math.Abs(dRotAngDeg - 270) > 45) Then
            ' se l'orientemanto è orizzontale e la macchina è stata abilitata
            If frMinRect.Orig().y < b3Tab.Center().y Then
               dPreferredRot = m_dPrefVertRotYPlus
            Else
               dPreferredRot = m_dPrefVertRotYMinus
            End If
         End If
         ' -------------------- FINE GESTIONE ROTAZIONE ASSE C PER NON ANDARE IN EXTRA-CORSA --------------------
         Dim dAngDelta As Double = If( Math.Abs( b3Vac.DimY() - b3Vac.DimX()) < 10 * EPS_SMALL, 90, 180)
         While dRotAngDeg - dPreferredRot >= dAngDelta / 2 
            dRotAngDeg -= dAngDelta
         End While
         While dRotAngDeg - dPreferredRot <= -dAngDelta / 2
            dRotAngDeg += dAngDelta
         End While
      Else
         dRotAngDeg = 0
         If (b3Vac.DimX() >= b3Vac.DimY() And b3Raw.DimX() < b3Raw.DimY()) Or
            (b3Vac.DimX() < b3Vac.DimY() And b3Raw.DimX() >= b3Raw.DimY()) Then
            dRotAngDeg = m_dPreferredRot
         End If
      End If
      ' Identificativi delle ventose
      Dim nCups(sCups.Length() - 1) As Integer
      For nJ As Integer = 0 To sCups.Length() - 1
         nCups(nJ) = EgtGetFirstNameInGroup(m_nVacId, sCups(nJ))
      Next
      ' Angoli di prova
      Dim vAngRot As New List(Of Double)
      ' Se asse rotante ventosa a step
      If GetVacRotAxisSteps(vAngRot) Then
         ' Ordino secondo distanza angolare crescente da direzione di allineamento (modulo 180 deg)
         Dim dRotRefDeg As Double = dRotAngDeg
         vAngRot.Sort(Function(P, Q)
                         Dim dDiffP = Math.Abs( P - dRotRefDeg)
                         If Math.Abs( dDiffP - 180) < 10 * EPS_ANG_SMALL Then dDiffP = 1
                         Dim dDiffQ = Math.Abs( Q - dRotRefDeg)
                         If Math.Abs( dDiffQ - 180) < 10 * EPS_ANG_SMALL Then dDiffQ = 1
                         Return ( dDiffP - dDiffQ)
                      End Function)
         ' Annullo la rotazione di allineamento
         dRotAngDeg = 0
      ' altrimenti in continuo
      Else
         ' Assegno più valori di ricerca e conservo angolo di allineamento
         vAngRot.AddRange({0, +5, -5, +12.5, -12.5, +25, -25, +45, -45})
      End If
      ' Eseguo verifica delle ventose rispetto al grezzo
      Dim bVacOk As Boolean = False
      For i As Integer = 0 To vAngRot.Count() - 1
         If TestVacuumCups(nCups, nRawRegId, vtMove, ptRotCen, dRotAngDeg + vAngRot(i)) Then
            dRotAngDeg += vAngRot(i)
            bVacOk = True
            Exit For
         End If
      Next
      If Not bVacOk Then Return INFINITO
      ' Recupero riferimento della testa ventose
      Dim frCurrRef As New Frame3d
      EgtFrame(m_nRefId, GDB_ID.ROOT, frCurrRef)
      Dim ptRef As Point3d = frCurrRef.Orig()
      ' Applico movimento e rotazione al punto
      ptRef.Move(vtMove)
      ptRef.Rotate(ptRotCen, Vector3d.Z_AX(), dRotAngDeg)

      ' -------------- INIZIO verifica di essere nel limite delle corse macchina --------------
      If VerifyOutOfStrokes(ptRef, dRotAngDeg) <> 0 Then
         bExtraStroke = True
         Return INFINITO
      End If
      ' se la posizione è accettbaile allora salvo i dati
      ptStartPointLift = ptRef
      dDegRotStartAng = dRotAngDeg
      ' -------------- FINE verifica di essere nel limite delle corse macchina --------------

      ' Ne calcolo la distanza dal centro della tavola
      Dim dDist As Double = Point3d.DistXY(ptRef, b3Tab.Center())
      Return dDist
   End Function

   ' assegante le posizioni del centro delle ventose e l'angolo di posizionamento orizzontale verifica che la posizione sia raggiungibile
   Public Function VerifyOutOfStrokes(ptRef As Point3d, dRotAngDeg As Double) As Integer
      Dim dX, dY, dZ As Double
      Dim nStat As Integer
      Dim dCHome As Double
      EgtGetAxisHomePos("C", dCHome)
      ' imposto la l'uscita della ventosa come fosse l'uscita di un utensile
      EgtSetCalcTool("", "H4", 1)
      EgtGetCalcPositions(ptRef, dRotAngDeg + dCHome, 0, nStat, dX, dY, dZ)
      EgtVerifyOutstroke(dX, dY, dZ, dRotAngDeg + dCHome, 0, nStat)
      Return nStat
   End Function

   ' dall'informazione di extra corsa recupera il valore indicato
   Public Function GetExtraStrokeValue(sInfo As String) As Double
      Dim dExtraStroke As Double = 0
      Dim sItems As String() = sInfo.Split("="c)
      If sItems.Count = 2 Then
         Dim nStartIndex As Integer = 0
         For Each ItemChar As Char In sItems(1)
            If ItemChar = "("c Then
               Exit For
            End If
            nStartIndex += 1
         Next
         If nStartIndex > 0 Then
            Dim sValue As String = sItems(1).Remove(nStartIndex, sItems(1).Count - nStartIndex)
            StringToLen(sValue, dExtraStroke)
         End If
      End If
      Return dExtraStroke
   End Function

   Private Function TestVacuumCups(nCups() As Integer, nRawRegId As Integer,
                                   vtMove As Vector3d, ptRotCen As Point3d, dRotAngDeg As Double) As Boolean
      ' Eseguo verifica delle ventose rispetto al grezzo
      Dim bVacOk As Boolean = True
      For nJ As Integer = 0 To nCups.Length() - 1
         Dim nCupId = nCups(nJ)
         ' Eseguo rototraslazione delle ventose per verificare se sono contenute nel grezzo
         EgtMove(nCupId, vtMove, GDB_RT.GLOB)
         EgtRotate(nCupId, ptRotCen, Vector3d.Z_AX(), dRotAngDeg, GDB_RT.GLOB)
         ' Confronto le regioni
         If EgtSurfFrChunkSimpleClassify(nRawRegId, 0, nCupId, 0) <> REGC.IN2 Then bVacOk = False
         ' Annullo rototraslazione
         EgtRotate(nCupId, ptRotCen, Vector3d.Z_AX(), -dRotAngDeg, GDB_RT.GLOB)
         EgtMove(nCupId, -vtMove, GDB_RT.GLOB)
         ' Se verifica fallita, esco dal ciclo
         If Not bVacOk Then Exit For
      Next
      Return bVacOk
   End Function

   ' dato il vettore delle camere restituisco il quali attuatori sono coinvolti
   Private Function GetPluggerFromCameras(sCups() As String) As Integer
      Dim nCountPlunger As Integer = 1
      Dim bPlugger1 As Boolean = False
      Dim bPlugger2 As Boolean = False
      For Each Camera As String In sCups
         If (Camera.Contains("1") Or Camera.Contains("2") Or Camera.Contains("3")) Then
            bPlugger1 = True
         Else
            bPlugger2 = True
         End If
      Next
      ' verifico quali sono le camere attive
      If bPlugger1 And bPlugger2 Then
         nCountPlunger = 2
      ElseIf Not bPlugger1 And Not bPlugger2 Then
         nCountPlunger = 0
      End If
      ' restituisco il numero di Plugger in uso
      Return nCountPlunger
   End Function

   Friend Function SaveOneMoveInfo(nId As Integer, rmData As RawMoveData) As Boolean
      ' Assegno le informazioni
      EgtSetInfo(nId, "Id", rmData.m_nId)
      EgtSetInfo(nId, "Mv", rmData.m_vtRawMove)
      EgtSetInfo(nId, "Rr", rmData.m_dRawAngRotDeg)
      EgtSetInfo(nId, "Dt", rmData.m_vtDelta)
      EgtSetInfo(nId, "Ad", rmData.m_dAngRotDeg)
      EgtSetInfo(nId, "Vc", rmData.m_sCups)
      EgtSetInfo(nId, "Vt", GetVacuumType())
      Return True
   End Function

   Friend Function RemoveOneMoveInfo(nId As Integer) As Boolean
      ' Rimuovo le informazioni
      EgtRemoveInfo(nId, "Id")
      EgtRemoveInfo(nId, "Mv")
      EgtRemoveInfo(nId, "Rr")
      EgtRemoveInfo(nId, "Dt")
      EgtRemoveInfo(nId, "Ad")
      EgtRemoveInfo(nId, "Vc")
      EgtRemoveInfo(nId, "Vt")
      Return True
   End Function

   Friend Function SaveOneMoveInfoInDisposition(nDispId As Integer, rmData As RawMoveData) As Boolean
      ' Se movimento e rotazione trascurabili, inutile salvare
      If rmData.m_vtRawMove.IsSmall() AndAlso Math.Abs(rmData.m_dRawAngRotDeg) < EPS_ANG_SMALL Then Return True
      ' Creo il gruppo
      Dim nRpmId As Integer = EgtCreateGroup(nDispId)
      If nRpmId = GDB_ID.NULL Then Return False
      EgtSetName(nRpmId, "Rpm" & rmData.m_nId.ToString())
      ' Assegno le informazioni
      SaveOneMoveInfo(nRpmId, rmData)
      Return True
   End Function

   Friend Function SaveMoveInfoInDisposition(nDispId As Integer, rmList As List(Of RawMoveData)) As Boolean
      ' Verifico DispId
      If EgtGetOperationType(nDispId) <> MCH_OY.DISP Then Return False
      ' aggiungo al gruppo disposizione dei sottogruppi con i dati di movimento dei grezzi spostati
      For Each rmData As RawMoveData In rmList
         If Not SaveOneMoveInfoInDisposition(nDispId, rmData) Then Return False
      Next
      Return True
   End Function

   Friend Function GetMoveInfoInDisposition(nDispId As Integer, ByRef rmList As List(Of RawMoveData)) As Boolean
      ' Verifico DispId
      If EgtGetOperationType(nDispId) <> MCH_OY.DISP Then Return False
      ' Recupero i gruppi con i dati
      Dim nRpmId As Integer = EgtGetFirstNameInGroup(nDispId, "Rpm*")
      While nRpmId <> GDB_ID.NULL
         ' Recupero le informazioni
         Dim rmData As New RawMoveData
         EgtGetInfo(nRpmId, "Id", rmData.m_nId)
         EgtGetInfo(nRpmId, "Mv", rmData.m_vtRawMove)
         EgtGetInfo(nRpmId, "Rr", rmData.m_dRawAngRotDeg)
         EgtGetInfo(nRpmId, "Dt", rmData.m_vtDelta)
         EgtGetInfo(nRpmId, "Ad", rmData.m_dAngRotDeg)
         EgtGetInfo(nRpmId, "Vc", rmData.m_sCups)
         EgtGetInfo(nRpmId, "Vt", GetVacuumType())
         ' se non già presenti, le inserisco nella lista
         If FindRawMoveData(rmData.m_nId, rmList) = -1 Then
            rmList.Add(rmData)
         End If
         ' Cerco un altro gruppo
         nRpmId = EgtGetNextName(nRpmId, "Rpm*")
      End While
      Return True
   End Function

   Friend Function FindRawMoveData(nRawId As Integer, ByRef rmList As List(Of RawMoveData)) As Integer
      ' Cerco in lista record con dati del grezzo indicato
      Dim nInd As Integer = -1
      For i As Integer = 0 To rmList.Count() - 1
         If rmList(i).m_nId = nRawId Then
            nInd = i
            Exit For
         End If
      Next
      Return nInd
   End Function

   Private Function AddRawMoveData(nRawId As Integer, ByRef rmList As List(Of RawMoveData)) As Integer
      ' Cerco in lista record con dati del grezzo indicato
      Dim nInd As Integer = -1
      For i As Integer = 0 To rmList.Count() - 1
         If rmList(i).m_nId = nRawId Then
            nInd = i
            Exit For
         End If
      Next
      ' Se trovato
      If nInd <> -1 Then
         ' Se con movimenti trascurabili e non alla fine, lo sposto alla fine
         If rmList(nInd).m_vtRawMove.IsSmall() AndAlso Math.Abs(rmList(nInd).m_dRawAngRotDeg) < EPS_ANG_SMALL AndAlso nInd <> rmList.Count() - 1 Then
            rmList.Add(rmList(nInd))
            rmList.RemoveAt(nInd)
            nInd = rmList.Count() - 1
         End If
      ' Se non trovato, lo accodo
      Else
         rmList.Add(New RawMoveData(nRawId))
         nInd = rmList.Count() - 1
      End If
      Return nInd
   End Function

   Friend Function AddRawMoveData(nRawId As Integer, vtMove As Vector3d, ByRef rmList As List(Of RawMoveData)) As Integer
      ' Recupero o creo record con dati del grezzo indicato
      Dim nInd As Integer = AddRawMoveData(nRawId, rmList)
      If nInd = -1 Then Return -1
      ' Aggiorno i valori
      rmList(nInd).m_vtRawMove += vtMove
      Return nInd
   End Function

   Friend Function AddRawMoveData(nRawId As Integer, dRawAngRotDeg As Double, ByRef rmList As List(Of RawMoveData)) As Integer
      ' Recupero o creo record con dati del grezzo indicato
      Dim nInd As Integer = AddRawMoveData(nRawId, rmList)
      If nInd = -1 Then Return -1
      ' Aggiorno i valori
      rmList(nInd).m_dRawAngRotDeg += dRawAngRotDeg
      Return nInd
   End Function

   Friend Function AddRawMoveData(rmData As RawMoveData, ByRef rmList As List(Of RawMoveData)) As Integer
      ' Recupero o creo record con dati del grezzo indicato
      Dim nInd As Integer = AddRawMoveData(rmData.m_nId, rmList)
      If nInd = -1 Then Return -1
      ' Aggiorno i valori (tengo conto di quanto già ruotato il pezzo)
      Dim vtDeltaRot As New Vector3d(rmData.m_vtDelta)
      vtDeltaRot.Rotate(Vector3d.Z_AX(), -rmList(nInd).m_dRawAngRotDeg)
      rmList(nInd).m_vtDelta = vtDeltaRot
      rmList(nInd).m_dAngRotDeg = rmData.m_dAngRotDeg - rmList(nInd).m_dRawAngRotDeg
      rmList(nInd).m_sCups = rmData.m_sCups
      Return nInd
   End Function

   Friend Sub RemoveRawMoveData(nRawId As Integer, ByRef rmList As List(Of RawMoveData))
      ' Cerco in lista record con dati del grezzo indicato
      Dim nInd As Integer = -1
      For i As Integer = 0 To rmList.Count() - 1
         If rmList(i).m_nId = nRawId Then
            nInd = i
            Exit For
         End If
      Next
      ' Se trovato, lo elimino
      If nInd >= 0 Then
         rmList.RemoveAt(nInd)
      End If
   End Sub

   Friend Function SaveRemoveByHandInDisposition(nDispId As Integer, bRemoveByHand As Boolean) As Boolean
      ' Verifico DispId
      If EgtGetOperationType(nDispId) <> MCH_OY.DISP Then Return False
      ' Imposto flag
      EgtSetInfo(nDispId, "Rbh", bRemoveByHand)
      Return True
   End Function

   Friend Function GetRemoveByHandInDisposition(nDispId As Integer) As Boolean
      ' Verifico DispId
      If EgtGetOperationType(nDispId) <> MCH_OY.DISP Then Return False
      ' Leggo flag
      Dim bRemoveByHand As Boolean = False
      EgtGetInfo(nDispId, "Rbh", bRemoveByHand)
      Return bRemoveByHand
   End Function

   Friend Function SaveMovePartsOnAuxTable(nDispId As Integer, bMovePartsOnAuxTab As Boolean) As Boolean
      ' Verifico DispId
      If EgtGetOperationType(nDispId) <> MCH_OY.DISP Then Return False
      ' Imposto flag
      EgtSetInfo(nDispId, "Pat", bMovePartsOnAuxTab)
      Return True
   End Function

End Module
