FreeCAD/src/Mod/Path/PathScripts/PathPocket.py
2016-05-23 13:04:58 -03:00

886 lines
38 KiB
Python

# -*- 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 *
#* *
#***************************************************************************
import FreeCAD,Path
from PySide import QtCore,QtGui
from PathScripts import PathUtils
import math
FreeCADGui = None
if FreeCAD.GuiUp:
import FreeCADGui
"""Path Pocket object and FreeCAD command"""
# Qt tanslation handling
try:
_encoding = QtGui.QApplication.UnicodeUTF8
def translate(context, text, disambig=None):
return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
def translate(context, text, disambig=None):
return QtGui.QApplication.translate(context, text, disambig)
class ObjectPocket:
def __init__(self,obj):
obj.addProperty("App::PropertyLinkSubList","Base","Path",translate("PathProject","The base geometry of this object"))
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::PropertyEnumeration", "Algorithm", "Algorithm",translate("PathProject", "The library to use to generate the path"))
obj.Algorithm = ['OCC Native','libarea']
#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
#Depth Properties
obj.addProperty("App::PropertyDistance", "ClearanceHeight", "Depth", translate("PathProject","The height needed to clear clamps and obstructions"))
obj.addProperty("App::PropertyDistance", "SafeHeight", "Depth", translate("PathProject","Rapid Safety Height between locations."))
obj.addProperty("App::PropertyFloatConstraint", "StepDown", "Depth", translate("PathProject","Incremental Step Down of Tool"))
obj.StepDown = (0.0, 0.01, 100.0, 0.5)
obj.addProperty("App::PropertyDistance", "StartDepth", "Depth", translate("PathProject","Starting Depth of Tool- first cut depth in Z"))
obj.addProperty("App::PropertyDistance", "FinalDepth", "Depth", translate("PathProject","Final Depth of Tool- lowest value in Z"))
obj.addProperty("App::PropertyDistance", "FinishDepth", "Depth", translate("PathProject","Maximum material removed on final pass."))
#Pocket Properties
obj.addProperty("App::PropertyEnumeration", "CutMode", "Pocket",translate("PathProject", "The direction that the toolpath should go around the part ClockWise CW or CounterClockWise CCW"))
obj.CutMode = ['Climb','Conventional']
obj.addProperty("App::PropertyDistance", "MaterialAllowance", "Pocket", translate("PathProject","Amount of material to leave"))
obj.addProperty("App::PropertyEnumeration", "StartAt", "Pocket",translate("PathProject", "Start pocketing at center or boundary"))
obj.StartAt = ['Center', 'Edge']
obj.addProperty("App::PropertyFloatConstraint","StepOver","Pocket",translate("PathProject","Amount to step over on each pass"))
obj.StepOver = (0.0, 0.01, 100.0, 0.5)
obj.addProperty("App::PropertyBool","KeepToolDown","Pocket",translate("PathProject","Attempts to avoid unnecessary retractions."))
obj.addProperty("App::PropertyBool","ZigUnidirectional","Pocket",translate("PathProject","Lifts tool at the end of each pass to respect cut mode."))
obj.addProperty("App::PropertyBool","UseZigZag","Pocket",translate("PathProject","Use Zig Zag pattern to clear area."))
obj.addProperty("App::PropertyFloat","ZigZagAngle","Pocket",translate("PathProject","Angle of the zigzag pattern"))
#Entry Properties
obj.addProperty("App::PropertyBool","UseEntry","Entry",translate("PathProject","Allow Cutter enter material with a straight plunge."))
obj.addProperty("App::PropertyFloatConstraint", "RampSize", "Entry", translate("PathProject","The minimum fraction of tool diameter to use for ramp length"))
obj.RampSize = (0.0, 0.01, 100.0, 0.5)
obj.addProperty("App::PropertyFloatConstraint", "HelixSize", "Entry", translate("PathProject","The fraction of tool diameter to use for calculating helix size."))
obj.HelixSize = (0.0, 0.01, 100.0, 0.5)
obj.addProperty("App::PropertyFloatConstraint", "RampAngle", "Entry", translate("PathProject","The Angle of the ramp entry."))
obj.RampAngle = (0.0, 0.01, 100.0, 0.5)
#Start Point Properties
obj.addProperty("App::PropertyVector","StartPoint","Start Point",translate("PathProject","The start point of this path"))
obj.addProperty("App::PropertyBool","UseStartPoint","Start Point",translate("PathProject","make True, if specifying a Start Point"))
obj.Proxy = self
def onChanged(self,obj,prop):
if prop == "UseEntry":
if obj.UseEntry:
obj.setEditorMode('HelixSize',0) #make this visible
obj.setEditorMode('RampAngle',0) #make this visible
obj.setEditorMode('RampSize',0) #make this visible
else:
obj.setEditorMode('HelixSize',2) #make this hidden
obj.setEditorMode('RampAngle',2) #make this hidden
obj.setEditorMode('RampSize',2) #make this hidden
def __getstate__(self):
return None
def __setstate__(self,state):
return None
def addpocketbase(self, obj, ss, sub=""):
baselist = obj.Base
if baselist == None:
baselist = []
if len(baselist) == 0: #When adding the first base object, guess at heights
try:
bb = ss.Shape.BoundBox #parent boundbox
subobj = ss.Shape.getElement(sub)
fbb = subobj.BoundBox #feature boundbox
obj.StartDepth = bb.ZMax
obj.ClearanceHeight = bb.ZMax + 5.0
obj.SafeHeight = bb.ZMax + 3.0
if fbb.ZMax < bb.ZMax:
obj.FinalDepth = fbb.ZMax
else:
obj.FinalDepth = bb.ZMin
except:
obj.StartDepth = 5.0
obj.ClearanceHeight = 10.0
obj.SafeHeight = 8.0
item = (ss, sub)
if item in baselist:
FreeCAD.Console.PrintWarning("this object already in the list"+ "\n")
else:
baselist.append (item)
obj.Base = baselist
print "this base is: " + str(baselist)
self.execute(obj)
def getStock(self,obj):
"retrieves a stock object from hosting project if any"
for o in obj.InList:
if hasattr(o,"Group"):
for g in o.Group:
if hasattr(g,"Height_Allowance"):
return o
# not found? search one level up
for o in obj.InList:
return self.getStock(o)
return None
def buildpathlibarea(self, obj, a):
import PathScripts.PathAreaUtils as PathAreaUtils
from PathScripts.PathUtils import depth_params
import area
FreeCAD.Console.PrintMessage(translate("PathPocket","Generating toolpath with libarea offsets.\n"))
depthparams = depth_params (obj.ClearanceHeight.Value, obj.SafeHeight.Value, obj.StartDepth.Value, obj.StepDown, obj.FinishDepth.Value, obj.FinalDepth.Value)
extraoffset = obj.MaterialAllowance.Value
stepover = obj.StepOver
use_zig_zag = obj.UseZigZag
zig_angle = obj.ZigZagAngle
from_center = (obj.StartAt == "Center")
keep_tool_down = obj.KeepToolDown
zig_unidirectional = obj.ZigUnidirectional
start_point = None
cut_mode = obj.CutMode
PathAreaUtils.flush_nc()
PathAreaUtils.output('mem')
PathAreaUtils.feedrate_hv(self.horizFeed, self.vertFeed)
if obj.UseStartPoint:
start_point = (obj.StartPoint.x,obj.StartPoint.y)
#print "a," + str(self.radius) + "," + str(extraoffset) + "," + str(stepover) + ",depthparams, " + str(from_center) + "," + str(keep_tool_down) + "," + str(use_zig_zag) + "," + str(zig_angle) + "," + str(zig_unidirectional) + "," + str(start_point) + "," + str(cut_mode)
PathAreaUtils.pocket(a,\
self.radius,\
extraoffset, \
stepover,\
depthparams,\
from_center,\
keep_tool_down,\
use_zig_zag,\
zig_angle,\
zig_unidirectional,\
start_point,\
cut_mode)
return PathAreaUtils.retrieve_gcode()
def buildpathocc(self, obj, shape):
import Part, DraftGeomUtils
FreeCAD.Console.PrintMessage(translate("PathPocket","Generating toolpath with OCC native offsets.\n"))
def prnt(vlu): return str("%.4f" % round(vlu, 4))
def rapid(x=None, y=None, z=None):
#Returns gcode to perform a rapid move
retstr = "G00"
if (x != None) or (y != None) or (z != None):
if (x != None):
retstr += " X" + str("%.4f" % x)
if (y != None):
retstr += " Y" + str("%.4f" % y)
if (z != None):
retstr += " Z" + str("%.4f" % z)
else:
return ""
return retstr + "\n"
def feed(x=None, y=None, z=None):
#Returns gcode to perform a linear feed
global feedxy
retstr = "G01 F"
if(x == None) and (y == None):
retstr += str("%.4f" % self.horizFeed)
else:
retstr += str("%.4f" % self.vertFeed)
if (x != None) or (y != None) or (z != None):
if (x != None):
retstr += " X" + str("%.4f" % x)
if (y != None):
retstr += " Y" + str("%.4f" % y)
if (z != None):
retstr += " Z" + str("%.4f" % z)
else:
return ""
return retstr + "\n"
def arc(cx, cy, sx, sy, ex, ey, ez=None, ccw=False):
#Returns gcode 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
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: Stand and end radii not equal"
return ""
#Set [C]CW and feed
retstr = ""
if ccw:
retstr += "G03 F"
else:
retstr += "G02 F"
retstr += str(self.horizFeed)
#End location
retstr += " X" + str("%.4f" % ex) + " Y" + str("%.4f" % ey)
#Helix if requested
if ez != None:
retstr += " Z" + str("%.4f" % ez)
#Append center offsets
retstr += " I" + str("%.4f" % (cx - sx)) + " J" + str("%.4f" % (cy - sy))
return retstr + "\n"
def helicalPlunge(plungePos, rampangle, destZ, startZ):
#Returns gcode to helically plunge
#destZ is the milling level
#startZ is the height we can safely feed down to before helix-ing
helixCmds = "(START HELICAL PLUNGE)\n"
if(plungePos == None):
raise Exception("Helical plunging requires a position!")
return None
if(not tool):
raise Exception("Helical plunging requires a tool!")
return None
helixX = plungePos.x + tool.Diameter/2. * plungeR
helixY = plungePos.y;
helixCirc = math.pi * tool.Diameter * 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 - tool.Diameter * plungeR, helixY, ez = (curZ + lastZ)/2., ccw=True)
helixCmds += arc(plungePos.x, plungePos.y, helixX - tool.Diameter * plungeR, helixY, helixX, helixY, ez = curZ, ccw=True)
lastZ = curZ
curZ = max(curZ - dzPerRev, destZ)
return helixCmds
def rampPlunge(edge, rampangle, destZ, startZ):
#Returns commands to linearly ramp into a cut
#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 == None):
raise Exception("Ramp plunging requires an edge!")
return None
if(not tool):
raise Exception("Ramp plunging requires a tool!")
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
#print "Start: " + str(sPoint) + " End: " + str(ePoint) + " Zhigh: " + prnt(startZ) + " ZLow: " + prnt(destZ)
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
output = ""
offsets = []
nextradius = self.radius
result = DraftGeomUtils.pocket2d(shape,nextradius)
while result:
#print "Adding " + str(len(result)) + " wires"
offsets.extend(result)
nextradius += self.radius
result = DraftGeomUtils.pocket2d(shape,nextradius)
# first move will be rapid, subsequent will be at feed rate
first = True
startPoint = None
fastZPos = max(obj.StartDepth.Value + 2, obj.ClearanceHeight.Value)
# revert the list so we start with the outer wires
if obj.StartAt != 'Edge':
offsets.reverse()
# print "startDepth: " + str(obj.StartDepth.Value)
# print "finalDepth: " + str(obj.FinalDepth.Value)
# print "stepDown: " + str(obj.StepDown)
# print "finishDepth" + str(obj.FinishDepth.Value)
# print "offsets:", len(offsets)
#Fraction of tool radius our plunge helix is to be
plungeR = obj.HelixSize
#(minimum) Fraction of tool DIAMETER to go back and forth while ramp-plunging
#FIXME: The ramp plunging should maybe even be limited to this distance; I don't know what's best
rampD = obj.RampSize
#Total offset from the desired pocket edge is tool radius plus the plunge helix radius
#Any point on these curves could be the center of a plunge
helixBounds = DraftGeomUtils.pocket2d(shape, self.radius * (1 + plungeR))
#Try to find a location to nicely plunge, starting with a helix, then ramp
#Can't do it without knowledge of a tool
plungePos = None
rampEdge = None
tool = PathUtils.getTool(obj,obj.ToolNumber)
if not tool:
raise Exception("Ramp plunge location-finding requires a tool")
return
else:
#Since we're going to start machining either the inner-most
#edge or the outer (depending on StartAt setting), try to
#plunge near that location
if helixBounds and obj.UseEntry:
#Edge is easy- pick a point on helixBounds and go with it
if obj.StartAt == 'Edge':
plungePos = helixBounds[0].Edges[0].Vertexes[0].Point
#Center is harder- use a point from the first offset, check if it works
else:
plungePos = offsets[0].Edges[0].Vertexes[0].Point
#If it turns out this is invalid for some reason, nuke plungePos
[perp,idx] = DraftGeomUtils.findPerpendicular(plungePos, shape.Edges)
if not perp or perp.Length < self.radius * (1 + plungeR):
plungePos = None
#FIXME: Really need to do a point-in-polygon operation to make sure this is within helixBounds
#Or some math to prove that it has to be (doubt that's true)
#Maybe reverse helixBounds and pick off that?
#If we didn't find a place to helix, how about a ramp?
if not plungePos and obj.UseEntry:
#Check first edge of our offsets
if (offsets[0].Edges[0].Length >= tool.Diameter * rampD) and not (isinstance(offsets[0].Edges[0].Curve, Part.Circle)):
rampEdge = offsets[0].Edges[0]
#The last edge also connects with the starting location- try that
elif (offsets[0].Edges[-1].Length >= tool.Diameter * rampD) and not (isinstance(offsets[0].Edges[-1].Curve, Part.Circle)):
rampEdge = offsets[0].Edges[-1]
else:
print "Neither edge works: " + str(offsets[0].Edges[0]) + ", " + str(offsets[0].Edges[-1])
#FIXME: There's got to be a smarter way to find a place to ramp
#For helix-ing/ramping, know where we were last time
#FIXME: Can probably get this from the "machine"?
lastZ = fastZPos
for vpos in PathUtils.frange(obj.StartDepth.Value, obj.FinalDepth.Value, obj.StepDown, obj.FinishDepth.Value):
#Every for every depth we should helix down
first = True
# loop over successive wires
for currentWire in offsets:
last = None
for edge in currentWire.Edges:
if not last:
# we set the base GO to our fast move to our starting pos
if first:
#If we can helix, do so
if plungePos:
output += helicalPlunge(plungePos, obj.RampAngle, vpos, lastZ)
#print output
lastZ = vpos
#Otherwise, see if we can ramp
#FIXME: This could be a LOT smarter (eg, searching for a longer leg of the edge to ramp along)
elif rampEdge:
output += rampPlunge(rampEdge, obj.RampAngle, vpos, lastZ)
lastZ = vpos
#Otherwise, straight plunge... Don't want to, but sometimes you might not have a choice.
#FIXME: At least not with the lazy ramp programming above...
else:
print "WARNING: Straight-plunging... probably not good, but we didn't find a place to helix or ramp"
startPoint = edge.Vertexes[0].Point
output += "G0 X" + prnt(startPoint.x) + " Y" + prnt(startPoint.y) +\
" Z" + prnt(fastZPos) + "\n"
first = False
#then move slow down to our starting point for our profile
last = edge.Vertexes[0].Point
output += "G1 X" + prnt(last.x) + " Y" + prnt(last.y) + " Z" + prnt(vpos) + "\n"
#if isinstance(edge.Curve,Part.Circle):
if DraftGeomUtils.geomType(edge) == "Circle":
point = edge.Vertexes[-1].Point
if point == last: # edges can come flipped
point = edge.Vertexes[0].Point
center = edge.Curve.Center
relcenter = center.sub(last)
v1 = last.sub(center)
v2 = point.sub(center)
if v1.cross(v2).z < 0:
output += "G2"
else:
output += "G3"
output += " X" + prnt(point.x) + " Y" + prnt(point.y) + " Z" + prnt(vpos)
output += " I" + prnt(relcenter.x) + " J" +prnt(relcenter.y) + " K" + prnt(relcenter.z)
output += "\n"
last = point
else:
point = edge.Vertexes[-1].Point
if point == last: # edges can come flipped
point = edge.Vertexes[0].Point
output += "G1 X" + prnt(point.x) + " Y" + prnt(point.y) + " Z" + prnt(vpos) + "\n"
last = point
#move back up
output += "G1 Z" + prnt(fastZPos) + "\n"
return output
#To reload this from FreeCAD, use: import PathScripts.PathPocket; reload(PathScripts.PathPocket)
def execute(self,obj):
output = ""
toolLoad = PathUtils.getLastToolLoad(obj)
if toolLoad == None:
self.vertFeed = 100
self.horizFeed = 100
self.radius = 0.25
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
if obj.Base:
for b in obj.Base:
print "object base: " + str(b)
import Part, PathScripts.PathKurveUtils
if "Face" in b[1]:
print "inside"
shape = getattr(b[0].Shape,b[1])
wire = shape.OuterWire
edges = wire.Edges
else:
print "in else"
edges = [getattr(b[0].Shape,sub) for sub in b[1]]
print "myedges: " + str(edges)
wire = Part.Wire(edges)
shape = None
# output = ""
if obj.Algorithm == "OCC Native":
if shape == None:
shape = wire
output += self.buildpathocc(obj, shape)
else:
try:
import area
except:
FreeCAD.Console.PrintError(translate("PathKurve","libarea needs to be installed for this command to work.\n"))
return
a = area.Area()
if shape == None:
c = PathScripts.PathKurveUtils.makeAreaCurve(wire.Edges, 'CW')
a.append(c)
else:
for w in shape.Wires:
c = PathScripts.PathKurveUtils.makeAreaCurve(w.Edges, 'CW')
# if w.isSame(shape.OuterWire):
# print "outerwire"
# if c.IsClockwise():
# c.Reverse()
# print "reverse outterwire"
# else:
# print "inner wire"
# if not c.IsClockwise():
# c.Reverse()
# print "reverse inner"
a.append(c)
########
##This puts out some interesting information from libarea
print a.text()
########
a.Reorder()
output += self.buildpathlibarea(obj, a)
if obj.Active:
path = Path.Path(output)
obj.Path = path
obj.ViewObject.Visibility = True
else:
path = Path.Path("(inactive operation)")
obj.Path = path
obj.ViewObject.Visibility = False
class _CommandSetPocketStartPoint:
def GetResources(self):
return {'Pixmap' : 'Path-StartPoint',
'MenuText': QtCore.QT_TRANSLATE_NOOP("PathPocket","Pick Start Point"),
'ToolTip': QtCore.QT_TRANSLATE_NOOP("PathPocket","Pick Start Point")}
def IsActive(self):
return not FreeCAD.ActiveDocument is None
def setpoint(self,point,o):
obj=FreeCADGui.Selection.getSelection()[0]
obj.StartPoint.x = point.x
obj.StartPoint.y = point.y
def Activated(self):
FreeCADGui.Snapper.getPoint(callback=self.setpoint)
class ViewProviderPocket:
def __init__(self,vobj):
vobj.Proxy = self
def attach(self,vobj):
self.Object = vobj.Object
return
def setEdit(self,vobj,mode=0):
FreeCADGui.Control.closeDialog()
taskd = TaskPanel()
taskd.obj = vobj.Object
FreeCADGui.Control.showDialog(taskd)
taskd.setupUi()
return True
def getIcon(self):
return ":/icons/Path-Pocket.svg"
def __getstate__(self):
return None
def __setstate__(self,state):
return None
class CommandPathPocket:
def GetResources(self):
return {'Pixmap' : 'Path-Pocket',
'MenuText': QtCore.QT_TRANSLATE_NOOP("PathPocket","Pocket"),
'Accel': "P, O",
'ToolTip': QtCore.QT_TRANSLATE_NOOP("PathPocket","Creates a Path Pocket object from a loop of edges or a face")}
def IsActive(self):
return not FreeCAD.ActiveDocument is None
def Activated(self):
# check that the selection contains exactly what we want
# selection = FreeCADGui.Selection.getSelectionEx()
# if len(selection) != 1:
# FreeCAD.Console.PrintError(translate("PathPocket","Please select an edges loop from one object, or a single face\n"))
# return
# if len(selection[0].SubObjects) == 0:
# FreeCAD.Console.PrintError(translate("PathPocket","Please select an edges loop from one object, or a single face\n"))
# return
# for s in selection[0].SubObjects:
# if s.ShapeType != "Edge":
# if (s.ShapeType != "Face") or (len(selection[0].SubObjects) != 1):
# FreeCAD.Console.PrintError(translate("PathPocket","Please select only edges or a single face\n"))
# return
# if selection[0].SubObjects[0].ShapeType == "Edge":
# try:
# import Part
# w = Part.Wire(selection[0].SubObjects)
# if w.isClosed() == False:
# FreeCAD.Console.PrintError(translate("PathPocket","The selected edges don't form a loop\n"))
# return
# except:
# FreeCAD.Console.PrintError(translate("PathPocket","The selected edges don't form a loop\n"))
# return
# Take a guess at some reasonable values for Finish depth.
# bb = selection[0].Object.Shape.BoundBox #parent boundbox
# fbb = selection[0].SubObjects[0].BoundBox #feature boundbox
# if fbb.ZMax < bb.ZMax:
# zbottom = fbb.ZMax
# else:
# zbottom = bb.ZMin
zbottom = 0.0
ztop = 10.0
# if everything is ok, execute and register the transaction in the undo/redo stack
FreeCAD.ActiveDocument.openTransaction(translate("PathPocket","Create Pocket"))
FreeCADGui.addModule("PathScripts.PathPocket")
FreeCADGui.doCommand('obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython","Pocket")')
FreeCADGui.doCommand('PathScripts.PathPocket.ObjectPocket(obj)')
FreeCADGui.doCommand('obj.Active = True')
FreeCADGui.doCommand('PathScripts.PathPocket.ViewProviderPocket(obj.ViewObject)')
FreeCADGui.doCommand('from PathScripts import PathUtils')
FreeCADGui.doCommand('obj.StepOver = 1.0')
FreeCADGui.doCommand('obj.ClearanceHeight = 10')# + str(bb.ZMax + 2.0))
FreeCADGui.doCommand('obj.StepDown = 1.0')
FreeCADGui.doCommand('obj.StartDepth= ' + str(ztop))
FreeCADGui.doCommand('obj.FinalDepth=' + str(zbottom))
FreeCADGui.doCommand('obj.ZigZagAngle=45')
FreeCADGui.doCommand('obj.UseEntry=True')
FreeCADGui.doCommand('obj.RampAngle = 3.0')
FreeCADGui.doCommand('obj.RampSize = 0.75')
FreeCADGui.doCommand('obj.HelixSize = 0.75')
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/PocketEdit.ui")
self.form = FreeCADGui.PySideUic.loadUi(":/panels/PocketEdit.ui")
self.updating = False
def accept(self):
self.getFields()
FreeCADGui.ActiveDocument.resetEdit()
FreeCADGui.Control.closeDialog()
FreeCAD.ActiveDocument.recompute()
FreeCADGui.Selection.removeObserver(self.s)
def reject(self):
FreeCADGui.Control.closeDialog()
FreeCAD.ActiveDocument.recompute()
FreeCADGui.Selection.removeObserver(self.s)
def getFields(self):
if self.obj:
if hasattr(self.obj,"StartDepth"):
self.obj.StartDepth = self.form.startDepth.text()
if hasattr(self.obj,"FinalDepth"):
self.obj.FinalDepth = self.form.finalDepth.text()
if hasattr(self.obj,"SafeHeight"):
self.obj.SafeHeight = self.form.safeHeight.text()
if hasattr(self.obj,"ClearanceHeight"):
self.obj.ClearanceHeight = self.form.clearanceHeight.text()
if hasattr(self.obj,"StepDown"):
self.obj.StepDown = self.form.stepDown.value()
if hasattr(self.obj,"MaterialAllowance"):
self.obj.MaterialAllowance = self.form.extraOffset.value()
if hasattr(self.obj,"UseStartPoint"):
self.obj.UseStartPoint = self.form.useStartPoint.isChecked()
if hasattr(self.obj,"Algorithm"):
self.obj.Algorithm = str(self.form.algorithmSelect.currentText())
if hasattr(self.obj,"CutMode"):
self.obj.CutMode = str(self.form.cutMode.currentText())
self.obj.Proxy.execute(self.obj)
def open(self):
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
selection = FreeCADGui.Selection.getSelectionEx()
if not len(selection) >= 1:
FreeCAD.Console.PrintError(translate("PathProject","Please select at least one profileable object\n"))
return
for s in selection:
if s.HasSubObjects:
for i in s.SubElementNames:
self.obj.Proxy.addpocketbase(self.obj, s.Object, i)
else:
self.obj.Proxy.addpocketbase(self.obj, s.Object)
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()
newlist = []
for d in dlist:
for i in self.obj.Base:
if i[0].Name != d.text().partition(".")[0] or i[1] != d.text().partition(".")[2] :
newlist.append (i)
self.form.baseList.takeItem(self.form.baseList.row(d))
self.obj.Base = newlist
self.obj.Proxy.execute(self.obj)
FreeCAD.ActiveDocument.recompute()
def itemActivated(self):
FreeCADGui.Selection.clearSelection()
slist = self.form.baseList.selectedItems()
for i in slist:
objstring = i.text().partition(".")
obj = FreeCAD.ActiveDocument.getObject(objstring[0])
# sub = o.Shape.getElement(objstring[2])
if objstring[2] != "":
FreeCADGui.Selection.addSelection(obj,objstring[2])
else:
FreeCADGui.Selection.addSelection(obj)
FreeCADGui.updateGui()
def reorderBase(self):
newlist = []
for i in range(self.form.baseList.count()):
s = self.form.baseList.item(i).text()
objstring = s.partition(".")
obj = FreeCAD.ActiveDocument.getObject(objstring[0])
item = (obj, str(objstring[2]))
newlist.append(item)
self.obj.Base=newlist
self.obj.Proxy.execute(self.obj)
FreeCAD.ActiveDocument.recompute()
def getStandardButtons(self):
return int(QtGui.QDialogButtonBox.Ok)
def edit(self,item,column):
if not self.updating:
self.resetObject()
def setupUi(self):
self.form.startDepth.setText(str(self.obj.StartDepth.Value))
self.form.finalDepth.setText(str(self.obj.FinalDepth.Value))
self.form.safeHeight.setText(str(self.obj.SafeHeight.Value))
self.form.clearanceHeight.setText(str(self.obj.ClearanceHeight.Value))
self.form.stepDown.setValue(self.obj.StepDown)
self.form.extraOffset.setValue(self.obj.MaterialAllowance.Value)
self.form.useStartPoint.setChecked(self.obj.UseStartPoint)
index = self.form.algorithmSelect.findText(self.obj.Algorithm, QtCore.Qt.MatchFixedString)
if index >= 0:
self.form.algorithmSelect.setCurrentIndex(index)
for i in self.obj.Base:
self.form.baseList.addItem(i[0].Name + "." + i[1])
#Connect Signals and Slots
#Base Controls
self.form.baseList.itemSelectionChanged.connect(self.itemActivated)
self.form.addBase.clicked.connect(self.addBase)
self.form.deleteBase.clicked.connect(self.deleteBase)
self.form.reorderBase.clicked.connect(self.reorderBase)
#Depths
self.form.startDepth.editingFinished.connect(self.getFields)
self.form.finalDepth.editingFinished.connect(self.getFields)
self.form.stepDown.editingFinished.connect(self.getFields)
#Heights
self.form.safeHeight.editingFinished.connect(self.getFields)
self.form.clearanceHeight.editingFinished.connect(self.getFields)
#operation
self.form.algorithmSelect.currentIndexChanged.connect(self.getFields)
self.form.cutMode.currentIndexChanged.connect(self.getFields)
self.form.useStartPoint.clicked.connect(self.getFields)
self.form.extraOffset.editingFinished.connect(self.getFields)
class SelObserver:
def __init__(self):
import PathScripts.PathSelection as PST
PST.pocketselect()
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 +')')
FreeCADGui.updateGui()
if FreeCAD.GuiUp:
# register the FreeCAD command
FreeCADGui.addCommand('Path_Pocket',CommandPathPocket())
FreeCADGui.addCommand('Set_PocketStartPoint',_CommandSetPocketStartPoint())
FreeCAD.Console.PrintLog("Loading PathPocket... done\n")