-- FeatureLib.lua by Egalware s.r.l. 2024/04/02 -- Libreria lettura o calcolo dati e proprietà della feature -- 2024/04/02 PRIMA VERSIONE CALCOLO LAVORAZIONI CON STRATEGIE -- Tabella per definizione modulo local FeatureLib = {} -- Carico i dati globali local BeamData = require( 'BeamDataNew') -- carico librerie local BeamLib = require( 'BeamLib') local FaceData = require( 'FaceData') local ID = require( 'Identity') ------------------------------------------------------------------------------------------------------------- function FeatureLib.GetProcFromTrimesh( idTrimesh, Part, ProcToCopyFrom) local Proc = {} -- eventuale copia dati da ProcToCopyFrom in arrivo if type( ProcToCopyFrom) == "table" then Proc = BeamLib.TableCopyDeep( ProcToCopyFrom) end -- dati specifici della Proc Proc.id = idTrimesh Proc.idPart = Part.id Proc.nFct = EgtSurfTmFacetCount( Proc.id) or 0 Proc.b3Box = EgtGetBBoxGlob( idTrimesh or GDB_ID.NULL, GDB_BB.STANDARD) Proc.AffectedFaces = BeamLib.GetAffectedFaces( Proc, Part) Proc.AdjacencyMatrix = FaceData.GetAdjacencyMatrix( Proc) Proc.Faces = FaceData.GetFacesInfo( Proc, Part) Proc.dVolume = FeatureLib.GetFeatureVolume( Proc, Part) if ProcToCopyFrom and Proc.dVolume < 10 * GEO.EPS_SMALL then Proc.dVolume = ProcToCopyFrom.dVolume end -- TODO servono anche altri dati raccolti nel collect?? return Proc end ------------------------------------------------------------------------------------------------------------- -- restituisce vero se la feature con box b3Proc taglia l'intera sezione della barra local function IsFeatureCuttingEntireSection( b3Proc, Part) return ( b3Proc:getDimY() > ( Part.b3Part:getDimY() - 500 * GEO.EPS_SMALL) and b3Proc:getDimZ() > ( Part.b3Part:getDimZ() - 500 * GEO.EPS_SMALL)) end ------------------------------------------------------------------------------------------------------------- -- restituisce vero se la feature con box b3Proc taglia l'intera lunghezza della barra local function IsFeatureCuttingEntireLength( b3Proc, Part) return ( ( b3Proc:getDimY() > ( Part.b3Part:getDimY() - 500 * GEO.EPS_SMALL) and b3Proc:getDimX() > ( Part.b3Part:getDimX() - 500 * GEO.EPS_SMALL)) or ( b3Proc:getDimZ() > ( Part.b3Part:getDimZ() - 500 * GEO.EPS_SMALL) and b3Proc:getDimX() > ( Part.b3Part:getDimX() - 500 * GEO.EPS_SMALL))) end ------------------------------------------------------------------------------------------------------------- -- restituisce vero se tutti i lati della faccia passata si trovano sul grezzo local function AreAllFaceEdgesOnRaw( Part, Proc, idFace) local bEdgeOnRaw = true local idTempGroup = Part.idTempGroup local nLoopId, nLoopCnt = EgtExtractSurfTmFacetLoops( Proc.id, idFace, idTempGroup) if nLoopCnt > 1 then error( 'AreAllFaceEdgesOnRaw : too many loops') end if nLoopId then local dUmin, dUmax = EgtCurveDomain( nLoopId) for dU = dUmin, dUmax-1 do local ptMid = ( EgtUP( nLoopId, dU, GDB_ID.ROOT) + EgtUP( nLoopId, dU+1, GDB_ID.ROOT)) / 2 -- se punto medio dell'entita NON si trova su faccia testa o coda if ptMid:getX() < Part.b3Part:getMin():getX() + 100 * GEO.EPS_SMALL or ptMid:getX() > Part.b3Part:getMax():getX() - 100 * GEO.EPS_SMALL or ptMid:getY() < Part.b3Part:getMin():getY() + 100 * GEO.EPS_SMALL or ptMid:getY() > Part.b3Part:getMax():getY() - 100 * GEO.EPS_SMALL or ptMid:getZ() < Part.b3Part:getMin():getZ() + 100 * GEO.EPS_SMALL or ptMid:getZ() > Part.b3Part:getMax():getZ() - 100 * GEO.EPS_SMALL then bEdgeOnRaw = true else bEdgeOnRaw = false break end end --for j = 1, nLoopCnt do -- EgtErase( nLoopId + j - 1) --end end return bEdgeOnRaw end --------------------------------------------------------------------- -- recupero topologia della feature function FeatureLib.NeedTopologyFeature( Proc, Part) -- feature BTL con calcolo topologia SEMPRE da geometria if ID.IsCut( Proc) then -- (1-10) return true elseif ID.IsLongitudinalCut( Proc) then -- (0-10) return true elseif ID.IsDoubleCut( Proc) then -- (1-11) return true elseif ID.IsDoubleLongitudinalCut( Proc) then -- (0-12) return true elseif ID.IsSlot( Proc) then -- (1-16) return true elseif ID.IsFrontSlot( Proc) then -- (1-17) return true elseif ID.IsBirdsMouth( Proc) then -- (1-20) return true elseif ID.IsLapJoint( Proc) then -- (0-30) return true elseif ID.IsNotchRabbet( Proc) then -- (1-32) return true elseif ID.IsBlockHaus( Proc) then -- (1-33) return true elseif ID.IsNotch( Proc) then -- (1-34) return true elseif ID.IsChamfer( Proc) then -- (0-36) return true elseif ID.IsPocket( Proc) then -- (1-39) return true elseif ID.IsStepJoint( Proc) then -- (1-80) return true elseif ID.IsStepJointNotch( Proc) then -- (0-80) return true -- feature con calcolo topologia da geometria SOLO se rispettano certe condizioni, altrimenti riconoscimento specifico elseif ID.IsSawCut( Proc) and Proc.nFct == 1 and AreAllFaceEdgesOnRaw( Part, Proc, 0) then return true elseif ID.IsHipValleyRafterNotch( Proc) and Proc.nFct < 4 then -- (0-25) return true elseif ID.IsRidgeLap( Proc) and Proc.nFct ~= 3 then -- (1-30) return true elseif ID.IsMortise( Proc) and Proc.nFct < 6 then -- (1-50) return true elseif ID.IsFrontMortise( Proc) and Proc.nFct < 6 then -- (1-51) return true end -- se feature non tra quelle sopra, riconoscimento topologico non necessario return false end --------------------------------------------------------------------- -- restituisce true se Proc ha tutti gli angoli concavi (bAllConcave) e, nei casi in cui ha senso, se questi sono esattamente 90 deg (bAllRight) local function AreAllAnglesConcaveOrRight( vAdj) -- se la feature ha una sola faccia, esco subito if #vAdj <= 1 then return end local bAllConcave, bAllRight = true, true local nFct = #( vAdj or {}) for i = 1, nFct do for j = 1, nFct do -- se trovo un angolo convesso restituisco falso e esco subito if vAdj[i][j] and vAdj[i][j] > 0 then bAllConcave = false bAllRight = false break elseif vAdj[i][j] and vAdj[i][j] ~= 0 and vAdj[i][j] + 90 > 500 * GEO.EPS_ANG_SMALL then bAllRight = false end end end -- se 1 faccia oppure 2 facce con angolo convesso non ha senso ritornare valori per bAllRight if nFct < 2 or ( nFct == 2 and vAdj[1][2] > 0) then return bAllConcave else return bAllConcave, bAllRight end end --------------------------------------------------------------------- -- restituisce true se almeno una delle dimensioni della feature è maggiore o uguale ad una delle dimensioni principali del pezzo (tolleranza 1 mm) local function IsAnyDimensionLongAsPart( Proc, Part) local bResult = false if Proc.b3Box:getDimX() > Part.b3Part:getDimX() - 1000 * GEO.EPS_SMALL or Proc.b3Box:getDimY() > Part.b3Part:getDimY() - 1000 * GEO.EPS_SMALL or Proc.b3Box:getDimZ() > Part.b3Part:getDimZ() - 1000 * GEO.EPS_SMALL then bResult = true end return bResult end --------------------------------------------------------------------- -- restituisce una stringa con il nome esteso della topologia della feature -- *famiglia - numero di facce - passante* local function GetTopologyName( sFamily, nNumberOfFaces, bIsThrough) return sFamily .. '-' .. tostring( nNumberOfFaces) .. '-' .. EgtIf( bIsThrough, 'Through', 'Blind') end --------------------------------------------------------------------- -- recupera topologia feature function FeatureLib.ClassifyTopology( Proc, Part) local FeatureTopology = {} if not Proc.AffectedFaces then Proc.AffectedFaces = BeamLib.GetAffectedFaces( Proc, Part) end local bIsFeatureCuttingEntireSection = IsFeatureCuttingEntireSection( Proc.b3Box, Part) local bIsFeatureCuttingEntireLength = IsFeatureCuttingEntireLength( Proc.b3Box, Part) local bIsAnyDimensionLongAsPart = IsAnyDimensionLongAsPart( Proc, Part) local vAdj = Proc.AdjacencyMatrix local bAllAnglesConcave, bAllRightAngles = AreAllAnglesConcaveOrRight( vAdj) local vTriangularFaces = FaceData.GetTriangularFaces( Proc) local vFacesByAdjNumber = FaceData.GetFacesByAdjacencyNumber( Proc) local sFamily local bIsThrough -- caso speciale taglio di testa if Proc.nFct == 1 and AreSameVectorApprox( Proc.Faces[1].vtN, X_AX()) and abs( Proc.Faces[1].ptCenter:getX() - Part.b3Part:getMax():getX()) < 10 * GEO.EPS_SMALL then sFamily = 'HeadCut' bIsThrough = true -- caso speciale taglio di coda elseif Proc.nFct == 1 and AreSameVectorApprox( Proc.Faces[1].vtN, -X_AX()) and abs( Proc.Faces[1].ptCenter:getX() - Part.b3Part:getMin():getX()) < 10 * GEO.EPS_SMALL then sFamily = 'TailCut' bIsThrough = true elseif Proc.nFct == 1 and ( bIsFeatureCuttingEntireSection or bIsFeatureCuttingEntireLength) then sFamily = 'Cut' bIsThrough = true elseif Proc.nFct == 1 then sFamily = 'Bevel' bIsThrough = true elseif Proc.nFct == 2 and bAllAnglesConcave and #vTriangularFaces == 1 then sFamily = 'Bevel' bIsThrough = false elseif Proc.nFct == 2 and bAllAnglesConcave and Proc.nParts == 1 and ( Proc.AffectedFaces.bLeft or Proc.AffectedFaces.bRight) and ( Proc.AffectedFaces.bFront or Proc.AffectedFaces.bBack) then sFamily = 'Rabbet' bIsThrough = true elseif Proc.nFct == 2 and bAllAnglesConcave and Proc.nParts == 1 then sFamily = 'VGroove' bIsThrough = true elseif Proc.nFct == 2 and ( not bAllAnglesConcave or Proc.nParts == 2) and bIsAnyDimensionLongAsPart then sFamily = 'DoubleBevel' bIsThrough = true elseif Proc.nFct == 3 and bAllAnglesConcave and #vFacesByAdjNumber[2] == 1 and #vTriangularFaces == 2 then sFamily = 'Bevel' bIsThrough = false elseif Proc.nFct == 3 and bAllAnglesConcave and #vFacesByAdjNumber[2] == 1 and bIsAnyDimensionLongAsPart then sFamily = 'Groove' bIsThrough = true elseif Proc.nFct == 3 and bAllAnglesConcave and #vFacesByAdjNumber[2] == 3 then sFamily = 'Groove' bIsThrough = false elseif Proc.nFct == 4 and ( ( not bAllAnglesConcave and ( ( #vFacesByAdjNumber[2] == 2 and #vTriangularFaces == 2) or ( #vFacesByAdjNumber[3] == 2))) or ( #vTriangularFaces == 2 and Proc.nParts == 2)) then sFamily = 'DoubleBevel' bIsThrough = false elseif Proc.nFct == 4 and bAllAnglesConcave and #vFacesByAdjNumber[3] == 2 then sFamily = 'Groove' bIsThrough = false elseif Proc.nFct == 4 and bAllAnglesConcave and #vFacesByAdjNumber[2] == 4 and bIsAnyDimensionLongAsPart then sFamily = 'Tunnel' bIsThrough = true elseif Proc.nFct >= 4 and #vFacesByAdjNumber[1] == 2 and bIsAnyDimensionLongAsPart then sFamily = 'Strip' bIsThrough = true elseif Proc.nFct == 5 and bAllAnglesConcave and #vFacesByAdjNumber[4] == 1 then sFamily = 'Pocket' bIsThrough = false elseif Proc.nFct == 6 and ( ( #vFacesByAdjNumber[1] == 4 and #vFacesByAdjNumber[3] == 2 and #vTriangularFaces == 4 and not bAllAnglesConcave) or ( #vFacesByAdjNumber[1] == 4 and #vTriangularFaces == 4 and Proc.nParts == 2)) then sFamily = 'DoubleBevel' bIsThrough = false end if sFamily then FeatureTopology.sFamily = sFamily FeatureTopology.bIsThrough = bIsThrough FeatureTopology.bAllRightAngles = bAllRightAngles -- caso speciale taglio testa e coda if FeatureTopology.sFamily == 'HeadCut' or FeatureTopology.sFamily == 'TailCut' then FeatureTopology.sName = FeatureTopology.sFamily else FeatureTopology.sName = GetTopologyName( sFamily, Proc.nFct, bIsThrough) end FeatureTopology.AdjacencyMatrix = vAdj -- feature che necessita di essere catalogata, ma non si capisce come else FeatureTopology.sFamily = 'NOT_IMPLEMENTED' FeatureTopology.sName = FeatureTopology.sFamily end return FeatureTopology end --------------------------------------------------------------------- -- recupera classificazione feature (da info BTL, non geometrica) function FeatureLib.GetTopologyFromFeature( Proc, Part) local Topology = {} Topology.sFamily = 'FEATURE' -- per feature 'Mortasa' si setta topologia da info BTL se raggiata if ID.IsMortise( Proc) or ID.IsFrontMortise( Proc) then Topology.sName = 'Pocket-Round' if Proc.FeatureInfo.bIsFrontMortise then Topology.sName = Topology.sName .. '-Front' elseif Proc.FeatureInfo.bIsMortiseThrough then Topology.sName = Topology.sName .. '-Through' end elseif ID.IsRidgeLap( Proc) then Topology.sName = 'RidgeLap-3-Through' elseif ID.IsHipValleyRafterNotch( Proc) then Topology.sName = 'RafterNotch-' .. tostring( Proc.nFct) .. '-Through' else Topology.sName = 'Feature' end return Topology end ------------------------------------------------------------------------------------------------------------- function FeatureLib.GetAdditionalInfo( Proc, Part) Proc.FeatureInfo = {} -- se foro if ID.IsDrill( Proc) then Proc.FeatureInfo = FeatureLib.GetDrillingData( Proc) -- se tenone o tenone a coda di rondine elseif ID.IsTenon( Proc) or ID.IsDovetailTenon( Proc) then Proc.FeatureInfo, Proc.AffectedFaces = FeatureLib.GetTenonData( Proc) -- se mortasa a coda di rondine o mortasa frontale a coda di rondine elseif ID.IsDovetailMortise( Proc) or ID.IsFrontDovetailMortise( Proc) then Proc.FeatureInfo = FeatureLib.GetDTMortiseData( Proc) -- se mortasa a coda di rondine o mortasa frontale a coda di rondine elseif ID.IsMortise( Proc) or ID.IsFrontMortise( Proc) then Proc.FeatureInfo = FeatureLib.GetMortiseData( Proc, Part) elseif ID.IsRidgeLap( Proc) then Proc.AdjacencyMatrix = FaceData.GetAdjacencyMatrix( Proc) Proc.Faces = FaceData.GetFacesInfo( Proc, Part) elseif ID.IsMarking( Proc) or ID.IsText( Proc) then Proc.FeatureInfo = FeatureLib.GetMarkTextData( Proc) elseif ID.IsHipValleyRafterNotch( Proc) then Proc.AdjacencyMatrix = FaceData.GetAdjacencyMatrix( Proc) Proc.Faces = FaceData.GetFacesInfo( Proc, Part) Proc.FeatureInfo, Proc.MainFaces = FeatureLib.GetRafterNotchData( Proc) end return Proc end ------------------------------------------------------------------------------------------------------------- -- Recupero dati foro e adattamento se speciale function FeatureLib.GetDrillingData( Proc) local idAux = EgtGetInfo( Proc.id, 'AUXID', 'i') if idAux then idAux = idAux + Proc.id end local FeatureExtraInfo = {} -- verifico se foro da adattare if EgtExistsInfo( Proc.id, 'DiamUser') then if idAux and EgtGetType( idAux) == GDB_TY.CRV_ARC and BeamData.USER_HOLE_DIAM and BeamData.USER_HOLE_DIAM > 1 then EgtModifyArcRadius( idAux, BeamData.USER_HOLE_DIAM / 2) end end FeatureExtraInfo.dDrillDiam = 2 * EgtArcRadius( idAux) or EgtGetInfo( Proc.id, 'P12', 'd') or 0 FeatureExtraInfo.dDrillLen = abs( EgtCurveThickness( idAux)) or 0 FeatureExtraInfo.ptDrillCenter = EgtCP( idAux, GDB_RT.GLOB) FeatureExtraInfo.vtDrillExtrusion = EgtCurveExtrusion( idAux, GDB_RT.GLOB) FeatureExtraInfo.nDrillFcs = EgtGetInfo( Proc.id, 'FCS', 'i') or 0 FeatureExtraInfo.nDrillFce = EgtGetInfo( Proc.id, 'FCE', 'i') or 0 FeatureExtraInfo.bIsDrillOpen = ( FeatureExtraInfo.nDrillFcs ~= 0 and FeatureExtraInfo.nDrillFce ~= 0) FeatureExtraInfo.bIsDrillHorizontal = abs( FeatureExtraInfo.vtDrillExtrusion:getZ()) < 10 * GEO.EPS_SMALL FeatureExtraInfo.idAddAuxGeom = idAux return FeatureExtraInfo end ------------------------------------------------------------------------------------------------------------- -- Recupero dati tenone e tenone a coda di rondine function FeatureLib.GetRafterNotchData( Proc) local FeatureExtraInfo = {} local MainFaces = {} local FacesByAdjacencyNumber = FaceData.GetFacesByAdjacencyNumber( Proc) if FacesByAdjacencyNumber then -- si prende sempre quella con più adiacenze MainFaces.BottomFaces = FacesByAdjacencyNumber[ Proc.nFct - 1] MainFaces.BottomFaces[1].vtN = Proc.Faces[MainFaces.BottomFaces[1].id+1].vtN MainFaces.BottomFaces[1].dElevation= Proc.Faces[MainFaces.BottomFaces[1].id+1].dElevation FeatureExtraInfo.dFaceLength = 9999 -- si cerca lato aperto più corto for i = 1, #MainFaces.BottomFaces[1].Edges do if MainFaces.BottomFaces[1].Edges[i].bIsOpen then FeatureExtraInfo.dFaceLength = min ( FeatureExtraInfo.dFaceLength, MainFaces.BottomFaces[1].Edges[i].dLength) end end end return FeatureExtraInfo, MainFaces end ------------------------------------------------------------------------------------------------------------- -- Recupero dati tenone e tenone a coda di rondine function FeatureLib.GetMarkTextData( Proc) local FeatureExtraInfo = {} -- recupero i dati della marcatura local vtExtr if EgtGetType( Proc.id) ~= GDB_TY.EXT_TEXT then vtExtr = EgtCurveExtrusion( Proc.id, GDB_RT.GLOB) else vtExtr = EgtTextNormVersor( Proc.id, GDB_ID.ROOT) end FeatureExtraInfo.vtExtr = vtExtr FeatureExtraInfo.AdditionalGeometries = EgtSplitString( EgtGetInfo( Proc.id, 'AUXID', 's')) or {} return FeatureExtraInfo end ------------------------------------------------------------------------------------------------------------- -- Recupero dati tenone e tenone a coda di rondine function FeatureLib.GetTenonData( Proc) local FeatureExtraInfo = {} -- recupero e verifico l'entità curva local idAux = EgtGetInfo( Proc.id, 'AUXID', 'i') if idAux then idAux = idAux + Proc.id end -- recupero i dati della curva local vtN = EgtCurveExtrusion( idAux, GDB_RT.GLOB) local ptBC = EgtGP( idAux, GDB_RT.GLOB) -- determino altezza del tenone local frTen = Frame3d( ptBC, vtN) local b3Ten = EgtGetBBoxRef( Proc.id, GDB_BB.STANDARD, frTen) local dTenH = b3Ten:getDimZ() -- assegno centro e normale della faccia top local ptC = ptBC + vtN * dTenH -- calcolo distanza massima della curva dal punto più lontano della base tenone (facet 0) local dMaxDist for i = 0, Proc.nFct - 1 do local ptFC, vtFN = EgtSurfTmFacetCenter( Proc.id, i, GDB_ID.ROOT) if not AreSameVectorApprox( vtFN, vtN) or abs( ( ptFC - ptBC) * vtN) > 100 * GEO.EPS_SMALL then break end local nLoopId, nLoopCnt = EgtExtractSurfTmFacetLoops( Proc.id, i, EgtGetParent( Proc.id)) if nLoopId then local dUmin, dUmax = EgtCurveDomain( nLoopId) for dU = dUmin, dUmax do local ptP = EgtUP( nLoopId, dU, GDB_ID.ROOT) local ptNear = EgtNP( idAux, ptP, GDB_ID.ROOT) local dDist = dist( ptP, ptNear) if not dMaxDist or dDist > dMaxDist then dMaxDist = dDist end end for j = 1, nLoopCnt do EgtErase( nLoopId + j - 1) end end end if not dMaxDist then local b3TenonAux = EgtGetBBoxRef( idAux, GDB_BB.STANDARD, frTen) dMaxDist = 2 * ( b3Ten:getRadius() - b3TenonAux:getRadius()) end FeatureExtraInfo.dTenonPathLength = EgtCurveLength( idAux) FeatureExtraInfo.dTenonLength = dTenH FeatureExtraInfo.dTenonMaxDist = dMaxDist FeatureExtraInfo.vtTenonN = vtN FeatureExtraInfo.ptTenonCenter = ptC FeatureExtraInfo.idAddAuxGeom = idAux -- aggiorno AffectedFaces in base al tipo di feature local UpdatedAffectedFaces = Proc.AffectedFaces if vtN:getX() > 0 then UpdatedAffectedFaces.bRight = true else UpdatedAffectedFaces.bLeft = true end return FeatureExtraInfo, UpdatedAffectedFaces end ------------------------------------------------------------------------------------------------------------- -- Recupero dati fmortasa a coda di rondine function FeatureLib.GetDTMortiseData( Proc) local FeatureExtraInfo = {} local idAux = EgtGetInfo( Proc.id, 'AUXID', 'i') if idAux then idAux = idAux + Proc.id end local vtMortiseN = EgtCurveExtrusion( idAux, GDB_RT.GLOB) -- ne determino l'asse local vtAx = EgtEV( idAux, GDB_RT.GLOB) - EgtSV( idAux, GDB_RT.GLOB) vtAx:normalize() -- determino l'altezza della mortasa (0=faccia di fondo) local rFrameDtMortise, dMortiseLength, dMortiseWidth = EgtSurfTmFacetMinAreaRectangle( Proc.id, 0, GDB_RT.GLOB) if abs( rFrameDtMortise:getVersY() * vtAx) > abs( rFrameDtMortise:getVersX() * vtAx) then rFrameDtMortise:rotate( rFrameDtMortise:getOrigin(), rFrameDtMortise:getVersZ(), 90) dMortiseLength, dMortiseWidth = dMortiseWidth, dMortiseLength end local b3DtMortise = EgtGetBBoxRef( Proc.id, GDB_BB.STANDARD, rFrameDtMortise) local dMortiseDepth = b3DtMortise:getDimZ() -- recupero il raggio minimo della mortasa local dMortiseMinRadius = 1000 local nSt, nEnd = EgtCurveDomain( idAux) for i = nSt, nEnd - 1 do local dRad = EgtCurveCompoRadius( idAux, i) if dRad > 0 and dRad < dMortiseMinRadius then dMortiseMinRadius = dRad end end -- distanza massima all'imbocco ortogonale all'asse local vtDiff = EgtEP( idAux, GDB_RT.GLOB) - EgtSP( idAux, GDB_RT.GLOB) local vtOrtDiff = vtDiff - vtDiff * vtAx * vtAx local dMortiseMaxDist = min( vtOrtDiff:len(), dMortiseWidth) FeatureExtraInfo.bIsFrontMortise = Proc.nPrc == 56 FeatureExtraInfo.dMortiseLength = dMortiseLength FeatureExtraInfo.dMortiseWidth = dMortiseWidth FeatureExtraInfo.dMortiseMaxDist = dMortiseMaxDist FeatureExtraInfo.dMortiseDepth = dMortiseDepth FeatureExtraInfo.dMortiseMinRadius = dMortiseMinRadius FeatureExtraInfo.vtMortiseN = vtMortiseN vtDiff:normalize() FeatureExtraInfo.vtMortisePathStart = EgtSV( idAux, GDB_RT.GLOB) * vtDiff FeatureExtraInfo.vtMortisePathEnd = EgtEV( idAux, GDB_RT.GLOB) * vtDiff FeatureExtraInfo.idAddAuxGeom = idAux return FeatureExtraInfo end ------------------------------------------------------------------------------------------------------------- -- Recupero dati mortasa function FeatureLib.GetMortiseData( Proc, Part) local FeatureExtraInfo = {} local idAux = EgtGetInfo( Proc.id, 'AUXID', 'i') if idAux then idAux = idAux + Proc.id end local vtMortiseN = EgtCurveExtrusion( idAux, GDB_RT.GLOB) -- recupero i dati della faccia di fondo local frMortise, dMortiseLength, dMortiseWidth = EgtSurfTmFacetMinAreaRectangle( Proc.id, 0, GDB_ID.ROOT) -- se curva di contorno aperta la rendo chiusa local _, bCurveModified = BeamLib.ConvertToClosedCurve( Proc, idAux) -- Confronto le direzioni dei 2 versori : se diverse la faccia 0 non è il fondo => mortasa passante local bMortiseThrough = not AreSameVectorApprox( vtMortiseN, frMortise:getVersZ()) -- in caso sia passante si ricalcola tutto sul contorno della tasca if bMortiseThrough then -- creo superficie chiusa sul contorno local nFlat = EgtSurfTmByFlatContour( EgtGetParent( idAux), idAux, 0.05) if nFlat then frMortise, dMortiseLength, dMortiseWidth = EgtSurfTmFacetMinAreaRectangle( nFlat, 0, GDB_ID.ROOT) -- verifico se copiare la geometria lungo l'asse Z local b3Aux = EgtGetBBoxRef( idAux, GDB_BB.STANDARD, frMortise) local bxMax = b3Aux:getMax() local b3Mor = EgtGetBBoxRef( Proc.id, GDB_BB.STANDARD, frMortise) local bxMin = b3Mor:getMin() local dMove = bxMin:getZ() - bxMax:getZ() -- se il percorso ausiliario è esterno al grezzo, lo riavvicino if abs( dMove) > GEO.EPS_SMALL then idAux = EgtCopyGlob( idAux, BeamLib.GetAddGroup( Part.id)) local vtMove = Vector3d( 0, 0, dMove) vtMove:toGlob( frMortise) EgtMove( idAux, vtMove, GDB_RT.GLOB) EgtMove( nFlat, vtMove, GDB_RT.GLOB) frMortise, dMortiseLength, dMortiseWidth = EgtSurfTmFacetMinAreaRectangle( nFlat, 0, GDB_ID.ROOT) end -- cancello la superficie piana utilizzata per ricalcolare dati EgtErase( nFlat) end end -- determino altezza della mortasa local b3Mortise = EgtGetBBoxRef( Proc.id, GDB_BB.STANDARD, frMortise) local dMortiseDepth = b3Mortise:getDimZ() -- recupero il raggio minimo della mortasa local dMortiseMinRadius = 1000 local nSt, nEnd = EgtCurveDomain( idAux) for i = nSt, nEnd - 1 do local dRad = EgtCurveCompoRadius( idAux, i) if dRad > 0 and dRad < dMortiseMinRadius then dMortiseMinRadius = dRad end end -- se la mortasa passante il contorno è sulla faccia della trave e il riconoscimento lati aperti non è corretto if not bCurveModified and not bMortiseThrough then BeamLib.SetOpenSide( idAux, Part.b3Part) end FeatureExtraInfo.bIsFrontMortise = Proc.nPrc == 51 FeatureExtraInfo.bIsMortiseThrough = bMortiseThrough FeatureExtraInfo.bIsMortiseOpen = bCurveModified FeatureExtraInfo.dMortiseLength = dMortiseLength FeatureExtraInfo.dMortiseWidth = dMortiseWidth FeatureExtraInfo.dMortiseDepth = dMortiseDepth FeatureExtraInfo.dMortiseMinRadius = dMortiseMinRadius FeatureExtraInfo.vtMortiseN = vtMortiseN FeatureExtraInfo.idAddAuxGeom = idAux -- aggiustamento affected faces in caso di feature frontale if FeatureExtraInfo.bIsFrontMortise and FeatureExtraInfo.vtMortiseN:getX() < 0 then Proc.AffectedFaces.bLeft = true elseif FeatureExtraInfo.bIsFrontMortise and FeatureExtraInfo.vtMortiseN:getX() > 0 then Proc.AffectedFaces.bRight = true end return FeatureExtraInfo end ------------------------------------------------------------------------------------------------------------- -- funzione che restituisce indice di completamento in base alla percentuale di volume lavorato function FeatureLib.GetFeatureCompletionIndex( dCompletionPercentage) -- indice di completamento local dCompletionIndex = 0 -- nullo if dCompletionPercentage < 5 then dCompletionIndex = 0 -- Low elseif dCompletionPercentage < 50 then dCompletionIndex = 1 -- Medium elseif dCompletionPercentage < 80 then dCompletionIndex = 2 -- High elseif dCompletionPercentage < 95 then dCompletionIndex = 4 -- High / Complete else dCompletionIndex = 5 end return dCompletionIndex end ------------------------------------------------------------------------------------------------------------- function FeatureLib.GetStrategyQuality( Parameter) local dQuality = 0 -- se viene passata una tabella, si calcola media pesata di tutte le lavorazioni if type( Parameter) == "table" then local Machinings = Parameter local dQualityNumerator = 0 local dQualityDenominator = 0 for i = 1, #Machinings do local Machining = Machinings[i] local dWeightedQuality = FeatureLib.GetStrategyQuality( TOOLS[Machining.nToolIndex].sFamily) * Machining.dLengthToMachine if Machining.bIsApplicable then dQualityNumerator = dQualityNumerator + dWeightedQuality dQualityDenominator = dQualityDenominator + Machining.dLengthToMachine end end dQuality = dQualityNumerator / dQualityDenominator -- se viene passato un tag, si ritorna direttamente il voto elseif type( Parameter) == "string" then local sMachiningTag = Parameter -- #### STATI DI LAVORAZIONE AGGREGATI #### -- -- BEST = lavorazione eccellente, la migliore qualità che si possa ottenere if sMachiningTag == 'BEST' then dQuality = 5 -- FINE = lavorazione ottima, non dovrebbero esscerci scheggiature elseif sMachiningTag == 'FINE' then dQuality = 4 -- STD = lavorazione accettabile, potrebbe avere scheggiature minime elseif sMachiningTag == 'STD' then dQuality = 3 -- SEMI = lavorazione accettabile, potrebbe avere scheggiature minime, mancano piccoli dettagli alla lavorazione (es. se vengono lasciati i raggi fresa sugli spigoli) elseif sMachiningTag == 'SEMI' then dQuality = 2.5 -- ROUGH = Lavorazione scadente, scheggiatura molto probabile, potrebbe non essere precisa elseif sMachiningTag == 'ROUGH' then dQuality = 1 -- #### UTENSILI DI LAVORAZIONE UTILIZZATI #### -- elseif sMachiningTag == 'SAWBLADE' then dQuality = 5 elseif sMachiningTag == 'DRILLBIT' then dQuality = 4 elseif sMachiningTag == 'MILL' then dQuality = 3 elseif sMachiningTag == 'MORTISE' then dQuality = 1 else dQuality = 0 end else dQuality = 0 end return dQuality end ------------------------------------------------------------------------------------------------------------- function FeatureLib.GetStrategyTimeToMachine( Machinings) local dTimeToMachine = 0 for i = 1, #Machinings do local Machining = Machinings[i] if Machining.bIsApplicable then dTimeToMachine = dTimeToMachine + Machining.dTimeToMachine end end return dTimeToMachine end ------------------------------------------------------------------------------------------------------------- function FeatureLib.GetStrategyResultNotApplicable( sInfo) local Result = {} if not type( sInfo) == "string" then sInfo = '' end Result.dTimeToMachine = 0 Result.dMRR = 0 Result.dCompletionPercentage = 0 Result.sStatus = 'Not-Applicable' Result.dCompletionIndex = 0 Result.dQuality = 0 Result.sInfo = sInfo return Result end ------------------------------------------------------------------------------------------------------------- local function GetMachiningStrategyCoefficients( sMachiningStrategy) local dCoeffQuality, dCoeffCompletion, dCoeffTime -- queste sono tutte le opzioni possibili. Da configurazione si può settare solo: AUTO, FASTEST, HIGH_QUALITY if sMachiningStrategy == 'FASTEST' then dCoeffQuality, dCoeffCompletion, dCoeffTime = 0.75, 0.75, 1.5 elseif sMachiningStrategy == 'HIGH_QUALITY' then dCoeffQuality, dCoeffCompletion, dCoeffTime = 1.5, 0.75, 0.75 elseif sMachiningStrategy == 'COMPLETEST' then dCoeffQuality, dCoeffCompletion, dCoeffTime = 0.75, 1.5, 0.75 elseif sMachiningStrategy == 'IGNORE_TIME' then dCoeffQuality, dCoeffCompletion, dCoeffTime = 1.25, 1.25, 0.5 elseif sMachiningStrategy == 'IGNORE_QUALITY' then dCoeffQuality, dCoeffCompletion, dCoeffTime = 0.5, 1.25, 1.25 elseif sMachiningStrategy == 'IGNORE_COMPLETION' then dCoeffQuality, dCoeffCompletion, dCoeffTime = 1.25, 0.5, 1.25 else -- sMachiningStrategy == 'FIRST_IN_LIST' or sMachiningStrategy == 'AUTO' (oppure non settato, ma dovrebbe essere sempre settato!) dCoeffQuality, dCoeffCompletion, dCoeffTime = 1, 1, 1 end return dCoeffQuality, dCoeffCompletion, dCoeffTime end ------------------------------------------------------------------------------------------------------------- -- TODO rivedere affidabilità del calcolo del composite rating -- funzione che calcola il 'CompositeRating' di ogni strategia function FeatureLib.CalculateStrategiesCompositeRating( AvailableStrategies, sMachiningStrategy) for n = 1, #AvailableStrategies do -- se ho tutti i dati che mi servono calcolo il rating della strategia applicato alla feature if AvailableStrategies[n].Result and AvailableStrategies[n].Result.dQuality and AvailableStrategies[n].Result.dCompletionIndex and AvailableStrategies[n].Result.dTimeToMachine then -- il tempo è pesato sul totale delle strategie, e riportato nell'intervallo 1-5, dove 5 è il tempo migliore (il più basso) local dIndexWeightTimeToMachine = 5 - ( 4 * ( EgtClamp( AvailableStrategies[n].Result.dTimeToMachine / AvailableStrategies.dAllStrategiesTotalTime, 0, 1))) AvailableStrategies[n].Result.dTimeIndex = dIndexWeightTimeToMachine -- si calcolano gli indici pesati in base alla configurazione utente. Possibili parametri di configurazione: local dQuality, dCompletion, dTime, dCoeffQuality, dCoeffCompletion, dCoeffTime dCoeffQuality, dCoeffCompletion, dCoeffTime = GetMachiningStrategyCoefficients( sMachiningStrategy) dQuality = AvailableStrategies[n].Result.dQuality * dCoeffQuality dCompletion = AvailableStrategies[n].Result.dCompletionIndex * dCoeffCompletion dTime = dIndexWeightTimeToMachine * dCoeffTime AvailableStrategies[n].Result.dCompositeRating = dQuality + dCompletion + dTime -- TODO da verificare se meglio sommare o moltiplicare gli indici else AvailableStrategies[n].Result.dCompositeRating = 0 end end return AvailableStrategies end ------------------------------------------------------------------------------------------------------------- -- TODO rivedere affidabilità del calcolo del composite rating -- funzione che calcola il 'CompositeRating' di ogni combinazione function FeatureLib.CalculateCombinationsCompositeRating( CombinationsList, sMachiningStrategy) for n = 1, #CombinationsList do -- se ho tutti i dati che mi servono calcolo il rating della strategia applicato alla feature if CombinationsList[n].dTotalQuality and CombinationsList[n].dTotalCompletionIndex and CombinationsList[n].dTotalTimeToMachine then -- il tempo è pesato sul totale delle strategie, e riportato nell'intervallo 1-5, dove 5 è il tempo migliore (il più basso) local dIndexWeightTimeToMachine = 5 - ( 4 * ( EgtClamp( CombinationsList[n].dTotalTimeToMachine / CombinationsList.dAllCombinationsTotalTime, 0, 1))) -- si calcolano gli indici pesati in base alla configurazione utente. Possibili parametri di configurazione: local dQuality, dCompletion, dTime, dCoeffQuality, dCoeffCompletion, dCoeffTime dCoeffQuality, dCoeffCompletion, dCoeffTime = GetMachiningStrategyCoefficients( sMachiningStrategy) dQuality = CombinationsList[n].dTotalQuality * dCoeffQuality dCompletion = CombinationsList[n].dTotalCompletionIndex * dCoeffCompletion dTime = dIndexWeightTimeToMachine * dCoeffTime CombinationsList[n].dTotalRating = dQuality + dCompletion + dTime -- TODO da verificare se meglio sommare o moltiplicare gli indici else CombinationsList[n].dTotalRating = 0 end end return CombinationsList end ------------------------------------------------------------------------------------------------------------- function FeatureLib.IsMachiningLong( dMachiningLengthOnX, Part, OptionalParameters) local bIsMachiningLong -- parametri opzionali if not OptionalParameters then OptionalParameters = {} end local dMaxSegmentLength = OptionalParameters.dMaxSegmentLength or BeamData.LONGCUT_MAXLEN bIsMachiningLong = ( dMachiningLengthOnX > dMaxSegmentLength + 10 * GEO.EPS_SMALL) or ( dMachiningLengthOnX > 0.7 * Part.b3Part:getDimX() + 10 * GEO.EPS_SMALL) return bIsMachiningLong end ------------------------------------------------------------------------------------------------------------- function FeatureLib.GetFeatureSplittingPoints( Proc, Part, OptionalParameters) local FeatureSplittingPoints = {} local bFeatureStartsOnEdgeLeft = false local bFeatureStartsOnEdgeRight = false local dSplitXLeft = Proc.b3Box:getMin():getX() local dSplitXRight = Proc.b3Box:getMax():getX() local dFeatureCentralLength = Proc.b3Box:getDimX() -- parametri opzionali if not OptionalParameters then OptionalParameters = {} end local dMaxSegmentLength = OptionalParameters.dMaxSegmentLength or BeamData.LONGCUT_MAXLEN local dMaxSegmentLengthOnEdges = OptionalParameters.dMaxSegmentLengthOnEdges or BeamData.LONGCUT_ENDLEN local dMinSegmentLength = OptionalParameters.dMinSegmentLength or dMaxSegmentLengthOnEdges / 2 local dToolOverlapBetweenSegments = OptionalParameters.dToolOverlapBetweenSegments or BeamData.MILL_OVERLAP -- verifica spezzatura necessaria if not FeatureLib.IsMachiningLong( Proc.b3Box:getDimX(), Part) then return {} end -- verifica se necessari spezzoni differenti sugli estremi if Proc.b3Box:getMin():getX() < Part.b3Part:getMin():getX() + dMaxSegmentLengthOnEdges - 10 * GEO.EPS_SMALL then bFeatureStartsOnEdgeLeft = true end if Proc.b3Box:getMax():getX() > Part.b3Part:getMax():getX() - dMaxSegmentLengthOnEdges + 10 * GEO.EPS_SMALL then bFeatureStartsOnEdgeRight = true end -- calcolo punto estremo sinistro local ptSplitXLeft if bFeatureStartsOnEdgeLeft then -- decido punto spezzatura verso la coda if Proc.b3Box:getDimX() > dMaxSegmentLengthOnEdges * 2 then dSplitXLeft = max( Part.b3Part:getMin():getX() + dMaxSegmentLengthOnEdges, Proc.b3Box:getMin():getX() + dMinSegmentLength) else -- se pezzo abbastanza piccolo, spezzo in mezzo al 'pezzo + grezzo restante' if Part.dRestLength + Part.b3Part:getDimX() < BeamData.dMinRaw * 1.5 then dSplitXLeft = Part.b3Part:getMax():getX() - ( ( Part.dRestLength + Part.b3Part:getDimX()) / 2) else dSplitXLeft = max( Proc.b3Box:getMin():getX() + ( BeamData.dMinRaw)/2 + 150, Part.b3Part:getMax():getX() - dMaxSegmentLengthOnEdges) end end dFeatureCentralLength = abs( dSplitXRight - dSplitXLeft) ptSplitXLeft = Point3d( dSplitXLeft, 0, 0) end -- calcolo punto estremo destro local ptSplitXRight if bFeatureStartsOnEdgeRight then dSplitXRight = min( ( Proc.b3Box:getMax():getX() - dMinSegmentLength), Part.b3Part:getMax():getX() - dMaxSegmentLengthOnEdges) if dSplitXRight - dSplitXLeft < 500 * GEO.EPS_SMALL then dSplitXRight = dSplitXLeft - dToolOverlapBetweenSegments dFeatureCentralLength = 0 bFeatureStartsOnEdgeLeft = false else dFeatureCentralLength = dSplitXRight - dSplitXLeft end ptSplitXRight = Point3d( dSplitXRight, 0, 0) end -- aggiungo eventuale punto estremo destro if bFeatureStartsOnEdgeRight then table.insert( FeatureSplittingPoints, ptSplitXRight) end -- aggiungo punti centrali della feature if dFeatureCentralLength > 0 then local nSplitParts = max( ceil( dFeatureCentralLength / dMaxSegmentLength + 10 * GEO.EPS_SMALL), 1) local dSplitPartsLen = dFeatureCentralLength / nSplitParts for i = 1, ( nSplitParts - 1) do local ptOn local dCurrentPointX = dSplitXRight - i * dSplitPartsLen ptOn = Point3d( dCurrentPointX, 0, 0) table.insert( FeatureSplittingPoints, ptOn) end end -- aggiungo eventuale punto estemo sinistro if bFeatureStartsOnEdgeLeft then table.insert( FeatureSplittingPoints, ptSplitXLeft) end -- TODO RIMUOVERE E SISTEMARE LA FUNZIONE!! -- se il punto finisce fuori si mette in mezzeria if #FeatureSplittingPoints == 1 and ( ( FeatureSplittingPoints[1]:getX() < Part.b3Part:getMin():getX() + 10 * GEO.EPS_SMALL) or ( FeatureSplittingPoints[1]:getX() > Part.b3Part:getMax():getX() - 10 * GEO.EPS_SMALL)) then FeatureSplittingPoints[1] = Point3d( Part.b3Part:getMin():getX() + ( Part.b3Part:getMax():getX() - Part.b3Part:getMin():getX()) / 2, 0, 0) end return FeatureSplittingPoints end ------------------------------------------------------------------------------------------------------------- function FeatureLib.GetFeatureVolume( Proc, Part) local dProcVolume = 0 local idTempGroup = Part.idTempGroup local idProcCopy = EgtCopyGlob( Proc.id, idTempGroup) or GDB_ID.NULL local b3PartCopy = BBox3d( Part.b3Part) b3PartCopy:expand( -100 * GEO.EPS_SMALL) local idPartCopy = EgtSurfTmBBox( idTempGroup, b3PartCopy , false, GDB_RT.GLOB) EgtSurfTmSubtract( idPartCopy, idProcCopy) dProcVolume = EgtSurfVolume( idPartCopy) return dProcVolume end ------------------------------------------------------------------------------------------------------------- -- funzione che verifica se la feature, lavorata in questa fase, compromette lettura misura laser function FeatureLib.CalculateFeatureHindersLaserMeasure( Proc, Part) local bFeatureHindersLaserMeasure = false -- se la feature è aperta frontalmente, posteriormente, di testa e più bassa di 40mm, allora potrebbe impattare sulla misura laser, controllo caso per caso if Proc.AffectedFaces.bRight and Proc.AffectedFaces.bFront and Proc.AffectedFaces.bBack and ( Proc.b3Box:getMin():getZ() - Part.b3Raw:getMin():getZ()) < 40 then bFeatureHindersLaserMeasure = true end return bFeatureHindersLaserMeasure end ------------------------------------------------------------------------------------------------------------- -- TODO funzione copiata direttamente da automatismo vecchio, da migliorare / completare function FeatureLib.CalculateFeatureNotClampableLengths( Proc, Part) local NotClampableLength = {} local dNotClampableLengthHead = 0 local dNotClampableLengthTail = 0 -- se il grezzo non è definito, prendo il box del pezzo -- TODO 1- si sta passando b3part per riferimento, 2- non dovrebbe essre sempre definito? if not Part.b3Raw then Part.b3Raw = Part.b3Part end -- TODO se la feature ha più di 3 facce non viene mai calcolato!! Es. Tenone ecc.... if Proc.nFct < 3 then -- eventuale segnalazione ingombro di testa o coda local dMinHIng = min( 0.5 * BeamData.VICE_MINH, 0.5 * Part.b3Raw:getDimZ()) local dMinZ = max( BeamData.MIN_HEIGHT, 0.35 * Part.b3Raw:getDimZ()) -- calcolo punto massimo in Z fino a dove considerare il pinzaggio. Minimo tra pinzaggio massimo e altezza pezzo local dMaxHZ = Part.b3Raw:getMin():getZ() + min( BeamData.VICE_MAXH or BeamData.MAX_HEIGHT, Part.b3Raw:getDimZ()) -- punto massimo in Z considerando anche la Z della feature local dMaxHZFeat = min( dMaxHZ, Proc.b3Box:getMax():getZ()) -- dimensione Z del pinzaggio (differenza massima Z pinzabile e box feature) local dDeltaZClamp = ( ( dMaxHZ - Part.b3Raw:getMin():getZ()) - max( 0, dMaxHZFeat - Proc.b3Box:getMin():getZ())) -- se pinzaggio minimo è come il massimo (oppure come l'altezza massima del pezzo) significa che è verticale local bIsVertClamps = BeamData.VICE_MINH > BeamData.MAX_HEIGHT - 100 * GEO.EPS_SMALL -- condizioni per limitare pinzaggio testa/coda local bUpdateIng = true -- se dimensione del box della feature maggiore di metà pinzaggio minimo o metà spessore pezzo -- bUpdateIng = bUpdateIng and Proc.b3Box:getDimZ() > dMinHIng -- se la feature si trova più in basso del minimo pinzabile in Z o il 35% dello spessore pezzo bUpdateIng = bUpdateIng and Proc.b3Box:getMin():getZ() < Part.b3Raw:getMin():getZ() + dMinZ -- se feature è al di sotto del pinzaggio massimo bUpdateIng = bUpdateIng and Proc.b3Box:getMin():getZ() < dMaxHZ -- se ho le morse verticali, o se la feature è in centro o verso alto, controllo se non prendo abbastanza if bIsVertClamps or ( Proc.b3Box:getMin():getZ() - Part.b3Raw:getMin():getZ()) > BeamData.MIN_HEIGHT then bUpdateIng = bUpdateIng and dDeltaZClamp < BeamData.VICE_MINH end if bUpdateIng then if Proc.AffectedFaces.bRight then local dOffs = Part.b3Part:getMax():getX() - Proc.b3Box:getMin():getX() -- se pinze a 45° e pinza abbastanza materiale, compenso comunque, ma solo inclinazione morse if not bIsVertClamps and dDeltaZClamp > BeamData.VICE_MINH and BeamData.VICE_MAXH then dOffs = min( dOffs, BeamData.VICE_MAXH - BeamData.VICE_MINH) end dNotClampableLengthHead = dOffs end if Proc.AffectedFaces.bLeft then local dOffs = Proc.b3Box:getMax():getX() - Part.b3Part:getMin():getX() -- se pinze a 45° e pinza abbastanza materiale, compenso comunque, ma solo inclinazione morse if not bIsVertClamps and dDeltaZClamp > BeamData.VICE_MINH and BeamData.VICE_MAXH then dOffs = min( dOffs, BeamData.VICE_MAXH - BeamData.VICE_MINH) end dNotClampableLengthTail = dOffs end end end NotClampableLength.dNotClampableLengthHead = dNotClampableLengthHead NotClampableLength.dNotClampableLengthTail = dNotClampableLengthTail return NotClampableLength end ------------------------------------------------------------------------------------------------------------- function FeatureLib.GetFeatureRotationNotClampableLengths( Proc, Part, nRotation) local nPartIndex = Part.nIndexInParts local nProcIndex = Proc.nIndexInVProc local Rotations = PROCESSINGS[nPartIndex].Rotation local dLenOnHead = 0 local dLenOnTail = 0 -- controllo che la rotazione sia attiva if string.sub( Part.ChosenCombination, nRotation, nRotation) == '1' then dLenOnHead = Rotations[nRotation][nProcIndex].NotClampableLength.dNotClampableLengthHead dLenOnTail = Rotations[nRotation][nProcIndex].NotClampableLength.dNotClampableLengthTail end return dLenOnHead, dLenOnTail end ------------------------------------------------------------------------------------------------------------- function FeatureLib.GetFeatureMaxNotClampableLengths( Proc, Part) local nPartIndex = Part.nIndexInParts local nProcIndex = Proc.nIndexInVProc local Rotations = PROCESSINGS[nPartIndex].Rotation local dMaxOnHead = 0 local dMaxOnTail = 0 for i = 1, #Part.CombinationList do for j = 1, 4 do -- controllo che la rotazione sia attiva if string.sub( Part.CombinationList[i].sBitIndexCombination, j, j) == '1' then dMaxOnHead = max( Rotations[j][nProcIndex].NotClampableLength.dNotClampableLengthHead, dMaxOnHead) dMaxOnTail = max( Rotations[j][nProcIndex].NotClampableLength.dNotClampableLengthTail, dMaxOnTail) end end end return dMaxOnHead, dMaxOnTail end ------------------------------------------------------------------------------------------------------------- return FeatureLib