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.
This commit is contained in:
sliptonic 2016-07-08 08:45:38 -05:00 committed by Yorik van Havre
parent 19306c6d1c
commit f6654c8a6d
6 changed files with 912 additions and 296 deletions

View File

@ -30,6 +30,7 @@ SET(PathScripts_SRCS
PathScripts/PathPocket.py PathScripts/PathPocket.py
PathScripts/PathDrilling.py PathScripts/PathDrilling.py
PathScripts/PathDressup.py PathScripts/PathDressup.py
PathScripts/DragknifeDressup.py
PathScripts/PathHop.py PathScripts/PathHop.py
PathScripts/PathUtils.py PathScripts/PathUtils.py
PathScripts/PathSelection.py PathScripts/PathSelection.py

View File

@ -67,16 +67,17 @@ class PathWorkbench (Workbench):
from PathScripts import PathSurface from PathScripts import PathSurface
from PathScripts import PathRemote from PathScripts import PathRemote
from PathScripts import PathSanity from PathScripts import PathSanity
from PathScripts import DragknifeDressup
# build commands list # build commands list
projcmdlist = ["Path_Project", "Path_Post", "Path_Inspect", "Path_Sanity"] 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", prepcmdlist = ["Path_Plane", "Path_Fixture", "Path_ToolLenOffset", "Path_Comment",
"Path_Stop", "Path_FaceProfile", "Path_FacePocket", "Path_Custom", "Path_FromShape"] "Path_Stop", "Path_FaceProfile", "Path_FacePocket", "Path_Custom", "Path_FromShape"]
opcmdlist = ["Path_Profile", "Path_Pocket", opcmdlist = ["Path_Profile", "Path_Pocket",
"Path_Drilling", "Path_Engrave", "Path_Surfacing"] "Path_Drilling", "Path_Engrave", "Path_Surfacing"]
modcmdlist = ["Path_Copy", "Path_CompoundExtended", 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"] remotecmdlist = ["Path_Remote"]
# Add commands to menu and toolbar # Add commands to menu and toolbar

View File

@ -0,0 +1,488 @@
# -*- coding: utf-8 -*-
# ***************************************************************************
# * *
# * Copyright (c) 2014 sliptonic <shopinthewoods@gmail.com> *
# * *
# * 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")

View File

@ -1,64 +1,67 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
#*************************************************************************** # ***************************************************************************
#* * # * *
#* Copyright (c) 2014 Yorik van Havre <yorik@uncreated.net> * # * Copyright (c) 2014 Yorik van Havre <yorik@uncreated.net> *
#* * # * *
#* This program is free software; you can redistribute it and/or modify * # * This program is free software; you can redistribute it and/or modify *
#* it under the terms of the GNU Lesser General Public License (LGPL) * # * it under the terms of the GNU Lesser General Public License (LGPL) *
#* as published by the Free Software Foundation; either version 2 of * # * as published by the Free Software Foundation; either version 2 of *
#* the License, or (at your option) any later version. * # * the License, or (at your option) any later version. *
#* for detail see the LICENCE text file. * # * for detail see the LICENCE text file. *
#* * # * *
#* This program is distributed in the hope that it will be useful, * # * This program is distributed in the hope that it will be useful, *
#* but WITHOUT ANY WARRANTY; without even the implied warranty of * # * but WITHOUT ANY WARRANTY; without even the implied warranty of *
#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * # * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
#* GNU Library General Public License for more details. * # * GNU Library General Public License for more details. *
#* * # * *
#* You should have received a copy of the GNU Library General Public * # * You should have received a copy of the GNU Library General Public *
#* License along with this program; if not, write to the Free Software * # * License along with this program; if not, write to the Free Software *
#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * # * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
#* USA * # * USA *
#* * # * *
#*************************************************************************** # ***************************************************************************
import FreeCAD
import FreeCAD,FreeCADGui,Path,PathGui import FreeCADGui
from PySide import QtCore,QtGui import Path
import PathGui
from PySide import QtCore, QtGui
"""Path Dressup object and FreeCAD command""" """Path Dressup object and FreeCAD command"""
# Qt tanslation handling # Qt tanslation handling
try: try:
_encoding = QtGui.QApplication.UnicodeUTF8 _encoding = QtGui.QApplication.UnicodeUTF8
def translate(context, text, disambig=None): def translate(context, text, disambig=None):
return QtGui.QApplication.translate(context, text, disambig, _encoding) return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError: except AttributeError:
def translate(context, text, disambig=None): def translate(context, text, disambig=None):
return QtGui.QApplication.translate(context, text, disambig) return QtGui.QApplication.translate(context, text, disambig)
class ObjectDressup: class ObjectDressup:
def __init__(self,obj): def __init__(self, obj):
obj.addProperty("App::PropertyLink","Base","Path","The base path to modify") 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("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.addProperty("Path::PropertyPath", "Modification", "Path", "The modification to be added")
obj.Proxy = self obj.Proxy = self
def __getstate__(self): def __getstate__(self):
return None return None
def __setstate__(self,state): def __setstate__(self, state):
return None return None
def execute(self,obj): def execute(self, obj):
if obj.Base: if obj.Base:
if obj.Base.isDerivedFrom("Path::Feature"): if obj.Base.isDerivedFrom("Path::Feature"):
before = [] before = []
after = [] after = []
oldtool = None
if obj.Base.Path: if obj.Base.Path:
if obj.Base.Path.Commands: if obj.Base.Path.Commands:
# split the base path # split the base path
@ -68,69 +71,76 @@ class ObjectDressup:
commands = before + obj.Modification.Commands + after commands = before + obj.Modification.Commands + after
path = Path.Path(commands) path = Path.Path(commands)
obj.Path = path obj.Path = path
class ViewProviderDressup: class ViewProviderDressup:
def __init__(self, vobj):
def __init__(self,vobj):
vobj.Proxy = self vobj.Proxy = self
def attach(self,vobj): def attach(self, vobj):
self.Object = vobj.Object self.Object = vobj.Object
return return
def claimChildren(self): 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] return [self.Object.Base]
def __getstate__(self): def __getstate__(self):
return None return None
def __setstate__(self,state): def __setstate__(self, state):
return None return None
class CommandPathDressup: class CommandPathDressup:
def GetResources(self): def GetResources(self):
return {'Pixmap' : 'Path-Dressup', return {'Pixmap': 'Path-Dressup',
'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_Dressup","Dress-up"), 'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_Dressup", "Dress-up"),
'Accel': "P, S", '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): def IsActive(self):
return not FreeCAD.ActiveDocument is None return FreeCAD.ActiveDocument is not None
def Activated(self): def Activated(self):
# check that the selection contains exactly what we want # check that the selection contains exactly what we want
selection = FreeCADGui.Selection.getSelection() selection = FreeCADGui.Selection.getSelection()
if len(selection) != 1: 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 return
if not selection[0].isDerivedFrom("Path::Feature"): 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 return
if selection[0].isDerivedFrom("Path::FeatureCompoundPython"): if selection[0].isDerivedFrom("Path::FeatureCompoundPython"):
FreeCAD.Console.PrintError(translate("Path_Dressup", "Please select a Path object")) FreeCAD.Console.PrintError(translate("Path_Dressup", "Please select a Path object"))
return return
# everything ok! # 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.PathDressup")
FreeCADGui.addModule("PathScripts.PathUtils") 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('PathScripts.PathDressup.ObjectDressup(obj)')
FreeCADGui.doCommand('obj.Base = FreeCAD.ActiveDocument.' + selection[0].Name) FreeCADGui.doCommand('obj.Base = FreeCAD.ActiveDocument.' + selection[0].Name)
FreeCADGui.doCommand('PathScripts.PathDressup.ViewProviderDressup(obj.ViewObject)') FreeCADGui.doCommand('PathScripts.PathDressup.ViewProviderDressup(obj.ViewObject)')
FreeCADGui.doCommand('PathScripts.PathUtils.addToProject(obj)') FreeCADGui.doCommand('PathScripts.PathUtils.addToProject(obj)')
FreeCADGui.doCommand('Gui.ActiveDocument.getObject(obj.Base.Name).Visibility = False')
FreeCAD.ActiveDocument.commitTransaction() FreeCAD.ActiveDocument.commitTransaction()
FreeCAD.ActiveDocument.recompute() FreeCAD.ActiveDocument.recompute()
if FreeCAD.GuiUp: if FreeCAD.GuiUp:
# register the FreeCAD command # register the FreeCAD command
FreeCADGui.addCommand('Path_Dressup',CommandPathDressup()) FreeCADGui.addCommand('Path_Dressup', CommandPathDressup())
FreeCAD.Console.PrintLog("Loading PathDressup... done\n") FreeCAD.Console.PrintLog("Loading PathDressup... done\n")

View File

@ -1,81 +1,92 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
#*************************************************************************** # ***************************************************************************
#* * # * *
#* Copyright (c) 2015 Dan Falck <ddfalck@gmail.com> * # * Copyright (c) 2015 Dan Falck <ddfalck@gmail.com> *
#* * # * *
#* This program is free software; you can redistribute it and/or modify * # * This program is free software; you can redistribute it and/or modify *
#* it under the terms of the GNU Lesser General Public License (LGPL) * # * it under the terms of the GNU Lesser General Public License (LGPL) *
#* as published by the Free Software Foundation; either version 2 of * # * as published by the Free Software Foundation; either version 2 of *
#* the License, or (at your option) any later version. * # * the License, or (at your option) any later version. *
#* for detail see the LICENCE text file. * # * for detail see the LICENCE text file. *
#* * # * *
#* This program is distributed in the hope that it will be useful, * # * This program is distributed in the hope that it will be useful, *
#* but WITHOUT ANY WARRANTY; without even the implied warranty of * # * but WITHOUT ANY WARRANTY; without even the implied warranty of *
#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * # * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
#* GNU Library General Public License for more details. * # * GNU Library General Public License for more details. *
#* * # * *
#* You should have received a copy of the GNU Library General Public * # * You should have received a copy of the GNU Library General Public *
#* License along with this program; if not, write to the Free Software * # * License along with this program; if not, write to the Free Software *
#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * # * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
#* USA * # * USA *
#* * # * *
#*************************************************************************** # ***************************************************************************
'''PathKurveUtils - functions needed for using libarea (created by Dan Heeks) for making simple CNC profile paths ''' '''PathKurveUtils - functions needed for using libarea (created by Dan Heeks) for making simple CNC profile paths '''
import FreeCAD import FreeCAD
from FreeCAD import Vector from FreeCAD import Vector
import FreeCADGui as Gui import FreeCADGui as Gui
import Part import Part
import DraftGeomUtils,DraftVecUtils import DraftGeomUtils
import DraftVecUtils
from DraftGeomUtils import geomType from DraftGeomUtils import geomType
import math import math
import area import area
import Path import Path
from PathScripts import PathUtils from PathScripts import PathUtils
# import PathSelection
from nc.nc import * from nc.nc import *
import PathScripts.nc.iso import PathScripts.nc.iso
from PathScripts.nc.nc import *
def makeAreaVertex(seg): def makeAreaVertex(seg):
if seg.ShapeType =='Edge': if seg.ShapeType == 'Edge':
if isinstance(seg.Curve,Part.Circle): if isinstance(seg.Curve, Part.Circle):
segtype = int(seg.Curve.Axis.z) #1=ccw arc,-1=cw arc 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)) vertex = area.Vertex(segtype, area.Point(seg.valueAt(seg.LastParameter)[0], seg.valueAt(
elif isinstance(seg.Curve,Part.Line): seg.LastParameter)[1]), area.Point(seg.Curve.Center.x, seg.Curve.Center.y))
point1 = seg.valueAt(seg.FirstParameter)[0],seg.valueAt(seg.FirstParameter)[1] elif isinstance(seg.Curve, Part.Line):
point2 = seg.valueAt(seg.LastParameter)[0],seg.valueAt(seg.LastParameter)[1] point1 = seg.valueAt(seg.FirstParameter)[
segtype = 0 #0=line 0], seg.valueAt(seg.FirstParameter)[1]
vertex = area.Point(seg.valueAt(seg.LastParameter)[0],seg.valueAt(seg.LastParameter)[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: else:
pass 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 return vertex
def makeAreaCurve(edges,direction,startpt=None,endpt=None):
def makeAreaCurve(edges, direction, startpt=None, endpt=None):
curveobj = area.Curve() curveobj = area.Curve()
cleanededges = Part.__sortEdges__(PathUtils.cleanedges(edges, 0.01)) 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)]: 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: else:
edge0 = PathUtils.reverseEdge(cleanededges[0]) edge0 = PathUtils.reverseEdge(cleanededges[0])
edgelist.append(edge0) 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:]: for edge in cleanededges[1:]:
if edge.valueAt(edge.FirstParameter) == edgelist[-1].valueAt(edgelist[-1].LastParameter): if edge.valueAt(edge.FirstParameter) == edgelist[-1].valueAt(edgelist[-1].LastParameter):
@ -83,8 +94,11 @@ def makeAreaCurve(edges,direction,startpt=None,endpt=None):
else: else:
nextedge = PathUtils.reverseEdge(edge) nextedge = PathUtils.reverseEdge(edge)
edgelist.append(nextedge) edgelist.append(nextedge)
#print "makeareacurve 87: " + "area.Point(" + str(edgelist[0].Vertexes[0].X) + ", " + str(edgelist[0].Vertexes[0].Y)+")" # print "makeareacurve 87: " + "area.Point(" +
curveobj.append(area.Point(edgelist[0].Vertexes[0].X,edgelist[0].Vertexes[0].Y)) # 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 =[] # seglist =[]
# if direction=='CW': # if direction=='CW':
# edgelist.reverse() # 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 # seglist.append(PathUtils.reverseEdge(e)) #swap end points on every segment
# else: # else:
# for e in edgelist: # for e in edgelist:
# seglist.append(e) # seglist.append(e)
for s in edgelist: for s in edgelist:
curveobj.append(makeAreaVertex(s)) curveobj.append(makeAreaVertex(s))
if startpt: if startpt:
# future nearest point code yet to be worked out -fixme # future nearest point code yet to be worked out -fixme
# v1 = Vector(startpt.X,startpt.Y,startpt.Z) # v1 = Vector(startpt.X,startpt.Y,startpt.Z)
# perppoint1 = DraftGeomUtils.findPerpendicular(v1,firstedge) # perppoint1 = DraftGeomUtils.findPerpendicular(v1,firstedge)
# perppoint1 = DraftGeomUtils.findDistance(v1,firstedge) # perppoint1 = DraftGeomUtils.findDistance(v1,firstedge)
# if perppoint1: # if perppoint1:
# curveobj.ChangeStart(area.Point(perppoint1[0].x,perppoint1[0].y)) # curveobj.ChangeStart(area.Point(perppoint1[0].x,perppoint1[0].y))
# else: # else:
# curveobj.ChangeStart(area.Point(startpt.X,startpt.Y)) # curveobj.ChangeStart(area.Point(startpt.X,startpt.Y))
curveobj.ChangeStart(area.Point(startpt.x,startpt.y)) curveobj.ChangeStart(area.Point(startpt.x, startpt.y))
if endpt: if endpt:
# future nearest point code yet to be worked out -fixme # future nearest point code yet to be worked out -fixme
# v2 = Vector(endpt.X,endpt.Y,endpt.Z) # v2 = Vector(endpt.X,endpt.Y,endpt.Z)
# perppoint2 = DraftGeomUtils.findPerpendicular(v2,lastedge) # perppoint2 = DraftGeomUtils.findPerpendicular(v2,lastedge)
# if perppoint2: # if perppoint2:
# curveobj.ChangeEnd(area.Point(perppoint2[0].x,perppoint2[0].y)) # curveobj.ChangeEnd(area.Point(perppoint2[0].x,perppoint2[0].y))
# else: # else:
# curveobj.ChangeEnd(area.Point(endpt.X,endpt.Y)) # curveobj.ChangeEnd(area.Point(endpt.X,endpt.Y))
curveobj.ChangeEnd(area.Point(endpt.x,endpt.y)) curveobj.ChangeEnd(area.Point(endpt.x, endpt.y))
if curveobj.IsClockwise() and direction == 'CCW': if curveobj.IsClockwise() and direction == 'CCW':
curveobj.Reverse() curveobj.Reverse()
@ -126,23 +140,24 @@ def makeAreaCurve(edges,direction,startpt=None,endpt=None):
# profile command, # profile command,
# side_of_line should be 'Left' or 'Right' or 'On' # 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, \ 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, \ rapid_safety_space=None, clearance=None, start_depth=None, stepdown=None,
final_depth=None,use_CRC=False, \ final_depth=None, use_CRC=False,
roll_on=None,roll_off=None,roll_start=False,roll_end=True,roll_radius=None, \ roll_on=None, roll_off=None, roll_start=False, roll_end=True, roll_radius=None,
roll_start_pt=None,roll_end_pt=None): roll_start_pt=None, roll_end_pt=None):
output = "" output = ""
output += "G0 Z" + str(clearance)+"\n" output += "G0 Z" + str(clearance) + "\n"
offset_curve = area.Curve(curve) offset_curve = area.Curve(curve)
if offset_curve.getNumVertices() <= 1: if offset_curve.getNumVertices() <= 1:
raise Exception,"Sketch has no elements!" raise Exception, "Sketch has no elements!"
if side_of_line == "On": if side_of_line == "On":
use_CRC =False use_CRC = False
elif (side_of_line == "Left") or (side_of_line == "Right"): 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 offset = radius + offset_extra
if side_of_line == 'Left': if side_of_line == 'Left':
offset_curve.Offset(offset) 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: else:
offset_curve.Offset(-offset) offset_curve.Offset(-offset)
if offset_curve == False: if offset_curve is False:
raise Exception, "couldn't offset kurve " + str(offset_curve) raise Exception, "couldn't offset kurve " + str(offset_curve)
else: 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 roll_off section
# roll_on_curve = area.Curve() # roll_on_curve = area.Curve()
# if offset_curve.getNumVertices() <= 1: return # 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) # off_v = area.Point(-v.y, v.x)
# rollstart = first_span.p + off_v * roll_radius # rollstart = first_span.p + off_v * roll_radius
# else: # else:
# rollstart = roll_on # rollstart = roll_on
# #
# rvertex = area.Vertex(first_span.p) # rvertex = area.Vertex(first_span.p)
# #
# if first_span.p == rollstart: # if first_span.p == rollstart:
# rvertex.type = 0 # rvertex.type = 0
# else: # else:
# v = first_span.GetVector(0.0) # get start direction # v = first_span.GetVector(0.0) # get start direction
# rvertex.c, rvertex.type = area.TangentialArc(first_span.p, rollstart, -v) # rvertex.c, rvertex.type = area.TangentialArc(first_span.p, rollstart, -v)
# rvertex.type = -rvertex.type # because TangentialArc was used in reverse # rvertex.type = -rvertex.type # because TangentialArc was used in reverse
# # add a start roll on point # # add a start roll on point
# roll_on_curve.append(rollstart) # roll_on_curve.append(rollstart)
# #
# # add the roll on arc # # add the roll on arc
# roll_on_curve.append(rvertex) # roll_on_curve.append(rvertex)
# #end of roll_on roll_off section # #end of roll_on roll_off section
#=============================================================================== # =========================================================================
# do multiple depths # do multiple depths
layer_count = int((start_depth - final_depth) / stepdown) 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 layer_count += 1
current_start_depth = start_depth current_start_depth = start_depth
prev_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: if i == layer_count:
depth = final_depth depth = final_depth
else: else:
depth = start_depth - i * stepdown depth = start_depth - i * stepdown
mat_depth = prev_depth mat_depth = prev_depth
start_z = mat_depth start_z = mat_depth
#first move # first move
output += "G0 X"+str(PathUtils.fmt(offset_curve.GetFirstSpan().p.x))+\ output += "G0 X" + str(PathUtils.fmt(offset_curve.GetFirstSpan().p.x)) +\
" Y"+str(PathUtils.fmt(offset_curve.GetFirstSpan().p.y))+\ " Y" + str(PathUtils.fmt(offset_curve.GetFirstSpan().p.y)) +\
" Z"+str(PathUtils.fmt(mat_depth + rapid_safety_space))+"\n" " Z" + str(PathUtils.fmt(mat_depth + rapid_safety_space)) + "\n"
# feed down to depth # feed down to depth
mat_depth = depth mat_depth = depth
if start_z > mat_depth: if start_z > mat_depth:
mat_depth = start_z mat_depth = start_z
# feed down in Z # feed down in Z
output += "G1 X"+str(PathUtils.fmt(offset_curve.GetFirstSpan().p.x))+\ 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))+\ " Y" + str(PathUtils.fmt(offset_curve.GetFirstSpan().p.y)) + " Z" + str(PathUtils.fmt(depth)) +\
" F"+str(PathUtils.fmt(vertfeed))+"\n" " F" + str(PathUtils.fmt(vertfeed)) + "\n"
if use_CRC: if use_CRC:
if side_of_line == 'left': if side_of_line == 'left':
output +="G41"+"\n" output += "G41" + "\n"
else: else:
output +="G42"+"\n" output += "G42" + "\n"
# cut the main kurve # cut the main kurve
current_perim = 0.0 current_perim = 0.0
lastx=offset_curve.GetFirstSpan().p.x lastx = offset_curve.GetFirstSpan().p.x
lasty=offset_curve.GetFirstSpan().p.y lasty = offset_curve.GetFirstSpan().p.y
for span in offset_curve.GetSpans(): for span in offset_curve.GetSpans():
current_perim += span.Length() current_perim += span.Length()
if span.v.type == 0:#line if span.v.type == 0: # line
#feed(span.v.p.x, span.v.p.y, ez) # 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))+\ 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" " Z" + str(PathUtils.fmt(depth)) + " F" + \
str(PathUtils.fmt(horizfeed)) + "\n"
lastx = span.v.p.x lastx = span.v.p.x
lasty = span.v.p.y lasty = span.v.p.y
elif (span.v.type == 1) or (span.v.type == -1): 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' command = 'G3'
elif span.v.type == -1:#clockwise arc elif span.v.type == -1: # clockwise arc
command = 'G2' command = 'G2'
arc_I= span.v.c.x-lastx arc_I = span.v.c.x - lastx
arc_J= span.v.c.y-lasty 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 += command + "X" + str(PathUtils.fmt(span.v.p.x)) + " Y" + str(
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" 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 lastx = span.v.p.x
lasty = span.v.p.y lasty = span.v.p.y
else: else:
raise Exception, "valid geometry identifier needed" raise Exception, "valid geometry identifier needed"
if use_CRC: if use_CRC:
#end_CRC() # end_CRC()
output +="G40"+"\n" output += "G40" + "\n"
# rapid up to the clearance height # 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 del offset_curve
return output 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)) curve.ChangeStart(curve.NearestPoint(start))
if finish != None: if finish is not None:
if end_beyond: if end_beyond:
curve2 = area.Curve(curve) curve2 = area.Curve(curve)
curve2.ChangeEnd(curve2.NearestPoint(finish)) curve2.ChangeEnd(curve2.NearestPoint(finish))
first = True first = True
for vertex in curve2.getVertices(): for vertex in curve2.getVertices():
if first == False: curve.append(vertex) if first is False:
curve.append(vertex)
first = False first = False
else: else:
curve.ChangeEnd(curve.NearestPoint(finish)) 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 '''The following procedures are copied almost directly from heekscnc
throughly optimized, understood, or tested for FreeCAD.''' 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): def profile2(curve, direction="on", radius=1.0, vertfeed=0.0,
from PathScripts.nc.nc import * 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 global tags
direction = direction.lower() direction = direction.lower()
offset_curve = area.Curve(curve) offset_curve = area.Curve(curve)
if direction == "on": if direction == "on":
use_CRC() == False use_CRC() == False
if direction != "on": if direction != "on":
if direction != "left" and direction != "right": if direction != "left" and direction != "right":
raise "direction must be left or right", direction raise "direction must be left or right", direction
# get tool diameter # get tool diameter
offset = radius + offset_extra 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 math.fabs(offset) > 0.00005:
if direction == "right": if direction == "right":
offset = -offset offset = -offset
offset_success = offset_curve.Offset(offset) offset_success = offset_curve.Offset(offset)
if offset_success == False: if offset_success is False:
global using_area_for_offset 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() cw = curve.IsClockwise()
using_area_for_offset = True using_area_for_offset = True
a = area.Area() 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: if cw != curve_cw:
curve.Reverse() curve.Reverse()
set_good_start_point(curve, False) 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 using_area_for_offset = False
return return
else: else:
raise Exception, "couldn't offset kurve " + str(offset_curve) raise Exception, "couldn't offset kurve " + \
str(offset_curve)
# extend curve # extend curve
if extend_at_start > 0.0: if extend_at_start > 0.0:
span = offset_curve.GetFirstSpan() 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 = area.Curve()
new_curve.append(new_start) new_curve.append(new_start)
for vertex in offset_curve.getVertices(): for vertex in offset_curve.getVertices():
new_curve.append(vertex) new_curve.append(vertex)
offset_curve = new_curve offset_curve = new_curve
if extend_at_end > 0.0: if extend_at_end > 0.0:
span = offset_curve.GetLastSpan() span = offset_curve.GetLastSpan()
new_end = span.v.p + span.GetVector(1.0) * extend_at_end new_end = span.v.p + span.GetVector(1.0) * extend_at_end
offset_curve.append(new_end) offset_curve.append(new_end)
# remove tags further than radius from the offset kurve # remove tags further than radius from the offset kurve
new_tags = [] new_tags = []
for tag in 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: if len(tags) > 0:
# make a copy to restore to after each level # make a copy to restore to after each level
copy_of_offset_curve = area.Curve(offset_curve) copy_of_offset_curve = area.Curve(offset_curve)
prev_depth = depthparams.start_depth prev_depth = depthparams.start_depth
endpoint = None endpoint = None
for depth in depths: for depth in depths:
mat_depth = prev_depth mat_depth = prev_depth
if len(tags) > 0: 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 # make the roll on and roll off kurves
roll_on_curve = area.Curve() 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() 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(): if use_CRC():
crc_start_point = area.Point() 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 # 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) start_z = get_tag_z_for_span(
if start_z > mat_depth: mat_depth = start_z 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 # rapid across to the start
s = roll_on_curve.FirstVertex().p s = roll_on_curve.FirstVertex().p
# start point # start point
if (endpoint == None) or (endpoint != s): if (endpoint is None) or (endpoint != s):
if use_CRC(): if use_CRC():
rapid(crc_start_point.x,crc_start_point.y) rapid(crc_start_point.x, crc_start_point.y)
else: else:
rapid(s.x, s.y) rapid(s.x, s.y)
# rapid down to just above the material # rapid down to just above the material
if endpoint == None: if endpoint is None:
rapid(z = mat_depth + depthparams.rapid_safety_space) rapid(z=mat_depth + depthparams.rapid_safety_space)
else: else:
rapid(z = mat_depth) rapid(z=mat_depth)
# feed down to depth # feed down to depth
mat_depth = depth mat_depth = depth
if start_z > mat_depth: mat_depth = start_z if start_z > mat_depth:
feed(z = mat_depth) mat_depth = start_z
feed(s.x, s.y, z=mat_depth)
if use_CRC(): if use_CRC():
start_CRC(direction == "left", radius) start_CRC(direction == "left", radius)
# move to the startpoint # move to the startpoint
feed(s.x, s.y) feed(s.x, s.y)
# cut the roll on arc # cut the roll on arc
cut_curve(roll_on_curve) cut_curve(roll_on_curve)
# cut the main kurve # cut the main kurve
current_perim = 0.0 current_perim = 0.0
for span in offset_curve.GetSpans(): for span in offset_curve.GetSpans():
# height for tags # height for tags
current_perim += span.Length() current_perim += span.Length()
ez = get_tag_z_for_span(current_perim, offset_curve, radius, depthparams.start_depth, depth, depthparams.final_depth) ez = get_tag_z_for_span(current_perim, offset_curve, radius,
depthparams.start_depth, depth, depthparams.final_depth)
if span.v.type == 0:#line if ez is None:
ez = depth
if span.v.type == 0: # line
feed(span.v.p.x, span.v.p.y, ez) feed(span.v.p.x, span.v.p.y, ez)
else: else:
if span.v.type == 1:# anti-clockwise arc 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) arc_ccw(span.v.p.x, span.v.p.y, ez,
i=span.v.c.x, j=span.v.c.y)
else: 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 the roll off arc
cut_curve(roll_off_curve) cut_curve(roll_off_curve)
endpoint = offset_curve.LastVertex().p endpoint = offset_curve.LastVertex().p
if roll_off_curve.getNumVertices() > 0: if roll_off_curve.getNumVertices() > 0:
endpoint = roll_off_curve.LastVertex().p endpoint = roll_off_curve.LastVertex().p
#add CRC end_line # add CRC end_line
if use_CRC(): if use_CRC():
crc_end_point = area.Point() 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": if direction == "on":
rapid(z = depthparams.clearance_height) rapid(z=depthparams.clearance_height)
else: else:
feed(crc_end_point.x, crc_end_point.y) feed(crc_end_point.x, crc_end_point.y)
# restore the unsplit kurve # restore the unsplit kurve
if len(tags) > 0: if len(tags) > 0:
offset_curve = area.Curve(copy_of_offset_curve) offset_curve = area.Curve(copy_of_offset_curve)
if use_CRC(): if use_CRC():
end_CRC() end_CRC()
if endpoint != s: if endpoint != s:
# rapid up to the clearance height # rapid up to the clearance height
rapid(z = depthparams.clearance_height) rapid(z=depthparams.clearance_height)
prev_depth = depth prev_depth = depth
rapid(z = depthparams.clearance_height) rapid(z=depthparams.clearance_height)
del offset_curve del offset_curve
if len(tags) > 0: if len(tags) > 0:
del copy_of_offset_curve del copy_of_offset_curve
class Tag: class Tag:
def __init__(self, p, width, angle, height): def __init__(self, p, width, angle, height):
self.p = p 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.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 # the angle of the ramp in radians. Between 0 and Pi/2; 0 is
self.height = height # the height of the tag, always measured above "final_depth" # 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) self.ramp_width = self.height / math.tan(self.angle)
def split_curve(self, curve, radius, start_depth, depth, final_depth): def split_curve(self, curve, radius, start_depth, depth, final_depth):
tag_top_depth = final_depth + self.height tag_top_depth = final_depth + self.height
if depth > tag_top_depth - 0.0000001: 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 height_above_depth = tag_top_depth - depth
ramp_width_at_depth = height_above_depth / math.tan(self.angle) ramp_width_at_depth = height_above_depth / math.tan(self.angle)
cut_depth = start_depth - depth cut_depth = start_depth - depth
@ -476,30 +531,38 @@ class Tag:
d0 = d - half_flat_top d0 = d - half_flat_top
perim = curve.Perim() perim = curve.Perim()
if curve.IsClosed(): if curve.IsClosed():
while d0 < 0: d0 += perim while d0 < 0:
while d0 > perim: d0 -= perim d0 += perim
while d0 > perim:
d0 -= perim
p = curve.PerimToPoint(d0) p = curve.PerimToPoint(d0)
curve.Break(p) curve.Break(p)
d1 = d + half_flat_top d1 = d + half_flat_top
if curve.IsClosed(): if curve.IsClosed():
while d1 < 0: d1 += perim while d1 < 0:
while d1 > perim: d1 -= perim d1 += perim
while d1 > perim:
d1 -= perim
p = curve.PerimToPoint(d1) p = curve.PerimToPoint(d1)
curve.Break(p) curve.Break(p)
d0 = d - half_flat_top - ramp_width_at_depth d0 = d - half_flat_top - ramp_width_at_depth
if curve.IsClosed(): if curve.IsClosed():
while d0 < 0: d0 += perim while d0 < 0:
while d0 > perim: d0 -= perim d0 += perim
while d0 > perim:
d0 -= perim
p = curve.PerimToPoint(d0) p = curve.PerimToPoint(d0)
curve.Break(p) curve.Break(p)
d1 = d + half_flat_top + ramp_width_at_depth d1 = d + half_flat_top + ramp_width_at_depth
if curve.IsClosed(): if curve.IsClosed():
while d1 < 0: d1 += perim while d1 < 0:
while d1 > perim: d1 -= perim d1 += perim
while d1 > perim:
d1 -= perim
p = curve.PerimToPoint(d1) p = curve.PerimToPoint(d1)
curve.Break(p) curve.Break(p)
def get_z_at_perim(self, current_perim, curve, radius, start_depth, depth, final_depth): 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 # 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 # if the position is not within the tag, then depth is returned
@ -516,9 +579,10 @@ class Tag:
# on ramp # on ramp
dist_up_ramp = (half_flat_top + self.ramp_width) - dist_from_d dist_up_ramp = (half_flat_top + self.ramp_width) - dist_from_d
z = final_depth + dist_up_ramp * math.tan(self.angle) z = final_depth + dist_up_ramp * math.tan(self.angle)
if z < depth: z = depth if z < depth:
z = depth
return z return z
def dist(self, curve): def dist(self, curve):
# return the distance from the tag point to the given kurve # return the distance from the tag point to the given kurve
d = curve.PointToPerim(self.p) d = curve.PointToPerim(self.p)
@ -528,12 +592,15 @@ class Tag:
tags = [] tags = []
def add_roll_on(curve, roll_on_curve, direction, roll_radius, offset_extra, roll_on): def add_roll_on(curve, roll_on_curve, direction, roll_radius, offset_extra, roll_on):
if direction == "on": roll_on = None if direction == "on":
if curve.getNumVertices() <= 1: return roll_on = None
if curve.getNumVertices() <= 1:
return
first_span = curve.GetFirstSpan() first_span = curve.GetFirstSpan()
if roll_on == None: if roll_on is None:
rollstart = first_span.p rollstart = first_span.p
elif roll_on == 'auto': elif roll_on == 'auto':
if roll_radius < 0.0000000001: 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) off_v = area.Point(-v.y, v.x)
rollstart = first_span.p + off_v * roll_radius rollstart = first_span.p + off_v * roll_radius
else: else:
rollstart = roll_on rollstart = roll_on
rvertex = area.Vertex(first_span.p) rvertex = area.Vertex(first_span.p)
if first_span.p == rollstart: if first_span.p == rollstart:
rvertex.type = 0 rvertex.type = 0
else: else:
v = first_span.GetVector(0.0) # get start direction v = first_span.GetVector(0.0) # get start direction
rvertex.c, rvertex.type = area.TangentialArc(first_span.p, rollstart, -v) rvertex.c, rvertex.type = area.TangentialArc(
rvertex.type = -rvertex.type # because TangentialArc was used in reverse first_span.p, rollstart, -v)
rvertex.type = -rvertex.type # because TangentialArc was used in reverse
# add a start roll on point # add a start roll on point
roll_on_curve.append(rollstart) roll_on_curve.append(rollstart)
# add the roll on arc # add the roll on arc
roll_on_curve.append(rvertex) roll_on_curve.append(rvertex)
def add_roll_off(curve, roll_off_curve, direction, roll_radius, offset_extra, roll_off): def add_roll_off(curve, roll_off_curve, direction, roll_radius, offset_extra, roll_off):
if direction == "on": return if direction == "on":
if roll_off == None: return return
if curve.getNumVertices() <= 1: return if roll_off is None:
return
if curve.getNumVertices() <= 1:
return
last_span = curve.GetLastSpan() last_span = curve.GetLastSpan()
if roll_off == 'auto': if roll_off == 'auto':
if roll_radius < 0.0000000001: return if roll_radius < 0.0000000001:
v = last_span.GetVector(1.0) # get end direction return
v = last_span.GetVector(1.0) # get end direction
if direction == 'right': if direction == 'right':
off_v = area.Point(v.y, -v.x) off_v = area.Point(v.y, -v.x)
else: else:
off_v = area.Point(-v.y, v.x) 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: else:
rollend = roll_off rollend = roll_off
# add the end of the original kurve # add the end of the original kurve
roll_off_curve.append(last_span.v.p) 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) 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) 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) roll_off_curve.append(rvertex)
def clear_tags(): def clear_tags():
global tags global tags
tags = [] tags = []
def add_tag(p, width, angle, height): def add_tag(p, width, angle, height):
global tags global tags
tag = Tag(p, width, angle, height) tag = Tag(p, width, angle, height)
tags.append(tag) 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 global tags
for tag in tags: for tag in tags:
tag.split_curve(curve, radius, start_depth, depth, final_depth) 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): def get_tag_z_for_span(current_perim, curve, radius, start_depth, depth, final_depth):
global tags global tags
max_z = None max_z = None
perim = curve.Perim() perim = curve.Perim()
for tag in tags: for tag in tags:
z = tag.get_z_at_perim(current_perim, curve, radius, start_depth, depth, final_depth) z = tag.get_z_at_perim(current_perim, curve,
if max_z == None or z > max_z: radius, start_depth, depth, final_depth)
if max_z is None or z > max_z:
max_z = z max_z = z
if curve.IsClosed(): if curve.IsClosed():
# do the same test, wrapped around the closed kurve # 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) z = tag.get_z_at_perim(
if max_z == None or z > max_z: current_perim - perim, curve, radius, start_depth, depth, final_depth)
if max_z is None or z > max_z:
max_z = z max_z = z
z = tag.get_z_at_perim(current_perim + perim, curve, radius, start_depth, depth, final_depth) z = tag.get_z_at_perim(
if max_z == None or z > max_z: current_perim + perim, curve, radius, start_depth, depth, final_depth)
if max_z is None or z > max_z:
max_z = z max_z = z
return max_z return max_z
def cut_curve(curve): def cut_curve(curve):
for span in curve.GetSpans(): 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) feed(span.v.p.x, span.v.p.y)
else: else:
if span.v.type == 1:# anti-clockwise arc 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) arc_ccw(span.v.p.x, span.v.p.y, i=span.v.c.x, j=span.v.c.y)
else: else:
arc_cw(span.v.p.x, span.v.p.y, i = span.v.c.x, j = span.v.c.y) 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):
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() first_span = curve.GetFirstSpan()
v = first_span.GetVector(0.0) v = first_span.GetVector(0.0)
if direction == 'right': if direction == 'right':
@ -641,11 +724,12 @@ def add_CRC_start_line(curve,roll_on_curve,roll_off_curve,radius,direction,crc_s
else: else:
off_v = area.Point(-v.y, v.x) off_v = area.Point(-v.y, v.x)
startpoint_roll_on = roll_on_curve.FirstVertex().p startpoint_roll_on = roll_on_curve.FirstVertex().p
crc_start = startpoint_roll_on + off_v * lead_in_line_len crc_start = startpoint_roll_on + off_v * lead_in_line_len
crc_start_point.x = crc_start.x crc_start_point.x = crc_start.x
crc_start_point.y = crc_start.y 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() last_span = curve.GetLastSpan()
v = last_span.GetVector(1.0) v = last_span.GetVector(1.0)
if direction == 'right': if direction == 'right':
@ -653,10 +737,8 @@ def add_CRC_end_line(curve,roll_on_curve,roll_off_curve,radius,direction,crc_end
else: else:
off_v = area.Point(-v.y, v.x) off_v = area.Point(-v.y, v.x)
endpoint_roll_off = roll_off_curve.LastVertex().p endpoint_roll_off = roll_off_curve.LastVertex().p
crc_end = endpoint_roll_off + off_v * lead_out_line_len crc_end = endpoint_roll_off + off_v * lead_out_line_len
crc_end_point.x = crc_end.x crc_end_point.x = crc_end.x
crc_end_point.y = crc_end.y crc_end_point.y = crc_end.y
using_area_for_offset = False using_area_for_offset = False

View File

@ -25,11 +25,13 @@
import FreeCAD import FreeCAD
import Part import Part
import math import math
import Path
from DraftGeomUtils import geomType from DraftGeomUtils import geomType
from DraftGeomUtils import findWires from DraftGeomUtils import findWires
import DraftVecUtils import DraftVecUtils
import PathScripts import PathScripts
from PathScripts import PathProject from PathScripts import PathProject
import itertools
def cleanedges(splines, precision): def cleanedges(splines, precision):
@ -120,6 +122,29 @@ def segments(poly):
''' A sequence of (x,y) numeric coordinates pairs ''' ''' A sequence of (x,y) numeric coordinates pairs '''
return zip(poly, poly[1:] + [poly[0]]) 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): def check_clockwise(poly):
''' '''
@ -352,6 +377,12 @@ def SortPath(wire, Side, radius, clockwise, firstedge=None, SegLen=0.5):
sortededges = Part.__sortEdges__(edgelist) sortededges = Part.__sortEdges__(edgelist)
newwire = findWires(sortededges)[0] 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': if Side == 'Left':
# we use the OCC offset feature # we use the OCC offset feature
offset = newwire.makeOffset(radius) # tool is outside line 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) offset = newwire.makeOffset(0.0)
else: else:
offset = newwire offset = newwire
print "offset wire is clockwise: " + str(is_clockwise(offset))
offset.reverse()
print "offset wire is clockwise: " + str(is_clockwise(offset))
return offset return offset