Files
databeamnew/LuaLibs/FeatureLib.lua
T

1001 lines
44 KiB
Lua

-- 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( 'BeamData')
-- 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)
-- 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
---------------------------------------------------------------------
-- recupero topologia della feature
function FeatureLib.NeedTopologyFeature( Proc, Part)
-- feature Egalware con calcolo topologia SEMPRE da geometria
if ID.IsHeadCut( Proc) then -- (1-340)
return true
elseif ID.IsTailCut( Proc) then -- (2-350)
return true
-- feature BTL con calcolo topologia SEMPRE da geometria
elseif 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 ( IsFeatureCuttingEntireSection( Proc.b3Box, Part)
or IsFeatureCuttingEntireLength( Proc.b3Box, Part)) then -- (0-13)
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
if 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.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 then
sFamily = 'VGroove'
bIsThrough = true
elseif Proc.nFct == 2 and not bAllAnglesConcave 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 #vFacesByAdjNumber[2] == 4 and #vTriangularFaces == 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[2] == 4 and #vFacesByAdjNumber[3] == 2 and #vTriangularFaces == 4 then
sFamily = 'DoubleBevel'
bIsThrough = false
end
if sFamily then
FeatureTopology.sFamily = sFamily
FeatureTopology.bIsThrough = bIsThrough
FeatureTopology.bAllRightAngles = bAllRightAngles
FeatureTopology.sName = GetTopologyName( sFamily, Proc.nFct, bIsThrough)
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) 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
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)))
-- 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 idAddGroup = BeamLib.GetAddGroup( Part.id)
if not idAddGroup then
-- TODO gestire meglio questo errore. Non conviene creare e verificare all'inizio se il gruppo esiste?
EgtOutLog( 'Error : missing AddGroup')
return 0
end
local idProcCopy = EgtCopyGlob( Proc.id, idAddGroup) or GDB_ID.NULL
local b3PartCopy = BBox3d( Part.b3Part)
b3PartCopy:expand( -100 * GEO.EPS_SMALL)
local idPartCopy = EgtSurfTmBBox( idAddGroup, b3PartCopy , false, GDB_RT.GLOB)
EgtSurfTmSubtract( idPartCopy, idProcCopy)
dProcVolume = EgtSurfVolume( idPartCopy)
EgtErase( { idProcCopy, 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 e di testa, allora potrebbe impattare sulla misura laser, controllo caso per caso
if Proc.AffectedFaces.bRight and Proc.AffectedFaces.bFront and Proc.AffectedFaces.bBack 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
-- 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
elseif 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
elseif Proc.b3Box:getCenter():getX() > Part.b3Part:getCenter():getX() then
local dOffs = Part.b3Part:getMax():getX() - Proc.b3Box:getMin():getX()
local dDist = Part.b3Part:getMax():getX() - Proc.b3Box:getMax():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
-- dDist serve??
dNotClampableLengthHead = 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