# -*- 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 PathScripts.PathDressup as PathDressup import PathScripts.PathGeom as PathGeom import PathScripts.PathLog as PathLog import PathScripts.PathUtils as PathUtils import math from PySide import QtCore """LeadInOut Dressup MASHIN-CRC USE ROLL-ON ROLL-OFF to profile""" # Qt tanslation handling def translate(text, context="Path_DressupLeadInOut", 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("Path_DressupLeadInOut", "The Style of LeadIn the Path")) obj.StyleOn = ["Arc", "Tangent", "Perpendicular"] obj.addProperty("App::PropertyEnumeration", "StyleOff", "Path", QtCore.QT_TRANSLATE_NOOP("Path_DressupLeadInOut", "The Style of LeadOut the Path")) obj.StyleOff = ["Arc", "Tangent", "Perpendicular"] obj.addProperty("App::PropertyEnumeration", "RadiusCenter", "Path", QtCore.QT_TRANSLATE_NOOP("Path_DressupLeadInOut", "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 positive 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): op = PathDressup.baseOp(obj.Base) if hasattr(op, 'Side') and op.Side == 'Outside': if hasattr(op, 'Direction') and op.Direction == 'CW': return 'left' else: return 'right' else: if hasattr(op, 'Direction') and op.Direction == 'CW': return 'right' return 'left' def normalize(self, Vector): x = Vector.x y = Vector.y length = math.sqrt(x*x + y*y) if((math.fabs(length)) > 0.0000000000001): vx = round(x / length, 0) vy = round(y / length, 0) return FreeCAD.Vector(vx, vy, 0) def getLeadStart(self, obj, queue, action): '''returns Lead In G-code.''' global currLocation results = [] # zdepth = currLocation["Z"] op = PathDressup.baseOp(obj.Base) tc = PathDressup.toolController(obj.Base) horizFeed = tc.HorizFeed.Value vertFeed = tc.VertFeed.Value toolnummer = tc.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.Vector(v.y*R, -v.x*R, 0.0) else: off_v = FreeCAD.Vector(-v.y*R, v.x*R, 0.0) offsetvector = FreeCAD.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": op.ClearanceHeight.Value}) results.append(extendcommand) extendcommand = Path.Command('G0', {"X": leadstart.x, "Y": leadstart.y, "Z": op.ClearanceHeight.Value}) results.append(extendcommand) extendcommand = Path.Command('G0', {"X": leadstart.x, "Y": leadstart.y, "Z": op.SafeHeight.Value}) results.append(extendcommand) if action == 'layer': if not obj.KeepToolDown: extendcommand = Path.Command('G0', {"Z": op.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, "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 = PathDressup.toolController(obj.Base).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.Vector(v.y*R, -v.x*R, 0.0) else: off_v = FreeCAD.Vector(-v.y*R, v.x*R, 0.0) offsetvector = FreeCAD.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, "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 = [] 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 is not None) or (curCommand.y is not 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) 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("Path_DressupLeadInOut", "LeadInOut Dressup"), 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_DressupLeadInOut", "Creates a Cutter Radius Compensation G41/G42 Entry Dressup object from a selected path")} def IsActive(self): op = PathDressup.selection() if op: return not PathDressup.hasEntryMethod(op) 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('base = FreeCAD.ActiveDocument.' + selection[0].Name) FreeCADGui.doCommand('job = PathScripts.PathUtils.findParentJob(base)') FreeCADGui.doCommand('obj.Base = base') FreeCADGui.doCommand('job.Proxy.addOperation(obj, base)') FreeCADGui.doCommand('PathScripts.PathDressupLeadInOut.ViewProviderDressup(obj.ViewObject)') FreeCADGui.doCommand('Gui.ActiveDocument.getObject(base.Name).Visibility = False') FreeCADGui.doCommand('dbo.setup(obj)') FreeCAD.ActiveDocument.commitTransaction() FreeCAD.ActiveDocument.recompute() if FreeCAD.GuiUp: # register the FreeCAD command FreeCADGui.addCommand('Path_DressupLeadInOut', CommandPathDressupLeadInOut()) PathLog.notice("Loading PathDressupLeadInOut... done\n")