From be03c2ad261da675cee155265b81302eb0783116 Mon Sep 17 00:00:00 2001 From: sliptonic Date: Fri, 13 May 2016 08:28:27 -0500 Subject: [PATCH] defaults if objects pre-selected. Fixes Smarter default for inside outside profiles first commit --- .../Path/Gui/Resources/panels/ToolControl.ui | 135 ++++++++++++++ src/Mod/Path/PathScripts/PathLoadTool.py | 120 ++++++++++++- src/Mod/Path/PathScripts/PathPocket.py | 109 +++++++---- src/Mod/Path/PathScripts/PathProfile.py | 12 +- src/Mod/Path/PathScripts/PathUtils.py | 169 ++++++++++++++++++ 5 files changed, 513 insertions(+), 32 deletions(-) create mode 100644 src/Mod/Path/Gui/Resources/panels/ToolControl.ui diff --git a/src/Mod/Path/Gui/Resources/panels/ToolControl.ui b/src/Mod/Path/Gui/Resources/panels/ToolControl.ui new file mode 100644 index 000000000..b974ea5c2 --- /dev/null +++ b/src/Mod/Path/Gui/Resources/panels/ToolControl.ui @@ -0,0 +1,135 @@ + + + ToolControlObject + + + + 0 + 0 + 320 + 503 + + + + Tool Control + + + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + Unknown + + + + + + + Unknown + + + + + + + Unknown + + + + + + + + + + Unknown + + + + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + Horiz. Feed + + + + + + + + + + Vert. Feed + + + + + + + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + Spindle Speed (RPM) + + + + + + + + + + + Forward + + + + + Reverse + + + + + + + + + + + + diff --git a/src/Mod/Path/PathScripts/PathLoadTool.py b/src/Mod/Path/PathScripts/PathLoadTool.py index ecaa721fd..3593dbcda 100644 --- a/src/Mod/Path/PathScripts/PathLoadTool.py +++ b/src/Mod/Path/PathScripts/PathLoadTool.py @@ -120,7 +120,12 @@ class _ViewProviderLoadTool: def setEdit(self, vobj, mode): # this is executed when the object is double-clicked in the tree - pass + FreeCADGui.Control.closeDialog() + taskd = TaskPanel() + taskd.obj = vobj.Object + FreeCADGui.Control.showDialog(taskd) + taskd.setupUi() + return True def unsetEdit(self, vobj, mode): # this is executed when the user cancels or terminates edit mode @@ -167,6 +172,119 @@ PathUtils.addToProject(obj) PathUtils.addToProject(obj) +class TaskPanel: + def __init__(self): + #self.form = FreeCADGui.PySideUic.loadUi(":/panels/ToolControl.ui") + self.form = FreeCADGui.PySideUic.loadUi(FreeCAD.getHomePath() + "Mod/Path/ToolControl.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, "VertFeed"): + self.obj.Label = self.form.tcoName.text() + + if hasattr(self.obj, "VertFeed"): + self.obj.VertFeed = self.form.vertFeed.value() + if hasattr(self.obj, "HorizFeed"): + self.obj.HorizFeed = self.form.horizFeed.value() + if hasattr(self.obj, "SpindleSpeed"): + self.obj.SpindleSpeed = self.form.spindleSpeed.value() + if hasattr(self.obj, "SpindleDir"): + self.obj.SpindleDir = str(self.form.cboSpindleDirection.currentText()) + if hasattr(self.obj, "ToolNumber"): + self.obj.ToolNumber = self.form.ToolNumber.value() + self.obj.Proxy.execute(self.obj) + + def setFields(self): + self.form.vertFeed.setText(str(self.obj.VertFeed.Value)) + self.form.horizFeed.setText(str(self.obj.HorizFeed.Value)) + self.form.spindleSpeed.setText(str(self.obj.SpindleSpeed.Value)) + self.form.cboSpindleDirection.setText(str(self.obj.SpindleDir.Value)) + self.form.ToolNumber.setValue(self.obj.ToolNumber) + + + def open(self): + self.s = SelObserver() + # install the function mode resident + FreeCADGui.Selection.addObserver(self.s) + + + def getStandardButtons(self): + return int(QtGui.QDialogButtonBox.Ok) + + def edit(self, item, column): + if not self.updating: + self.resetObject() + + def resetObject(self, remove=None): + "transfers the values from the widget to the object" + # loc = [] + # h = [] + # l = [] + # a = [] + + # for i in range(self.form.tagTree.topLevelItemCount()): + # it = self.form.tagTree.findItems( + # str(i+1), QtCore.Qt.MatchExactly, 0)[0] + # if (remove is None) or (remove != i): + # if it.text(1): + # x = float(it.text(1).split()[0].rstrip(",")) + # y = float(it.text(1).split()[1].rstrip(",")) + # z = float(it.text(1).split()[2].rstrip(",")) + # loc.append(Vector(x, y, z)) + + # else: + # loc.append(0.0) + # if it.text(2): + # h.append(float(it.text(2))) + # else: + # h.append(4.0) + # if it.text(3): + # l.append(float(it.text(3))) + # else: + # l.append(5.0) + # if it.text(4): + # a.append(float(it.text(4))) + # else: + # a.append(45.0) + + # self.obj.locs = loc + # self.obj.heights = h + # self.obj.lengths = l + # self.obj.angles = a + + # self.obj.touch() + FreeCAD.ActiveDocument.recompute() + + def setupUi(self): + pass + # Connect Signals and Slots + # Base Controls + # self.form.baseList.itemSelectionChanged.connect(self.itemActivated) + self.setFields() + +class SelObserver: + def __init__(self): + pass + + def __del__(self): + pass + + if FreeCAD.GuiUp: # register the FreeCAD command FreeCADGui.addCommand('Path_LoadTool', CommandPathLoadTool()) diff --git a/src/Mod/Path/PathScripts/PathPocket.py b/src/Mod/Path/PathScripts/PathPocket.py index 1e9cac892..92d622412 100644 --- a/src/Mod/Path/PathScripts/PathPocket.py +++ b/src/Mod/Path/PathScripts/PathPocket.py @@ -118,6 +118,23 @@ class ObjectPocket: if baselist is 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 + try: bb = ss.Shape.BoundBox # parent boundbox subobj = ss.Shape.getElement(sub) @@ -126,15 +143,23 @@ class ObjectPocket: obj.ClearanceHeight = bb.ZMax + 5.0 obj.SafeHeight = bb.ZMax + 3.0 - if fbb.ZMax < bb.ZMax: - obj.FinalDepth = fbb.ZMax - else: + if fbb.ZMax == fbb.ZMin and fbb.ZMax == bb.ZMax: # top face + obj.FinalDepth = bb.ZMin + elif fbb.ZMax > fbb.ZMin and fbb.ZMax == bb.ZMax: # vertical face, full cut + obj.FinalDepth = fbb.ZMin + elif fbb.ZMax > fbb.ZMin and fbb.ZMin > bb.ZMin: # internal vertical wall + obj.FinalDepth = fbb.ZMin + elif fbb.ZMax == fbb.ZMin and fbb.ZMax > bb.ZMin: # face/shelf + obj.FinalDepth = fbb.ZMin + else: #catch all 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(translate("Path", "this object already in the list" + "\n")) @@ -219,9 +244,9 @@ class ObjectPocket: offsets = [] nextradius = self.radius result = DraftGeomUtils.pocket2d(shape, nextradius) - + print "did we get something: " + str(result) while result: - # print "Adding " + str(len(result)) + " wires" + print "Adding " + str(len(result)) + " wires" offsets.extend(result) nextradius += self.radius result = DraftGeomUtils.pocket2d(shape, nextradius) @@ -495,7 +520,7 @@ class CommandPathPocket: 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.UseEntry = False') FreeCADGui.doCommand('obj.RampAngle = 3.0') FreeCADGui.doCommand('obj.RampSize = 0.75') FreeCADGui.doCommand('obj.HelixSize = 0.75') @@ -547,6 +572,24 @@ class TaskPanel: self.obj.CutMode = str(self.form.cutMode.currentText()) self.obj.Proxy.execute(self.obj) + def setFields(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]) + + + def open(self): self.s = SelObserver() # install the function mode resident @@ -556,17 +599,31 @@ class TaskPanel: # 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) + # 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. + if len(selection) != 1: + FreeCAD.Console.PrintError(translate("PathProject", "Please select only faces from one solid\n")) + return + sel = selection[0] + if not sel.HasSubObjects: + FreeCAD.Console.PrintError(translate("PathProject", "Please select faces from one solid\n")) + return + if not selection[0].SubObjects[0].ShapeType == "Face": + FreeCAD.Console.PrintError(translate("PathProject", "Please select faces from one solid\n")) + return + for i in sel.SubElementNames: + self.obj.Proxy.addpocketbase(self.obj, sel.Object, i) + + + self.setFields() # defaults may have changed. Reload. self.form.baseList.clear() for i in self.obj.Base: self.form.baseList.addItem(i[0].Name + "." + i[1]) @@ -618,20 +675,6 @@ class TaskPanel: 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 @@ -655,6 +698,12 @@ class TaskPanel: self.form.useStartPoint.clicked.connect(self.getFields) self.form.extraOffset.editingFinished.connect(self.getFields) + self.setFields() + + sel = FreeCADGui.Selection.getSelectionEx() + if len(sel) != 0 and sel[0].HasSubObjects: + self.addBase() + class SelObserver: def __init__(self): diff --git a/src/Mod/Path/PathScripts/PathProfile.py b/src/Mod/Path/PathScripts/PathProfile.py index 098c6f7b2..76319e0d8 100644 --- a/src/Mod/Path/PathScripts/PathProfile.py +++ b/src/Mod/Path/PathScripts/PathProfile.py @@ -58,6 +58,7 @@ class ObjectProfile: obj.addProperty("App::PropertyLinkSubList", "Base", "Path", translate("Parent Object", "The base geometry of this toolpath")) 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::PropertyString", "UserLabel", "Path", translate("Path", "User Assigned Label")) obj.addProperty("App::PropertyEnumeration", "Algorithm", "Algorithm", translate("Path", "The library or algorithm used to generate the path")) obj.Algorithm = ['OCC Native', 'libarea'] @@ -120,6 +121,10 @@ class ObjectProfile: def __setstate__(self, state): return None + # def onChanged(self, obj, prop): + # if prop == "Label": + # print "we're here" + def addprofilebase(self, obj, ss, sub=""): baselist = obj.Base if len(baselist) == 0: # When adding the first base object, guess at heights @@ -146,6 +151,11 @@ class ObjectProfile: obj.ClearanceHeight = 10.0 obj.SafeHeight = 8.0 + if bb.XLength == fbb.XLength and bb.YLength == fbb.YLength: + obj.Side = "Left" + else: + obj.Side = "Right" + item = (ss, sub) if item in baselist: FreeCAD.Console.PrintWarning("this object already in the list" + "\n") @@ -259,6 +269,7 @@ print "y - " + str(point.y) tool = PathUtils.getTool(obj, toolLoad.ToolNumber) self.radius = tool.Diameter/2 obj.ToolNumber = toolLoad.ToolNumber + #obj.Label = obj.Label + "(" + toolLoad.Label + ")" if obj.Base: hfaces = [] @@ -739,7 +750,6 @@ class TaskPanel: sel = FreeCADGui.Selection.getSelectionEx() if len(sel) != 0 and sel[0].HasSubObjects: -# if sel[0].SubObjects[0].ShapeType == "Face": self.addBase() diff --git a/src/Mod/Path/PathScripts/PathUtils.py b/src/Mod/Path/PathScripts/PathUtils.py index 897be775b..0816b792a 100644 --- a/src/Mod/Path/PathScripts/PathUtils.py +++ b/src/Mod/Path/PathScripts/PathUtils.py @@ -482,6 +482,175 @@ def frange(start, stop, step, finish): 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: