-- BLADEKEEPWASTE.lua by Egalware s.r.l. 2025/03/17 -- Libreria di supporto a strategie con funzioni comune a strategie diverse. -- Tabella per definizione modulo local BLADEKEEPWASTE = {} -- Include require( 'EgtBase') -- Carico i dati globali local FeatureLib = require( 'FeatureLib') local FaceData = require( 'FaceData') local MachiningLib = require( 'MachiningLib') local BeamLib = require('BeamLib') -- strategie di base local FaceByBlade = require('FACEBYBLADE') local FaceByMill = require( 'FACEBYMILL') local AntiSplintOnFace = require( 'ANTISPLINTONFACE') -- tabelle per definizione modulo ------------------------------------------------------------------------------------------------------------- local function CompareEdgesLongestTop( EdgeA, EdgeB) -- si preferiscono i lati più lunghi if EdgeA.dLength > EdgeB.dLength + 10 * GEO.EPS_SMALL then return true elseif EdgeA.dLength < EdgeB.dLength - 10 * GEO.EPS_SMALL then return false -- se stessa lunghezza si preferiscono i lati più in basso 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 GetLongEdgeToMachine( Face, bHeadType) local Edge = {} local EdgesSorted = {} for i = 1, #Face.Edges do table.insert( EdgesSorted, Face.Edges[i]) end table.sort( EdgesSorted, CompareEdgesLongestTop) -- se il lato migliore è accessibile si sceglie questo, altrimenti il lato opposto; se entrambi non accessibili (faccia chiusa da due lati) la lavorazione non è applicabile Edge = EdgesSorted[1] local EdgeOpposite = BeamLib.FindEdgeBestOrientedAsDirection( Face.Edges, -Edge.vtN) if not EdgeOpposite.bIsOpen then if Edge.bIsOpen then Edge = EdgeOpposite -- entrambi i lati non accessibili: codolo non applicabile else return nil end end return Edge end local function SortMachiningsBySegment( MachiningA, MachiningB) if MachiningA.nFeatureSegment > MachiningB.nFeatureSegment then return false elseif MachiningB.nFeatureSegment > MachiningA.nFeatureSegment then return true -- se segmento uguale, si guarda la priorità else if MachiningA.nInternalSortingPriority > MachiningB.nInternalSortingPriority then return false elseif MachiningB.nInternalSortingPriority > MachiningA.nInternalSortingPriority then return true -- se priorità uguale, si minimizzano i cambi di lato else local bIsOddSegment = ( MachiningA.nFeatureSegment % 2 ~= 0) if MachiningA.vtToolDirection:getY() < MachiningB.vtToolDirection:getY() - 10 * GEO.EPS_SMALL then if bIsOddSegment then return true else return false end elseif MachiningA.vtToolDirection:getY() > MachiningB.vtToolDirection:getY() + 10 * GEO.EPS_SMALL then if bIsOddSegment then return false else return true end else return false end end end end local function GetStrategyCompletionPercentage( Machinings) local dCompletionPercentage = 0 local dCompletionPercentageNumerator = 0 local dCompletionPercentageDenominator = 0 local nWeightsCount = 0 for i = 1, #Machinings do local Machining = Machinings[i] local dWeight = Machining.dResultWeight if not dWeight or ( dWeight < 10 * GEO.EPS_SMALL) then dWeight = 1 else nWeightsCount = nWeightsCount + 1 end -- il peso deve essere settato in tutte le lavorazioni o in nessuna if nWeightsCount ~= 0 and nWeightsCount ~= i then error( 'GetWeightedCompletionPercentage : inconsistent weights') end local dWeightedCompletionPercentage = ( Machining.dCompletionPercentage or 0) / 100 * dWeight if Machining.bIsApplicable then dCompletionPercentageNumerator = dCompletionPercentageNumerator + dWeightedCompletionPercentage end dCompletionPercentageDenominator = dCompletionPercentageDenominator + dWeight end dCompletionPercentage = min( 100 * dCompletionPercentageNumerator / dCompletionPercentageDenominator, 100) return dCompletionPercentage end local function MakeBottomFace( Proc, Part, BottomFace, EdgeToMachine, Parameters) local Cuttings = {} local Cutting1 = {} local Cutting2 = {} -- parametri dal chiamante local bIsSplitFeature = Parameters.bIsSplitFeature local dExtendAfterTail = Parameters.dExtendAfterTail local nToolIndex = Parameters.nToolIndex local dStripWidth = Parameters.dStripWidth local OtherBottomFace = Parameters.OtherBottomFace local dDepthToMachine = EdgeToMachine.dElevation / 2 - dStripWidth / 2 local OptionalParametersFaceByBlade1 = { dDepthToMachine = dDepthToMachine, bIsSplitFeature = bIsSplitFeature, dExtendAfterTail = dExtendAfterTail, nToolIndex = nToolIndex} local EdgeToMachineOpposite = BeamLib.FindEdgeBestOrientedAsDirection( BottomFace.Edges, -EdgeToMachine.vtN) -- primo lato if EdgeToMachineOpposite.bIsOpen then Cutting1 = FaceByBlade.Make( Proc, Part, BottomFace, EdgeToMachine, OptionalParametersFaceByBlade1) end Cutting1.nInternalSortingPriority = 2 Cutting1.dResultWeight = 0.3 -- secondo lato local OptionalParametersFaceByBlade2 = BeamLib.TableCopyDeep( OptionalParametersFaceByBlade1) OptionalParametersFaceByBlade2.OppositeToolDirectionMode = 'Enabled' if EdgeToMachine.bIsOpen then Cutting2 = FaceByBlade.Make( Proc, Part, BottomFace, EdgeToMachine, OptionalParametersFaceByBlade2) end Cutting2.nInternalSortingPriority = 2 Cutting2.dResultWeight = 0.3 -- se uno dei due lati non è riuscito, si estende il più possibile il lato rimasto if not Cutting1.bIsApplicable and Cutting2.bIsApplicable then -- se si lavora il lato in comune con l'altra BottomFace significa ci si deve fermare piú indietro if OtherBottomFace and ( EdgeToMachine.idAdjacentFace == OtherBottomFace.id) then dStripWidth = TOOLS[Cutting2.nToolIndex].dThickness + 2 * dStripWidth end dDepthToMachine = min( TOOLS[Cutting2.nToolIndex].dMaxMaterial, EdgeToMachine.dElevation - dStripWidth) OptionalParametersFaceByBlade2.dDepthToMachine = dDepthToMachine Cutting2 = FaceByBlade.Make( Proc, Part, BottomFace, EdgeToMachine, OptionalParametersFaceByBlade2) Cutting2.nInternalSortingPriority = 2 Cutting2.dResultWeight = 0.3 table.insert( Cuttings, Cutting2) elseif not Cutting2.bIsApplicable and Cutting1.bIsApplicable then -- se si lavora il lato in comune con l'altra BottomFace significa ci si deve fermare piú indietro if OtherBottomFace and ( EdgeToMachine.idAdjacentFace == OtherBottomFace.id) then dStripWidth = TOOLS[Cutting1.nToolIndex].dThickness + 2 * dStripWidth end dDepthToMachine = min( TOOLS[Cutting1.nToolIndex].dMaxMaterial, EdgeToMachine.dElevation - dStripWidth) OptionalParametersFaceByBlade1.dDepthToMachine = dDepthToMachine Cutting1 = FaceByBlade.Make( Proc, Part, BottomFace, EdgeToMachine, OptionalParametersFaceByBlade1) Cutting1.nInternalSortingPriority = 2 Cutting1.dResultWeight = 0.3 table.insert( Cuttings, Cutting1) else table.insert( Cuttings, Cutting1) table.insert( Cuttings, Cutting2) end return Cuttings end function BLADEKEEPWASTE.Make( Proc, Part, OptionalParameters) -- TODO verificare funzionamento con lama da sotto -- TODO scelta utensile è corretto lasciarla a FaceByBlade? -- TODO aggiungere accorciamento se angolo < 90 local Result = {} local Machinings = {} local CalculatedMachinings = {} -- controlli preventivi if Proc.nFct > 3 and ( not Proc.Topology.sFamily == 'DoubleBevel') then Result = FeatureLib.GetStrategyResultNotApplicable( 'BladeKeepWaste : max 3 faces supported') return Machinings, Result elseif Proc.nFct == 2 then -- per angolo tra le facce >= 90deg (feature convessa) non applicabile if Proc.AdjacencyMatrix[1][2] > 10 * GEO.EPS_SMALL or Proc.AdjacencyMatrix[1][2] < -91 then Result = FeatureLib.GetStrategyResultNotApplicable( 'BladeKeepWaste : angle between faces must be concave and >= 90deg') return Machinings, Result end elseif Proc.nFct == 3 then -- caso speciale RidgeLap - per angolo tra le facce >= 90deg (feature convessa) non applicabile if Proc.AdjacencyMatrix[1][2] > 10 * GEO.EPS_SMALL or Proc.AdjacencyMatrix[1][2] < -91 then Result = FeatureLib.GetStrategyResultNotApplicable( 'BladeKeepWaste : angle between faces must be concave and >= 90deg') return Machinings, Result end if Proc.AdjacencyMatrix[1][3] > 10 * GEO.EPS_SMALL or Proc.AdjacencyMatrix[1][3] < -91 then Result = FeatureLib.GetStrategyResultNotApplicable( 'BladeKeepWaste : angle between faces must be concave and >= 90deg') return Machinings, Result end if Proc.AdjacencyMatrix[2][3] > 10 * GEO.EPS_SMALL or Proc.AdjacencyMatrix[2][3] < -91 then Result = FeatureLib.GetStrategyResultNotApplicable( 'BladeKeepWaste : angle between faces must be concave and >= 90deg') return Machinings, Result end end -- parametri opzionali e default if not OptionalParameters then OptionalParameters = {} end local nToolIndex = OptionalParameters.nToolIndex local dExtendAfterTail = OptionalParameters.dExtendAfterTail or 10000 local bFinishWithMill = ( OptionalParameters.bFinishWithMill ~= false) local dMillingOffsetFromSide = OptionalParameters.dMillingOffsetFromSide or 1 local dStripWidth = OptionalParameters.dStripWidth or 5 local bForced = OptionalParameters.bForced or false -- volume della feature local dFeatureVolume = Proc.dVolume -- si trovano le facce da lavorare (solo 4 lati esatti) local BottomFace1 local BottomFace2 if Proc.nFct == 1 then BottomFace1 = Proc.Faces[1] else if not Proc.MainFaces then Proc.MainFaces = FaceData.GetMainFaces( Proc, Part) end BottomFace1 = Proc.MainFaces.BottomFaces[1] BottomFace2 = Proc.MainFaces.BottomFaces[2] end if #BottomFace1.Edges ~= 4 then Result = FeatureLib.GetStrategyResultNotApplicable() return Machinings, Result end local bConvexAngle if BottomFace2 then bConvexAngle = ( Proc.AdjacencyMatrix[BottomFace1.id + 1][BottomFace2.id + 1]) > 0 end -- eventuali punti di spezzatura local FeatureSplittingPoints = FeatureLib.GetFeatureSplittingPoints( Proc, Part) local bIsSplitFeature = false if #FeatureSplittingPoints > 0 then bIsSplitFeature = true end -- calcolo lavorazioni faccia principale -- ricerca lato da lavorare local BottomEdgeToMachine1 = GetLongEdgeToMachine( BottomFace1, { bTop = true}) if not BottomEdgeToMachine1 then Result = FeatureLib.GetStrategyResultNotApplicable() return Machinings, Result end -- calcolo lavorazione local Parameters1 = { bIsSplitFeature = bIsSplitFeature, dExtendAfterTail = dExtendAfterTail, nToolIndex = nToolIndex, dStripWidth = dStripWidth, OtherBottomFace = BottomFace2 } local Cuttings1 = MakeBottomFace( Proc, Part, BottomFace1, BottomEdgeToMachine1, Parameters1) -- aggiunta lavorazioni alla lista principale for i = 1, #Cuttings1 do table.insert( CalculatedMachinings, Cuttings1[i]) end -- calcolo lavorazioni faccia secondaria, solo se lato convesso; se concavo, sarà lavorato come antisplint local Cuttings2 local BottomEdgeToMachine2 if BottomFace2 then if bConvexAngle then -- ricerca lato da lavorare BottomEdgeToMachine2 = GetLongEdgeToMachine( BottomFace2, { bTop = true}) if BottomEdgeToMachine2 then -- calcolo lavorazione local Parameters2 = BeamLib.TableCopyDeep( Parameters1) Parameters2.OtherBottomFace = BottomFace1 Cuttings2 = MakeBottomFace( Proc, Part, BottomFace2, BottomEdgeToMachine2, Parameters2) for i = 1, #Cuttings2 do table.insert( CalculatedMachinings, Cuttings2[i]) end end end end -- antischeggia sulle facce di chiusura delle facce lavorate local OptionalParametersAntiSplint = { bIsSplitFeature = bIsSplitFeature, dExtendAfterTail = dExtendAfterTail, nInternalSortingPriority = 1, dResultWeight = 0.15, bMachineAllClosedEdges = true } local AntiSplints1 = AntiSplintOnFace.Make( Proc, Part, BottomFace1, OptionalParametersAntiSplint) for i = 1, #AntiSplints1 do table.insert( CalculatedMachinings, AntiSplints1[i]) end if BottomFace2 and bConvexAngle then OptionalParametersAntiSplint.bMachineAllClosedEdges = false local AntiSplints2 = AntiSplintOnFace.Make( Proc, Part, BottomFace2, OptionalParametersAntiSplint) for i = 1, #AntiSplints2 do table.insert( CalculatedMachinings, AntiSplints2[i]) end end -- pulitura con fresa dei lati chiusi non lavorati -- TODO funzione if bFinishWithMill then if Cuttings1 then -- si recuperano i lati chiusi non lavorati local EdgesClosedNotMachined = {} for i = 1, #BottomFace1.Edges do if not( ( BottomFace1.Edges[i].id == BottomEdgeToMachine1.id) or BottomFace1.Edges[i].bIsOpen) then table.insert( EdgesClosedNotMachined, BottomFace1.Edges[i]) end end -- su ognuno si fa la fresatura di pulizia for i = 1, #EdgesClosedNotMachined do local dDepthToMachine = EdgesClosedNotMachined[i].dElevation - dMillingOffsetFromSide local dToolMarkLength = 0 -- si prende l'impronta dell'utensile più grande for j = 1, #Cuttings1 do if Cuttings1[j].dToolMarkLength > dToolMarkLength + 10 * GEO.EPS_SMALL then dToolMarkLength = Cuttings1[j].dToolMarkLength end end local OptionalParametersMilling = { bIsSplitFeature = bIsSplitFeature, dExtendAfterTail = dExtendAfterTail, dRadialStepSpan = dToolMarkLength, dDepthToMachine = dDepthToMachine } local Milling = FaceByMill.Make( Proc, Part, BottomFace1, EdgesClosedNotMachined[i], OptionalParametersMilling) Milling.nInternalSortingPriority = 3 Milling.dResultWeight = 0.05 table.insert( CalculatedMachinings, Milling) end end if Cuttings2 and BottomEdgeToMachine2 then -- si recuperano i lati chiusi non lavorati local EdgesClosedNotMachined = {} for i = 1, #BottomFace2.Edges do if not( ( BottomFace2.Edges[i].id == BottomEdgeToMachine2.id) or BottomFace2.Edges[i].bIsOpen) then table.insert( EdgesClosedNotMachined, BottomFace2.Edges[i]) end end -- su ognuno si fa la fresatura di pulizia for i = 1, #EdgesClosedNotMachined do local dDepthToMachine = EdgesClosedNotMachined[i].dElevation - dMillingOffsetFromSide local dToolMarkLength = 0 -- si prende l'impronta dell'utensile più grande for j = 1, #Cuttings2 do if Cuttings2[j].dToolMarkLength > dToolMarkLength + 10 * GEO.EPS_SMALL then dToolMarkLength = Cuttings2[j].dToolMarkLength end end local OptionalParametersMilling = { bIsSplitFeature = bIsSplitFeature, dExtendAfterTail = dExtendAfterTail, dRadialStepSpan = dToolMarkLength, dDepthToMachine = dDepthToMachine } local Milling = FaceByMill.Make( Proc, Part, BottomFace2, EdgesClosedNotMachined[i], OptionalParametersMilling) Milling.nInternalSortingPriority = 3 Milling.dResultWeight = 0.05 table.insert( CalculatedMachinings, Milling) end end end -- lavorazioni da applicare spostate in lista finale for i = 1, #CalculatedMachinings do if CalculatedMachinings[i].bIsApplicable then table.insert( Machinings, CalculatedMachinings[i]) end end -- calcolo completamento (non applicabili devono essere incluse) -- se codolo non forzato si ritorna completa max al 94% (completion index 4) local dCalculatedCompletionPercentage = GetStrategyCompletionPercentage( CalculatedMachinings) Result.dCompletionPercentage = bForced and dCalculatedCompletionPercentage or min( 94, dCalculatedCompletionPercentage) Result.dCompletionIndex = FeatureLib.GetFeatureCompletionIndex( Result.dCompletionPercentage) -- aggiunta eventuali lavorazioni splittate if bIsSplitFeature then Machinings = MachiningLib.GetSplitMachinings( Machinings, FeatureSplittingPoints, Part) end -- ordinamento table.sort( Machinings, SortMachiningsBySegment) -- calcolo risultati if ( Cuttings1 and ( ( Cuttings1[1] and Cuttings1[1].bIsApplicable) or ( Cuttings1[2] and Cuttings1[2].bIsApplicable))) or ( Cuttings2 and ( ( Cuttings2[1] and Cuttings2[1].bIsApplicable) or ( Cuttings2[2] and Cuttings2[2].bIsApplicable))) then Result.dQuality = FeatureLib.GetStrategyQuality( Machinings) Result.dTimeToMachine = FeatureLib.GetStrategyTimeToMachine( Machinings) Result.dMRR = ( dFeatureVolume / Result.dTimeToMachine) / pow( 10, 6) if Result.dCompletionPercentage > 100 - 10 * GEO.EPS_SMALL then Result.sStatus = 'Completed' if bForced then Result.sInfo = 'Waste attached. Remove manually.' end else Result.sStatus = 'Not-Completed' end else Result = FeatureLib.GetStrategyResultNotApplicable() end return Machinings, Result end ------------------------------------------------------------------------------------------------------------- return BLADEKEEPWASTE