PEP8 cleanup

This commit is contained in:
brad 2016-04-22 15:55:43 -05:00 committed by Yorik van Havre
parent eeec4f88a3
commit 2f17f11049
8 changed files with 994 additions and 991 deletions

View File

@ -1,29 +1,30 @@
# -*- coding: utf-8 -*-
#***************************************************************************
#* *
#* Copyright (c) 2014 Yorik van Havre <yorik@uncreated.net> *
#* *
#* 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) 2014 Yorik van Havre <yorik@uncreated.net> *
# * *
# * 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,Path
from PySide import QtCore,QtGui
import FreeCAD
import Path
from PySide import QtCore, QtGui
from PathScripts import PathUtils
FreeCADGui = None
@ -35,6 +36,7 @@ if FreeCAD.GuiUp:
# Qt tanslation handling
try:
_encoding = QtGui.QApplication.UnicodeUTF8
def translate(context, text, disambig=None):
return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
@ -44,47 +46,46 @@ except AttributeError:
class ObjectDrilling:
def __init__(self, obj):
obj.addProperty("App::PropertyLinkSubList", "Base","Path", translate("PathProject", "The base geometry of this toolpath"))
obj.addProperty("App::PropertyBool", "Active", "Path", translate("PathProject", "Make False, to prevent operation from generating code"))
obj.addProperty("App::PropertyString", "Comment", "Path", translate("PathProject", "An optional comment for this profile"))
def __init__(self,obj):
obj.addProperty("App::PropertyLinkSubList","Base","Path",translate("PathProject","The base geometry of this toolpath"))
obj.addProperty("App::PropertyBool","Active","Path",translate("PathProject","Make False, to prevent operation from generating code"))
obj.addProperty("App::PropertyString","Comment","Path",translate("PathProject","An optional comment for this profile"))
obj.addProperty("App::PropertyLength", "PeckDepth", "Depth", translate("PathProject", "Incremental Drill depth before retracting to clear chips"))
obj.addProperty("App::PropertyLength", "StartDepth", "Depth", translate("PathProject", "Starting Depth of Tool- first cut depth in Z"))
obj.addProperty("App::PropertyDistance", "ClearanceHeight", "Depth", translate("PathProject", "The height needed to clear clamps and obstructions"))
obj.addProperty("App::PropertyDistance", "FinalDepth", "Depth", translate("PathProject", "Final Depth of Tool- lowest value in Z"))
obj.addProperty("App::PropertyDistance", "SafeHeight", "Depth", translate("PathProject", "Height to clear top of materil"))
obj.addProperty("App::PropertyDistance", "RetractHeight", "Depth", translate("PathProject", "The height where feed starts and height during retract tool when path is finished"))
obj.addProperty("App::PropertyLength", "PeckDepth", "Depth", translate("PathProject","Incremental Drill depth before retracting to clear chips"))
obj.addProperty("App::PropertyLength", "StartDepth", "Depth", translate("PathProject","Starting Depth of Tool- first cut depth in Z"))
obj.addProperty("App::PropertyDistance", "ClearanceHeight", "Depth", translate("PathProject","The height needed to clear clamps and obstructions"))
obj.addProperty("App::PropertyDistance", "FinalDepth", "Depth", translate("PathProject","Final Depth of Tool- lowest value in Z"))
obj.addProperty("App::PropertyDistance", "SafeHeight", "Depth", translate("PathProject","Height to clear top of materil"))
obj.addProperty("App::PropertyDistance", "RetractHeight", "Depth", translate("PathProject","The height where feed starts and height during retract tool when path is finished"))
#Tool Properties
obj.addProperty("App::PropertyIntegerConstraint","ToolNumber","Tool",translate("PathProfile","The tool number in use"))
obj.ToolNumber = (0,0,1000,1)
obj.setEditorMode('ToolNumber',1) #make this read only
# Tool Properties
obj.addProperty("App::PropertyIntegerConstraint", "ToolNumber", "Tool", translate("PathProfile", "The tool number in use"))
obj.ToolNumber = (0, 0, 1000, 1)
obj.setEditorMode('ToolNumber', 1) # make this read only
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):
output = ""
toolLoad = PathUtils.getLastToolLoad(obj)
if toolLoad == None:
if toolLoad is None:
self.vertFeed = 100
self.horizFeed = 100
self.radius = 0.25
obj.ToolNumber= 0
obj.ToolNumber = 0
else:
self.vertFeed = toolLoad.VertFeed.Value
self.horizFeed = toolLoad.HorizFeed.Value
obj.ToolNumber= toolLoad.ToolNumber
obj.ToolNumber = toolLoad.ToolNumber
tool = PathUtils.getTool(obj, toolLoad.ToolNumber)
if tool == None:
if tool is None:
self.radius = 0.25
else:
self.radius = tool.Diameter/2
@ -94,11 +95,11 @@ class ObjectDrilling:
for loc in obj.Base:
if "Face" in loc[1] or "Edge" in loc[1]:
s = getattr(loc[0].Shape,loc[1])
s = getattr(loc[0].Shape, loc[1])
else:
s = loc[0].Shape
if s.ShapeType in ['Face', 'Wire', 'Edge' ]:
if s.ShapeType in ['Face', 'Wire', 'Edge']:
X = s.Edges[0].Curve.Center.x
Y = s.Edges[0].Curve.Center.y
Z = s.Edges[0].Curve.Center.z
@ -107,40 +108,43 @@ class ObjectDrilling:
Y = s.Point.y
Z = s.Point.z
locations.append(FreeCAD.Vector(X,Y,Z))
locations.append(FreeCAD.Vector(X, Y, Z))
output = "G90 G98\n"
# rapid to clearance height
output += "G0 Z" + str(obj.ClearanceHeight.Value)
# rapid to first hole location, with spindle still retracted:
p0 = locations[0]
output += "G0 X"+str(p0.x) + " Y" + str(p0.y)+ "\n"
output += "G0 X" + str(p0.x) + " Y" + str(p0.y) + "\n"
# move tool to clearance plane
output += "G0 Z" + str(obj.ClearanceHeight.Value) + "\n"
if obj.PeckDepth.Value > 0:
cmd = "G83"
qword = " Q"+ str(obj.PeckDepth.Value)
qword = " Q" + str(obj.PeckDepth.Value)
else:
cmd = "G81"
qword = ""
for p in locations:
output += cmd + " X" + str(p.x) + " Y" + str(p.y) + " Z" + str(obj.FinalDepth.Value) + qword + " R" + str(obj.RetractHeight.Value) + " F" + str(self.vertFeed) + "\n"
output += cmd + \
" X" + str(p.x) + \
" Y" + str(p.y) + \
" Z" + str(obj.FinalDepth.Value) + qword + \
" R" + str(obj.RetractHeight.Value) + \
" F" + str(self.vertFeed) + "\n" \
output += "G80\n"
path = Path.Path(output)
obj.Path = path
def addDrillableLocation(self, obj, ss, sub=""):
baselist = obj.Base
item = (ss, sub)
if len(baselist) == 0: #When adding the first base object, guess at heights
if len(baselist) == 0: # When adding the first base object, guess at heights
try:
bb = ss.Shape.BoundBox #parent boundbox
bb = ss.Shape.BoundBox # parent boundbox
subobj = ss.Shape.getElement(sub)
fbb = subobj.BoundBox #feature boundbox
fbb = subobj.BoundBox # feature boundbox
obj.StartDepth = bb.ZMax
obj.ClearanceHeight = bb.ZMax + 5.0
obj.SafeHeight = bb.ZMax + 3.0
@ -157,34 +161,35 @@ class ObjectDrilling:
obj.RetractHeight = 6.0
if item in baselist:
FreeCAD.Console.PrintWarning("Drillable location already in the list"+ "\n")
FreeCAD.Console.PrintWarning("Drillable location already in the list" + "\n")
else:
baselist.append (item)
baselist.append(item)
obj.Base = baselist
self.execute(obj)
class _ViewProviderDrill:
def __init__(self,obj): #mandatory
def __init__(self, obj):
obj.Proxy = self
def __getstate__(self): #mandatory
def __getstate__(self):
return None
def __setstate__(self,state): #mandatory
def __setstate__(self, state):
return None
def getIcon(self): #optional
def getIcon(self):
return ":/icons/Path-Drilling.svg"
def onChanged(self,obj,prop): #optional
def onChanged(self, obj, prop):
# this is executed when a property of the VIEW PROVIDER changes
pass
def updateData(self,obj,prop): #optional
def updateData(self, obj, prop):
# this is executed when a property of the APP OBJECT changes
pass
def setEdit(self,vobj,mode=0):
def setEdit(self, vobj, mode=0):
FreeCADGui.Control.closeDialog()
taskd = TaskPanel()
taskd.obj = vobj.Object
@ -192,28 +197,28 @@ class _ViewProviderDrill:
taskd.setupUi()
return True
def unsetEdit(self,vobj,mode): #optional
def unsetEdit(self, vobj, mode):
# this is executed when the user cancels or terminates edit mode
pass
class CommandPathDrilling:
def GetResources(self):
return {'Pixmap' : 'Path-Drilling',
'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_Drilling","Drilling"),
return {'Pixmap': 'Path-Drilling',
'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_Drilling", "Drilling"),
'Accel': "P, D",
'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_Drilling","Creates a Path Drilling object")}
'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_Drilling", "Creates a Path Drilling object")}
def IsActive(self):
return not FreeCAD.ActiveDocument is None
return FreeCAD.ActiveDocument is not None
def Activated(self):
# if everything is ok, execute and register the transaction in the undo/redo stack
FreeCAD.ActiveDocument.openTransaction(translate("Path_Drilling","Create Drilling"))
FreeCAD.ActiveDocument.openTransaction(translate("Path_Drilling", "Create Drilling"))
FreeCADGui.addModule("PathScripts.PathDrilling")
FreeCADGui.doCommand('obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython","Drilling")')
FreeCADGui.doCommand('obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", "Drilling")')
FreeCADGui.doCommand('PathScripts.PathDrilling.ObjectDrilling(obj)')
FreeCADGui.doCommand('obj.Active = True')
FreeCADGui.doCommand('PathScripts.PathDrilling._ViewProviderDrill(obj.ViewObject)')
@ -229,9 +234,9 @@ class CommandPathDrilling:
FreeCAD.ActiveDocument.recompute()
FreeCADGui.doCommand('obj.ViewObject.startEditing()')
class TaskPanel:
def __init__(self):
#self.form = FreeCADGui.PySideUic.loadUi(FreeCAD.getHomePath() + "Mod/Path/DrillingEdit.ui")
self.form = FreeCADGui.PySideUic.loadUi(":/panels/DrillingEdit.ui")
def accept(self):
@ -249,32 +254,31 @@ class TaskPanel:
def getFields(self):
if self.obj:
if hasattr(self.obj,"StartDepth"):
if hasattr(self.obj, "StartDepth"):
self.obj.StartDepth = self.form.startDepth.text()
if hasattr(self.obj,"FinalDepth"):
if hasattr(self.obj, "FinalDepth"):
self.obj.FinalDepth = self.form.finalDepth.text()
if hasattr(self.obj,"PeckDepth"):
if hasattr(self.obj, "PeckDepth"):
self.obj.PeckDepth = self.form.peckDepth.text()
if hasattr(self.obj,"SafeHeight"):
if hasattr(self.obj, "SafeHeight"):
self.obj.SafeHeight = self.form.safeHeight.text()
if hasattr(self.obj,"ClearanceHeight"):
if hasattr(self.obj, "ClearanceHeight"):
self.obj.ClearanceHeight = self.form.clearanceHeight.text()
if hasattr(self.obj,"RetractHeight"):
if hasattr(self.obj, "RetractHeight"):
self.obj.RetractHeight = self.form.retractHeight.text()
self.obj.Proxy.execute(self.obj)
def open(self):
self.s =SelObserver()
# install the function mode resident
self.s = SelObserver()
FreeCADGui.Selection.addObserver(self.s)
def addBase(self):
# check that the selection contains exactly what we want
# check that the selection contains exactly what we want
selection = FreeCADGui.Selection.getSelectionEx()
if not len(selection) >= 1:
FreeCAD.Console.PrintError(translate("PathProject","Please select at least one Drillable Location\n"))
FreeCAD.Console.PrintError(translate("PathProject", "Please select at least one Drillable Location\n"))
return
for s in selection:
if s.HasSubObjects:
@ -283,19 +287,18 @@ class TaskPanel:
else:
self.obj.Proxy.addDrillableLocation(self.obj, s.Object)
self.setupUi() #defaults may have changed. Reload.
self.setupUi() # defaults may have changed. Reload.
self.form.baseList.clear()
for i in self.obj.Base:
self.form.baseList.addItem(i[0].Name + "." + i[1])
def deleteBase(self):
dlist = self.form.baseList.selectedItems()
for d in dlist:
newlist = []
for i in self.obj.Base:
if not i[0].Name == d.text().partition(".")[0]:
newlist.append (i)
newlist.append(i)
self.obj.Base = newlist
self.form.baseList.takeItem(self.form.baseList.row(d))
# self.obj.Proxy.execute(self.obj)
@ -307,9 +310,9 @@ class TaskPanel:
for i in slist:
objstring = i.text().partition(".")
obj = FreeCAD.ActiveDocument.getObject(objstring[0])
# sub = o.Shape.getElement(objstring[2])
# sub = o.Shape.getElement(objstring[2])
if objstring[2] != "":
FreeCADGui.Selection.addSelection(obj,objstring[2])
FreeCADGui.Selection.addSelection(obj, objstring[2])
else:
FreeCADGui.Selection.addSelection(obj)
@ -324,7 +327,7 @@ class TaskPanel:
obj = FreeCAD.ActiveDocument.getObject(objstring[0])
item = (obj, str(objstring[2]))
newlist.append(item)
self.obj.Base=newlist
self.obj.Base = newlist
self.obj.Proxy.execute(self.obj)
FreeCAD.ActiveDocument.recompute()
@ -340,12 +343,11 @@ class TaskPanel:
self.form.clearanceHeight.setText(str(self.obj.ClearanceHeight.Value))
self.form.retractHeight.setText(str(self.obj.RetractHeight.Value))
for i in self.obj.Base:
self.form.baseList.addItem(i[0].Name + "." + i[1])
#Connect Signals and Slots
self.form.startDepth.editingFinished.connect(self.getFields) #This is newer syntax
# Connect Signals and Slots
self.form.startDepth.editingFinished.connect(self.getFields)
self.form.finalDepth.editingFinished.connect(self.getFields)
self.form.safeHeight.editingFinished.connect(self.getFields)
self.form.clearanceHeight.editingFinished.connect(self.getFields)
@ -356,6 +358,7 @@ class TaskPanel:
self.form.baseList.itemSelectionChanged.connect(self.itemActivated)
class SelObserver:
def __init__(self):
import PathScripts.PathSelection as PST
@ -365,13 +368,13 @@ class SelObserver:
import PathScripts.PathSelection as PST
PST.clear()
def addSelection(self,doc,obj,sub,pnt): # Selection object
FreeCADGui.doCommand('Gui.Selection.addSelection(FreeCAD.ActiveDocument.' + obj +')')
def addSelection(self, doc, obj, sub, pnt):
FreeCADGui.doCommand('Gui.Selection.addSelection(FreeCAD.ActiveDocument.' + obj + ')')
FreeCADGui.updateGui()
if FreeCAD.GuiUp:
# register the FreeCAD command
FreeCADGui.addCommand('Path_Drilling',CommandPathDrilling())
FreeCADGui.addCommand('Path_Drilling', CommandPathDrilling())
FreeCAD.Console.PrintLog("Loading PathDrilling... done\n")

View File

@ -1,30 +1,33 @@
# -*- coding: utf-8 -*-
#***************************************************************************
#* *
#* Copyright (c) 2014 Yorik van Havre <yorik@uncreated.net> *
#* *
#* 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) 2014 Yorik van Havre <yorik@uncreated.net> *
# * *
# * 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,Draft
import FreeCAD
import FreeCADGui
import Path
import Draft
from PySide import QtCore,QtGui
from PySide import QtCore, QtGui
from PathScripts import PathUtils
"""Path Engrave object and FreeCAD command"""
@ -32,6 +35,7 @@ from PathScripts import PathUtils
# Qt tanslation handling
try:
_encoding = QtGui.QApplication.UnicodeUTF8
def translate(context, text, disambig=None):
return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
@ -41,26 +45,25 @@ except AttributeError:
class ObjectPathEngrave:
def __init__(self,obj):
obj.addProperty("App::PropertyLinkSubList","Base","Path","The base geometry of this object")
obj.addProperty("App::PropertyBool","Active","Path",translate("Path","Make False, to prevent operation from generating code"))
obj.addProperty("App::PropertyString","Comment","Path",translate("Path","An optional comment for this profile"))
obj.addProperty("App::PropertyLinkSubList", "Base", "Path", "The base geometry of this object")
obj.addProperty("App::PropertyBool", "Active", "Path", translate("Path", "Make False, to prevent operation from generating code"))
obj.addProperty("App::PropertyString", "Comment", "Path", translate("Path", "An optional comment for this profile"))
obj.addProperty("App::PropertyEnumeration", "Algorithm", "Algorithm",translate("Path", "The library or Algorithm used to generate the path"))
obj.addProperty("App::PropertyEnumeration", "Algorithm", "Algorithm", translate("Path", "The library or Algorithm used to generate the path"))
obj.Algorithm = ['OCC Native']
#Tool Properties
obj.addProperty("App::PropertyIntegerConstraint","ToolNumber","Tool",translate("Path","The tool number in use"))
obj.ToolNumber = (0,0,1000,1)
obj.setEditorMode('ToolNumber',1) #make this read only
# Tool Properties
obj.addProperty("App::PropertyIntegerConstraint", "ToolNumber", "Tool", translate("Path", "The tool number in use"))
obj.ToolNumber = (0, 0, 1000, 1)
obj.setEditorMode('ToolNumber', 1) # make this read only
#Depth Properties
obj.addProperty("App::PropertyDistance", "ClearanceHeight", "Depth", translate("Path","The height needed to clear clamps and obstructions"))
obj.addProperty("App::PropertyDistance", "SafeHeight", "Depth", translate("Path","Rapid Safety Height between locations."))
obj.addProperty("App::PropertyDistance", "StartDepth", "Depth", translate("Path","Starting Depth of Tool- first cut depth in Z"))
obj.addProperty("App::PropertyDistance", "FinalDepth", "Depth", translate("Path","Final Depth of Tool- lowest value in Z"))
obj.addProperty("App::PropertyInteger","StartVertex","Path","The vertex index to start the path from")
# Depth Properties
obj.addProperty("App::PropertyDistance", "ClearanceHeight", "Depth", translate("Path", "The height needed to clear clamps and obstructions"))
obj.addProperty("App::PropertyDistance", "SafeHeight", "Depth", translate("Path", "Rapid Safety Height between locations."))
obj.addProperty("App::PropertyDistance", "StartDepth", "Depth", translate("Path", "Starting Depth of Tool- first cut depth in Z"))
obj.addProperty("App::PropertyDistance", "FinalDepth", "Depth", translate("Path", "Final Depth of Tool- lowest value in Z"))
obj.addProperty("App::PropertyInteger", "StartVertex", "Path", "The vertex index to start the path from")
if FreeCAD.GuiUp:
_ViewProviderEngrave(obj.ViewObject)
@ -70,25 +73,25 @@ class ObjectPathEngrave:
def __getstate__(self):
return None
def __setstate__(self,state):
def __setstate__(self, state):
return None
def execute(self,obj):
def execute(self, obj):
output = ""
toolLoad = PathUtils.getLastToolLoad(obj)
if toolLoad == None:
if toolLoad is None:
self.vertFeed = 100
self.horizFeed = 100
self.radius = 0.25
obj.ToolNumber= 0
obj.ToolNumber = 0
else:
self.vertFeed = toolLoad.VertFeed.Value
self.horizFeed = toolLoad.HorizFeed.Value
obj.ToolNumber= toolLoad.ToolNumber
obj.ToolNumber = toolLoad.ToolNumber
tool = PathUtils.getTool(obj, toolLoad.ToolNumber)
if tool == None:
if tool is None:
self.radius = 0.25
else:
self.radius = tool.Diameter/2
@ -102,14 +105,15 @@ class ObjectPathEngrave:
if obj.Algorithm == "OCC Native":
output += self.buildpathocc(obj, wires)
#print output
# print output
if output == "":
output +="G0"
output += "G0"
path = Path.Path(output)
obj.Path = path
def buildpathocc(self, obj, wires):
import Part,DraftGeomUtils
import Part
import DraftGeomUtils
output = "G90\nG21\nG40\n"
output += "G0 Z" + str(obj.ClearanceHeight.Value)
@ -119,7 +123,7 @@ class ObjectPathEngrave:
offset = wire
# reorder the wire
offset = DraftGeomUtils.rebaseWire(offset,obj.StartVertex)
offset = DraftGeomUtils.rebaseWire(offset, obj.StartVertex)
# we create the path from the offset shape
last = None
@ -127,11 +131,11 @@ class ObjectPathEngrave:
if not last:
# we set the first move to our first point
last = edge.Vertexes[0].Point
output += "G0" + " X" + str("%f" % last.x) + " Y" + str("%f" % last.y) #Rapid sto starting position
output += "G1" + " Z" + str("%f" % last.z) +"F " + str(self.vertFeed)+ "\n" #Vertical feed to depth
if isinstance(edge.Curve,Part.Circle):
output += "G0" + " X" + str("%f" % last.x) + " Y" + str("%f" % last.y) # Rapid sto starting position
output += "G1" + " Z" + str("%f" % last.z) + "F " + str(self.vertFeed) + "\n" # Vertical feed to depth
if isinstance(edge.Curve, Part.Circle):
point = edge.Vertexes[-1].Point
if point == last: # edges can come flipped
if point == last: # edges can come flipped
point = edge.Vertexes[0].Point
center = edge.Curve.Center
relcenter = center.sub(last)
@ -143,27 +147,27 @@ class ObjectPathEngrave:
output += "G3"
output += " X" + str("%f" % point.x) + " Y" + str("%f" % point.y) + " Z" + str("%f" % point.z)
output += " I" + str("%f" % relcenter.x) + " J" + str("%f" % relcenter.y) + " K" + str("%f" % relcenter.z)
output += " F " + str(self.horizFeed)
output += " F " + str(self.horizFeed)
output += "\n"
last = point
else:
point = edge.Vertexes[-1].Point
if point == last: # edges can come flipped
if point == last: # edges can come flipped
point = edge.Vertexes[0].Point
output += "G1 X" + str("%f" % point.x) + " Y" + str("%f" % point.y) + " Z" + str("%f" % point.z)
output += " F " + str(self.horizFeed)
output += "\n"
output += " F " + str(self.horizFeed)
output += "\n"
last = point
output += "G0 Z " + str(obj.SafeHeight.Value)
return output
def addShapeString(self, obj, ss):
baselist = obj.Base
if len(baselist) == 0: #When adding the first base object, guess at heights
if len(baselist) == 0: # When adding the first base object, guess at heights
try:
bb = ss.Shape.BoundBox #parent boundbox
bb = ss.Shape.BoundBox # parent boundbox
subobj = ss.Shape.getElement(sub)
fbb = subobj.BoundBox #feature boundbox
fbb = subobj.BoundBox # feature boundbox
obj.StartDepth = bb.ZMax
obj.ClearanceHeight = bb.ZMax + 5.0
obj.SafeHeight = bb.ZMax + 3.0
@ -179,24 +183,24 @@ class ObjectPathEngrave:
item = (ss, "")
if item in baselist:
FreeCAD.Console.PrintWarning("ShapeString already in the Engraving list"+ "\n")
FreeCAD.Console.PrintWarning("ShapeString already in the Engraving list" + "\n")
else:
baselist.append (item)
baselist.append(item)
obj.Base = baselist
self.execute(obj)
class _ViewProviderEngrave:
def __init__(self,vobj):
def __init__(self, vobj):
vobj.Proxy = self
def attach(self,vobj):
def attach(self, vobj):
self.Object = vobj.Object
return
def setEdit(self,vobj,mode=0):
def setEdit(self, vobj, mode=0):
FreeCADGui.Control.closeDialog()
taskd = TaskPanel()
taskd.obj = vobj.Object
@ -210,43 +214,42 @@ class _ViewProviderEngrave:
def __getstate__(self):
return None
def __setstate__(self,state):
def __setstate__(self, state):
return None
class CommandPathEngrave:
def GetResources(self):
return {'Pixmap' : 'Path-Engrave',
'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_Engrave","ShapeString Engrave"),
'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_Engrave","Creates an Engraving Path around a Draft ShapeString")}
return {'Pixmap': 'Path-Engrave',
'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_Engrave", "ShapeString Engrave"),
'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_Engrave", "Creates an Engraving Path around a Draft ShapeString")}
def IsActive(self):
return not FreeCAD.ActiveDocument is None
return FreeCAD.ActiveDocument is not None
def Activated(self):
# if everything is ok, execute and register the transaction in the undo/redo stack
FreeCAD.ActiveDocument.openTransaction("Create Engrave Path")
FreeCADGui.addModule("PathScripts.PathFaceProfile")
FreeCADGui.addModule("PathScripts.PathUtils")
FreeCADGui.doCommand('obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython","PathEngrave")')
FreeCADGui.doCommand('obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", "PathEngrave")')
FreeCADGui.doCommand('PathScripts.PathEngrave.ObjectPathEngrave(obj)')
FreeCADGui.doCommand('obj.ClearanceHeight = 10')
FreeCADGui.doCommand('obj.StartDepth= 0')
FreeCADGui.doCommand('obj.FinalDepth= -0.1' )
FreeCADGui.doCommand('obj.SafeHeight= 5.0' )
FreeCADGui.doCommand('obj.FinalDepth= -0.1')
FreeCADGui.doCommand('obj.SafeHeight= 5.0')
#FreeCADGui.doCommand('obj.ViewObject.Proxy = 0')
FreeCADGui.doCommand('PathScripts.PathUtils.addToProject(obj)')
FreeCAD.ActiveDocument.commitTransaction()
FreeCAD.ActiveDocument.recompute()
FreeCADGui.doCommand('obj.ViewObject.startEditing()')
class TaskPanel:
def __init__(self):
#self.form = FreeCADGui.PySideUic.loadUi(FreeCAD.getHomePath() + "Mod/Path/EngraveEdit.ui")
self.form = FreeCADGui.PySideUic.loadUi(":/panels/EngraveEdit.ui")
def accept(self):
@ -264,33 +267,32 @@ class TaskPanel:
def getFields(self):
if self.obj:
if hasattr(self.obj,"StartDepth"):
if hasattr(self.obj, "StartDepth"):
self.obj.StartDepth = self.form.startDepth.text()
if hasattr(self.obj,"FinalDepth"):
if hasattr(self.obj, "FinalDepth"):
self.obj.FinalDepth = self.form.finalDepth.text()
if hasattr(self.obj,"SafeHeight"):
if hasattr(self.obj, "SafeHeight"):
self.obj.SafeHeight = self.form.safeHeight.text()
if hasattr(self.obj,"ClearanceHeight"):
if hasattr(self.obj, "ClearanceHeight"):
self.obj.ClearanceHeight = self.form.clearanceHeight.text()
self.obj.Proxy.execute(self.obj)
def open(self):
self.s =SelObserver()
self.s = SelObserver()
# install the function mode resident
FreeCADGui.Selection.addObserver(self.s)
def addBase(self):
# check that the selection contains exactly what we want
# check that the selection contains exactly what we want
selection = FreeCADGui.Selection.getSelectionEx()
if not len(selection) >= 1:
FreeCAD.Console.PrintError(translate("Path_Engrave","Please select at least one ShapeString\n"))
FreeCAD.Console.PrintError(translate("Path_Engrave", "Please select at least one ShapeString\n"))
return
for s in selection:
if not Draft.getType(s.Object) == "ShapeString":
FreeCAD.Console.PrintError(translate("Path_Engrave","Please select at least one ShapeString\n"))
FreeCAD.Console.PrintError(translate("Path_Engrave", "Please select at least one ShapeString\n"))
return
self.obj.Proxy.addShapeString(self.obj, s.Object)
@ -304,11 +306,9 @@ class TaskPanel:
newlist = []
for i in self.obj.Base:
if not i[0].Name == d.text():
newlist.append (i)
newlist.append(i)
self.obj.Base = newlist
self.form.baseList.takeItem(self.form.baseList.row(d))
# self.obj.Proxy.execute(self.obj)
# FreeCAD.ActiveDocument.recompute()
def itemActivated(self):
FreeCADGui.Selection.clearSelection()
@ -321,10 +321,10 @@ class TaskPanel:
def reorderBase(self):
newlist = []
for i in range(self.form.baseList.count()):
s = self.form.baseList.item(i).text()
s = self.form.baseList.item(i).text()
obj = FreeCAD.ActiveDocument.getObject(s)
newlist.append(obj)
self.obj.Base=newlist
self.obj.Base = newlist
self.obj.Proxy.execute(self.obj)
FreeCAD.ActiveDocument.recompute()
@ -340,7 +340,7 @@ class TaskPanel:
for i in self.obj.Base:
self.form.baseList.addItem(i[0].Name)
#Connect Signals and Slots
# Connect Signals and Slots
self.form.startDepth.editingFinished.connect(self.getFields)
self.form.finalDepth.editingFinished.connect(self.getFields)
self.form.safeHeight.editingFinished.connect(self.getFields)
@ -362,10 +362,9 @@ class SelObserver:
import PathScripts.PathSelection as PST
PST.clear()
def addSelection(self,doc,obj,sub,pnt): # Selection object
FreeCADGui.doCommand('Gui.Selection.addSelection(FreeCAD.ActiveDocument.' + obj +')')
def addSelection(self, doc, obj, sub, pnt):
FreeCADGui.doCommand('Gui.Selection.addSelection(FreeCAD.ActiveDocument.' + obj + ')')
FreeCADGui.updateGui()
if FreeCAD.GuiUp:
# register the FreeCAD command
FreeCADGui.addCommand('Path_Engrave',CommandPathEngrave())
FreeCADGui.addCommand('Path_Engrave', CommandPathEngrave())

View File

@ -1,142 +1,144 @@
# -*- coding: utf-8 -*-
#***************************************************************************
#* *
#* Copyright (c) 2015 Dan Falck <ddfalck@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 *
#* *
#***************************************************************************
# ***************************************************************************
# * *
# * Copyright (c) 2015 Dan Falck <ddfalck@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 *
# * *
# ***************************************************************************
''' Used for CNC machine to load cutting Tool ie M6T3'''
import FreeCAD,FreeCADGui,Path,PathGui
import PathScripts, PathUtils
from PathScripts import PathProject
from PySide import QtCore,QtGui
import FreeCAD
import FreeCADGui
import Path
# import PathGui
import PathScripts
import PathUtils
# from PathScripts import PathProject
from PySide import QtCore, QtGui
# 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 LoadTool:
def __init__(self,obj):
obj.addProperty("App::PropertyIntegerConstraint", "ToolNumber","Tool","The active tool")
obj.ToolNumber = (0,0,10000,1)
obj.addProperty("App::PropertyFloat", "SpindleSpeed", "Tool","The speed of the cutting spindle in RPM")
obj.addProperty("App::PropertyEnumeration", "SpindleDir", "Tool","Direction of spindle rotation")
obj.SpindleDir = ['Forward','Reverse']
obj.addProperty("App::PropertySpeed", "VertFeed", "Feed",translate("Path","Feed rate for vertical moves in Z"))
obj.addProperty("App::PropertySpeed", "HorizFeed", "Feed",translate("Path","Feed rate for horizontal moves"))
def __init__(self, obj):
obj.addProperty("App::PropertyIntegerConstraint", "ToolNumber", "Tool", translate("Tool Number", "The active tool"))
obj.ToolNumber = (0, 0, 10000, 1)
obj.addProperty("App::PropertyFloat", "SpindleSpeed", "Tool", translate("Spindle Speed", "The speed of the cutting spindle in RPM"))
obj.addProperty("App::PropertyEnumeration", "SpindleDir", "Tool", translate("Spindle Dir", "Direction of spindle rotation"))
obj.SpindleDir = ['Forward', 'Reverse']
obj.addProperty("App::PropertySpeed", "VertFeed", "Feed", translate("Path", "Feed rate for vertical moves in Z"))
obj.addProperty("App::PropertySpeed", "HorizFeed", "Feed", translate("Path", "Feed rate for horizontal moves"))
obj.Proxy = self
mode = 2
obj.setEditorMode('Placement',mode)
obj.setEditorMode('Placement', mode)
def execute(self,obj):
def execute(self, obj):
commands = ""
commands = 'M6T'+str(obj.ToolNumber)+'\n'
if obj.SpindleDir =='Forward':
commands +='M3S'+str(obj.SpindleSpeed)+'\n'
if obj.SpindleDir == 'Forward':
commands += 'M3S' + str(obj.SpindleSpeed) + '\n'
else:
commands +='M4S'+str(obj.SpindleSpeed)+'\n'
commands += 'M4S' + str(obj.SpindleSpeed) + '\n'
obj.Path = Path.Path(commands)
obj.Label = "Tool"+str(obj.ToolNumber)
def onChanged(self,obj,prop):
def onChanged(self, obj, prop):
mode = 2
obj.setEditorMode('Placement',mode)
#if prop == "ToolNumber":
proj = PathUtils.findProj()
obj.setEditorMode('Placement', mode)
# if prop == "ToolNumber":
proj = PathUtils.findProj()
for g in proj.Group:
if not(isinstance(g.Proxy, PathScripts.PathLoadTool.LoadTool)):
g.touch()
g.touch()
class _ViewProviderLoadTool:
def __init__(self,vobj): #mandatory
def __init__(self, vobj):
vobj.Proxy = self
mode = 2
vobj.setEditorMode('LineWidth',mode)
vobj.setEditorMode('MarkerColor',mode)
vobj.setEditorMode('NormalColor',mode)
vobj.setEditorMode('ShowFirstRapid',mode)
vobj.setEditorMode('DisplayMode',mode)
vobj.setEditorMode('BoundingBox',mode)
vobj.setEditorMode('Selectable',mode)
vobj.setEditorMode('ShapeColor',mode)
vobj.setEditorMode('Transparency',mode)
vobj.setEditorMode('Visibility',mode)
vobj.setEditorMode('LineWidth', mode)
vobj.setEditorMode('MarkerColor', mode)
vobj.setEditorMode('NormalColor', mode)
vobj.setEditorMode('ShowFirstRapid', mode)
vobj.setEditorMode('DisplayMode', mode)
vobj.setEditorMode('BoundingBox', mode)
vobj.setEditorMode('Selectable', mode)
vobj.setEditorMode('ShapeColor', mode)
vobj.setEditorMode('Transparency', mode)
vobj.setEditorMode('Visibility', mode)
def __getstate__(self): #mandatory
def __getstate__(self):
return None
def __setstate__(self,state): #mandatory
def __setstate__(self, state):
return None
def getIcon(self): #optional
def getIcon(self):
return ":/icons/Path-LoadTool.svg"
def onChanged(self,vobj,prop): #optional
def onChanged(self, vobj, prop):
mode = 2
vobj.setEditorMode('LineWidth',mode)
vobj.setEditorMode('MarkerColor',mode)
vobj.setEditorMode('NormalColor',mode)
vobj.setEditorMode('ShowFirstRapid',mode)
vobj.setEditorMode('DisplayMode',mode)
vobj.setEditorMode('BoundingBox',mode)
vobj.setEditorMode('Selectable',mode)
#vobj.setEditorMode('ShapeColor',mode)
#vobj.setEditorMode('Transparency',mode)
#vobj.setEditorMode('Visibility',mode)
vobj.setEditorMode('LineWidth', mode)
vobj.setEditorMode('MarkerColor', mode)
vobj.setEditorMode('NormalColor', mode)
vobj.setEditorMode('ShowFirstRapid', mode)
vobj.setEditorMode('DisplayMode', mode)
vobj.setEditorMode('BoundingBox', mode)
vobj.setEditorMode('Selectable', mode)
def updateData(self,vobj,prop): #optional
def updateData(self, vobj, prop):
# this is executed when a property of the APP OBJECT changes
pass
def setEdit(self,vobj,mode): #optional
def setEdit(self, vobj, mode):
# this is executed when the object is double-clicked in the tree
pass
def unsetEdit(self,vobj,mode): #optional
def unsetEdit(self, vobj, mode):
# this is executed when the user cancels or terminates edit mode
pass
class CommandPathLoadTool:
def GetResources(self):
return {'Pixmap' : 'Path-LoadTool',
'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_LoadTool","Tool Number to Load"),
return {'Pixmap': 'Path-LoadTool',
'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_LoadTool", "Tool Number to Load"),
'Accel': "P, T",
'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_LoadTool","Tool Number to Load")}
'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_LoadTool", "Tool Number to Load")}
def IsActive(self):
return not FreeCAD.ActiveDocument is None
return FreeCAD.ActiveDocument is not None
def Activated(self):
FreeCAD.ActiveDocument.openTransaction(translate("Current Tool","Tool Number to Load"))
FreeCAD.ActiveDocument.openTransaction(translate("Current Tool", "Tool Number to Load"))
snippet = '''
import Path, PathScripts
from PathScripts import PathUtils, PathLoadTool
@ -153,23 +155,20 @@ PathUtils.addToProject(obj)
@staticmethod
def Create():
#FreeCADGui.addModule("PathScripts.PathLoadTool")
import Path, PathScripts, PathUtils
# FreeCADGui.addModule("PathScripts.PathLoadTool")
# import Path
import PathScripts
import PathUtils
obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython","Tool")
obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", "Tool")
PathScripts.PathLoadTool.LoadTool(obj)
PathScripts.PathLoadTool._ViewProviderLoadTool(obj.ViewObject)
PathUtils.addToProject(obj)
if FreeCAD.GuiUp:
if FreeCAD.GuiUp:
# register the FreeCAD command
FreeCADGui.addCommand('Path_LoadTool', CommandPathLoadTool())
FreeCAD.Console.PrintLog("Loading PathLoadTool... done\n")

File diff suppressed because it is too large Load Diff

View File

@ -142,7 +142,7 @@ class ObjectProfile:
item = (ss, sub)
if item in baselist:
FreeCAD.Console.PrintWarning("this object already in the list" + "\n")
FreeCAD.Console.PrintWarning(translate("Path", "this object already in the list" + "\n"))
else:
baselist.append(item)
obj.Base = baselist
@ -247,6 +247,7 @@ print "y - " + str(point.y)
self.horizFeed = 100
self.radius = 0.25
obj.ToolNumber = 0
FreeCAD.Console.PrintWarning(translate("Path", "No tool found. Using default values for now." + "\n"))
else:
self.vertFeed = toolLoad.VertFeed.Value
self.horizFeed = toolLoad.HorizFeed.Value
@ -444,6 +445,9 @@ class TaskPanel:
self.form = FreeCADGui.PySideUic.loadUi(":/panels/ProfileEdit.ui")
self.updating = False
def __del__(self):
FreeCADGui.Selection.removeObserver(self.s)
def accept(self):
self.getFields()
@ -725,7 +729,7 @@ class SelObserver:
PST.clear()
def addSelection(self, doc, obj, sub, pnt):
FreeCADGui.doCommand('Gui.Selection.addSelection(FreeCAD.ActiveDocument.' + obj + ')')
#FreeCADGui.doCommand('Gui.Selection.addSelection(FreeCAD.ActiveDocument.' + obj + ')')
FreeCADGui.updateGui()

View File

@ -1,28 +1,29 @@
# -*- coding: utf-8 -*-
#***************************************************************************
#* *
#* Copyright (c) 2016 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 *
#* *
#***************************************************************************
# ***************************************************************************
# * *
# * Copyright (c) 2016 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,Path
import FreeCAD
import Path
from PathScripts import PathUtils
import urllib2
import json
@ -31,12 +32,11 @@ if FreeCAD.GuiUp:
import FreeCADGui
from PySide import QtCore, QtGui
from DraftTools import translate
from pivy import coin
else:
def translate(ctxt,txt):
def translate(ctxt, txt):
return txt
__title__="Path Remote Operation"
__title__ = "Path Remote Operation"
__author__ = "sliptonic (Brad Collette)"
__url__ = "http://www.freecadweb.org"
@ -45,6 +45,7 @@ __url__ = "http://www.freecadweb.org"
# Qt tanslation handling
try:
_encoding = QtGui.QApplication.UnicodeUTF8
def translate(context, text, disambig=None):
return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
@ -54,39 +55,39 @@ except AttributeError:
class ObjectRemote:
def __init__(self,obj):
def __init__(self, obj):
obj.addProperty("App::PropertyLinkSubList","Base","Path",translate("Parent Object(s)","The base geometry of this toolpath"))
obj.addProperty("App::PropertyBool","Active","Path",translate("Active","Make False, to prevent operation from generating code"))
obj.addProperty("App::PropertyString","Comment","Path",translate("PathProject","An optional comment for this profile"))
obj.addProperty("App::PropertyLinkSubList", "Base", "Path", translate("Parent Object(s)", "The base geometry of this toolpath"))
obj.addProperty("App::PropertyBool", "Active", "Path", translate("Active", "Make False, to prevent operation from generating code"))
obj.addProperty("App::PropertyString", "Comment", "Path", translate("PathProject", "An optional comment for this profile"))
obj.addProperty("App::PropertyString","URL", "API", translate("RemotePath", "The Base URL of the remote path service"))
obj.addProperty("App::PropertyStringList", "proplist","Path",translate("Path","list of remote properties"))
obj.setEditorMode('proplist',2) #make this hidden
obj.addProperty("App::PropertyString", "URL", "API", translate("RemotePath", "The Base URL of the remote path service"))
obj.addProperty("App::PropertyStringList", "proplist", "Path", translate("Path", "list of remote properties"))
obj.setEditorMode('proplist', 2) # make this hidden
#Tool Properties
obj.addProperty("App::PropertyIntegerConstraint","ToolNumber","Tool",translate("PathProfile","The tool number in use"))
# Tool Properties
obj.addProperty("App::PropertyIntegerConstraint", "ToolNumber", "Tool", translate("PathProfile", "The tool number in use"))
obj.ToolNumber = (0, 0, 1000, 0)
obj.setEditorMode('ToolNumber',1) #make this read only
obj.setEditorMode('ToolNumber', 1) # make this read only
#Depth Properties
obj.addProperty("App::PropertyFloat", "ClearanceHeight", "Depth", translate("PathProject","The height needed to clear clamps and obstructions"))
obj.addProperty("App::PropertyFloat", "SafeHeight", "Depth", translate("PathProject","Rapid Safety Height between locations."))
obj.addProperty("App::PropertyFloatConstraint", "StepDown", "Step", translate("PathProject","Incremental Step Down of Tool"))
# Depth Properties
obj.addProperty("App::PropertyFloat", "ClearanceHeight", "Depth", translate("PathProject", "The height needed to clear clamps and obstructions"))
obj.addProperty("App::PropertyFloat", "SafeHeight", "Depth", translate("PathProject", "Rapid Safety Height between locations."))
obj.addProperty("App::PropertyFloatConstraint", "StepDown", "Step", translate("PathProject", "Incremental Step Down of Tool"))
obj.StepDown = (0.0, 0.01, 100.0, 0.5)
obj.addProperty("App::PropertyFloat", "StartDepth", "Depth", translate("PathProject","Starting Depth of Tool- first cut depth in Z"))
obj.addProperty("App::PropertyFloat", "FinalDepth", "Depth", translate("PathProject","Final Depth of Tool- lowest value in Z"))
obj.addProperty("App::PropertyFloat", "FinishDepth", "Depth", translate("PathProject","Maximum material removed on final pass."))
obj.addProperty("App::PropertyFloat", "StartDepth", "Depth", translate("PathProject", "Starting Depth of Tool- first cut depth in Z"))
obj.addProperty("App::PropertyFloat", "FinalDepth", "Depth", translate("PathProject", "Final Depth of Tool- lowest value in Z"))
obj.addProperty("App::PropertyFloat", "FinishDepth", "Depth", translate("PathProject", "Maximum material removed on final pass."))
obj.Proxy = self
def addbaseobject(self, obj, ss, sub=""):
baselist = obj.Base
if len(baselist) == 0: #When adding the first base object, guess at heights
if len(baselist) == 0: # When adding the first base object, guess at heights
try:
bb = ss.Shape.BoundBox #parent boundbox
bb = ss.Shape.BoundBox # parent boundbox
subobj = ss.Shape.getElement(sub)
fbb = subobj.BoundBox #feature boundbox
fbb = subobj.BoundBox # feature boundbox
obj.StartDepth = bb.ZMax
obj.ClearanceHeight = bb.ZMax + 5.0
obj.SafeHeight = bb.ZMax + 3.0
@ -102,16 +103,16 @@ class ObjectRemote:
item = (ss, sub)
if item in baselist:
FreeCAD.Console.PrintWarning("this object already in the list"+ "\n")
FreeCAD.Console.PrintWarning("this object already in the list" + "\n")
else:
baselist.append (item)
baselist.append(item)
obj.Base = baselist
self.execute(obj)
def __getstate__(self):
return None
def __setstate__(self,state):
def __setstate__(self, state):
return None
def onChanged(self, obj, prop):
@ -143,54 +144,58 @@ class ObjectRemote:
pl = obj.proplist
pl = []
for prop in properties:
obj.addProperty(prop['type'], prop['propertyname'], "Remote", prop['description'])
obj.addProperty(
prop['type'],
prop['propertyname'],
"Remote",
prop['description'])
pl.append(prop['propertyname'])
print "adding: " + str(prop)
obj.proplist = pl
def execute(self,obj):
def execute(self, obj):
output = ""
toolLoad = PathUtils.getLastToolLoad(obj)
if toolLoad == None:
if toolLoad is None:
self.vertFeed = 100
self.horizFeed = 100
self.radius = 0.25
obj.ToolNumber= 0
obj.ToolNumber = 0
else:
self.vertFeed = toolLoad.VertFeed.Value
self.horizFeed = toolLoad.HorizFeed.Value
tool = PathUtils.getTool(obj, toolLoad.ToolNumber)
self.radius = tool.Diameter/2
obj.ToolNumber= toolLoad.ToolNumber
obj.ToolNumber = toolLoad.ToolNumber
output += "(remote gcode goes here)"
path = Path.Path(output)
obj.Path = path
class ViewProviderRemote:
def __init__(self,obj): #mandatory
# obj.addProperty("App::PropertyFloat","SomePropertyName","PropertyGroup","Description of this property")
def __init__(self, obj):
obj.Proxy = self
def __getstate__(self): #mandatory
def __getstate__(self):
return None
def __setstate__(self,state): #mandatory
def __setstate__(self, state):
return None
def getIcon(self): #optional
def getIcon(self):
return ":/icons/Path-Remote.svg"
def onChanged(self,obj,prop): #optional
def onChanged(self, obj, prop):
# this is executed when a property of the VIEW PROVIDER changes
pass
def updateData(self,obj,prop): #optional
def updateData(self, obj, prop): # optional
# this is executed when a property of the APP OBJECT changes
pass
def setEdit(self,vobj,mode=0):
def setEdit(self, vobj, mode=0):
FreeCADGui.Control.closeDialog()
taskd = TaskPanel()
taskd.obj = vobj.Object
@ -198,37 +203,37 @@ class ViewProviderRemote:
taskd.setupUi()
return True
def unsetEdit(self,vobj,mode): #optional
def unsetEdit(self, vobj, mode):
# this is executed when the user cancels or terminates edit mode
pass
class _RefreshRemotePath:
def GetResources(self):
return {'Pixmap' : 'Path-Refresh',
'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_Remote","Refresh Remote Path Data"),
'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_Remote","Refresh Remote Path Data")}
return {'Pixmap': 'Path-Refresh',
'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_Remote", "Refresh Remote Path Data"),
'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_Remote", "Refresh Remote Path Data")}
def IsActive(self):
return not FreeCAD.ActiveDocument is None
return FreeCAD.ActiveDocument is not None
def refresh(self):
obj=FreeCADGui.Selection.getSelection()[0]
obj = FreeCADGui.Selection.getSelection()[0]
values = {}
for i in obj.PropertiesList:
if obj.getGroupOfProperty(i) in ["Remote"]:
values.update({i : obj.getPropertyByName(i)})
values.update({i: obj.getPropertyByName(i)})
if obj.getGroupOfProperty(i) in ["Depth"]:
print str(i)
values.update({i : obj.getPropertyByName(i)})
values.update({i: obj.getPropertyByName(i)})
if obj.getGroupOfProperty(i) in ["Step"]:
values.update({i : obj.getPropertyByName(i)})
values.update({i: obj.getPropertyByName(i)})
if obj.getGroupOfProperty(i) in ["Tool"]:
tool = PathUtils.getTool(obj,obj.ToolNumber)
tool = PathUtils.getTool(obj, obj.ToolNumber)
if tool:
tradius = tool.Diameter/2
tlength = tool.LengthOffset
@ -238,10 +243,9 @@ class _RefreshRemotePath:
tlength = 1
ttype = "undefined"
values.update({"tool_diameter" : tradius})
values.update({"tool_length" : tlength})
values.update({"tool_type" : ttype})
values.update({"tool_diameter": tradius})
values.update({"tool_length": tlength})
values.update({"tool_type": ttype})
payload = json.dumps(values)
@ -256,7 +260,6 @@ class _RefreshRemotePath:
print "service not defined or not responding"
return
path = data['path']
output = ""
for command in path:
@ -264,33 +267,31 @@ class _RefreshRemotePath:
path = Path.Path(output)
obj.Path = path
def Activated(self):
self.refresh()
class CommandPathRemote:
def GetResources(self):
return {'Pixmap' : 'Path-Remote',
'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_Remote","Remote"),
return {'Pixmap': 'Path-Remote',
'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_Remote", "Remote"),
'Accel': "P, R",
'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_Remote","Request a Path from a remote cloud service")}
'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_Remote", "Request a Path from a remote cloud service")}
def IsActive(self):
return not FreeCAD.ActiveDocument is None
return FreeCAD.ActiveDocument is not None
def Activated(self):
ztop = 10.0
zbottom = 0.0
FreeCAD.ActiveDocument.openTransaction(translate("Path_Remote","Create remote path operation"))
FreeCAD.ActiveDocument.openTransaction(translate("Path_Remote", "Create remote path operation"))
FreeCADGui.addModule("PathScripts.PathRemote")
FreeCADGui.doCommand('obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython","Remote")')
FreeCADGui.doCommand('obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", "Remote")')
FreeCADGui.doCommand('PathScripts.PathRemote.ObjectRemote(obj)')
FreeCADGui.doCommand('obj.Active = True')
FreeCADGui.doCommand('PathScripts.PathRemote.ViewProviderRemote(obj.ViewObject)')
#FreeCADGui.doCommand('obj.Base = (FreeCAD.ActiveDocument.'+selection[0].ObjectName+',[])')
FreeCADGui.doCommand('from PathScripts import PathUtils')
FreeCADGui.doCommand('obj.ClearanceHeight = ' + str(ztop + 2))
FreeCADGui.doCommand('obj.StartDepth = ' + str(ztop))
@ -304,9 +305,9 @@ class CommandPathRemote:
FreeCAD.ActiveDocument.recompute()
FreeCADGui.doCommand('obj.ViewObject.startEditing()')
class TaskPanel:
def __init__(self):
#self.form = FreeCADGui.PySideUic.loadUi(FreeCAD.getHomePath() + "Mod/Path/RemoteEdit.ui")
self.form = FreeCADGui.PySideUic.loadUi(":/panels/RemoteEdit.ui")
def accept(self):
@ -327,41 +328,31 @@ class TaskPanel:
self.obj.URL = self.form.remoteURL.text()
print "getRemote:320"
#self.form.label_a = QtGui.QLabel(self.form.remoteProperties)
#self.form.label_a.setObjectName("label_a")
#self.form.formLayoutREMOTE.setWidget(0, QtGui.QFormLayout.LabelRole, self.form.label_a)
#self.form.sampleLE = QtGui.QLineEdit(self.form.remoteProperties)
#self.form.sampleLE.setObjectName("sampleLE")
#self.form.formLayoutREMOTE.setWidget(0, QtGui.QFormLayout.FieldRole, self.form.sampleLE)
##self.formLayout_2.setWidget(1, QtGui.QFormLayout.SpanningRole, self.remoteProperties)
def getFields(self):
if self.obj:
if hasattr(self.obj,"StartDepth"):
if hasattr(self.obj, "StartDepth"):
self.obj.StartDepth = float(self.form.startDepth.text())
if hasattr(self.obj,"FinalDepth"):
if hasattr(self.obj, "FinalDepth"):
self.obj.FinalDepth = float(self.form.finalDepth.text())
if hasattr(self.obj,"SafeHeight"):
if hasattr(self.obj, "SafeHeight"):
self.obj.SafeHeight = float(self.form.safeHeight.text())
if hasattr(self.obj,"ClearanceHeight"):
if hasattr(self.obj, "ClearanceHeight"):
self.obj.ClearanceHeight = float(self.form.clearanceHeight.text())
if hasattr(self.obj,"StepDown"):
if hasattr(self.obj, "StepDown"):
self.obj.StepDown = float(self.form.stepDown.value())
self.obj.Proxy.execute(self.obj)
def open(self):
self.s =SelObserver()
# install the function mode resident
self.s = SelObserver()
FreeCADGui.Selection.addObserver(self.s)
def addBase(self):
# check that the selection contains exactly what we want
# check that the selection contains exactly what we want
selection = FreeCADGui.Selection.getSelectionEx()
if not len(selection) >= 1:
FreeCAD.Console.PrintError(translate("PathProject","Please select at least one suitable object\n"))
FreeCAD.Console.PrintError(translate("PathProject", "Please select at least one suitable object\n"))
return
for s in selection:
if s.HasSubObjects:
@ -370,7 +361,7 @@ class TaskPanel:
else:
self.obj.Proxy.addbaseobject(self.obj, s.Object)
self.setupUi() #defaults may have changed. Reload.
self.setupUi() # defaults may have changed. Reload.
self.form.baseList.clear()
for i in self.obj.Base:
self.form.baseList.addItem(i[0].Name + "." + i[1])
@ -381,7 +372,7 @@ class TaskPanel:
newlist = []
for i in self.obj.Base:
if not i[0].Name == d.text():
newlist.append (i)
newlist.append(i)
self.obj.Base = newlist
self.form.baseList.takeItem(self.form.baseList.row(d))
self.obj.Proxy.execute(self.obj)
@ -398,10 +389,10 @@ class TaskPanel:
def reorderBase(self):
newlist = []
for i in range(self.form.baseList.count()):
s = self.form.baseList.item(i).text()
s = self.form.baseList.item(i).text()
obj = FreeCAD.ActiveDocument.getObject(s)
newlist.append(obj)
self.obj.Base=newlist
self.obj.Base = newlist
self.obj.Proxy.execute(self.obj)
FreeCAD.ActiveDocument.recompute()
@ -411,16 +402,15 @@ class TaskPanel:
def changeURL(self):
from urlparse import urlparse
t = self.form.remoteURL.text()
if t == '' and self.obj.URL != '': #if the url was deleted, cleanup.
if t == '' and self.obj.URL != '': # if the url was deleted, cleanup.
self.obj.URL = ''
if urlparse(t).scheme != '' and t != self.obj.URL: #validate new url.
if urlparse(t).scheme != '' and t != self.obj.URL: # validate new url.
self.obj.URL = t
#next make sure the property fields reflect the current attached service
# next make sure the property fields reflect the current attached service
for p in self.obj.proplist:
print p
def setupUi(self):
self.form.startDepth.setText(str(self.obj.StartDepth))
self.form.finalDepth.setText(str(self.obj.FinalDepth))
@ -431,7 +421,7 @@ class TaskPanel:
for i in self.obj.Base:
self.form.baseList.addItem(i[0].Name)
#Connect Signals and Slots
# Connect Signals and Slots
self.form.startDepth.editingFinished.connect(self.getFields)
self.form.finalDepth.editingFinished.connect(self.getFields)
self.form.safeHeight.editingFinished.connect(self.getFields)
@ -443,24 +433,23 @@ class TaskPanel:
self.form.reorderBase.clicked.connect(self.reorderBase)
self.form.remoteURL.editingFinished.connect(self.changeURL)
#self.form.remoteURL.returnPressed.connect(self.testOne)
class SelObserver:
def __init__(self):
import PathScripts.PathSelection as PST
#PST.surfaceselect()
def __del__(self):
import PathScripts.PathSelection as PST
PST.clear()
def addSelection(self,doc,obj,sub,pnt): # Selection object
FreeCADGui.doCommand('Gui.Selection.addSelection(FreeCAD.ActiveDocument.' + obj +')')
def addSelection(self, doc, obj, sub, pnt):
FreeCADGui.doCommand('Gui.Selection.addSelection(FreeCAD.ActiveDocument.' + obj + ')')
FreeCADGui.updateGui()
if FreeCAD.GuiUp:
# register the FreeCAD command
FreeCADGui.addCommand('Path_Remote',CommandPathRemote())
FreeCADGui.addCommand('Refresh_Path',_RefreshRemotePath())
FreeCADGui.addCommand('Path_Remote', CommandPathRemote())
FreeCADGui.addCommand('Refresh_Path', _RefreshRemotePath())
FreeCAD.Console.PrintLog("Loading PathRemote... done\n")

View File

@ -125,6 +125,11 @@ class PROFILEGate:
elif obj.ShapeType == 'Compound':
profileable = True
if sub[0:4] == 'Face':
profileable = True
if sub[0:4] == 'Edge':
profileable = True
elif obj.ShapeType == 'Face':
profileable = True

View File

@ -1,65 +1,61 @@
# -*- coding: utf-8 -*-
#***************************************************************************
#* *
#* Copyright (c) 2014 Dan Falck <ddfalck@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 *
#* *
#***************************************************************************
# ***************************************************************************
# * *
# * Copyright (c) 2014 Dan Falck <ddfalck@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 *
# * *
# ***************************************************************************
'''PathUtils -common functions used in PathScripts for filterig, sorting, and generating gcode toolpath data '''
import FreeCAD
import Part
from FreeCAD import Vector
import FreeCADGui
import math
import DraftGeomUtils
from DraftGeomUtils import geomType
import DraftVecUtils
import PathScripts
from PathScripts import PathProject
def cleanedges(splines,precision):
def cleanedges(splines, precision):
'''cleanedges([splines],precision). Convert BSpline curves, Beziers, to arcs that can be used for cnc paths.
Returns Lines as is. Filters Circle and Arcs for over 180 degrees. Discretizes Ellipses. Ignores other geometry. '''
edges = []
for spline in splines:
if geomType(spline)=="BSplineCurve":
if geomType(spline) == "BSplineCurve":
arcs = spline.Curve.toBiArcs(precision)
for i in arcs:
edges.append(Part.Edge(i))
elif geomType(spline)=="BezierCurve":
newspline=spline.Curve.toBSpline()
elif geomType(spline) == "BezierCurve":
newspline = spline.Curve.toBSpline()
arcs = newspline.toBiArcs(precision)
for i in arcs:
edges.append(Part.Edge(i))
elif geomType(spline)=="Ellipse":
edges = curvetowire(spline, 1.0) #fixme hardcoded value
elif geomType(spline) == "Ellipse":
edges = curvetowire(spline, 1.0) # fixme hardcoded value
elif geomType(spline)=="Circle":
arcs=filterArcs(spline)
elif geomType(spline) == "Circle":
arcs = filterArcs(spline)
for i in arcs:
edges.append(Part.Edge(i))
elif geomType(spline)=="Line":
elif geomType(spline) == "Line":
edges.append(spline)
else:
@ -67,71 +63,80 @@ def cleanedges(splines,precision):
return edges
def curvetowire(obj,steps):
def curvetowire(obj, steps):
'''adapted from DraftGeomUtils, because the discretize function changed a bit '''
points = obj.copy().discretize(Distance = eval('steps'))
points = obj.copy().discretize(Distance=eval('steps'))
p0 = points[0]
edgelist = []
for p in points[1:]:
edge = Part.makeLine((p0.x,p0.y,p0.z),(p.x,p.y,p.z))
edge = Part.makeLine((p0.x, p0.y, p0.z), (p.x, p.y, p.z))
edgelist.append(edge)
p0 = p
return edgelist
def fmt(val): return format(val, '.4f') #fixme set at 4 decimal places for testing
def isSameEdge(e1,e2):
# fixme set at 4 decimal places for testing
def fmt(val): return format(val, '.4f')
def isSameEdge(e1, e2):
"""isSameEdge(e1,e2): return True if the 2 edges are both lines or arcs/circles and have the same
points - inspired by Yorik's function isSameLine"""
if not (isinstance(e1.Curve,Part.Line) or isinstance(e1.Curve,Part.Circle)):
if not (isinstance(e1.Curve, Part.Line) or isinstance(e1.Curve, Part.Circle)):
return False
if not (isinstance(e2.Curve,Part.Line) or isinstance(e2.Curve,Part.Circle)):
if not (isinstance(e2.Curve, Part.Line) or isinstance(e2.Curve, Part.Circle)):
return False
if type(e1.Curve) <> type(e2.Curve):
if type(e1.Curve) != type(e2.Curve):
return False
if isinstance(e1.Curve,Part.Line):
if (DraftVecUtils.equals(e1.Vertexes[0].Point,e2.Vertexes[0].Point)) and \
(DraftVecUtils.equals(e1.Vertexes[-1].Point,e2.Vertexes[-1].Point)):
if isinstance(e1.Curve, Part.Line):
if (DraftVecUtils.equals(e1.Vertexes[0].Point, e2.Vertexes[0].Point)) and \
(DraftVecUtils.equals(e1.Vertexes[-1].Point, e2.Vertexes[-1].Point)):
return True
elif (DraftVecUtils.equals(e1.Vertexes[-1].Point,e2.Vertexes[0].Point)) and \
(DraftVecUtils.equals(e1.Vertexes[0].Point,e2.Vertexes[-1].Point)):
elif (DraftVecUtils.equals(e1.Vertexes[-1].Point, e2.Vertexes[0].Point)) and \
(DraftVecUtils.equals(e1.Vertexes[0].Point, e2.Vertexes[-1].Point)):
return True
if isinstance(e1.Curve,Part.Circle):
center = False; radius= False; endpts=False
if isinstance(e1.Curve, Part.Circle):
center = False
radius = False
endpts = False
if e1.Curve.Center == e2.Curve.Center:
center = True
if e1.Curve.Radius == e2.Curve.Radius:
radius = True
if (DraftVecUtils.equals(e1.Vertexes[0].Point,e2.Vertexes[0].Point)) and \
(DraftVecUtils.equals(e1.Vertexes[-1].Point,e2.Vertexes[-1].Point)):
if (DraftVecUtils.equals(e1.Vertexes[0].Point, e2.Vertexes[0].Point)) and \
(DraftVecUtils.equals(e1.Vertexes[-1].Point, e2.Vertexes[-1].Point)):
endpts = True
elif (DraftVecUtils.equals(e1.Vertexes[-1].Point,e2.Vertexes[0].Point)) and \
(DraftVecUtils.equals(e1.Vertexes[0].Point,e2.Vertexes[-1].Point)):
elif (DraftVecUtils.equals(e1.Vertexes[-1].Point, e2.Vertexes[0].Point)) and \
(DraftVecUtils.equals(e1.Vertexes[0].Point, e2.Vertexes[-1].Point)):
endpts = True
if (center and radius and endpts):
return True
return False
def segments(poly):
''' A sequence of (x,y) numeric coordinates pairs '''
return zip(poly, poly[1:] + [poly[0]])
def check_clockwise(poly):
'''
check_clockwise(poly) a function for returning a boolean if the selected wire is clockwise or counter clockwise
based on point order. poly = [(x1,y1),(x2,y2),(x3,y3)]
'''
clockwise = False
if (sum(x0*y1 - x1*y0 for ((x0, y0), (x1, y1)) in segments(poly))) < 0:
if (sum(x0 * y1 - x1 * y0 for ((x0, y0), (x1, y1)) in segments(poly))) < 0:
clockwise = not clockwise
return clockwise
def filterArcs(arcEdge):
'''filterArcs(Edge) -used to split arcs that over 180 degrees. Returns list '''
s = arcEdge
if isinstance(s.Curve,Part.Circle):
splitlist =[]
angle = abs(s.LastParameter-s.FirstParameter)
if isinstance(s.Curve, Part.Circle):
splitlist = []
angle = abs(s.LastParameter - s.FirstParameter)
overhalfcircle = False
goodarc = False
if (angle > math.pi):
@ -139,14 +144,17 @@ def filterArcs(arcEdge):
else:
goodarc = True
if not goodarc:
arcstpt = s.valueAt(s.FirstParameter)
arcmid = s.valueAt((s.LastParameter-s.FirstParameter)*0.5+s.FirstParameter)
arcquad1 = s.valueAt((s.LastParameter-s.FirstParameter)*0.25+s.FirstParameter)#future midpt for arc1
arcquad2 = s.valueAt((s.LastParameter-s.FirstParameter)*0.75+s.FirstParameter) #future midpt for arc2
arcstpt = s.valueAt(s.FirstParameter)
arcmid = s.valueAt(
(s.LastParameter - s.FirstParameter) * 0.5 + s.FirstParameter)
arcquad1 = s.valueAt(
(s.LastParameter - s.FirstParameter) * 0.25 + s.FirstParameter) # future midpt for arc1
arcquad2 = s.valueAt(
(s.LastParameter - s.FirstParameter) * 0.75 + s.FirstParameter) # future midpt for arc2
arcendpt = s.valueAt(s.LastParameter)
# reconstruct with 2 arcs
arcseg1 = Part.ArcOfCircle(arcstpt,arcquad1,arcmid)
arcseg2 = Part.ArcOfCircle(arcmid,arcquad2,arcendpt)
arcseg1 = Part.ArcOfCircle(arcstpt, arcquad1, arcmid)
arcseg2 = Part.ArcOfCircle(arcmid, arcquad2, arcendpt)
eseg1 = arcseg1.toShape()
eseg2 = arcseg2.toShape()
@ -154,43 +162,50 @@ def filterArcs(arcEdge):
splitlist.append(eseg2)
else:
splitlist.append(s)
elif isinstance(s.Curve,Part.Line):
elif isinstance(s.Curve, Part.Line):
pass
return splitlist
def reverseEdge(e):
if geomType(e) == "Circle":
arcstpt = e.valueAt(e.FirstParameter)
arcmid = e.valueAt((e.LastParameter-e.FirstParameter)*0.5+e.FirstParameter)
arcstpt = e.valueAt(e.FirstParameter)
arcmid = e.valueAt(
(e.LastParameter - e.FirstParameter) * 0.5 + e.FirstParameter)
arcendpt = e.valueAt(e.LastParameter)
arcofCirc = Part.ArcOfCircle(arcendpt,arcmid,arcstpt)
arcofCirc = Part.ArcOfCircle(arcendpt, arcmid, arcstpt)
newedge = arcofCirc.toShape()
elif geomType(e) == "Line":
stpt = e.valueAt(e.FirstParameter)
endpt = e.valueAt(e.LastParameter)
newedge = Part.makeLine(endpt,stpt)
newedge = Part.makeLine(endpt, stpt)
return newedge
def convert(toolpath,Side,radius,clockwise=False,Z=0.0,firstedge=None,vf=1.0,hf=2.0):
def convert(
toolpath, Side, radius, clockwise=False, Z=0.0, firstedge=None,
vf=1.0, hf=2.0):
'''convert(toolpath,Side,radius,clockwise=False,Z=0.0,firstedge=None) Converts lines and arcs to G1,G2,G3 moves. Returns a string.'''
last = None
output = ""
# create the path from the offset shape
for edge in toolpath:
if not last:
#set the first point
# set the first point
last = edge.Vertexes[0].Point
#FreeCAD.Console.PrintMessage("last pt= " + str(last)+ "\n")
output += "G1 X"+str(fmt(last.x))+" Y"+str(fmt(last.y))+" Z"+str(fmt(Z))+" F"+str(vf)+"\n"
if isinstance(edge.Curve,Part.Circle):
#FreeCAD.Console.PrintMessage("arc\n")
# FreeCAD.Console.PrintMessage("last pt= " + str(last)+ "\n")
output += "G1 X" + str(fmt(last.x)) + " Y" + str(fmt(last.y)) + \
" Z" + str(fmt(Z)) + " F" + str(vf) + "\n"
if isinstance(edge.Curve, Part.Circle):
# FreeCAD.Console.PrintMessage("arc\n")
arcstartpt = edge.valueAt(edge.FirstParameter)
midpt = edge.valueAt((edge.FirstParameter+edge.LastParameter)*0.5)
midpt = edge.valueAt(
(edge.FirstParameter + edge.LastParameter) * 0.5)
arcendpt = edge.valueAt(edge.LastParameter)
arcchkpt=edge.valueAt(edge.LastParameter*.99)
# arcchkpt = edge.valueAt(edge.LastParameter * .99)
if DraftVecUtils.equals(last,arcstartpt):
if DraftVecUtils.equals(last, arcstartpt):
startpt = arcstartpt
endpt = arcendpt
else:
@ -198,54 +213,59 @@ def convert(toolpath,Side,radius,clockwise=False,Z=0.0,firstedge=None,vf=1.0,hf=
endpt = arcstartpt
center = edge.Curve.Center
relcenter = center.sub(last)
#FreeCAD.Console.PrintMessage("arc startpt= " + str(startpt)+ "\n")
#FreeCAD.Console.PrintMessage("arc midpt= " + str(midpt)+ "\n")
#FreeCAD.Console.PrintMessage("arc endpt= " + str(endpt)+ "\n")
arc_cw = check_clockwise([(startpt.x,startpt.y),(midpt.x,midpt.y),(endpt.x,endpt.y)])
#FreeCAD.Console.PrintMessage("arc_cw="+ str(arc_cw)+"\n")
# FreeCAD.Console.PrintMessage("arc startpt= " + str(startpt)+ "\n")
# FreeCAD.Console.PrintMessage("arc midpt= " + str(midpt)+ "\n")
# FreeCAD.Console.PrintMessage("arc endpt= " + str(endpt)+ "\n")
arc_cw = check_clockwise(
[(startpt.x, startpt.y), (midpt.x, midpt.y), (endpt.x, endpt.y)])
# FreeCAD.Console.PrintMessage("arc_cw="+ str(arc_cw)+"\n")
if arc_cw:
output += "G2"
else:
output += "G3"
output += " X"+str(fmt(endpt.x))+" Y"+str(fmt(endpt.y))+" Z"+str(fmt(Z))+" F"+str(hf)
output += " I" + str(fmt(relcenter.x)) + " J" + str(fmt(relcenter.y)) + " K" + str(fmt(relcenter.z))
output += " X" + str(fmt(endpt.x)) + " Y" + \
str(fmt(endpt.y)) + " Z" + str(fmt(Z)) + " F" + str(hf)
output += " I" + str(fmt(relcenter.x)) + " J" + \
str(fmt(relcenter.y)) + " K" + str(fmt(relcenter.z))
output += "\n"
last = endpt
#FreeCAD.Console.PrintMessage("last pt arc= " + str(last)+ "\n")
# FreeCAD.Console.PrintMessage("last pt arc= " + str(last)+ "\n")
else:
point = edge.Vertexes[-1].Point
if DraftVecUtils.equals(point , last): # edges can come flipped
if DraftVecUtils.equals(point, last): # edges can come flipped
point = edge.Vertexes[0].Point
output += "G1 X"+str(fmt(point.x))+" Y"+str(fmt(point.y))+" Z"+str(fmt(Z))+" F"+str(hf)+"\n"
output += "G1 X" + str(fmt(point.x)) + " Y" + str(fmt(point.y)) + \
" Z" + str(fmt(Z)) + " F" + str(hf) + "\n"
last = point
#FreeCAD.Console.PrintMessage("line\n")
#FreeCAD.Console.PrintMessage("last pt line= " + str(last)+ "\n")
# FreeCAD.Console.PrintMessage("line\n")
# FreeCAD.Console.PrintMessage("last pt line= " + str(last)+ "\n")
return output
def SortPath(wire,Side,radius,clockwise,firstedge=None,SegLen =0.5):
def SortPath(wire, Side, radius, clockwise, firstedge=None, SegLen=0.5):
'''SortPath(wire,Side,radius,clockwise,firstedge=None,SegLen =0.5) Sorts the wire and reverses it, if needed. Splits arcs over 180 degrees in two. Returns the reordered offset of the wire. '''
if firstedge:
edgelist = wire.Edges[:]
if wire.isClosed():
elindex = None
n=0
n = 0
for e in edgelist:
if isSameEdge(e,firstedge):
# FreeCAD.Console.PrintMessage('found first edge\n')
if isSameEdge(e, firstedge):
# FreeCAD.Console.PrintMessage('found first edge\n')
elindex = n
n=n+1
n = n + 1
l1 = edgelist[:elindex]
l2 = edgelist[elindex:]
newedgelist = l2+l1
newedgelist = l2 + l1
if clockwise:
newedgelist.reverse()
last = newedgelist.pop(-1)
newedgelist.insert(0, last)
preoffset= []
preoffset = []
for e in newedgelist:
if clockwise:
if clockwise:
r = reverseEdge(e)
preoffset.append(r)
else:
@ -266,16 +286,16 @@ def SortPath(wire,Side,radius,clockwise,firstedge=None,SegLen =0.5):
elif geomType(e) == "Line":
edgelist.append(e)
elif geomType(e) == "BSplineCurve" or \
geomType(e) == "BezierCurve" or \
geomType(e) == "Ellipse":
edgelist.append(Part.Wire(curvetowire(e,(SegLen))))
geomType(e) == "BezierCurve" or \
geomType(e) == "Ellipse":
edgelist.append(Part.Wire(curvetowire(e, (SegLen))))
newwire = Part.Wire(edgelist)
if Side == 'Left':
# we use the OCC offset feature
offset = newwire.makeOffset(radius)#tool is outside line
# we use the OCC offset feature
offset = newwire.makeOffset(radius) # tool is outside line
elif Side == 'Right':
offset = newwire.makeOffset(-radius)#tool is inside line
offset = newwire.makeOffset(-radius) # tool is inside line
else:
if wire.isClosed():
offset = newwire.makeOffset(0.0)
@ -284,48 +304,57 @@ def SortPath(wire,Side,radius,clockwise,firstedge=None,SegLen =0.5):
return offset
def MakePath(wire,Side,radius,clockwise,ZClearance,StepDown,ZStart,ZFinalDepth,firstedge=None,PathClosed=True,SegLen =0.5,VertFeed=1.0,HorizFeed=2.0):
def MakePath(wire, Side, radius, clockwise, ZClearance, StepDown, ZStart, ZFinalDepth, firstedge=None, PathClosed=True, SegLen=0.5, VertFeed=1.0, HorizFeed=2.0):
''' makes the path - just a simple profile for now '''
offset = SortPath(wire,Side,radius,clockwise,firstedge,SegLen=0.5)
offset = SortPath(wire, Side, radius, clockwise, firstedge, SegLen=0.5)
toolpath = offset.Edges[:]
paths = ""
paths += "G0 Z" + str(ZClearance)+"\n"
paths += "G0 Z" + str(ZClearance) + "\n"
first = toolpath[0].Vertexes[0].Point
paths += "G0 X"+str(fmt(first.x))+"Y"+str(fmt(first.y))+"\n"
ZCurrent = ZStart- StepDown
paths += "G0 X" + str(fmt(first.x)) + "Y" + str(fmt(first.y)) + "\n"
ZCurrent = ZStart - StepDown
if PathClosed:
while ZCurrent > ZFinalDepth:
paths += convert(toolpath,Side,radius,clockwise,ZCurrent,firstedge,VertFeed,HorizFeed)
ZCurrent = ZCurrent-abs(StepDown)
paths += convert(toolpath,Side,radius,clockwise,ZFinalDepth,firstedge,VertFeed,HorizFeed)
paths += convert(toolpath, Side, radius, clockwise,
ZCurrent, firstedge, VertFeed, HorizFeed)
ZCurrent = ZCurrent - abs(StepDown)
paths += convert(toolpath, Side, radius, clockwise,
ZFinalDepth, firstedge, VertFeed, HorizFeed)
paths += "G0 Z" + str(ZClearance)
else:
while ZCurrent > ZFinalDepth:
paths += convert(toolpath,Side,radius,clockwise,ZCurrent,firstedge,VertFeed,HorizFeed)
paths += convert(toolpath, Side, radius, clockwise,
ZCurrent, firstedge, VertFeed, HorizFeed)
paths += "G0 Z" + str(ZClearance)
paths += "G0 X"+str(fmt(first.x))+"Y"+str(fmt(first.y))+"\n"
ZCurrent = ZCurrent-abs(StepDown)
paths += convert(toolpath,Side,radius,clockwise,ZFinalDepth,firstedge,VertFeed,HorizFeed)
paths += "G0 X" + str(fmt(first.x)) + "Y" + \
str(fmt(first.y)) + "\n"
ZCurrent = ZCurrent - abs(StepDown)
paths += convert(toolpath, Side, radius, clockwise,
ZFinalDepth, firstedge, VertFeed, HorizFeed)
paths += "G0 Z" + str(ZClearance)
return paths
# the next two functions are for automatically populating tool numbers/height offset numbers based on previously active toolnumbers
# the next two functions are for automatically populating tool
# numbers/height offset numbers based on previously active toolnumbers
def changeTool(obj,proj):
def changeTool(obj, proj):
tlnum = 0
for p in proj.Group:
if not hasattr(p,"Group"):
if isinstance(p.Proxy,PathScripts.PathLoadTool.LoadTool) and p.ToolNumber > 0:
if not hasattr(p, "Group"):
if isinstance(p.Proxy, PathScripts.PathLoadTool.LoadTool) and p.ToolNumber > 0:
tlnum = p.ToolNumber
if p == obj:
return tlnum
elif hasattr(p,"Group"):
elif hasattr(p, "Group"):
for g in p.Group:
if isinstance(g.Proxy,PathScripts.PathLoadTool.LoadTool):
if isinstance(g.Proxy, PathScripts.PathLoadTool.LoadTool):
tlnum = g.ToolNumber
if g == obj:
return tlnum
def getLastTool(obj):
toolNum = obj.ToolNumber
if obj.ToolNumber == 0:
@ -334,11 +363,13 @@ def getLastTool(obj):
toolNum = changeTool(obj, proj)
return getTool(obj, toolNum)
def getLastToolLoad(obj):
#This walks up the hierarchy and tries to find the closest preceding toolchange.
# This walks up the hierarchy and tries to find the closest preceding
# ToolLoadOject (tlo).
import PathScripts
tc = None
tlo = None
lastfound = None
try:
@ -347,42 +378,43 @@ def getLastToolLoad(obj):
except:
parent = None
while parent != None:
while parent is not None:
sibs = parent.Group
for g in sibs:
if isinstance(g.Proxy,PathScripts.PathLoadTool.LoadTool):
if isinstance(g.Proxy, PathScripts.PathLoadTool.LoadTool):
lastfound = g
if g == child:
tc = lastfound
tlo = lastfound
if tc == None:
if tlo is None:
try:
child = parent
parent = parent.InList[0]
parent = child.InList[0]
except:
parent = None
else:
return tc
return tlo
if tc == None:
for g in FreeCAD.ActiveDocument.Objects: #top level object
if isinstance(g.Proxy,PathScripts.PathLoadTool.LoadTool):
lastfound = g
if tlo is None:
for g in FreeCAD.ActiveDocument.Objects: # Look in top level
if hasattr(g, "Proxy"):
if isinstance(g.Proxy, PathScripts.PathLoadTool.LoadTool):
lastfound = g
if g == obj:
tc = lastfound
return tc
tlo = lastfound
return tlo
def getTool(obj,number=0):
def getTool(obj, number=0):
"retrieves a tool from a hosting object with a tooltable, if any"
for o in obj.InList:
if o.TypeId == "Path::FeatureCompoundPython":
for m in o.Group:
if hasattr(m,"Tooltable"):
if hasattr(m, "Tooltable"):
return m.Tooltable.getTool(number)
# not found? search one level up
for o in obj.InList:
return getTool(o,number)
return getTool(o, number)
return None
@ -392,6 +424,7 @@ def findProj():
if isinstance(o.Proxy, PathProject.ObjectPathProject):
return o
def findMachine():
'''find machine object for the tooltable editor '''
for o in FreeCAD.ActiveDocument.Objects:
@ -399,10 +432,11 @@ def findMachine():
if isinstance(o.Proxy, PathScripts.PathMachine.Machine):
return o
def addToProject(obj):
"""Adds a path obj to this document, if no PathParoject exists it's created on the fly"""
p = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Path")
if p.GetBool("pathAutoProject",True):
if p.GetBool("pathAutoProject", True):
project = findProj()
if not project:
project = PathProject.CommandProject.Create()
@ -420,9 +454,10 @@ def getLastZ(obj):
for c in g.Path.Commands:
for n in c.Parameters:
if n == 'Z':
lastZ= c.Parameters['Z']
lastZ = c.Parameters['Z']
return lastZ
def frange(start, stop, step, finish):
x = []
curdepth = start
@ -435,7 +470,8 @@ def frange(start, stop, step, finish):
curdepth = stop + finish
x.append(curdepth)
# we might have to do a last pass or else finish round might be too far away
# we might have to do a last pass or else finish round might be too far
# away
if curdepth - stop > finish:
x.append(stop + finish)
@ -444,10 +480,182 @@ def frange(start, stop, step, finish):
curdepth = stop
x.append(curdepth)
return x
def rapid(x=None, y=None, z=None):
""" Returns gcode string to perform a rapid move."""
retstr = "G00"
if (x is not None) or (y is not None) or (z is not None):
if (x is not None):
retstr += " X" + str("%.4f" % x)
if (y is not None):
retstr += " Y" + str("%.4f" % y)
if (z is not None):
retstr += " Z" + str("%.4f" % z)
else:
return ""
return retstr + "\n"
def feed(x=None, y=None, z=None, horizFeed=0, vertFeed=0):
""" Return gcode string to perform a linear feed."""
global feedxy
retstr = "G01 F"
if(x is None) and (y is None):
retstr += str("%.4f" % horizFeed)
else:
retstr += str("%.4f" % vertFeed)
if (x is not None) or (y is not None) or (z is not None):
if (x is not None):
retstr += " X" + str("%.4f" % x)
if (y is not None):
retstr += " Y" + str("%.4f" % y)
if (z is not None):
retstr += " Z" + str("%.4f" % z)
else:
return ""
return retstr + "\n"
def arc(cx, cy, sx, sy, ex, ey, horizFeed=0, ez=None, ccw=False):
"""
Return gcode string to perform an arc.
Assumes XY plane or helix around Z
Don't worry about starting Z- assume that's dealt with elsewhere
If start/end radii aren't within eps, abort.
cx, cy -- arc center coordinates
sx, sy -- arc start coordinates
ex, ey -- arc end coordinates
ez -- ending Z coordinate. None unless helix.
horizFeed -- horiz feed speed
ccw -- arc direction
"""
eps = 0.01
if (math.sqrt((cx - sx)**2 + (cy - sy)**2) - math.sqrt((cx - ex)**2 + (cy - ey)**2)) >= eps:
print "ERROR: Illegal arc: Start and end radii not equal"
return ""
retstr = ""
if ccw:
retstr += "G03 F" + str(horizFeed)
else:
retstr += "G02 F" + str(horizFeed)
retstr += " X" + str("%.4f" % ex) + " Y" + str("%.4f" % ey)
if ez is not None:
retstr += " Z" + str("%.4f" % ez)
retstr += " I" + str("%.4f" % (cx - sx)) + " J" + str("%.4f" % (cy - sy))
return retstr + "\n"
def helicalPlunge(plungePos, rampangle, destZ, startZ, toold, plungeR, horizFeed):
"""
Return gcode string to perform helical entry move.
plungePos -- vector of the helical entry location
destZ -- the lowest Z position or milling level
startZ -- Starting Z position for helical move
rampangle -- entry angle
toold -- tool diameter
plungeR -- the radius of the entry helix
"""
# toold = self.radius * 2
helixCmds = "(START HELICAL PLUNGE)\n"
if(plungePos is None):
raise Exception("Helical plunging requires a position!")
return None
helixX = plungePos.x + toold/2 * plungeR
helixY = plungePos.y
helixCirc = math.pi * toold * plungeR
dzPerRev = math.sin(rampangle/180. * math.pi) * helixCirc
# Go to the start of the helix position
helixCmds += rapid(helixX, helixY)
helixCmds += rapid(z=startZ)
# Helix as required to get to the requested depth
lastZ = startZ
curZ = max(startZ-dzPerRev, destZ)
done = False
while not done:
done = (curZ == destZ)
# NOTE: FreeCAD doesn't render this, but at least LinuxCNC considers it valid
# helixCmds += arc(plungePos.x, plungePos.y, helixX, helixY, helixX, helixY, ez = curZ, ccw=True)
# Use two half-helixes; FreeCAD renders that correctly,
# and it fits with the other code breaking up 360-degree arcs
helixCmds += arc(plungePos.x, plungePos.y, helixX, helixY, helixX - toold * plungeR, helixY, horizFeed, ez=(curZ + lastZ)/2., ccw=True)
helixCmds += arc(plungePos.x, plungePos.y, helixX - toold * plungeR, helixY, helixX, helixY, horizFeed, ez=curZ, ccw=True)
lastZ = curZ
curZ = max(curZ - dzPerRev, destZ)
return helixCmds
def rampPlunge(edge, rampangle, destZ, startZ):
"""
Return gcode string to linearly ramp down to milling level.
edge -- edge to follow
rampangle -- entry angle
destZ -- Final Z depth
startZ -- Starting Z depth
FIXME: This ramps along the first edge, assuming it's long
enough, NOT just wiggling back and forth by ~0.75 * toolD.
Not sure if that's any worse, but it's simpler
I think this should be changed to be limited to a maximum ramp size. Otherwise machine time will get longer than it needs to be.
"""
rampCmds = "(START RAMP PLUNGE)\n"
if(edge is None):
raise Exception("Ramp plunging requires an edge!")
return None
sPoint = edge.Vertexes[0].Point
ePoint = edge.Vertexes[1].Point
# Evidently edges can get flipped- pick the right one in this case
# FIXME: This is iffy code, based on what already existed in the "for vpos ..." loop below
if ePoint == sPoint:
# print "FLIP"
ePoint = edge.Vertexes[-1].Point
rampDist = edge.Length
rampDZ = math.sin(rampangle/180. * math.pi) * rampDist
rampCmds += rapid(sPoint.x, sPoint.y)
rampCmds += rapid(z=startZ)
# Ramp down to the requested depth
# FIXME: This might be an arc, so handle that as well
curZ = max(startZ-rampDZ, destZ)
done = False
while not done:
done = (curZ == destZ)
# If it's an arc, handle it!
if isinstance(edge.Curve, Part.Circle):
raise Exception("rampPlunge: Screw it, not handling an arc.")
# Straight feed! Easy!
else:
rampCmds += feed(ePoint.x, ePoint.y, curZ)
rampCmds += feed(sPoint.x, sPoint.y)
curZ = max(curZ - rampDZ, destZ)
return rampCmds
class depth_params:
def __init__(self, clearance_height, rapid_safety_space, start_depth, step_down, z_finish_depth, final_depth, user_depths=None):
self.clearance_height = clearance_height
self.rapid_safety_space = math.fabs(rapid_safety_space)
@ -459,17 +667,19 @@ class depth_params:
def get_depths(self):
depths = []
if self.user_depths != None:
if self.user_depths is not None:
depths = self.user_depths
else:
depth = self.final_depth
depths = [depth]
depth += self.z_finish_depth
if depth + 0.0000001 < self.start_depth:
if self.z_finish_depth > 0.0000001: depths.insert(0, depth)
layer_count = int((self.start_depth - depth) / self.step_down - 0.0000001) + 1
if self.z_finish_depth > 0.0000001:
depths.insert(0, depth)
layer_count = int((self.start_depth - depth) /
self.step_down - 0.0000001) + 1
if layer_count > 0:
layer_depth = (self.start_depth - depth)/layer_count
layer_depth = (self.start_depth - depth) / layer_count
for i in range(1, layer_count):
depth += layer_depth
depths.append(depth)