From f6654c8a6df2a84807530df5584ca75ac820dd90 Mon Sep 17 00:00:00 2001 From: sliptonic Date: Fri, 8 Jul 2016 08:45:38 -0500 Subject: [PATCH] Dressup to add dragknife corner actions to a path Dragknives have an offset so paths must be extended to complete the cut. They also require special handling if the incident angle between two segments is small. This dressup provides properties for the filter angle, offset distance, and pivot height. One known area still needs to be addressed: If the segment being processed is shorter than the offset distance, the extension may be added incorrectly. Additional corner strategies could also be added in the future to enhance drag knife performance. Some of the files also got a pep8 cleanup. PathKurveUtils: logic around line #460 to always pass Z value. Previously, the Z was only passed if it changed. This caused some downstream problems for dressup functions. Changes to Dressup so it works with parent objects correctly. --- src/Mod/Path/CMakeLists.txt | 1 + src/Mod/Path/InitGui.py | 5 +- src/Mod/Path/PathScripts/DragknifeDressup.py | 488 ++++++++++++++++ src/Mod/Path/PathScripts/PathDressup.py | 118 ++-- src/Mod/Path/PathScripts/PathKurveUtils.py | 562 +++++++++++-------- src/Mod/Path/PathScripts/PathUtils.py | 34 ++ 6 files changed, 912 insertions(+), 296 deletions(-) create mode 100644 src/Mod/Path/PathScripts/DragknifeDressup.py diff --git a/src/Mod/Path/CMakeLists.txt b/src/Mod/Path/CMakeLists.txt index 916b4a4bd..29ff0c5b1 100644 --- a/src/Mod/Path/CMakeLists.txt +++ b/src/Mod/Path/CMakeLists.txt @@ -30,6 +30,7 @@ SET(PathScripts_SRCS PathScripts/PathPocket.py PathScripts/PathDrilling.py PathScripts/PathDressup.py + PathScripts/DragknifeDressup.py PathScripts/PathHop.py PathScripts/PathUtils.py PathScripts/PathSelection.py diff --git a/src/Mod/Path/InitGui.py b/src/Mod/Path/InitGui.py index 8fd3d2a8c..45e3b4ad7 100644 --- a/src/Mod/Path/InitGui.py +++ b/src/Mod/Path/InitGui.py @@ -67,16 +67,17 @@ class PathWorkbench (Workbench): from PathScripts import PathSurface from PathScripts import PathRemote from PathScripts import PathSanity + from PathScripts import DragknifeDressup # build commands list projcmdlist = ["Path_Project", "Path_Post", "Path_Inspect", "Path_Sanity"] - toolcmdlist = ["Path_ToolTableEdit", "Path_LoadTool"] + toolcmdlist = ["Path_ToolTableEdit", "Path_ToolLibraryEdit", "Path_LoadTool"] prepcmdlist = ["Path_Plane", "Path_Fixture", "Path_ToolLenOffset", "Path_Comment", "Path_Stop", "Path_FaceProfile", "Path_FacePocket", "Path_Custom", "Path_FromShape"] opcmdlist = ["Path_Profile", "Path_Pocket", "Path_Drilling", "Path_Engrave", "Path_Surfacing"] modcmdlist = ["Path_Copy", "Path_CompoundExtended", - "Path_Dressup", "Path_Hop", "Path_Array", "Path_SimpleCopy"] + "Path_Dressup", "Path_Hop", "Path_Array", "Path_SimpleCopy", "DragKnife_Dressup"] remotecmdlist = ["Path_Remote"] # Add commands to menu and toolbar diff --git a/src/Mod/Path/PathScripts/DragknifeDressup.py b/src/Mod/Path/PathScripts/DragknifeDressup.py new file mode 100644 index 000000000..1fe05449c --- /dev/null +++ b/src/Mod/Path/PathScripts/DragknifeDressup.py @@ -0,0 +1,488 @@ +# -*- coding: utf-8 -*- + +# *************************************************************************** +# * * +# * Copyright (c) 2014 sliptonic * +# * * +# * 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 * +# * * +# *************************************************************************** + +import FreeCAD +import FreeCADGui +import Path +import PathGui +from PySide import QtCore, QtGui +import math +import DraftVecUtils as D +import PathScripts.PathUtils as P + +"""Dragknife Dressup object and FreeCAD command""" + +# Qt tanslation handling +try: + _encoding = QtGui.QApplication.UnicodeUTF8 + + def translate(context, text, disambig=None): + return QtGui.QApplication.translate(context, text, disambig, _encoding) +except AttributeError: + def translate(context, text, disambig=None): + return QtGui.QApplication.translate(context, text, disambig) + +movecommands = ['G1', 'G01', 'G2', 'G02', 'G3', 'G03'] +rapidcommands = ['G0', 'G00'] +arccommands = ['G2', 'G3', 'G02', 'G03'] + +currLocation = {} + + +class ObjectDressup: + + def __init__(self, obj): + obj.addProperty("App::PropertyLink", "Base", "Path", "The base path to modify") + obj.addProperty("App::PropertyAngle", "filterangle", "Filter Angle", "Angles less than filter angle will not receive corner actions") + obj.addProperty("App::PropertyFloat", "offset", "Offset", "Distance the point trails behind the spindle") + obj.addProperty("App::PropertyFloat", "pivotheight", "Pivot Height", "Height to raise during corner action") + + obj.Proxy = self + + def __getstate__(self): + return None + + def __setstate__(self, state): + return None + + def getIncidentAngle(self, queue): + global currLocation + '''returns in the incident angle in radians between the current and previous moves''' + # get the vector of the last move + if queue[1].Name in arccommands: + print queue + print currLocation + arcLoc = FreeCAD.Base.Vector(queue[2].X + queue[1].I, queue[2].Y + queue[1].J, currLocation['Z']) + radvector = queue[1].Placement.Base.sub(arcLoc) # vector of chord from center to point + # vector of line perp to chord. + v1 = radvector.cross(FreeCAD.Base.Vector(0, 0, 1)) + else: + v1 = queue[1].Placement.Base.sub(queue[2].Placement.Base) + + # get the vector of the current move + if queue[0].Name in arccommands: + arcLoc = FreeCAD.Base.Vector((queue[1].x + queue[0].I), (queue[1].y + queue[0].J), currLocation['Z']) + radvector = queue[1].Placement.Base.sub(arcLoc) # calculate arcangle + + v2 = radvector.cross(FreeCAD.Base.Vector(0, 0, 1)) + + # if switching between G2 and G3, reverse orientation + if queue[1].Name in arccommands: + if queue[0].Name != queue[1].Name: + v2 = D.rotate2D(v2, math.radians(180)) + else: + v2 = queue[0].Placement.Base.sub(queue[1].Placement.Base) + + incident_angle = D.angle(v1, v2, FreeCAD.Base.Vector(0, 0, -1)) + return incident_angle + + def arcExtension(self, obj, queue): + '''returns gcode for arc extension''' + global currLocation + results = [] + + offset = obj.offset + # Find the center of the old arc + C = FreeCAD.Base.Vector(queue[2].x + queue[1].I, queue[2].y + queue[1].J, currLocation['Z']) + + # Find radius of old arc + R = math.hypot(queue[1].I, queue[1].J) + + # Find angle subtended by the extension arc + theta = math.atan2(queue[1].y - C.y, queue[1].x - C.x) + if queue[1].Name in ["G2", "G02"]: + theta = theta - offset / R + else: + theta = theta + offset / R + + # XY coordinates of new arc endpoint. + Bx = C.x + R * math.cos(theta) + By = C.y + R * math.sin(theta) + + # endpoint = FreeCAD.Base.Vector(Bx, By, currLocation["Z"]) + startpoint = queue[1].Placement.Base + offsetvector = C.sub(startpoint) + + I = offsetvector.x + J = offsetvector.y + + extend = Path.Command(queue[1].Name, {"I": I, "J": J, "X": Bx, "Y": By}) + results.append(extend) + currLocation.update(extend.Parameters) + + replace = None + return (results, replace) + + def arcTwist(self, obj, queue, lastXY, twistCW=False): + '''returns gcode to do an arc move toward an arc to perform + a corner action twist. Inclues lifting and plungeing the knife''' + + global currLocation + pivotheight = obj.pivotheight + offset = obj.offset + results = [] + + # set the correct twist command + if twistCW is False: + arcdir = "G3" + else: + arcdir = "G2" + + # move to the pivot heigth + zdepth = currLocation["Z"] + retract = Path.Command("G0", {"Z": pivotheight}) + results.append(retract) + currLocation.update(retract.Parameters) + + # get the center of the destination arc + arccenter = FreeCAD.Base.Vector(queue[1].x + queue[0].I, queue[1].y + queue[0].J, currLocation["Z"]) + + # The center of the twist arc is the old line end point. + C = queue[1].Placement.Base + + # Find radius of old arc + R = math.hypot(queue[0].I, queue[0].J) + + # find angle of original center to startpoint + v1 = queue[1].Placement.Base.sub(arccenter) + segAngle = D.angle(v1, FreeCAD.Base.Vector(1, 0, 0), FreeCAD.Base.Vector(0, 0, -1)) + + # Find angle subtended by the offset + theta = offset / R + + # add or subtract theta depending on direction + if queue[1].Name in ["G2", "G02"]: + newangle = segAngle + theta + else: + newangle = segAngle - theta + + # calculate endpoints + Bx = arccenter.x + R * math.cos(newangle) + By = arccenter.y + R * math.sin(newangle) + endpointvector = FreeCAD.Base.Vector(Bx, By, currLocation['Z']) + + # calculate IJ offsets of twist arc from current position. + offsetvector = C.sub(lastXY) + # I = offsetvector.x + # J = offsetvector.y + + # add G2/G3 move + arcmove = Path.Command( + arcdir, {"X": endpointvector.x, "Y": endpointvector.y, "I": offsetvector.x, "J": offsetvector.y}) + results.append(arcmove) + currLocation.update(arcmove.Parameters) + + # plunge back to depth + plunge = Path.Command("G1", {"Z": zdepth}) + results.append(plunge) + currLocation.update(plunge.Parameters) + + # The old arc move won't work so calculate a replacement command + offsetv = arccenter.sub(endpointvector) + + # I = offsetv.x + # J = offsetv.y + + replace = Path.Command( + queue[0].Name, {"X": queue[0].X, "Y": queue[0].Y, "I": offsetv.x, "J": offsetv.y}) + replace = None + return (results, replace) + + def lineExtension(self, obj, queue): + '''returns gcode for line extension''' + global currLocation + + offset = float(obj.offset) + results = [] + + v1 = queue[1].Placement.Base.sub(queue[2].Placement.Base) + + # extend the current segment to comp for offset + segAngle = D.angle(v1, FreeCAD.Base.Vector(1, 0, 0), FreeCAD.Base.Vector(0, 0, -1)) + xoffset = math.cos(segAngle) * offset + yoffset = math.sin(segAngle) * offset + + newX = currLocation["X"] + xoffset + newY = currLocation["Y"] + yoffset + + extendcommand = Path.Command('G1', {"X": newX, "Y": newY}) + results.append(extendcommand) + + currLocation.update(extendcommand.Parameters) + + replace = None + return (results, replace) + + def lineTwist(self, obj, queue, lastXY, twistCW=False): + '''returns gcode to do an arc move toward a line to perform + a corner action twist. Includes lifting and plungeing the knife''' + global currLocation + pivotheight = obj.pivotheight + offset = obj.offset + + results = [] + + # set the correct twist command + if twistCW is False: + arcdir = "G3" + else: + arcdir = "G2" + + # move to pivot height + zdepth = currLocation["Z"] + retract = Path.Command("G0", {"Z": pivotheight}) + results.append(retract) + currLocation.update(retract.Parameters) + + C = queue[1].Placement.Base + + # get the vectors between endpoints to calculate twist + v2 = queue[0].Placement.Base.sub(queue[1].Placement.Base) + + # calc arc endpoints to twist to + segAngle = D.angle(v2, FreeCAD.Base.Vector(1, 0, 0), FreeCAD.Base.Vector(0, 0, -1)) + xoffset = math.cos(segAngle) * offset + yoffset = math.sin(segAngle) * offset + newX = queue[1].x + xoffset + newY = queue[1].y + yoffset + + offsetvector = C.sub(lastXY) + I = offsetvector.x + J = offsetvector.y + + # add the arc move + arcmove = Path.Command( + arcdir, {"X": newX, "Y": newY, "I": I, "J": J}) # add G2/G3 move + results.append(arcmove) + + currLocation.update(arcmove.Parameters) + + # plunge back to depth + plunge = Path.Command("G1", {"Z": zdepth}) + results.append(plunge) + currLocation.update(plunge.Parameters) + + replace = None + return (results, replace) + + def execute(self, obj): + newpath = [] + global currLocation + + if not obj.Base: + return + + if not obj.Base.isDerivedFrom("Path::Feature"): + return + + if obj.Base.Path.Commands: + + firstmove = Path.Command("G0", {"X": 0, "Y": 0, "Z": 0}) + currLocation.update(firstmove.Parameters) + + queue = [] + + 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: + if (curCommand.z > obj.pivotheight) and (len(queue) == 3): + # Process the exit move + tempqueue = queue + tempqueue.insert(0, curCommand) + + if queue[1].Name in ['G01', 'G1']: + temp = self.lineExtension(obj, tempqueue) + newpath.extend(temp[0]) + lastxy = temp[0][-1].Placement.Base + elif queue[1].Name in arccommands: + temp = self.arcExtension(obj, tempqueue) + newpath.extend(temp[0]) + lastxy = temp[0][-1].Placement.Base + + newpath.append(curCommand) + currLocation.update(curCommand.Parameters) + queue = [] + continue + + # keep a queue of feed moves and check for needed corners + if curCommand.Name in movecommands: + changedXYFlag = False + if queue: + if (curCommand.x != queue[0].x) or (curCommand.y != queue[0].y): + queue.insert(0, curCommand) + if len(queue) > 3: + queue.pop() + changedXYFlag = True + else: + queue = [curCommand] + + # vertical feeding to depth + if curCommand.z != currLocation["Z"]: + newpath.append(curCommand) + currLocation.update(curCommand.Parameters) + continue + + # Corner possibly needed + if changedXYFlag and (len(queue) == 3): + + # check if the inciden angle incident exceeds the filter + incident_angle = math.degrees(self.getIncidentAngle(queue)) + + if abs(incident_angle) >= obj.filterangle: + if incident_angle >= 0: + twistCW = True + else: + twistCW = False + # + # DO THE EXTENSION + # + if queue[1].Name in ['G01', 'G1']: + temp = self.lineExtension(obj, queue) + newpath.extend(temp[0]) + replace = temp[1] + lastxy = temp[0][-1].Placement.Base + elif queue[1].Name in arccommands: + temp = self.arcExtension(obj, queue) + newpath.extend(temp[0]) + replace = temp[1] + lastxy = temp[0][-1].Placement.Base + else: + FreeCAD.Console.PrintWarning("I don't know what's up") + # + # DO THE TWIST + # + if queue[0].Name in ['G01', 'G1']: + temp = self.lineTwist(obj, queue, lastxy, twistCW) + replace = temp[1] + newpath.extend(temp[0]) + elif queue[0].Name in arccommands: + temp = self.arcTwist(obj, queue, lastxy, twistCW) + replace = temp[1] + newpath.extend(temp[0]) + else: + FreeCAD.Console.PrintWarning("I don't know what's up") + if replace is None: + newpath.append(curCommand) + else: + newpath.append(replace) + currLocation.update(curCommand.Parameters) + continue + + commands = newpath + path = Path.Path(commands) + obj.Path = path + + +class ViewProviderDressup: + + def __init__(self, vobj): + vobj.Proxy = self + + def attach(self, vobj): + self.Object = vobj.Object + return + + + def unsetEdit(self, vobj, mode=0): + return False + + def setEdit(self, vobj, mode=0): + return True + + def claimChildren(self): + for i in self.Object.Base.InList: + if hasattr(i, "Group"): + group = i.Group + for g in group: + if g.Name == self.Object.Base.Name: + group.remove(g) + i.Group = group + print i.Group + #FreeCADGui.ActiveDocument.getObject(obj.Base.Name).Visibility = False + return [self.Object.Base] + + def __getstate__(self): + return None + + def __setstate__(self, state): + return None + + +class CommandDragknifeDressup: + + def GetResources(self): + return {'Pixmap': 'Path-Dressup', + 'MenuText': QtCore.QT_TRANSLATE_NOOP("DragKnife_Dressup", "DragKnife Dress-up"), + 'Accel': "P, S", + 'ToolTip': QtCore.QT_TRANSLATE_NOOP("DragKnife_Dressup", "Modifies a path to add dragknife corner actions")} + + def IsActive(self): + return FreeCAD.ActiveDocument is not None + + def Activated(self): + + # check that the selection contains exactly what we want + selection = FreeCADGui.Selection.getSelection() + if len(selection) != 1: + FreeCAD.Console.PrintError( + translate("DragKnife_Dressup", "Please select one path object\n")) + return + if not selection[0].isDerivedFrom("Path::Feature"): + FreeCAD.Console.PrintError( + translate("DragKnife_Dressup", "The selected object is not a path\n")) + return + if selection[0].isDerivedFrom("Path::FeatureCompoundPython"): + FreeCAD.Console.PrintError( + translate("DragKnife_Dressup", "Please select a Path object")) + return + + # everything ok! + FreeCAD.ActiveDocument.openTransaction(translate("DragKnife_Dressup", "Create Dress-up")) + FreeCADGui.addModule("PathScripts.DragknifeDressup") + FreeCADGui.addModule("PathScripts.PathUtils") + FreeCADGui.doCommand('obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython","DragknifeDressup")') + FreeCADGui.doCommand('PathScripts.DragknifeDressup.ObjectDressup(obj)') + FreeCADGui.doCommand('obj.Base = FreeCAD.ActiveDocument.' + selection[0].Name) + FreeCADGui.doCommand('PathScripts.DragknifeDressup.ViewProviderDressup(obj.ViewObject)') + FreeCADGui.doCommand('PathScripts.PathUtils.addToProject(obj)') + FreeCADGui.doCommand('Gui.ActiveDocument.getObject(obj.Base.Name).Visibility = False') + FreeCADGui.doCommand('obj.filterangle = 20') + FreeCADGui.doCommand('obj.offset = 2') + FreeCADGui.doCommand('obj.pivotheight = 4') + + FreeCAD.ActiveDocument.commitTransaction() + FreeCAD.ActiveDocument.recompute() + + +if FreeCAD.GuiUp: + # register the FreeCAD command + FreeCADGui.addCommand('DragKnife_Dressup', CommandDragknifeDressup()) + +FreeCAD.Console.PrintLog("Loading DragKnife_Dressup... done\n") diff --git a/src/Mod/Path/PathScripts/PathDressup.py b/src/Mod/Path/PathScripts/PathDressup.py index a478b1363..cf2d604f6 100644 --- a/src/Mod/Path/PathScripts/PathDressup.py +++ b/src/Mod/Path/PathScripts/PathDressup.py @@ -1,64 +1,67 @@ # -*- coding: utf-8 -*- -#*************************************************************************** -#* * -#* Copyright (c) 2014 Yorik van Havre * -#* * -#* 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 * -#* * -#*************************************************************************** - -import FreeCAD,FreeCADGui,Path,PathGui -from PySide import QtCore,QtGui +# *************************************************************************** +# * * +# * Copyright (c) 2014 Yorik van Havre * +# * * +# * 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 * +# * * +# *************************************************************************** +import FreeCAD +import FreeCADGui +import Path +import PathGui +from PySide import QtCore, QtGui """Path Dressup object and FreeCAD command""" # Qt tanslation handling try: _encoding = QtGui.QApplication.UnicodeUTF8 + def translate(context, text, disambig=None): return QtGui.QApplication.translate(context, text, disambig, _encoding) + except AttributeError: + def translate(context, text, disambig=None): return QtGui.QApplication.translate(context, text, disambig) class ObjectDressup: - - def __init__(self,obj): - obj.addProperty("App::PropertyLink","Base","Path","The base path to modify") - obj.addProperty("App::PropertyInteger","Position","Path","The position of this dressup in the base path") - obj.addProperty("Path::PropertyPath","Modification","Path","The modification to be added") + def __init__(self, obj): + obj.addProperty("App::PropertyLink", "Base","Path", "The base path to modify") + obj.addProperty("App::PropertyInteger", "Position", "Path", "The position of this dressup in the base path") + obj.addProperty("Path::PropertyPath", "Modification", "Path", "The modification to be added") obj.Proxy = self def __getstate__(self): return None - def __setstate__(self,state): + def __setstate__(self, state): return None - def execute(self,obj): - + def execute(self, obj): + if obj.Base: if obj.Base.isDerivedFrom("Path::Feature"): before = [] after = [] - oldtool = None if obj.Base.Path: if obj.Base.Path.Commands: # split the base path @@ -68,69 +71,76 @@ class ObjectDressup: commands = before + obj.Modification.Commands + after path = Path.Path(commands) obj.Path = path - - + + class ViewProviderDressup: - - def __init__(self,vobj): + def __init__(self, vobj): vobj.Proxy = self - def attach(self,vobj): + def attach(self, vobj): self.Object = vobj.Object - return - + return + def claimChildren(self): + for i in self.Object.Base.InList: + if hasattr(i, "Group"): + group = i.Group + for g in group: + if g.Name == self.Object.Base.Name: + group.remove(g) + i.Group = group + print i.Group return [self.Object.Base] def __getstate__(self): return None - def __setstate__(self,state): + def __setstate__(self, state): return None class CommandPathDressup: - def GetResources(self): - return {'Pixmap' : 'Path-Dressup', - 'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_Dressup","Dress-up"), + return {'Pixmap': 'Path-Dressup', + 'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_Dressup", "Dress-up"), 'Accel': "P, S", - 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_Dressup","Creates a Path Dress-up object from a selected path")} + 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_Dressup", "Creates a Path Dress-up object from a selected path")} def IsActive(self): - return not FreeCAD.ActiveDocument is None - + return FreeCAD.ActiveDocument is not None + def Activated(self): - + # check that the selection contains exactly what we want selection = FreeCADGui.Selection.getSelection() if len(selection) != 1: - FreeCAD.Console.PrintError(translate("Path_Dressup","Please select one path object\n")) + FreeCAD.Console.PrintError(translate("Path_Dressup", "Please select one path object\n")) return if not selection[0].isDerivedFrom("Path::Feature"): - FreeCAD.Console.PrintError(translate("Path_Dressup","The selected object is not a path\n")) + FreeCAD.Console.PrintError(translate("Path_Dressup", "The selected object is not a path\n")) return if selection[0].isDerivedFrom("Path::FeatureCompoundPython"): FreeCAD.Console.PrintError(translate("Path_Dressup", "Please select a Path object")) return # everything ok! - FreeCAD.ActiveDocument.openTransaction(translate("Path_Dressup","Create Dress-up")) + FreeCAD.ActiveDocument.openTransaction(translate("Path_Dressup", "Create Dress-up")) FreeCADGui.addModule("PathScripts.PathDressup") FreeCADGui.addModule("PathScripts.PathUtils") - FreeCADGui.doCommand('obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython","Dressup")') + FreeCADGui.doCommand('obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", "Dressup")') FreeCADGui.doCommand('PathScripts.PathDressup.ObjectDressup(obj)') FreeCADGui.doCommand('obj.Base = FreeCAD.ActiveDocument.' + selection[0].Name) FreeCADGui.doCommand('PathScripts.PathDressup.ViewProviderDressup(obj.ViewObject)') FreeCADGui.doCommand('PathScripts.PathUtils.addToProject(obj)') + FreeCADGui.doCommand('Gui.ActiveDocument.getObject(obj.Base.Name).Visibility = False') FreeCAD.ActiveDocument.commitTransaction() FreeCAD.ActiveDocument.recompute() -if FreeCAD.GuiUp: +if FreeCAD.GuiUp: # register the FreeCAD command - FreeCADGui.addCommand('Path_Dressup',CommandPathDressup()) + FreeCADGui.addCommand('Path_Dressup', CommandPathDressup()) FreeCAD.Console.PrintLog("Loading PathDressup... done\n") diff --git a/src/Mod/Path/PathScripts/PathKurveUtils.py b/src/Mod/Path/PathScripts/PathKurveUtils.py index 1a24d140d..b6fa75add 100644 --- a/src/Mod/Path/PathScripts/PathKurveUtils.py +++ b/src/Mod/Path/PathScripts/PathKurveUtils.py @@ -1,81 +1,92 @@ # -*- coding: utf-8 -*- -#*************************************************************************** -#* * -#* Copyright (c) 2015 Dan Falck * -#* * -#* 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 * -#* * -#*************************************************************************** +# *************************************************************************** +# * * +# * Copyright (c) 2015 Dan Falck * +# * * +# * 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 * +# * * +# *************************************************************************** '''PathKurveUtils - functions needed for using libarea (created by Dan Heeks) for making simple CNC profile paths ''' import FreeCAD from FreeCAD import Vector import FreeCADGui as Gui import Part -import DraftGeomUtils,DraftVecUtils +import DraftGeomUtils +import DraftVecUtils from DraftGeomUtils import geomType import math import area import Path from PathScripts import PathUtils -# import PathSelection from nc.nc import * import PathScripts.nc.iso - +from PathScripts.nc.nc import * def makeAreaVertex(seg): - if seg.ShapeType =='Edge': - if isinstance(seg.Curve,Part.Circle): - segtype = int(seg.Curve.Axis.z) #1=ccw arc,-1=cw arc - vertex = area.Vertex(segtype, area.Point(seg.valueAt(seg.LastParameter)[0],seg.valueAt(seg.LastParameter)[1]), area.Point(seg.Curve.Center.x, seg.Curve.Center.y)) - elif isinstance(seg.Curve,Part.Line): - point1 = seg.valueAt(seg.FirstParameter)[0],seg.valueAt(seg.FirstParameter)[1] - point2 = seg.valueAt(seg.LastParameter)[0],seg.valueAt(seg.LastParameter)[1] - segtype = 0 #0=line - vertex = area.Point(seg.valueAt(seg.LastParameter)[0],seg.valueAt(seg.LastParameter)[1]) + if seg.ShapeType == 'Edge': + if isinstance(seg.Curve, Part.Circle): + segtype = int(seg.Curve.Axis.z) # 1=ccw arc,-1=cw arc + vertex = area.Vertex(segtype, area.Point(seg.valueAt(seg.LastParameter)[0], seg.valueAt( + seg.LastParameter)[1]), area.Point(seg.Curve.Center.x, seg.Curve.Center.y)) + elif isinstance(seg.Curve, Part.Line): + point1 = seg.valueAt(seg.FirstParameter)[ + 0], seg.valueAt(seg.FirstParameter)[1] + point2 = seg.valueAt(seg.LastParameter)[ + 0], seg.valueAt(seg.LastParameter)[1] + segtype = 0 # 0=line + vertex = area.Point(seg.valueAt(seg.LastParameter)[ + 0], seg.valueAt(seg.LastParameter)[1]) else: pass - #print "returning vertex: area.Point(" + str(seg.valueAt(seg.LastParameter)[0]) +"," + str(seg.valueAt(seg.LastParameter)[1]) +")" + # print "returning vertex: area.Point(" + + # str(seg.valueAt(seg.LastParameter)[0]) +"," + + # str(seg.valueAt(seg.LastParameter)[1]) +")" return vertex -def makeAreaCurve(edges,direction,startpt=None,endpt=None): + +def makeAreaCurve(edges, direction, startpt=None, endpt=None): curveobj = area.Curve() cleanededges = Part.__sortEdges__(PathUtils.cleanedges(edges, 0.01)) - - #for e in cleanededges: - #print str(e.valueAt(e.FirstParameter)) + "," + str(e.valueAt(e.LastParameter)) - edgelist=[] - - if len(cleanededges) == 1: #user selected a single edge. - edgelist = cleanededges - else: - #edgelist = [] #Multiple edges. Need to sequence the vetexes. - #First get the first segment oriented correctly. - #We first compare the last parameter of the first segment to see if it matches either end of the second segment. If not, it must need flipping. + # for e in cleanededges: + # print str(e.valueAt(e.FirstParameter)) + "," + + # str(e.valueAt(e.LastParameter)) + edgelist = [] + + if len(cleanededges) == 1: # user selected a single edge. + edgelist = cleanededges + else: + # edgelist = [] #Multiple edges. Need to sequence the vetexes. + # First get the first segment oriented correctly. + + # We first compare the last parameter of the first segment to see if it + # matches either end of the second segment. If not, it must need + # flipping. if cleanededges[0].valueAt(cleanededges[0].LastParameter) in [cleanededges[1].valueAt(cleanededges[1].FirstParameter), cleanededges[1].valueAt(cleanededges[1].LastParameter)]: - edge0 = cleanededges[0] + edge0 = cleanededges[0] else: edge0 = PathUtils.reverseEdge(cleanededges[0]) - + edgelist.append(edge0) - #Now iterate the rest of the edges matching the last parameter of the previous segment. + # Now iterate the rest of the edges matching the last parameter of the + # previous segment. for edge in cleanededges[1:]: if edge.valueAt(edge.FirstParameter) == edgelist[-1].valueAt(edgelist[-1].LastParameter): @@ -83,8 +94,11 @@ def makeAreaCurve(edges,direction,startpt=None,endpt=None): else: nextedge = PathUtils.reverseEdge(edge) edgelist.append(nextedge) - #print "makeareacurve 87: " + "area.Point(" + str(edgelist[0].Vertexes[0].X) + ", " + str(edgelist[0].Vertexes[0].Y)+")" - curveobj.append(area.Point(edgelist[0].Vertexes[0].X,edgelist[0].Vertexes[0].Y)) + # print "makeareacurve 87: " + "area.Point(" + + # str(edgelist[0].Vertexes[0].X) + ", " + + # str(edgelist[0].Vertexes[0].Y)+")" + curveobj.append(area.Point(edgelist[0].Vertexes[ + 0].X, edgelist[0].Vertexes[0].Y)) # seglist =[] # if direction=='CW': # edgelist.reverse() @@ -92,30 +106,30 @@ def makeAreaCurve(edges,direction,startpt=None,endpt=None): # seglist.append(PathUtils.reverseEdge(e)) #swap end points on every segment # else: # for e in edgelist: -# seglist.append(e) +# seglist.append(e) for s in edgelist: curveobj.append(makeAreaVertex(s)) if startpt: # future nearest point code yet to be worked out -fixme -# v1 = Vector(startpt.X,startpt.Y,startpt.Z) -# perppoint1 = DraftGeomUtils.findPerpendicular(v1,firstedge) -# perppoint1 = DraftGeomUtils.findDistance(v1,firstedge) -# if perppoint1: -# curveobj.ChangeStart(area.Point(perppoint1[0].x,perppoint1[0].y)) -# else: -# curveobj.ChangeStart(area.Point(startpt.X,startpt.Y)) - curveobj.ChangeStart(area.Point(startpt.x,startpt.y)) + # v1 = Vector(startpt.X,startpt.Y,startpt.Z) + # perppoint1 = DraftGeomUtils.findPerpendicular(v1,firstedge) + # perppoint1 = DraftGeomUtils.findDistance(v1,firstedge) + # if perppoint1: + # curveobj.ChangeStart(area.Point(perppoint1[0].x,perppoint1[0].y)) + # else: + # curveobj.ChangeStart(area.Point(startpt.X,startpt.Y)) + curveobj.ChangeStart(area.Point(startpt.x, startpt.y)) if endpt: # future nearest point code yet to be worked out -fixme -# v2 = Vector(endpt.X,endpt.Y,endpt.Z) -# perppoint2 = DraftGeomUtils.findPerpendicular(v2,lastedge) -# if perppoint2: -# curveobj.ChangeEnd(area.Point(perppoint2[0].x,perppoint2[0].y)) -# else: -# curveobj.ChangeEnd(area.Point(endpt.X,endpt.Y)) - curveobj.ChangeEnd(area.Point(endpt.x,endpt.y)) + # v2 = Vector(endpt.X,endpt.Y,endpt.Z) + # perppoint2 = DraftGeomUtils.findPerpendicular(v2,lastedge) + # if perppoint2: + # curveobj.ChangeEnd(area.Point(perppoint2[0].x,perppoint2[0].y)) + # else: + # curveobj.ChangeEnd(area.Point(endpt.X,endpt.Y)) + curveobj.ChangeEnd(area.Point(endpt.x, endpt.y)) if curveobj.IsClockwise() and direction == 'CCW': curveobj.Reverse() @@ -126,23 +140,24 @@ def makeAreaCurve(edges,direction,startpt=None,endpt=None): # profile command, # side_of_line should be 'Left' or 'Right' or 'On' -def profile(curve,side_of_line,radius=1.0,vertfeed=0.0,horizfeed=0.0,offset_extra=0.0, \ - rapid_safety_space=None,clearance=None,start_depth=None,stepdown=None, \ - final_depth=None,use_CRC=False, \ - roll_on=None,roll_off=None,roll_start=False,roll_end=True,roll_radius=None, \ - roll_start_pt=None,roll_end_pt=None): +def profile(curve, side_of_line, radius=1.0, vertfeed=0.0, horizfeed=0.0, offset_extra=0.0, + rapid_safety_space=None, clearance=None, start_depth=None, stepdown=None, + final_depth=None, use_CRC=False, + roll_on=None, roll_off=None, roll_start=False, roll_end=True, roll_radius=None, + roll_start_pt=None, roll_end_pt=None): output = "" - output += "G0 Z" + str(clearance)+"\n" + output += "G0 Z" + str(clearance) + "\n" offset_curve = area.Curve(curve) if offset_curve.getNumVertices() <= 1: - raise Exception,"Sketch has no elements!" + raise Exception, "Sketch has no elements!" if side_of_line == "On": - use_CRC =False + use_CRC = False elif (side_of_line == "Left") or (side_of_line == "Right"): - # get tool radius plus little bit of extra offset, if needed to clean up profile a little more + # get tool radius plus little bit of extra offset, if needed to clean + # up profile a little more offset = radius + offset_extra if side_of_line == 'Left': offset_curve.Offset(offset) @@ -150,12 +165,12 @@ def profile(curve,side_of_line,radius=1.0,vertfeed=0.0,horizfeed=0.0,offset_extr else: offset_curve.Offset(-offset) - if offset_curve == False: + if offset_curve is False: raise Exception, "couldn't offset kurve " + str(offset_curve) else: - raise Exception,"Side must be 'Left','Right', or 'On'" + raise Exception, "Side must be 'Left','Right', or 'On'" -#=============================================================================== +# ========================================================================= # #roll_on roll_off section # roll_on_curve = area.Curve() # if offset_curve.getNumVertices() <= 1: return @@ -172,23 +187,23 @@ def profile(curve,side_of_line,radius=1.0,vertfeed=0.0,horizfeed=0.0,offset_extr # off_v = area.Point(-v.y, v.x) # rollstart = first_span.p + off_v * roll_radius # else: -# rollstart = roll_on -# +# rollstart = roll_on +# # rvertex = area.Vertex(first_span.p) -# +# # if first_span.p == rollstart: -# rvertex.type = 0 +# rvertex.type = 0 # else: # v = first_span.GetVector(0.0) # get start direction # rvertex.c, rvertex.type = area.TangentialArc(first_span.p, rollstart, -v) # rvertex.type = -rvertex.type # because TangentialArc was used in reverse # # add a start roll on point # roll_on_curve.append(rollstart) -# +# # # add the roll on arc -# roll_on_curve.append(rvertex) +# roll_on_curve.append(rvertex) # #end of roll_on roll_off section -#=============================================================================== +# ========================================================================= # do multiple depths layer_count = int((start_depth - final_depth) / stepdown) @@ -196,107 +211,131 @@ def profile(curve,side_of_line,radius=1.0,vertfeed=0.0,horizfeed=0.0,offset_extr layer_count += 1 current_start_depth = start_depth prev_depth = start_depth - for i in range(1, layer_count+1): + for i in range(1, layer_count + 1): if i == layer_count: depth = final_depth else: depth = start_depth - i * stepdown mat_depth = prev_depth start_z = mat_depth - #first move - output += "G0 X"+str(PathUtils.fmt(offset_curve.GetFirstSpan().p.x))+\ - " Y"+str(PathUtils.fmt(offset_curve.GetFirstSpan().p.y))+\ - " Z"+str(PathUtils.fmt(mat_depth + rapid_safety_space))+"\n" + # first move + output += "G0 X" + str(PathUtils.fmt(offset_curve.GetFirstSpan().p.x)) +\ + " Y" + str(PathUtils.fmt(offset_curve.GetFirstSpan().p.y)) +\ + " Z" + str(PathUtils.fmt(mat_depth + rapid_safety_space)) + "\n" # feed down to depth mat_depth = depth - if start_z > mat_depth: + if start_z > mat_depth: mat_depth = start_z # feed down in Z - output += "G1 X"+str(PathUtils.fmt(offset_curve.GetFirstSpan().p.x))+\ - " Y"+str(PathUtils.fmt(offset_curve.GetFirstSpan().p.y))+" Z"+str(PathUtils.fmt(depth))+\ - " F"+str(PathUtils.fmt(vertfeed))+"\n" + output += "G1 X" + str(PathUtils.fmt(offset_curve.GetFirstSpan().p.x)) +\ + " Y" + str(PathUtils.fmt(offset_curve.GetFirstSpan().p.y)) + " Z" + str(PathUtils.fmt(depth)) +\ + " F" + str(PathUtils.fmt(vertfeed)) + "\n" if use_CRC: if side_of_line == 'left': - output +="G41"+"\n" + output += "G41" + "\n" else: - output +="G42"+"\n" + output += "G42" + "\n" # cut the main kurve current_perim = 0.0 - lastx=offset_curve.GetFirstSpan().p.x - lasty=offset_curve.GetFirstSpan().p.y + lastx = offset_curve.GetFirstSpan().p.x + lasty = offset_curve.GetFirstSpan().p.y for span in offset_curve.GetSpans(): current_perim += span.Length() - if span.v.type == 0:#line - #feed(span.v.p.x, span.v.p.y, ez) - output +="G1 X"+str(PathUtils.fmt(span.v.p.x))+" Y"+str(PathUtils.fmt(span.v.p.y))+\ - " Z"+str(PathUtils.fmt(depth))+" F"+str(PathUtils.fmt(horizfeed))+"\n" + if span.v.type == 0: # line + # feed(span.v.p.x, span.v.p.y, ez) + output += "G1 X" + str(PathUtils.fmt(span.v.p.x)) + " Y" + str(PathUtils.fmt(span.v.p.y)) +\ + " Z" + str(PathUtils.fmt(depth)) + " F" + \ + str(PathUtils.fmt(horizfeed)) + "\n" lastx = span.v.p.x lasty = span.v.p.y elif (span.v.type == 1) or (span.v.type == -1): - if span.v.type == 1:# anti-clockwise arc + if span.v.type == 1: # anti-clockwise arc command = 'G3' - elif span.v.type == -1:#clockwise arc + elif span.v.type == -1: # clockwise arc command = 'G2' - arc_I= span.v.c.x-lastx - arc_J= span.v.c.y-lasty - output +=command +"X"+str(PathUtils.fmt(span.v.p.x))+" Y"+ str(PathUtils.fmt(span.v.p.y))#+" Z"+ str(PathUtils.fmt(depth)) - output +=" I"+str(PathUtils.fmt(arc_I))+ " J"+str(PathUtils.fmt(arc_J))+" F"+str(PathUtils.fmt(horizfeed))+'\n'#" K"+str(PathUtils.fmt(depth)) +"\n" + arc_I = span.v.c.x - lastx + arc_J = span.v.c.y - lasty + output += command + "X" + str(PathUtils.fmt(span.v.p.x)) + " Y" + str( + PathUtils.fmt(span.v.p.y)) # +" Z"+ str(PathUtils.fmt(depth)) + output += " I" + str(PathUtils.fmt(arc_I)) + " J" + str(PathUtils.fmt(arc_J)) + " F" + str( + PathUtils.fmt(horizfeed)) + '\n' # " K"+str(PathUtils.fmt(depth)) +"\n" lastx = span.v.p.x lasty = span.v.p.y else: raise Exception, "valid geometry identifier needed" if use_CRC: - #end_CRC() - output +="G40"+"\n" + # end_CRC() + output += "G40" + "\n" # rapid up to the clearance height - output +="G0 Z"+str(PathUtils.fmt(clearance))+"\n" + output += "G0 Z" + str(PathUtils.fmt(clearance)) + "\n" del offset_curve return output -def make_smaller( curve, start = None, finish = None, end_beyond = False ): - if start != None: + +def make_smaller(curve, start=None, finish=None, end_beyond=False): + if start is not None: curve.ChangeStart(curve.NearestPoint(start)) - if finish != None: + if finish is not None: if end_beyond: curve2 = area.Curve(curve) curve2.ChangeEnd(curve2.NearestPoint(finish)) first = True for vertex in curve2.getVertices(): - if first == False: curve.append(vertex) + if first is False: + curve.append(vertex) first = False else: curve.ChangeEnd(curve.NearestPoint(finish)) -'''The following procedures are copied almost directly from heekscnc kurve_funcs.py. They depend on nc directory existing below PathScripts and have not been -throughly optimized, understood, or tested for FreeCAD.''' +'''The following procedures are copied almost directly from heekscnc +kurve_funcs.py. They depend on nc directory existing below PathScripts +and have not been throughly optimized, understood, or tested for FreeCAD.''' -def profile2(curve, direction = "on", radius = 1.0, vertfeed=0.0,horizfeed=0.0, offset_extra = 0.0, roll_radius = 2.0, roll_on = None, roll_off = None, depthparams = None, extend_at_start = 0.0, extend_at_end = 0.0, lead_in_line_len=0.0,lead_out_line_len= 0.0): - from PathScripts.nc.nc import * +def profile2(curve, direction="on", radius=1.0, vertfeed=0.0, + horizfeed=0.0, offset_extra=0.0, roll_radius=2.0, + roll_on=None, roll_off=None, depthparams=None, + extend_at_start=0.0, extend_at_end=0.0, lead_in_line_len=0.0, + lead_out_line_len=0.0): + + # print "direction: " + str(direction) + # print "radius: " + str(radius) + # print "vertfeed: " + str(vertfeed) + # print "horizfeed: " + str(horizfeed) + # print "offset_extra: " + str(offset_extra) + # print "roll_radius: " + str(roll_radius) + # print "roll_on: " + str(roll_on) + # print "roll_off: " + str(roll_off) + # print "depthparams: " + str(depthparams) + # print "extend_at_start: " + str(extend_at_start) + # print "extend_at_end: " + str(extend_at_end) + # print "lead_in_line_len: " + str(lead_in_line_len) + # print "lead_out_line_len: " + str(lead_out_line_len) + global tags direction = direction.lower() offset_curve = area.Curve(curve) if direction == "on": - use_CRC() == False - + use_CRC() == False + if direction != "on": if direction != "left" and direction != "right": raise "direction must be left or right", direction # get tool diameter offset = radius + offset_extra - if use_CRC() == False or (use_CRC()==True and CRC_nominal_path()==True): + if use_CRC() is False or (use_CRC() is True and CRC_nominal_path() is True): if math.fabs(offset) > 0.00005: if direction == "right": offset = -offset offset_success = offset_curve.Offset(offset) - if offset_success == False: + if offset_success is False: global using_area_for_offset - if curve.IsClosed() and (using_area_for_offset == False): + if curve.IsClosed() and (using_area_for_offset is False): cw = curve.IsClockwise() using_area_for_offset = True a = area.Area() @@ -307,27 +346,29 @@ def profile2(curve, direction = "on", radius = 1.0, vertfeed=0.0,horizfeed=0.0, if cw != curve_cw: curve.Reverse() set_good_start_point(curve, False) - profile(curve, direction, 0.0, 0.0, roll_radius, roll_on, roll_off, depthparams, extend_at_start, extend_at_end, lead_in_line_len, lead_out_line_len) + profile(curve, direction, 0.0, 0.0, roll_radius, roll_on, roll_off, depthparams, + extend_at_start, extend_at_end, lead_in_line_len, lead_out_line_len) using_area_for_offset = False - return + return else: - raise Exception, "couldn't offset kurve " + str(offset_curve) - + raise Exception, "couldn't offset kurve " + \ + str(offset_curve) + # extend curve if extend_at_start > 0.0: span = offset_curve.GetFirstSpan() - new_start = span.p + span.GetVector(0.0) * ( -extend_at_start) + new_start = span.p + span.GetVector(0.0) * (-extend_at_start) new_curve = area.Curve() new_curve.append(new_start) for vertex in offset_curve.getVertices(): new_curve.append(vertex) offset_curve = new_curve - + if extend_at_end > 0.0: span = offset_curve.GetLastSpan() new_end = span.v.p + span.GetVector(1.0) * extend_at_end offset_curve.append(new_end) - + # remove tags further than radius from the offset kurve new_tags = [] for tag in tags: @@ -347,126 +388,140 @@ def profile2(curve, direction = "on", radius = 1.0, vertfeed=0.0,horizfeed=0.0, if len(tags) > 0: # make a copy to restore to after each level copy_of_offset_curve = area.Curve(offset_curve) - + prev_depth = depthparams.start_depth - + endpoint = None - + for depth in depths: mat_depth = prev_depth - + if len(tags) > 0: - split_for_tags(offset_curve, radius, depthparams.start_depth, depth, depthparams.final_depth) + split_for_tags( + offset_curve, radius, depthparams.start_depth, depth, depthparams.final_depth) # make the roll on and roll off kurves roll_on_curve = area.Curve() - add_roll_on(offset_curve, roll_on_curve, direction, roll_radius, offset_extra, roll_on) + add_roll_on(offset_curve, roll_on_curve, direction, + roll_radius, offset_extra, roll_on) roll_off_curve = area.Curve() - add_roll_off(offset_curve, roll_off_curve, direction, roll_radius, offset_extra, roll_off) + add_roll_off(offset_curve, roll_off_curve, direction, + roll_radius, offset_extra, roll_off) if use_CRC(): crc_start_point = area.Point() - add_CRC_start_line(offset_curve,roll_on_curve,roll_off_curve,radius,direction,crc_start_point,lead_in_line_len) - + add_CRC_start_line(offset_curve, roll_on_curve, roll_off_curve, + radius, direction, crc_start_point, lead_in_line_len) + # get the tag depth at the start - start_z = get_tag_z_for_span(0, offset_curve, radius, depthparams.start_depth, depth, depthparams.final_depth) - if start_z > mat_depth: mat_depth = start_z + start_z = get_tag_z_for_span( + 0, offset_curve, radius, depthparams.start_depth, depth, depthparams.final_depth) + if start_z > mat_depth: + mat_depth = start_z # rapid across to the start s = roll_on_curve.FirstVertex().p - - # start point - if (endpoint == None) or (endpoint != s): + + # start point + if (endpoint is None) or (endpoint != s): if use_CRC(): - rapid(crc_start_point.x,crc_start_point.y) + rapid(crc_start_point.x, crc_start_point.y) else: rapid(s.x, s.y) - + # rapid down to just above the material - if endpoint == None: - rapid(z = mat_depth + depthparams.rapid_safety_space) + if endpoint is None: + rapid(z=mat_depth + depthparams.rapid_safety_space) else: - rapid(z = mat_depth) + rapid(z=mat_depth) # feed down to depth mat_depth = depth - if start_z > mat_depth: mat_depth = start_z - feed(z = mat_depth) + if start_z > mat_depth: + mat_depth = start_z + feed(s.x, s.y, z=mat_depth) if use_CRC(): start_CRC(direction == "left", radius) # move to the startpoint feed(s.x, s.y) - + # cut the roll on arc cut_curve(roll_on_curve) - + # cut the main kurve current_perim = 0.0 - + for span in offset_curve.GetSpans(): # height for tags current_perim += span.Length() - ez = get_tag_z_for_span(current_perim, offset_curve, radius, depthparams.start_depth, depth, depthparams.final_depth) - - if span.v.type == 0:#line + ez = get_tag_z_for_span(current_perim, offset_curve, radius, + depthparams.start_depth, depth, depthparams.final_depth) + if ez is None: + ez = depth + if span.v.type == 0: # line feed(span.v.p.x, span.v.p.y, ez) else: - if span.v.type == 1:# anti-clockwise arc - arc_ccw(span.v.p.x, span.v.p.y, ez, i = span.v.c.x, j = span.v.c.y) + if span.v.type == 1: # anti-clockwise arc + arc_ccw(span.v.p.x, span.v.p.y, ez, + i=span.v.c.x, j=span.v.c.y) else: - arc_cw(span.v.p.x, span.v.p.y, ez, i = span.v.c.x, j = span.v.c.y) - - + arc_cw(span.v.p.x, span.v.p.y, ez, + i=span.v.c.x, j=span.v.c.y) + # cut the roll off arc cut_curve(roll_off_curve) endpoint = offset_curve.LastVertex().p if roll_off_curve.getNumVertices() > 0: endpoint = roll_off_curve.LastVertex().p - - #add CRC end_line + + # add CRC end_line if use_CRC(): crc_end_point = area.Point() - add_CRC_end_line(offset_curve,roll_on_curve,roll_off_curve,radius,direction,crc_end_point,lead_out_line_len) + add_CRC_end_line(offset_curve, roll_on_curve, roll_off_curve, + radius, direction, crc_end_point, lead_out_line_len) if direction == "on": - rapid(z = depthparams.clearance_height) + rapid(z=depthparams.clearance_height) else: feed(crc_end_point.x, crc_end_point.y) - - + # restore the unsplit kurve if len(tags) > 0: offset_curve = area.Curve(copy_of_offset_curve) if use_CRC(): - end_CRC() - + end_CRC() + if endpoint != s: # rapid up to the clearance height - rapid(z = depthparams.clearance_height) - + rapid(z=depthparams.clearance_height) + prev_depth = depth - rapid(z = depthparams.clearance_height) + rapid(z=depthparams.clearance_height) del offset_curve - + if len(tags) > 0: del copy_of_offset_curve + class Tag: + def __init__(self, p, width, angle, height): self.p = p - self.width = width # measured at the top of the tag. In the toolpath, the tag width will be this with plus the tool diameter, so that the finished tag has this "width" at it's smallest - self.angle = angle # the angle of the ramp in radians. Between 0 and Pi/2; 0 is horizontal, Pi/2 is vertical - self.height = height # the height of the tag, always measured above "final_depth" + self.width = width # measured at the top of the tag. In the toolpath, the tag width will be this with plus the tool diameter, so that the finished tag has this "width" at it's smallest + # the angle of the ramp in radians. Between 0 and Pi/2; 0 is + # horizontal, Pi/2 is vertical + self.angle = angle + self.height = height # the height of the tag, always measured above "final_depth" self.ramp_width = self.height / math.tan(self.angle) - + def split_curve(self, curve, radius, start_depth, depth, final_depth): tag_top_depth = final_depth + self.height - + if depth > tag_top_depth - 0.0000001: - return # kurve is above this tag, so doesn't need splitting - + return # kurve is above this tag, so doesn't need splitting + height_above_depth = tag_top_depth - depth ramp_width_at_depth = height_above_depth / math.tan(self.angle) cut_depth = start_depth - depth @@ -476,30 +531,38 @@ class Tag: d0 = d - half_flat_top perim = curve.Perim() if curve.IsClosed(): - while d0 < 0: d0 += perim - while d0 > perim: d0 -= perim + while d0 < 0: + d0 += perim + while d0 > perim: + d0 -= perim p = curve.PerimToPoint(d0) curve.Break(p) d1 = d + half_flat_top if curve.IsClosed(): - while d1 < 0: d1 += perim - while d1 > perim: d1 -= perim + while d1 < 0: + d1 += perim + while d1 > perim: + d1 -= perim p = curve.PerimToPoint(d1) curve.Break(p) - + d0 = d - half_flat_top - ramp_width_at_depth if curve.IsClosed(): - while d0 < 0: d0 += perim - while d0 > perim: d0 -= perim + while d0 < 0: + d0 += perim + while d0 > perim: + d0 -= perim p = curve.PerimToPoint(d0) curve.Break(p) d1 = d + half_flat_top + ramp_width_at_depth if curve.IsClosed(): - while d1 < 0: d1 += perim - while d1 > perim: d1 -= perim + while d1 < 0: + d1 += perim + while d1 > perim: + d1 -= perim p = curve.PerimToPoint(d1) curve.Break(p) - + def get_z_at_perim(self, current_perim, curve, radius, start_depth, depth, final_depth): # return the z for this position on the kurve ( specified by current_perim ), for this tag # if the position is not within the tag, then depth is returned @@ -516,9 +579,10 @@ class Tag: # on ramp dist_up_ramp = (half_flat_top + self.ramp_width) - dist_from_d z = final_depth + dist_up_ramp * math.tan(self.angle) - if z < depth: z = depth + if z < depth: + z = depth return z - + def dist(self, curve): # return the distance from the tag point to the given kurve d = curve.PointToPerim(self.p) @@ -528,12 +592,15 @@ class Tag: tags = [] + def add_roll_on(curve, roll_on_curve, direction, roll_radius, offset_extra, roll_on): - if direction == "on": roll_on = None - if curve.getNumVertices() <= 1: return + if direction == "on": + roll_on = None + if curve.getNumVertices() <= 1: + return first_span = curve.GetFirstSpan() - if roll_on == None: + if roll_on is None: rollstart = first_span.p elif roll_on == 'auto': if roll_radius < 0.0000000001: @@ -545,95 +612,111 @@ def add_roll_on(curve, roll_on_curve, direction, roll_radius, offset_extra, roll off_v = area.Point(-v.y, v.x) rollstart = first_span.p + off_v * roll_radius else: - rollstart = roll_on + rollstart = roll_on rvertex = area.Vertex(first_span.p) - + if first_span.p == rollstart: rvertex.type = 0 else: - v = first_span.GetVector(0.0) # get start direction - rvertex.c, rvertex.type = area.TangentialArc(first_span.p, rollstart, -v) - rvertex.type = -rvertex.type # because TangentialArc was used in reverse + v = first_span.GetVector(0.0) # get start direction + rvertex.c, rvertex.type = area.TangentialArc( + first_span.p, rollstart, -v) + rvertex.type = -rvertex.type # because TangentialArc was used in reverse # add a start roll on point roll_on_curve.append(rollstart) # add the roll on arc roll_on_curve.append(rvertex) - + + def add_roll_off(curve, roll_off_curve, direction, roll_radius, offset_extra, roll_off): - if direction == "on": return - if roll_off == None: return - if curve.getNumVertices() <= 1: return + if direction == "on": + return + if roll_off is None: + return + if curve.getNumVertices() <= 1: + return last_span = curve.GetLastSpan() - + if roll_off == 'auto': - if roll_radius < 0.0000000001: return - v = last_span.GetVector(1.0) # get end direction + if roll_radius < 0.0000000001: + return + v = last_span.GetVector(1.0) # get end direction if direction == 'right': off_v = area.Point(v.y, -v.x) else: off_v = area.Point(-v.y, v.x) - rollend = last_span.v.p + off_v * roll_radius; + rollend = last_span.v.p + off_v * roll_radius else: - rollend = roll_off - + rollend = roll_off + # add the end of the original kurve roll_off_curve.append(last_span.v.p) - if rollend == last_span.v.p: return + if rollend == last_span.v.p: + return rvertex = area.Vertex(rollend) - v = last_span.GetVector(1.0) # get end direction + v = last_span.GetVector(1.0) # get end direction rvertex.c, rvertex.type = area.TangentialArc(last_span.v.p, rollend, v) - # add the roll off arc + # add the roll off arc roll_off_curve.append(rvertex) + def clear_tags(): global tags tags = [] - + + def add_tag(p, width, angle, height): global tags tag = Tag(p, width, angle, height) tags.append(tag) -def split_for_tags( curve, radius, start_depth, depth, final_depth ): + +def split_for_tags(curve, radius, start_depth, depth, final_depth): global tags for tag in tags: tag.split_curve(curve, radius, start_depth, depth, final_depth) + def get_tag_z_for_span(current_perim, curve, radius, start_depth, depth, final_depth): global tags max_z = None perim = curve.Perim() for tag in tags: - z = tag.get_z_at_perim(current_perim, curve, radius, start_depth, depth, final_depth) - if max_z == None or z > max_z: + z = tag.get_z_at_perim(current_perim, curve, + radius, start_depth, depth, final_depth) + if max_z is None or z > max_z: max_z = z if curve.IsClosed(): # do the same test, wrapped around the closed kurve - z = tag.get_z_at_perim(current_perim - perim, curve, radius, start_depth, depth, final_depth) - if max_z == None or z > max_z: + z = tag.get_z_at_perim( + current_perim - perim, curve, radius, start_depth, depth, final_depth) + if max_z is None or z > max_z: max_z = z - z = tag.get_z_at_perim(current_perim + perim, curve, radius, start_depth, depth, final_depth) - if max_z == None or z > max_z: + z = tag.get_z_at_perim( + current_perim + perim, curve, radius, start_depth, depth, final_depth) + if max_z is None or z > max_z: max_z = z - + return max_z + def cut_curve(curve): for span in curve.GetSpans(): - if span.v.type == 0:#line + if span.v.type == 0: # line feed(span.v.p.x, span.v.p.y) else: - if span.v.type == 1:# anti-clockwise arc - arc_ccw(span.v.p.x, span.v.p.y, i = span.v.c.x, j = span.v.c.y) + if span.v.type == 1: # anti-clockwise arc + arc_ccw(span.v.p.x, span.v.p.y, i=span.v.c.x, j=span.v.c.y) else: - arc_cw(span.v.p.x, span.v.p.y, i = span.v.c.x, j = span.v.c.y) - -def add_CRC_start_line(curve,roll_on_curve,roll_off_curve,radius,direction,crc_start_point,lead_in_line_len): + arc_cw(span.v.p.x, span.v.p.y, i=span.v.c.x, j=span.v.c.y) + + +def add_CRC_start_line(curve, roll_on_curve, roll_off_curve, radius, direction, crc_start_point, lead_in_line_len): first_span = curve.GetFirstSpan() v = first_span.GetVector(0.0) if direction == 'right': @@ -641,11 +724,12 @@ def add_CRC_start_line(curve,roll_on_curve,roll_off_curve,radius,direction,crc_s else: off_v = area.Point(-v.y, v.x) startpoint_roll_on = roll_on_curve.FirstVertex().p - crc_start = startpoint_roll_on + off_v * lead_in_line_len - crc_start_point.x = crc_start.x - crc_start_point.y = crc_start.y + crc_start = startpoint_roll_on + off_v * lead_in_line_len + crc_start_point.x = crc_start.x + crc_start_point.y = crc_start.y -def add_CRC_end_line(curve,roll_on_curve,roll_off_curve,radius,direction,crc_end_point,lead_out_line_len): + +def add_CRC_end_line(curve, roll_on_curve, roll_off_curve, radius, direction, crc_end_point, lead_out_line_len): last_span = curve.GetLastSpan() v = last_span.GetVector(1.0) if direction == 'right': @@ -653,10 +737,8 @@ def add_CRC_end_line(curve,roll_on_curve,roll_off_curve,radius,direction,crc_end else: off_v = area.Point(-v.y, v.x) endpoint_roll_off = roll_off_curve.LastVertex().p - crc_end = endpoint_roll_off + off_v * lead_out_line_len - crc_end_point.x = crc_end.x - crc_end_point.y = crc_end.y + crc_end = endpoint_roll_off + off_v * lead_out_line_len + crc_end_point.x = crc_end.x + crc_end_point.y = crc_end.y using_area_for_offset = False - - diff --git a/src/Mod/Path/PathScripts/PathUtils.py b/src/Mod/Path/PathScripts/PathUtils.py index 2b3d966ed..e3fddee7d 100644 --- a/src/Mod/Path/PathScripts/PathUtils.py +++ b/src/Mod/Path/PathScripts/PathUtils.py @@ -25,11 +25,13 @@ import FreeCAD import Part import math +import Path from DraftGeomUtils import geomType from DraftGeomUtils import findWires import DraftVecUtils import PathScripts from PathScripts import PathProject +import itertools def cleanedges(splines, precision): @@ -120,6 +122,29 @@ def segments(poly): ''' A sequence of (x,y) numeric coordinates pairs ''' return zip(poly, poly[1:] + [poly[0]]) +def is_clockwise(obj): + '''tests if a wire or Path is clockwise''' + sum = 0 + if isinstance(obj, Part.Wire): + for first, second in itertools.izip(obj.Edges, obj.Edges[1:]): + sum = (second.Vertexes[0].X - first.Vertexes[0].X) * (second.Vertexes[0].Y + first.Vertexes[0].Y) + sum += (obj.Edges[0].Vertexes[0].X - obj.Edges[-1].Vertexes[0].X) * (obj.Edges[0].Vertexes[0].Y + obj.Edges[-1].Vertexes[0].Y) + elif isinstance(obj, Path.Path): + movecommands = ['G1', 'G01', 'G2', 'G02', 'G3', 'G03'] + + lastLocation = {'Y': 0, 'X': 0, 'Z': 0.0} + currLocation = {'Y': 0, 'X': 0, 'Z': 0.0} + sum = 0 + + for curCommand in obj.Commands: + + if curCommand.Name in movecommands: + lastLocation.update(currLocation) + currLocation.update(curCommand.Parameters) + sum += (currLocation["X"] - lastLocation["X"]) * (currLocation["Y"] + lastLocation["Y"]) + sum += (0 - lastLocation["X"]) * (0 + lastLocation["Y"]) + + return sum >= 0 def check_clockwise(poly): ''' @@ -352,6 +377,12 @@ def SortPath(wire, Side, radius, clockwise, firstedge=None, SegLen=0.5): sortededges = Part.__sortEdges__(edgelist) newwire = findWires(sortededges)[0] + print "newwire is clockwise: " + str(is_clockwise(newwire)) + if is_clockwise(newwire) is not clockwise: + newwire.reverse() + + print "newwire is clockwise: " + str(is_clockwise(newwire)) + if Side == 'Left': # we use the OCC offset feature offset = newwire.makeOffset(radius) # tool is outside line @@ -362,6 +393,9 @@ def SortPath(wire, Side, radius, clockwise, firstedge=None, SegLen=0.5): offset = newwire.makeOffset(0.0) else: offset = newwire + print "offset wire is clockwise: " + str(is_clockwise(offset)) + offset.reverse() + print "offset wire is clockwise: " + str(is_clockwise(offset)) return offset