diff --git a/src/Mod/Path/CMakeLists.txt b/src/Mod/Path/CMakeLists.txt index 5332631a5a..6bdeab8bb3 100644 --- a/src/Mod/Path/CMakeLists.txt +++ b/src/Mod/Path/CMakeLists.txt @@ -29,7 +29,9 @@ SET(PathScripts_SRCS PathScripts/PathDressupDogbone.py PathScripts/PathDressupDragknife.py PathScripts/PathDressupHoldingTags.py + PathScripts/PathDressupLeadInOut.py PathScripts/PathDressupRampEntry.py + PathScripts/PathDressupTag.py PathScripts/PathDressupTagGui.py PathScripts/PathDressupTagPreferences.py PathScripts/PathDrilling.py diff --git a/src/Mod/Path/InitGui.py b/src/Mod/Path/InitGui.py index cdb5f11f8c..bdd95b8d93 100644 --- a/src/Mod/Path/InitGui.py +++ b/src/Mod/Path/InitGui.py @@ -48,12 +48,12 @@ class PathWorkbench (Workbench): # load python modules from PathScripts import PathArray from PathScripts import PathComment - # from PathScripts import PathCompoundExtended from PathScripts import PathCustom from PathScripts import PathDressupDogbone from PathScripts import PathDressupDragknife from PathScripts import PathDressupRampEntry from PathScripts import PathDressupTagGui + from PathScripts import PathDressupLeadInOut from PathScripts import PathDrillingGui from PathScripts import PathEngraveGui from PathScripts import PathFixture @@ -86,7 +86,7 @@ class PathWorkbench (Workbench): twodopcmdlist = ["Path_Contour", "Path_Profile_Faces", "Path_Profile_Edges", "Path_Pocket_Shape", "Path_Drilling", "Path_Engrave", "Path_MillFace", "Path_Helix"] threedopcmdlist = ["Path_Pocket_3D"] modcmdlist = ["Path_OperationCopy", "Path_Array", "Path_SimpleCopy" ] - dressupcmdlist = ["Path_DressupDogbone", "Path_DressupDragKnife", "Path_DressupTag", "Path_DressupRampEntry"] + dressupcmdlist = ["Path_DressupDogbone", "Path_DressupDragKnife", "PathDressup_LeadInOut", "Path_DressupTag", "Path_DressupRampEntry"] extracmdlist = [] #modcmdmore = ["Path_Hop",] #remotecmdlist = ["Path_Remote"] diff --git a/src/Mod/Path/PathScripts/PathDressupLeadInOut.py b/src/Mod/Path/PathScripts/PathDressupLeadInOut.py new file mode 100644 index 0000000000..602c5a2674 --- /dev/null +++ b/src/Mod/Path/PathScripts/PathDressupLeadInOut.py @@ -0,0 +1,372 @@ +# -*- coding: utf-8 -*- + +# *************************************************************************** +# * * +# * Copyright (c) 2017 LTS under LGPL * +# * * +# * This program is free software; you can redistribute it and/or modify * +# * it under the terms of the GNU Lesser General Public License (LGPL) * +# * as published by the Free Software Foundation; either version 2 of * +# * the License, or (at your option) any later version. * +# * for detail see the LICENCE text file. * +# * * +# * This program is distributed in the hope that it will be useful, * +# * but WITHOUT ANY WARRANTY; without even the implied warranty of * +# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +# * GNU Library General Public License for more details. * +# * * +# * You should have received a copy of the GNU Library General Public * +# * License along with this program; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** + +from __future__ import print_function +import FreeCAD +import FreeCADGui +import Path +import Part +from PySide import QtCore, QtGui +import math +#import DraftVecUtils as D +import PathScripts.PathLog as PathLog +import PathScripts.PathUtils as PathUtils +from PathScripts.PathGeom import PathGeom + +"""LeadInOut Dressup MASHIN-CRC USE ROLL-ON ROLL-OFF to profile""" + +# Qt tanslation handling +def translate(text, context="PathDressup_LeadInOut", disambig=None): + return QtCore.QCoreApplication.translate(context, text, disambig) + +PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + +movecommands = ['G1', 'G01', 'G2', 'G02', 'G3', 'G03'] +rapidcommands = ['G0', 'G00'] +arccommands = ['G2', 'G3', 'G02', 'G03'] +global currLocation +currLocation = {} + + +class ObjectDressup: + + def __init__(self, obj): + self.obj = obj + obj.addProperty("App::PropertyLink", "Base", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property","The base path to modify")) + obj.addProperty("App::PropertyBool", "LeadIn", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property","Calculate roll-on to path")) + obj.addProperty("App::PropertyBool", "LeadOut", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property","Calculate roll-off from path")) + obj.addProperty("App::PropertyBool", "KeepToolDown", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property","Keep the Tool Down in Path")) + obj.addProperty("App::PropertyBool", "UseMashineCRC", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property","Use Mashine Cutter Radius Compensation /Tool Path Offset G41/G42")) + obj.addProperty("App::PropertyDistance", "Length", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property","Length or Radius of the approach")) + obj.addProperty("App::PropertyEnumeration", "StyleOn", "Path", QtCore.QT_TRANSLATE_NOOP("PathDressup_LeadInOut", "The Style of LeadIn the Path")) + obj.StyleOn = ["Arc", "Tangent", "Perpendicular"] + obj.addProperty("App::PropertyEnumeration", "StyleOff", "Path", QtCore.QT_TRANSLATE_NOOP("PathDressup_LeadInOut", "The Style of LeadOut the Path")) + obj.StyleOff = ["Arc", "Tangent", "Perpendicular"] + obj.addProperty("App::PropertyEnumeration", "RadiusCenter", "Path", QtCore.QT_TRANSLATE_NOOP("PathDressup_LeadInOut", "The Mode of Point Radiusoffset or Center")) + obj.RadiusCenter = ["Radius", "Center"] + obj.Proxy = self + + def __getstate__(self): + return None + + def __setstate__(self, state): + return None + def setup(self, obj): + obj.Length = 5.0 + obj.LeadIn = True + obj.LeadOut = True + obj.KeepToolDown=False + obj.UseMashineCRC=False + obj.StyleOn = 'Arc' + obj.StyleOff = 'Arc' + obj.RadiusCenter = 'Radius' + + def execute(self, obj): + if not obj.Base: + return + if not obj.Base.isDerivedFrom("Path::Feature"): + return + if not obj.Base.Path: + return + if obj.Length < 0: + PathLog.error(translate("Length/Radius positiv not Null\n")) + obj.Length = 0.1 + self.wire, self.rapids = PathGeom.wireForPath(obj.Base.Path) + obj.Path = self.generateLeadInOutCurve(obj) + + def getDirectionOfPath(self,obj): + if obj.Base.Side == 'Outside': + if obj.Base.Direction =='CW': + return 'left' + else: + return 'right' + else: + if obj.Base.Direction =='CW': + return 'right' + return 'left' + + def normalize(self, Vector): + x=Vector.x + y=Vector.y + len = math.sqrt( x*x + y*y ) + if((math.fabs(len))> 0.0000000000001): + vx = round(x / len,0) + vy = round(y / len,0) + return FreeCAD.Base.Vector(vx,vy,0) + + def getLeadStart(self,obj, queue,action): + '''returns Lead In G-code.''' + global currLocation + results = [] + zdepth = currLocation["Z"] + horizFeed = obj.Base.ToolController.HorizFeed.Value + vertFeed = obj.Base.ToolController.VertFeed.Value + toolnummer = obj.Base.ToolController.ToolNumber + # set the correct twist command + if self.getDirectionOfPath(obj) == 'left': + arcdir = "G3" + else: + arcdir = "G2" + R= obj.Length.Value #Radius of roll or length + if queue[1].Name == "G1": #line + p0 = queue[0].Placement.Base + p1 = queue[1].Placement.Base + v = self.normalize(p1.sub(p0)) + #PathLog.notice(" CURRENT_IN : P0 Z:{} p1 Z:{}".format(p0.z,p1.z)) + else: + p0 = queue[0].Placement.Base + p1 = queue[1].Placement.Base + #PathLog.notice(" CURRENT_IN ARC : P0 X:{} Y:{} P1 X:{} Y:{} ".format(p0.x,p0.y,p1.x,p1.y)) + v = self.normalize(p1.sub(p0)) + if self.getDirectionOfPath(obj) == 'right': + off_v = FreeCAD.Base.Vector(v.y*R,-v.x*R,0.0) + else: + off_v = FreeCAD.Base.Vector(-v.y*R,v.x*R,0.0) + offsetvector = FreeCAD.Base.Vector(v.x*R,v.y*R, 0) #IJ + if obj.RadiusCenter == 'Radius': + leadstart = (p0.add(off_v)).sub(offsetvector) #Rmode + else: + leadstart = p0.add(off_v) #Dmode + if action == 'start': + extendcommand = Path.Command('G0', {"X": 0.0, "Y": 0.0, "Z": obj.Base.ClearanceHeight.Value}) + results.append(extendcommand) + extendcommand = Path.Command('G0', {"X": leadstart.x, "Y": leadstart.y, "Z": obj.Base.ClearanceHeight.Value}) + results.append(extendcommand) + extendcommand = Path.Command('G0', {"X": leadstart.x, "Y": leadstart.y, "Z": obj.Base.SafeHeight.Value}) + results.append(extendcommand) + if action == 'layer': + if not obj.KeepToolDown: + extendcommand = Path.Command('G0', {"Z": obj.Base.SafeHeight.Value}) + results.append(extendcommand) + extendcommand = Path.Command('G0', {"X": leadstart.x, "Y": leadstart.y}) + results.append(extendcommand) + extendcommand = Path.Command('G1', {"X": leadstart.x, "Y": leadstart.y, "Z": p1.z, "F": vertFeed}) + results.append(extendcommand) + if obj.UseMashineCRC: + if self.getDirectionOfPath(obj) == 'right': + results.append(Path.Command('G42', {'D':toolnummer})) + else: + results.append(Path.Command('G41', {'D':toolnummer})) + if obj.StyleOn == 'Arc': + arcmove = Path.Command(arcdir, {"X": p0.x, "Y": p0.y, "I": offsetvector.x, "J": offsetvector.y, "F": horizFeed}) # add G2/G3 move + results.append(arcmove) + elif obj.StyleOn == 'Tangent': + extendcommand = Path.Command('G1', {"X": p0.x, "Y": p0.y, "Z": p0.z, "F": horizFeed}) + results.append(extendcommand) + else : + PathLog.notice(" CURRENT_IN Perp") + return results + + def getLeadEnd(self,obj, queue,action): + '''returns the Gcode of LeadOut.''' + global currLocation + results = [] + horizFeed = obj.Base.ToolController.HorizFeed.Value + R= obj.Length.Value #Radius of roll or length + # set the correct twist command + if self.getDirectionOfPath(obj) == 'right': + arcdir = "G2" + else: + arcdir = "G3" + if queue[1].Name == "G1": #line + p0 = queue[0].Placement.Base + p1 = queue[1].Placement.Base + v = self.normalize(p1.sub(p0)) + else:#dealing with a circle + p0 = queue[0].Placement.Base + p1 = queue[1].Placement.Base + v = self.normalize(p1.sub(p0)) + if self.getDirectionOfPath(obj) == 'right': + off_v = FreeCAD.Base.Vector(v.y*R,-v.x*R,0.0) + else: + off_v = FreeCAD.Base.Vector(-v.y*R,v.x*R,0.0) + offsetvector = FreeCAD.Base.Vector(v.x*R,v.y*R, 0.0) + if obj.RadiusCenter == 'Radius': + leadend = (p1.add(off_v)).add(offsetvector) #Rmode + else: + leadend = p1.add(off_v) #Dmode + IJ= off_v#.negative() + results.append(queue[1]) + if obj.StyleOff == 'Arc': + arcmove = Path.Command(arcdir, {"X": leadend.x, "Y": leadend.y, "I": IJ.x, "J": IJ.y, "F": horizFeed}) # add G2/G3 move + results.append(arcmove) + elif obj.StyleOff == 'Tangent': + extendcommand = Path.Command('G1', {"X": leadend.x, "Y": leadend.y, "Z": currLocation["Z"], "F": horizFeed}) + results.append(extendcommand) + else : + PathLog.notice(" CURRENT_IN Perp") + if obj.UseMashineCRC:#crc off + results.append(Path.Command('G40', {})) + return results + + def generateLeadInOutCurve(self, obj): + global currLocation + firstmove = Path.Command("G0", {"X": 0, "Y": 0, "Z": 0}) + currLocation.update(firstmove.Parameters) + newpath = [] + queue = [] + num=0 + action= 'start' + for curCommand in obj.Base.Path.Commands: + replace = None + # don't worry about non-move commands, just add to output + if curCommand.Name not in movecommands + rapidcommands: + newpath.append(curCommand) + continue + + # rapid retract triggers exit move, else just add to output + if curCommand.Name in rapidcommands: + #detect start position + if (curCommand.x != None) or (curCommand.y != None): + firstmove = curCommand + currLocation.update(curCommand.Parameters) + if action !='start':#done move out + if obj.LeadOut: + temp = self.getLeadEnd(obj,queue,'end') + newpath.extend(temp) + newpath.append(curCommand) #Z clear DONE + + if curCommand.Name in movecommands: + queue.append(curCommand) + if action == 'start' and len(queue) <2: + continue + if action == 'layer': + if len(queue) > 2: queue.pop(0) + if obj.LeadIn: + temp=self.getLeadStart(obj,queue,action) + newpath.extend(temp) + newpath.append(curCommand) + action='none' + currLocation.update(curCommand.Parameters) + else: + newpath.append(curCommand) + if curCommand.z != currLocation["Z"] and action != 'start':# vertical feeding to depth + if obj.LeadOut:#fish cycle + if len(queue) > 2: queue.pop(len(queue)-1) + temp = self.getLeadEnd(obj,queue,action) + newpath.extend(temp) + action = 'layer' + if len(queue) > 2: queue.pop(0) + continue + else: + newpath.append(curCommand) + if len(queue) > 2: queue.pop(0) + if obj.LeadIn and len(queue)>=2 and action == 'start': + temp=self.getLeadStart(obj,queue,action) + newpath.extend(temp) + newpath.append(curCommand) + action='none' + currLocation.update(curCommand.Parameters) + else: + newpath.append(curCommand) + currLocation.update(curCommand.Parameters) + commands = newpath + return Path.Path(commands) + +class ViewProviderDressup: + + def __init__(self, vobj): + vobj.Proxy = self + + def attach(self, vobj): + self.obj = vobj.Object + + def claimChildren(self): + if hasattr(self.obj.Base, "InList"): + for i in self.obj.Base.InList: + if hasattr(i, "Group"): + group = i.Group + for g in group: + if g.Name == self.obj.Base.Name: + group.remove(g) + i.Group = group + print(i.Group) + # FreeCADGui.ActiveDocument.getObject(obj.Base.Name).Visibility = False + return [self.obj.Base] + + def onDelete(self, arg1=None, arg2=None): + PathLog.debug("Deleting Dressup") + '''this makes sure that the base operation is added back to the project and visible''' + FreeCADGui.ActiveDocument.getObject(arg1.Object.Base.Name).Visibility = True + job = PathUtils.findParentJob(self.obj) + job.Proxy.addOperation(arg1.Object.Base) + arg1.Object.Base = None + return True + + def __getstate__(self): + return None + + def __setstate__(self, state): + return None + + +class CommandPathDressupLeadInOut: + + def GetResources(self): + return {'Pixmap': 'Path-Dressup', + 'MenuText': QtCore.QT_TRANSLATE_NOOP("PathDressup_LeadInOut", "LeadInOut Dressup"), + 'ToolTip': QtCore.QT_TRANSLATE_NOOP("PathDressup_LeadInOut", "Creates a Cutter Radius Compensation G41/G42 Entry Dressup object from a selected path")} + + def IsActive(self): + if FreeCAD.ActiveDocument is not None: + for o in FreeCAD.ActiveDocument.Objects: + if o.Name[:3] == "Job": + return True + return False + + def Activated(self): + + # check that the selection contains exactly what we want + selection = FreeCADGui.Selection.getSelection() + if len(selection) != 1: + PathLog.error(translate("Please select one path object\n")) + return + baseObject = selection[0] + if not baseObject.isDerivedFrom("Path::Feature"): + PathLog.error(translate("The selected object is not a path\n")) + return + if baseObject.isDerivedFrom("Path::FeatureCompoundPython"): + PathLog.error(translate("Please select a Profile object")) + return + + # everything ok! + FreeCAD.ActiveDocument.openTransaction(translate("Create LeadInOut Dressup")) + FreeCADGui.addModule("PathScripts.PathDressupLeadInOut") + FreeCADGui.addModule("PathScripts.PathUtils") + FreeCADGui.doCommand('obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", "LeadInOutDressup")') + FreeCADGui.doCommand('dbo = PathScripts.PathDressupLeadInOut.ObjectDressup(obj)') + FreeCADGui.doCommand('obj.Base = FreeCAD.ActiveDocument.' + selection[0].Name) + FreeCADGui.doCommand('PathScripts.PathDressupLeadInOut.ViewProviderDressup(obj.ViewObject)') + FreeCADGui.doCommand('PathScripts.PathUtils.addToJob(obj)') + FreeCADGui.doCommand('Gui.ActiveDocument.getObject(obj.Base.Name).Visibility = False') + FreeCADGui.doCommand('dbo.setup(obj)') + FreeCAD.ActiveDocument.commitTransaction() + FreeCAD.ActiveDocument.recompute() + + +if FreeCAD.GuiUp: + # register the FreeCAD command + FreeCADGui.addCommand('PathDressup_LeadInOut', CommandPathDressupLeadInOut()) + +PathLog.notice("Loading PathDressupLeadInOut... done\n")