From b4ae9b69b222b60a10f9e01061066d3c2b80d2cd Mon Sep 17 00:00:00 2001 From: Pekka Roivainen Date: Mon, 1 May 2017 23:01:35 +0300 Subject: [PATCH] Drilling refactoring and cleanup --- .../Path/Gui/Resources/panels/DrillingEdit.ui | 48 +-- src/Mod/Path/PathScripts/PathDrilling.py | 286 +++++++++--------- 2 files changed, 174 insertions(+), 160 deletions(-) diff --git a/src/Mod/Path/Gui/Resources/panels/DrillingEdit.ui b/src/Mod/Path/Gui/Resources/panels/DrillingEdit.ui index 159523c459..a503164409 100644 --- a/src/Mod/Path/Gui/Resources/panels/DrillingEdit.ui +++ b/src/Mod/Path/Gui/Resources/panels/DrillingEdit.ui @@ -46,38 +46,26 @@ - - - Drag to reorder, then update. - - - QAbstractItemView::DragDrop - - - Qt::MoveAction - - - false - + - + - Add item selected in window. + Enable all features in the list. - add + Enable All - + - Remove Item selected in list, then update. + Disable all features in the list - Remove + Disable all @@ -91,7 +79,27 @@ - + + + + Enable selected items in the list + + + Enable + + + + + + + Disable selected items in the list. + + + Disable + + + + All objects will be processed using the same operation properties. diff --git a/src/Mod/Path/PathScripts/PathDrilling.py b/src/Mod/Path/PathScripts/PathDrilling.py index 7695d3041d..8672ad350f 100644 --- a/src/Mod/Path/PathScripts/PathDrilling.py +++ b/src/Mod/Path/PathScripts/PathDrilling.py @@ -58,10 +58,12 @@ except AttributeError: class ObjectDrilling: def __init__(self, obj): - # Base & location - obj.addProperty("App::PropertyLinkSubList", "Base","Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "The base geometry of this toolpath")) - #obj.addProperty("App::PropertyVectorList", "Positions", "Tag", QtCore.QT_TRANSLATE_NOOP("PathDressup_HoldingTags", "Locations of insterted holding tags")) - obj.addProperty("App::PropertyIntegerList", "Disabled", "Path", QtCore.QT_TRANSLATE_NOOP("PathDressup_HoldingTags", "Ids of disabled holding tags")) + #Properties of the holes + obj.addProperty("App::PropertyStringList", "Names", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "Names of the holes")) + obj.addProperty("App::PropertyVectorList", "Positions", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "Locations of insterted holes")) + obj.addProperty("App::PropertyIntegerList", "Enabled", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "Enable/disable status of the holes")) + obj.addProperty("App::PropertyFloatList", "Diameters", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "Diameters holes")) + # General Properties obj.addProperty("App::PropertyBool", "Active", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property","Make False, to prevent operation from generating code")) @@ -82,6 +84,7 @@ class ObjectDrilling: # Tool Properties obj.addProperty("App::PropertyLink", "ToolController", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "The tool controller that will be used to calculate the path")) + obj.Proxy = self self.vertFeed = 0.0 self.horizFeed = 0.0 @@ -94,6 +97,37 @@ class ObjectDrilling: def __setstate__(self, state): return None + def sort_locations(self, locations): + """ sort holes by the nearest neighbor method + originally written by m0n5t3r for PathHelix + """ + from Queue import PriorityQueue + + def sqdist(a, b): + """ square Euclidean distance """ + return (a['x'] - b['x'] ) ** 2 + (a['y'] - b['y']) ** 2 + + def find_closest(location_list, location, dist): + q = PriorityQueue() + + for j in location_list: + q.put((dist(j, location) + location['x'], j)) + + prio, result = q.get() + return result + + out = [] + zero = {'x': 0,'y': 0} + + out.append(find_closest(locations, zero, sqdist)) + + while locations: + closest = find_closest(locations, out[-1], sqdist) + out.append(closest) + locations.remove(closest) + + return out + def onChanged(self, obj, prop): pass # if prop == "UserLabel": @@ -121,51 +155,42 @@ class ObjectDrilling: else: self.radius = tool.Diameter/2 - if not obj.Base: + if len(obj.Names) == 0: parentJob = PathUtils.findParentJob(obj) if parentJob is None: return baseobject = parentJob.Base - if baseobject is None: - return holes = self.findHoles(obj, baseobject.Shape) - for hole in holes: - self.addDrillableLocation(obj, baseobject, hole[0]) + names = [] + positions = [] + enabled = [] + diameters = [] + for h in holes: + names.append(h['featureName']) + positions.append(FreeCAD.Vector(h['x'], h['y'],0)) + enabled.append(1) + diameters.append(h['d']) + obj.Names = names + obj.Positions = positions + obj.Enabled = enabled + obj.Diameters = diameters + locations = [] output = "(Begin Drilling)\n" - if obj.Base: - for loc in obj.Base: - #print loc - for sub in loc[1]: - #locations.append(self._findDrillingVector(loc)) - - if "Face" in sub or "Edge" in sub: - s = getattr(loc[0].Shape, sub) - else: - s = loc[0].Shape - - if s.ShapeType in ['Wire', 'Edge']: - X = s.Edges[0].Curve.Center.x - Y = s.Edges[0].Curve.Center.y - Z = s.Edges[0].Curve.Center.z - elif s.ShapeType in ['Vertex']: - X = s.Point.x - Y = s.Point.y - Z = s.Point.z - elif s.ShapeType in ['Face']: - #if abs(s.normalAt(0, 0).z) == 1: # horizontal face - X = s.CenterOfMass.x - Y = s.CenterOfMass.y - Z = s.CenterOfMass.z - locations.append(FreeCAD.Vector(X, Y, Z)) + if len(obj.Names) > 0: + for i in range(len(obj.Names)): + if(obj.Enabled[i]>0): + locations.append({'x':obj.Positions[i].x, 'y': obj.Positions[i].y}) + locations = self.sort_locations(locations) output += "G90 G98\n" # rapid to clearance height output += "G0 Z" + str(obj.ClearanceHeight.Value) + "F " + PathUtils.fmt(self.vertRapid) + "\n" # rapid to first hole location, with spindle still retracted: + p0 = locations[0] - output += "G0 X" + fmt(p0.x) + " Y" + fmt(p0.y) + "F " + PathUtils.fmt(self.horizRapid) + "\n" + output += "G0 X" + fmt(p0['x']) + " Y" + fmt(p0['y']) + "F " + PathUtils.fmt(self.horizRapid) + "\n" # move tool to clearance plane output += "G0 Z" + fmt(obj.ClearanceHeight.Value) + "F " + PathUtils.fmt(self.vertRapid) + "\n" pword = "" @@ -180,8 +205,8 @@ class ObjectDrilling: cmd = "G81" for p in locations: output += cmd + \ - " X" + fmt(p.x) + \ - " Y" + fmt(p.y) + \ + " X" + fmt(p['x']) + \ + " Y" + fmt(p['y']) + \ " Z" + fmt(obj.FinalDepth.Value) + qword + pword + \ " R" + str(obj.RetractHeight.Value) + \ " F" + str(self.vertFeed) + "\n" \ @@ -214,7 +239,7 @@ class ObjectDrilling: x = e.Curve.Center.x y = e.Curve.Center.y diameter = e.BoundBox.XLength - holelist.append((candidateEdgeName, e, x, y, diameter)) + holelist.append({'featureName':candidateEdgeName, 'feature':e, 'x':x, 'y':y, 'd':diameter, 'enabled': True}) else: PathLog.debug("shape is not planar") for i in range(len(shape.Faces)): @@ -225,38 +250,11 @@ class ObjectDrilling: x = f.Surface.Center.x y = f.Surface.Center.y diameter = f.BoundBox.XLength - holelist.append((candidateFaceName, f, x, y, diameter)) + holelist.append({'featureName':candidateFaceName, 'feature':f, 'x':x, 'y':y, 'd':diameter, 'enabled': True}) PathLog.debug("holes found: {}".format(holelist)) return holelist - def addDrillableLocation(self, obj, ss, sub=""): - PathLog.track('ss: {} sub: {}'.format(ss.Label, sub)) - baselist = obj.Base - item = (ss, sub) - 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 - obj.RetractHeight = bb.ZMax + 1.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 - obj.RetractHeight = 6.0 - baselist.append(item) - - obj.Base = baselist - #self.execute(obj) class _ViewProviderDrill: @@ -336,8 +334,6 @@ class TaskPanel: self.form = FreeCADGui.PySideUic.loadUi(":/panels/DrillingEdit.ui") def accept(self): - #self.getFields() - FreeCADGui.ActiveDocument.resetEdit() FreeCADGui.Control.closeDialog() FreeCAD.ActiveDocument.recompute() @@ -369,6 +365,31 @@ class TaskPanel: self.obj.ToolController = tc self.obj.Proxy.execute(self.obj) + def updateFeatureList(self): + + self.form.baseList.itemChanged.disconnect(self.checkedChanged) #disconnect this slot while creating objects + self.form.baseList.clear() + self.form.baseList.setColumnCount(2) + self.form.baseList.setRowCount(0) + self.form.baseList.setHorizontalHeaderLabels(["Feature", "Diameter"]) + self.form.baseList.horizontalHeader().setResizeMode(QtGui.QHeaderView.Stretch) + + for i in range(len(self.obj.Names)): + self.form.baseList.insertRow(self.form.baseList.rowCount()) + item = QtGui.QTableWidgetItem(self.obj.Names[i]) + item.setFlags(item.flags() | QtCore.Qt.ItemIsUserCheckable) + if self.obj.Enabled[i] == True: + item.setCheckState(QtCore.Qt.Checked) + else: + item.setCheckState(QtCore.Qt.Unchecked) + self.form.baseList.setItem(self.form.baseList.rowCount()-1,0,item) + item = QtGui.QTableWidgetItem("{:.3f}".format(self.obj.Diameters[i])) + self.form.baseList.setItem(self.form.baseList.rowCount()-1,1,item) + + self.form.baseList.itemChanged.connect(self.checkedChanged) + + self.form.baseList.setSortingEnabled(True) + def setFields(self): PathLog.track() self.form.startDepth.setText(FreeCAD.Units.Quantity(self.obj.StartDepth.Value, FreeCAD.Units.Length).UserString) @@ -378,10 +399,7 @@ class TaskPanel: self.form.clearanceHeight.setText(FreeCAD.Units.Quantity(self.obj.ClearanceHeight.Value, FreeCAD.Units.Length).UserString) self.form.retractHeight.setText(FreeCAD.Units.Quantity(self.obj.RetractHeight.Value, FreeCAD.Units.Length).UserString) - self.form.baseList.clear() - for i in self.obj.Base: - for sub in i[1]: - self.form.baseList.addItem(i[0].Name + "." + sub) + self.updateFeatureList() controllers = PathUtils.getToolControllers(self.obj) labels = [c.Label for c in controllers] @@ -402,81 +420,68 @@ class TaskPanel: self.s = SelObserver() 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 Drillable Location\n")) - # return - # for s in selection: - # if s.HasSubObjects: - # for i in s.SubElementNames: - # self.obj.Proxy.addDrillableLocation(self.obj, s.Object, i) - # else: - # self.obj.Proxy.addDrillableLocation(self.obj, s.Object) - - # self.setFields() # defaults may have changed. Reload. - # self.form.baseList.clear() - - # for i in self.obj.Base: - # for sub in i[1]: - # self.form.baseList.addItem(i[0].Name + "." + sub) - - def deleteBase(self): - dlist = self.form.baseList.selectedItems() - for d in dlist: - - newlist = [] - for i in self.obj.Base[0][1]: - if not i == d.text().partition(".")[2]: - newlist.append(i) - bodyObj = self.obj.Base[0][0] - self.obj.Base = [] - newB = [(bodyObj, newlist)] - self.obj.Base = newB - self.form.baseList.takeItem(self.form.baseList.row(d)) - 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) + if i.column() == 0: + + parentJob = PathUtils.findParentJob(self.obj) + obj = parentJob.Base + if i.text() != "": + FreeCADGui.Selection.addSelection(obj, i.text()) + 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(".") + def checkedChanged(self): + enabledlist = self.obj.Enabled - # obj = FreeCAD.ActiveDocument.getObject(objstring[0]) - # item = (obj, str(objstring[2])) - # newlist.append(item) - # self.obj.Base = newlist + for i in xrange(0,self.form.baseList.rowCount()): + try: + ind = self.obj.Names.index(self.form.baseList.item(i,0).text()) + if self.form.baseList.item(i,0).checkState() == QtCore.Qt.Checked: + enabledlist[ind] = 1 + else: + enabledlist[ind] = 0 + except: + PathLog.track("Not found:"+self.form.baseList.item(i,0).text()+ " in "+str(self.obj.Names)) - # self.obj.Proxy.execute(self.obj) - # FreeCAD.ActiveDocument.recompute() + self.obj.Enabled = enabledlist + FreeCAD.ActiveDocument.recompute() + def enableAll(self): + for i in xrange(0,self.form.baseList.rowCount()): + self.form.baseList.item(i,0).setCheckState(QtCore.Qt.Checked) + + def enableSelected(self): + slist = self.form.baseList.selectedItems() + for i in slist: + r = i.row() + self.form.baseList.item(r,0).setCheckState(QtCore.Qt.Checked) + + def disableAll(self): + for i in xrange(0,self.form.baseList.rowCount()): + self.form.baseList.item(i,0).setCheckState(QtCore.Qt.Unchecked) + + def disableSelected(self): + slist = self.form.baseList.selectedItems() + for i in slist: + r = i.row() + self.form.baseList.item(r,0).setCheckState(QtCore.Qt.Unchecked) def findAll(self): - self.obj.Base = [] + """ Reset the list of features by running the findHoles again """ + self.obj.Names = [] + self.obj.Diameters = [] + self.obj.Enabled = [] + self.obj.Positions = [] + self.obj.Proxy.execute(self.obj) - self.form.baseList.clear() - for i in self.obj.Base: - for sub in i[1]: - self.form.baseList.addItem(i[0].Name + "." + sub) + self.updateFeatureList() + FreeCAD.ActiveDocument.recompute() - Freecad.ActiveDocument.recompute() def getStandardButtons(self): return int(QtGui.QDialogButtonBox.Ok) @@ -490,17 +495,18 @@ class TaskPanel: self.form.safeHeight.editingFinished.connect(self.getFields) self.form.clearanceHeight.editingFinished.connect(self.getFields) - #self.form.addBase.clicked.connect(self.addBase) - self.form.deleteBase.clicked.connect(self.deleteBase) + #buttons + self.form.uiEnableAll.clicked.connect(self.enableAll) + self.form.uiEnableSelected.clicked.connect(self.enableSelected) + self.form.uiDisableAll.clicked.connect(self.disableAll) + self.form.uiDisableSelected.clicked.connect(self.disableSelected) self.form.uiFindAllHoles.clicked.connect(self.findAll) - #self.form.reorderBase.clicked.connect(self.reorderBase) + self.form.baseList.itemSelectionChanged.connect(self.itemActivated) - self.form.uiToolController.currentIndexChanged.connect(self.getFields) + self.form.baseList.itemChanged.connect(self.checkedChanged) - # sel = FreeCADGui.Selection.getSelectionEx() - # if len(sel) != 0 and sel[0].HasSubObjects: - # self.addBase() + self.form.uiToolController.currentIndexChanged.connect(self.getFields) self.setFields()