-- BLADETOWASTE.lua by Egalware s.r.l. 2025/01/08 -- Libreria di supporto a strategie con funzioni comune a strategie diverse. -- Tabella per definizione modulo local BLADETOWASTE = {} -- Include require( 'EgtBase') -- Carico i dati globali local BeamData = require( 'BeamData') local BeamLib = require( 'BeamLib') local FaceData = require( 'FaceData') local FeatureLib = require( 'FeatureLib') local MachiningLib = require( 'MachiningLib') local DiceCut = require( 'DiceCut') -- strategie di base local FaceByBlade = require('FACEBYBLADE') -- tabella per definizione modulo local Machinings = {} local FeatureInfo = {} ------------------------------------------------------------------------------------------------------------- local function CompareEdgesTopHead( EdgeA, EdgeB) -- prima i lati a minore elevazione (se entro 5 mm si considerano uguali) if EdgeA.dElevation < EdgeB.dElevation - 5 then return true elseif EdgeA.dElevation > EdgeB.dElevation + 5 then return false -- se stessa elevazione si preferiscono i lati più in basso (testa sopra) else if EdgeA.vtN:getZ() > EdgeB.vtN:getZ() + 10 * GEO.EPS_SMALL then return true elseif EdgeA.vtN:getZ() < EdgeB.vtN:getZ() - 10 * GEO.EPS_SMALL then return false -- se stessa Z si preferiscono i lati verso il fronte della trave else if EdgeA.vtN:getY() > EdgeB.vtN:getY() + 10 * GEO.EPS_SMALL then return true elseif EdgeA.vtN:getY() < EdgeB.vtN:getY() - 10 * GEO.EPS_SMALL then return false else return false end end end end local function CompareEdgesBottomHead( EdgeA, EdgeB) -- prima i lati a minore elevazione (se entro 5 mm si considerano uguali) if EdgeA.dElevation < EdgeB.dElevation - 5 then return true elseif EdgeA.dElevation > EdgeB.dElevation + 5 then return false -- se stessa elevazione si preferiscono i lati più in alto (testa sotto) else if EdgeA.vtN:getZ() < EdgeB.vtN:getZ() - 10 * GEO.EPS_SMALL then return true elseif EdgeA.vtN:getZ() > EdgeB.vtN:getZ() + 10 * GEO.EPS_SMALL then return false -- se stessa Z si preferiscono i lati verso il fronte della trave else if EdgeA.vtN:getY() > EdgeB.vtN:getY() + 10 * GEO.EPS_SMALL then return true elseif EdgeA.vtN:getY() < EdgeB.vtN:getY() - 10 * GEO.EPS_SMALL then return false else return false end end end end local function CompareEdgesNoPreference( EdgeA, EdgeB) -- prima i lati a minore elevazione if EdgeA.dElevation < EdgeB.dElevation then return true elseif EdgeA.dElevation > EdgeB.dElevation then return false -- se stessa elevazione si preferiscono i lati più in basso (testa sopra) else if EdgeA.vtN:getZ() > EdgeB.vtN:getZ() + 10 * GEO.EPS_SMALL then return true elseif EdgeA.vtN:getZ() < EdgeB.vtN:getZ() - 10 * GEO.EPS_SMALL then return false -- se stessa Z si preferiscono i lati verso il fronte della trave else if EdgeA.vtN:getY() > EdgeB.vtN:getY() + 10 * GEO.EPS_SMALL then return true elseif EdgeA.vtN:getY() < EdgeB.vtN:getY() - 10 * GEO.EPS_SMALL then return false else return false end end end end local function GetEdgeToMachine( Edges, vtNFace, sBladeType) local EdgeToMachine = {} local EdgesSorted = {} for i = 1, #Edges do table.insert( EdgesSorted, Edges[i]) end if sBladeType == 'Top' then table.sort( EdgesSorted, CompareEdgesBottomHead) EdgeToMachine = EdgesSorted[1] elseif sBladeType == 'Bottom' then table.sort( EdgesSorted, CompareEdgesTopHead) EdgeToMachine = EdgesSorted[1] elseif sBladeType == 'TopDownUp' then local vtEdgeDirection if vtNFace:getY() > -0.02 then vtEdgeDirection = -Y_AX() else vtEdgeDirection = Y_AX() end EdgeToMachine = BeamLib.FindEdgeBestOrientedAsDirection( Edges, vtEdgeDirection) else table.sort( EdgesSorted, CompareEdgesNoPreference) EdgeToMachine = EdgesSorted[1] end return EdgeToMachine end local function GetBestBlade( Proc, Part, Face, OptionalParameters) local nChosenToolIndex local sChosenBladeType -- parametri opzionali local dMinNzTopBladeIfEqual = OptionalParameters.dMinNzTopBlade or sin(-5) local dMaxNyTopBlade = OptionalParameters.dMaxNyTopBlade or sin(1) local dShortPartLength = OptionalParameters.dShortPartLength or BeamData.LEN_SHORT_PART local dElevationTop = OptionalParameters.dElevationTopBlade local dElevationBottom = OptionalParameters.dElevationBottomBlade local dElevationTopDownUp = OptionalParameters.dElevationTopBladeDownUp or 0 -- ricerca lama testa sopra local ToolSearchParameters = {} ToolSearchParameters.vtN = Face.vtN ToolSearchParameters.bAllowTopHead = true ToolSearchParameters.bAllowBottomHead = false ToolSearchParameters.bForceLongcutBlade = false ToolSearchParameters.dElevation = dElevationTop ToolInfo = MachiningLib.FindBlade( Proc, ToolSearchParameters) local nToolIndexTop if ToolInfo.dResidualDepth < 10 * GEO.EPS_SMALL then nToolIndexTop = ToolInfo.nToolIndex end -- ricerca lama testa sotto ToolSearchParameters = {} ToolSearchParameters.vtN = Face.vtN ToolSearchParameters.bAllowTopHead = false ToolSearchParameters.bAllowBottomHead = true ToolSearchParameters.bForceLongcutBlade = false ToolSearchParameters.dElevation = dElevationBottom ToolInfo = MachiningLib.FindBlade( Proc, ToolSearchParameters) local nToolIndexBottom if ToolInfo.dResidualDepth < 10 * GEO.EPS_SMALL then nToolIndexBottom = ToolInfo.nToolIndex end -- lama sopra e sotto if nToolIndexTop and nToolIndexBottom then -- angolo minimo che determina la preferenza tra lama sopra e sotto local dMinNzTopBlade -- entrambe le lame senza aggregato o entrambe con aggregato if TOOLS[nToolIndexTop].SetupInfo.bIsCSymmetrical == TOOLS[nToolIndexBottom].SetupInfo.bIsCSymmetrical then dMinNzTopBlade = dMinNzTopBladeIfEqual -- lama sopra con aggregato - preferenza testa sopra elseif not TOOLS[nToolIndexTop].SetupInfo.bIsCSymmetrical then dMinNzTopBlade = OptionalParameters.dMinNzTopBlade or TOOLS[nToolIndexTop].SetupInfo.GetMinNz( Face.vtN) / 2 -- lama sotto con aggregato - preferenza testa sotto elseif not TOOLS[nToolIndexBottom].SetupInfo.bIsCSymmetrical then dMinNzTopBlade = OptionalParameters.dMinNzTopBlade or TOOLS[nToolIndexBottom].SetupInfo.GetMaxNz( Face.vtN) / 2 else error( 'GetBestBlade : unknown blade type') end -- se la Z della faccia è sotto all'angolo minimo e inclinata in Y oppure il pezzo è corto, si preferisce la lama sotto if Face.vtN:getZ() < dMinNzTopBlade - GEO.EPS_SMALL and ( abs( Face.vtN:getY()) > dMaxNyTopBlade or Part.b3Raw:getDimX() < dShortPartLength - 10 * GEO.EPS_SMALL) then nChosenToolIndex = nToolIndexBottom else nChosenToolIndex = nToolIndexTop end -- solo lama sopra elseif nToolIndexTop then nChosenToolIndex = nToolIndexTop -- solo lama sotto elseif nToolIndexBottom then nChosenToolIndex = nToolIndexBottom end -- assegnazione tipo lama if nChosenToolIndex == nToolIndexBottom then sChosenBladeType = 'Bottom' -- se lama sopra va verificato se va usata in DownUp elseif nChosenToolIndex == nToolIndexTop then local dMinNzDownUp = TOOLS[nToolIndexTop].SetupInfo.GetMinNzDownUp( Part.b3Raw, Face.vtN) local dCurrentResidualDepth = dElevationTopDownUp - TOOLS[nToolIndexTop].dMaxDepth if Face.vtN:getZ() < dMinNzDownUp then if ( dCurrentResidualDepth > 10 * GEO.EPS_SMALL) then -- TODO serve messaggio per motivare il non applicabile?? nChosenToolIndex = nil end sChosenBladeType = 'TopDownUp' else sChosenBladeType = 'Top' end end -- se non trovata alcuna lama ritorna nil return nChosenToolIndex, sChosenBladeType end local function SetDiceFaceInfo( Proc, idDiceFace, bSaveAddedGeometries) EgtSetName( idDiceFace, 'Face' .. tostring( Proc.idFeature) .. '_Dice') -- TODO la scrittura del TaskId probabilmente non serve (è fatta nell'AddOperations) EgtSetInfo( idDiceFace, 'TASKID', Proc.idTask) if not bSaveAddedGeometries then EgtSetLevel( idDiceFace, GDB_LV.TEMP) end end local function CutWholeWaste( Proc, Part, OptionalParameters) local Cutting = {} local Result = {} local EdgeToMachine = {} local nToolIndex local sChosenBladeType = '' local dDepthToMachine = 0 local dCompletionPercentage = 0 -- lato da lavorare in base al tipo di lama local EdgeToMachineTopBlade = GetEdgeToMachine( Proc.Faces[1].Edges, Proc.Faces[1].vtN, 'Top') local EdgeToMachineBottomBlade = GetEdgeToMachine( Proc.Faces[1].Edges, Proc.Faces[1].vtN, 'Bottom') local EdgeToMachineTopBladeDownUp = GetEdgeToMachine( Proc.Faces[1].Edges, Proc.Faces[1].vtN, 'TopDownUp') -- scelta lama da sopra o da sotto if OptionalParameters.nToolIndex then nToolIndex = OptionalParameters.nToolIndex else local OptionalParametersGetBestBlade = { dElevationTop = EdgeToMachineTopBlade.dElevation + BeamData.CUT_EXTRA, dElevationBottom = EdgeToMachineBottomBlade.dElevation + BeamData.CUT_EXTRA, dElevationTopDownUp = EdgeToMachineTopBladeDownUp.dElevation + BeamData.CUT_EXTRA } nToolIndex, sChosenBladeType = GetBestBlade( Proc, Part, Proc.Faces[1], OptionalParametersGetBestBlade) end -- utensile non trovato if not nToolIndex then Result = FeatureLib.GetStrategyResultNotApplicable( 'Blade not found') return Machinings, Result end -- lato da lavorare definitivo if sChosenBladeType == 'Top' then EdgeToMachine = EdgeToMachineTopBlade elseif sChosenBladeType == 'Bottom' then EdgeToMachine = EdgeToMachineBottomBlade elseif sChosenBladeType == 'TopDownUp' then EdgeToMachine = EdgeToMachineTopBladeDownUp end dDepthToMachine = EdgeToMachine.dElevation + BeamData.CUT_EXTRA local dResidualDepth = dDepthToMachine - TOOLS[nToolIndex].dMaxMaterial -- TODO qui gestire il caso in cui si può tagliare da due lati (inizialmente solo se vtN:Y è ~= 0?). Andranno ricercati gli utensili di nuovo con l'elevazione a metà?? if dResidualDepth < 10 * GEO.EPS_SMALL then local OptionalParametersFaceByBlade = { dDepthToMachine = dDepthToMachine, nToolIndex = nToolIndex, dExtendAfterTail = OptionalParameters.dExtendAfterTail} Cutting = FaceByBlade.Make( Proc, Part, Proc.Faces[1], EdgeToMachine, OptionalParametersFaceByBlade) end if Cutting.bIsApplicable then table.insert( Machinings, Cutting) dCompletionPercentage = Cutting.dCompletionPercentage or dCompletionPercentage end -- risultati del calcolo -- TODO funzione? if Cutting.bIsApplicable then if dCompletionPercentage > 100 - 10 * GEO.EPS_SMALL then Result.sStatus = 'Completed' else Result.sStatus = 'Not-Completed' end else Result.sStatus = 'Not-Applicable' end Result.dCompletionPercentage = dCompletionPercentage Result.nCompletionIndex = FeatureLib.GetFeatureCompletionIndex( dCompletionPercentage) Result.nQuality = TOOLS[nToolIndex].nQuality local dTimeToMachine = FeatureLib.GetStrategyTimeToMachine( {Cutting}) Result.dMRR = ( FeatureInfo.dFeatureVolume / dTimeToMachine) / pow( 10, 6) return Machinings, Result end function BLADETOWASTE.Make( ProcOrId, Part, OptionalParameters) Machinings = {} local Result = {} -- disambiguazione feature vs id trimesh local Proc = {} if type( ProcOrId) == "table" then Proc = ProcOrId elseif type( ProcOrId) == "number" then Proc = FeatureLib.GetProcFromTrimesh( ProcOrId, Part) else error( 'BLADETOWASTE : Only feature or trimesh supported') end -- controlli preventivi if Proc.nFct > 2 then error( 'BladeToWaste : max 2 faces supported') elseif Proc.nFct == 2 then if Proc.AdjacencyMatrix[1][2] > 10 * GEO.EPS_SMALL or Proc.AdjacencyMatrix[1][2] < -91 then error( 'BladeToWaste : angle between faces must be concave and >= 90deg') end end -- parametri opzionali e default if not OptionalParameters then OptionalParameters = {} end local nToolIndex = OptionalParameters.nToolIndex local dMaxWasteVolume = OptionalParameters.dMaxWasteVolume or 0 local dMaxWasteLength = OptionalParameters.dMaxWasteLength or 0 local dExtendAfterTail = OptionalParameters.dExtendAfterTail or 10000 local bSaveAddedGeometries = OptionalParameters.bSaveAddedGeometries if bSaveAddedGeometries == nil then bSaveAddedGeometries = true end -- dimensioni feature FeatureInfo.dFeatureVolume = FeatureLib.GetFeatureVolume( Proc, Part) FeatureInfo.dFeatureMaxDimension = max( Proc.b3Box:getDimX(), Proc.b3Box:getDimY()) FeatureInfo.bIsFeatureSmall = FeatureInfo.dFeatureVolume < dMaxWasteVolume + 10 * GEO.EPS_SMALL and FeatureInfo.dFeatureMaxDimension < dMaxWasteLength + 10 * GEO.EPS_SMALL -- si taglia tutto lo scarto in una sola lavorazione -- TODO qui si deve entrare anche se lo spessore lama è maggiore dell'elevazione delle faccia if Proc.nFct == 1 and FeatureInfo.bIsFeatureSmall then Machinings, Result = CutWholeWaste( Proc, Part, OptionalParameters) if Result.sStatus == 'Completed' then return Machinings, Result end end -- TODO la lavorazione con cubetti va messa in una funzione -- lavorazione con cubetti -- scelta faccia da lavorare -- se due facce, la faccia principale Face1 è la più grande local Face1 = Proc.Faces[1] local Face2 = {} if Proc.nFct == 2 then Face2 = Proc.Faces[2] if Face2.dArea > Face1.dArea then Face1, Face2 = Face2, Face1 end end -- scelta lama da sopra o da sotto local sChosenBladeType = '' if not nToolIndex then nToolIndex, sChosenBladeType = GetBestBlade( Proc, Part, Face1, OptionalParameters) end -- se non trovata lama la lavorazione non è fattibile if not nToolIndex then return Machinings, Result end -- limite per taglio DownUp local dMinNzDownUp = TOOLS[nToolIndex].SetupInfo.GetMinNzDownUp( Part.b3Raw, Face1.vtN) -- calcolo dimensione cubetto e eventuale cubetto ridotto (tagli orizzontali con affondamento verticale) local dDiceDimension = min( TOOLS[nToolIndex].dMaxMaterial, BeamData.MAX_DIM_DICE) local dDiceDimensionReduced = dDiceDimension dDiceDimensionReduced = min( dDiceDimension, dDiceDimension - TOOLS[nToolIndex].SetupInfo.dMaxMatDecrease) dDiceDimension = dDiceDimension - BeamData.CUT_EXTRA dDiceDimensionReduced = dDiceDimensionReduced - BeamData.CUT_EXTRA -- calcolo cubetti local OptionalParametersDiceCut = {} OptionalParametersDiceCut.dOffsetParallel = dDiceDimension OptionalParametersDiceCut.dOffsetOrthogonal = dDiceDimension OptionalParametersDiceCut.dOffsetOrthogonalReduced = dDiceDimensionReduced OptionalParametersDiceCut.dMinNzDownUp = dMinNzDownUp local vCuts = DiceCut.GetDice( Part, Face1, Face2, OptionalParametersDiceCut) -- se nessun cubetto trovato, si richiama il taglio singolo if #vCuts == 0 then Machinings, Result = CutWholeWaste( Proc, Part, OptionalParameters) return Machinings, Result end -- lavorazione cubetti local bIsDicingOk = true local bMoveAfterSplit = false -- eventuale inversione tagli ortogonali e aggiunta informazioni alla geometria local bAreOrthogonalCutsInverted = false for i = 1, #vCuts do for j = 1, #vCuts[i] do SetDiceFaceInfo( Proc, vCuts[i][j]) if ( i % 2) == 1 then local vtO = EgtSurfTmFacetNormVersor( vCuts[i][j], 0, GDB_ID.ROOT) if ( Face1.vtN:getY() > 0.766 and vtO:getY() < -0.05) or ( Face1.vtN:getY() < -0.766 and vtO:getY() > 0.05) then EgtInvertSurf( vCuts[i][j]) bAreOrthogonalCutsInverted = true end end end end -- calcolo lavorazioni for i = 1, #vCuts do -- determinazione direzione di taglio local vtToolDirection local bNoPerpCuts = false if i % 2 == 1 then vtToolDirection = Vector3d( Face1.vtN) else local vtO if #vCuts[i-1] > 0 then vtO = EgtSurfTmFacetNormVersor( vCuts[i-1][1], 0, GDB_ID.ROOT) elseif vCuts[i+1] and #vCuts[i+1] > 0 then -- lunghezza faccia nell'eventuale direzione ortogonale local asseX = EgtSurfTmFacetNormVersor( vCuts[i+1][1], 0, GDB_ID.ROOT) local asseY = asseX ^ Face1.vtN local Frame = Frame3d( Face1.ptCenter, Face1.ptCenter + asseX, Face1.ptCenter + asseY) local b3Fac = EgtGetBBoxRef( vCuts[i][1], GDB_BB.STANDARD, Frame) -- se lunghezza inferiore al limite, accetto la direzione if b3Fac:getDimX() < TOOLS[nToolIndex].dMaxDepth - BeamData.CUT_EXTRA then vtO = asseX else bNoPerpCuts = true end else bNoPerpCuts = true end if vtO then vtToolDirection = Vector3d( vtO) * EgtIf( bAreOrthogonalCutsInverted, -1, 1) else -- scelta lato da lavorare per stabilire la vtToolDirection local EdgeToMachine = {} local _, Edges = FaceData.GetEdgesInfo( vCuts[i][1], 0) local vtNCurrentFace = EgtSurfTmFacetNormVersor( vCuts[i][1], 0, GDB_ID.ROOT) EdgeToMachine = GetEdgeToMachine( Edges, vtNCurrentFace, sChosenBladeType) vtToolDirection = EdgeToMachine.vtN end end -- calcolo lavorazione della singola faccia for j = 1, #vCuts[i] do local Cutting = {} local _, Edges = FaceData.GetEdgesInfo( vCuts[i][1], 0) local vtNCurrentFace = EgtSurfTmFacetNormVersor( vCuts[i][j], 0, GDB_ID.ROOT) local FaceToMachine = { id = 0, vtN = vtNCurrentFace} -- se taglio DownUp e strato pari composto da 1 o 2 elementi (tutti gli altri casi vengono saltati) if ( vtNCurrentFace:getZ() < dMinNzDownUp) and ( ( i % 2) == 0) and ( #vCuts[i] <= 2) then -- il primo elemento prende la direzione prevista, il secondo quella opposta local vtToolDirectionNew = Vector3d( vtToolDirection) if j ~= 1 then vtToolDirectionNew = -vtToolDirection end -- lavorazione local ProcTrimesh = FeatureLib.GetProcFromTrimesh( vCuts[i][j], Part) local EdgeToMachine = BeamLib.FindEdgeBestOrientedAsDirection( Edges, vtToolDirectionNew) local dDepthToMachine = EdgeToMachine.dElevation + BeamData.CUT_EXTRA local OptionalParametersFaceByBlade = { dDepthToMachine = dDepthToMachine, nToolIndex = nToolIndex, dRadialStepSpan = 0, dExtendAfterTail = dExtendAfterTail } Cutting = FaceByBlade.Make( ProcTrimesh, Part, FaceToMachine, EdgeToMachine, OptionalParametersFaceByBlade) Cutting.ptCenter = Point3d( ProcTrimesh.Faces[1].ptCenter:getX(), 0, 0) if Cutting.bIsApplicable then table.insert( Machinings, Cutting) else bIsDicingOk = false end if Cutting.sStage == 'AfterTail' then bMoveAfterSplit = true end -- caso generale else -- in generale sta sollevato di pochissimo local dExtraCut = -0.1 -- se tagli paralleli if ( i % 2) == 0 then -- se non ci sono tagli ortogonali devo affondare if bNoPerpCuts then dExtraCut = BeamData.CUT_EXTRA -- se altrimenti tagli ortogonali invertiti, devo approfondire dello spessore lama elseif bAreOrthogonalCutsInverted then dExtraCut = TOOLS[nToolIndex].dThickness -- se ultimo taglio, devo affondare elseif j == #vCuts[i] then dExtraCut = BeamData.CUT_EXTRA end end local ProcTrimesh = FeatureLib.GetProcFromTrimesh( vCuts[i][j], Part) local EdgeToMachine = BeamLib.FindEdgeBestOrientedAsDirection( Edges, vtToolDirection) local EdgeToMachineAlternative = {} if ( i % 2 == 0) and ( Proc.Fct == 1) and bNoPerpCuts then EdgeToMachineAlternative = BeamLib.FindEdgeBestOrientedAsDirection( Edges, -EdgeToMachine.vtN) end local dDepthToMachine = EdgeToMachine.dElevation + dExtraCut local OptionalParametersFaceByBlade = { dDepthToMachine = dDepthToMachine, nToolIndex = nToolIndex, EdgeToMachineAlternative = EdgeToMachineAlternative, dRadialStepSpan = 0, dExtendAfterTail = dExtendAfterTail } Cutting = FaceByBlade.Make( ProcTrimesh, Part, FaceToMachine, EdgeToMachine, OptionalParametersFaceByBlade) Cutting.ptCenter = Point3d( ProcTrimesh.Faces[1].ptCenter:getX(), 0, 0) if Cutting.bIsApplicable then table.insert( Machinings, Cutting) else bIsDicingOk = false end if Cutting.sStage == 'AfterTail' then bMoveAfterSplit = true end end end end -- se presente anche solo una lavorazione AfterTail si spostano tutte if bMoveAfterSplit then for i = 1, #Machinings do Machinings[i].sStage = 'AfterTail' end end -- risultati del calcolo -- TODO contemplare il caso di lavorazione incompleta per i cubetti????? if bIsDicingOk then Result.sStatus = 'Completed' Result.dCompletionPercentage = 100 else Result = FeatureLib.GetStrategyResultNotApplicable() end Result.nQuality = TOOLS[nToolIndex].nQuality local dTimeToMachine = FeatureLib.GetStrategyTimeToMachine( Machinings) Result.dMRR = ( FeatureInfo.dFeatureVolume / dTimeToMachine) / pow( 10, 6) -- restituire tabella contenente lavorazioni, già con cloni se necessari return Machinings, Result end ------------------------------------------------------------------------------------------------------------- return BLADETOWASTE