diff --git a/src/Mod/Path/CMakeLists.txt b/src/Mod/Path/CMakeLists.txt index 5ade432462..098221bdb7 100644 --- a/src/Mod/Path/CMakeLists.txt +++ b/src/Mod/Path/CMakeLists.txt @@ -42,6 +42,7 @@ SET(PathScripts_SRCS PathScripts/PathFaceProfile.py PathScripts/PathFixture.py PathScripts/PathGeom.py + PathScripts/PathGetPoint.py PathScripts/PathHelix.py PathScripts/PathHelixGui.py PathScripts/PathHop.py diff --git a/src/Mod/Path/Gui/Resources/Path.qrc b/src/Mod/Path/Gui/Resources/Path.qrc index 7eb284ddde..d620a393d1 100644 --- a/src/Mod/Path/Gui/Resources/Path.qrc +++ b/src/Mod/Path/Gui/Resources/Path.qrc @@ -60,6 +60,7 @@ panels/JobEdit.ui panels/PageBaseGeometryEdit.ui panels/PageBaseHoleGeometryEdit.ui + panels/PageBaseLocationEdit.ui panels/PageDepthsEdit.ui panels/PageHeightsEdit.ui panels/PageOpDrillingEdit.ui diff --git a/src/Mod/Path/Gui/Resources/panels/PageBaseLocationEdit.ui b/src/Mod/Path/Gui/Resources/panels/PageBaseLocationEdit.ui new file mode 100644 index 0000000000..913c251ec3 --- /dev/null +++ b/src/Mod/Path/Gui/Resources/panels/PageBaseLocationEdit.ui @@ -0,0 +1,94 @@ + + + Form + + + + 0 + 0 + 415 + 573 + + + + Form + + + + + + + + <html><head/><body><p>List of locations to be processed.</p></body></html> + + + true + + + + X + + + + + Y + + + + + + + + <html><head/><body><p>Edit selected location.</p></body></html> + + + Edit + + + + + + + All locations will be processed using the same operation properties. + + + Qt::AutoText + + + true + + + + + + + <html><head/><body><p>Remove selected location from the list. The operation is no longer applied to them.</p></body></html> + + + Remove + + + + + + + <html><head/><body><p>Opens a newe dialog that lets you add arbitrary locations.</p></body></html> + + + Add + + + + + + + + + baseList + addLocation + removeLocation + editLocation + + + + diff --git a/src/Mod/Path/PathScripts/PathCircularHoleBase.py b/src/Mod/Path/PathScripts/PathCircularHoleBase.py index d47f8b8af8..988167c95b 100644 --- a/src/Mod/Path/PathScripts/PathCircularHoleBase.py +++ b/src/Mod/Path/PathScripts/PathCircularHoleBase.py @@ -104,7 +104,7 @@ class ObjectOp(PathOp.ObjectOp): def opExecute(self, obj): PathLog.track() - if len(obj.Base) == 0: + if len(obj.Base) == 0 and len(obj.Locations) == 0: # Arch PanelSheet features = [] if self.baseIsArchPanel(obj, self.baseobject): @@ -133,6 +133,8 @@ class ObjectOp(PathOp.ObjectOp): if self.isHoleEnabled(obj, base, sub): pos = self.holePosition(obj, base, sub) holes.append({'x': pos.x, 'y': pos.y, 'r': self.holeDiameter(obj, base, sub)}) + for location in obj.Locations: + holes.append({'x': location.x, 'y': location.y, 'r': 0}) if len(holes) > 0: self.circularHoleExecute(obj, holes) @@ -168,9 +170,9 @@ class ObjectOp(PathOp.ObjectOp): def setDepths(self, obj, zmax, zmin, bb): PathLog.track(obj.Label, zmax, zmin, bb) if zmax is None: - zmax = 5 + zmax = bb.ZMax if zmin is None: - zmin = 0 + zmin = bb.ZMin if zmin > zmax: zmax = zmin diff --git a/src/Mod/Path/PathScripts/PathDrilling.py b/src/Mod/Path/PathScripts/PathDrilling.py index 77a0fc4dbf..1a98c92d80 100644 --- a/src/Mod/Path/PathScripts/PathDrilling.py +++ b/src/Mod/Path/PathScripts/PathDrilling.py @@ -52,7 +52,7 @@ class ObjectDrilling(PathCircularHoleBase.ObjectOp): def circularHoleFeatures(self, obj): # drilling works on anything - return PathOp.FeatureBaseGeometry + return PathOp.FeatureBaseGeometry | PathOp.FeatureLocations def initCircularHoleOperation(self, obj): @@ -71,6 +71,9 @@ class ObjectDrilling(PathCircularHoleBase.ObjectOp): self.commandlist.append(Path.Command("(Begin Drilling)")) + # rapid to clearance height + self.commandlist.append(Path.Command('G0', {'Z': obj.ClearanceHeight.Value, 'F': self.vertRapid})) + tiplength = 0.0 if obj.AddTipLength: tiplength = PathUtils.drillTipLength(self.tool) @@ -78,14 +81,13 @@ class ObjectDrilling(PathCircularHoleBase.ObjectOp): holes = PathUtils.sort_jobs(holes, ['x', 'y']) self.commandlist.append(Path.Command('G90')) self.commandlist.append(Path.Command(obj.ReturnLevel)) - # rapid to clearance height - self.commandlist.append(Path.Command('G0', {'Z': obj.ClearanceHeight.Value, 'F': self.vertRapid})) - # rapid to first hole location, with spindle still retracted: - p0 = holes[0] - self.commandlist.append(Path.Command('G0', {'X': p0['x'], 'Y': p0['y'], 'F': self.horizRapid})) - # move tool to clearance plane - self.commandlist.append(Path.Command('G0', {'Z': obj.ClearanceHeight.Value, 'F': self.vertRapid})) + # ml: I'm not sure whey these were here, they seem redundant + ## rapid to first hole location, with spindle still retracted: + #p0 = holes[0] + #self.commandlist.append(Path.Command('G0', {'X': p0['x'], 'Y': p0['y'], 'F': self.horizRapid})) + ## move tool to clearance plane + #self.commandlist.append(Path.Command('G0', {'Z': obj.ClearanceHeight.Value, 'F': self.vertRapid})) cmd = "G81" cmdParams = {} @@ -118,6 +120,14 @@ class ObjectDrilling(PathCircularHoleBase.ObjectOp): def opSetDefaultValues(self, obj): obj.RetractHeight = 10 + def opOnChanged(self, obj, prop): + super(self.__class__, self).opOnChanged(obj, prop) + if prop == 'Locations' and not 'Restore' in obj.State and obj.Locations and not obj.Base: + if not hasattr(self, 'baseobject'): + job = PathUtils.findParentJob(obj) + if job and job.Base: + self.setupDepthsFrom(obj, [], job.Base) + def Create(name): obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name) proxy = ObjectDrilling(obj) diff --git a/src/Mod/Path/PathScripts/PathGetPoint.py b/src/Mod/Path/PathScripts/PathGetPoint.py new file mode 100644 index 0000000000..91e1dadf9f --- /dev/null +++ b/src/Mod/Path/PathScripts/PathGetPoint.py @@ -0,0 +1,158 @@ +# -*- coding: utf-8 -*- + +# *************************************************************************** +# * * +# * Copyright (c) 2017 sliptonic * +# * * +# * 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 Draft +import FreeCAD +import FreeCADGui + +from PySide import QtCore, QtGui +from pivy import coin + +class TaskPanel: + + def __init__(self, formOrig, formPoint): + self.formOrig = formOrig + self.formPoint = formPoint + + self.setupUi() + self.buttonBox = None + + def setupUi(self): + self.formPoint.buttonBox.accepted.connect(self.pointAccept) + self.formPoint.buttonBox.rejected.connect(self.pointReject) + + self.formPoint.ifValueX.editingFinished.connect(self.updatePoint) + self.formPoint.ifValueY.editingFinished.connect(self.updatePoint) + self.formPoint.ifValueZ.editingFinished.connect(self.updatePoint) + + def addEscapeShortcut(self): + # The only way I could get to intercept the escape key, or really any key was + # by creating an action with a shortcut ..... + self.escape = QtGui.QAction(self.formPoint) + self.escape.setText('Done') + self.escape.setShortcut(QtGui.QKeySequence.fromString('Esc')) + QtCore.QObject.connect(self.escape, QtCore.SIGNAL('triggered()'), self.pointDone) + self.formPoint.addAction(self.escape) + + def removeEscapeShortcut(self): + if self.escape: + self.formPoint.removeAction(self.escape) + self.escape = None + + def getPoint(self, whenDone, start=None): + + def displayPoint(p): + self.formPoint.ifValueX.setText(FreeCAD.Units.Quantity(p.x, FreeCAD.Units.Length).UserString) + self.formPoint.ifValueY.setText(FreeCAD.Units.Quantity(p.y, FreeCAD.Units.Length).UserString) + self.formPoint.ifValueZ.setText(FreeCAD.Units.Quantity(p.z, FreeCAD.Units.Length).UserString) + self.formPoint.ifValueX.setFocus() + self.formPoint.ifValueX.selectAll() + + def mouseMove(cb): + event = cb.getEvent() + pos = event.getPosition() + cntrl = event.wasCtrlDown() + shift = event.wasShiftDown() + self.pt = FreeCADGui.Snapper.snap(pos, lastpoint=start, active=cntrl, constrain=shift) + plane = FreeCAD.DraftWorkingPlane + p = plane.getLocalCoords(self.pt) + displayPoint(p) + + def click(cb): + event = cb.getEvent() + if event.getButton() == 1 and event.getState() == coin.SoMouseButtonEvent.DOWN: + accept() + + def accept(): + if start: + self.pointAccept() + else: + self.pointAcceptAndContinue() + + def cancel(): + self.pointCancel() + + self.pointWhenDone = whenDone + self.formOrig.hide() + self.formPoint.show() + self.addEscapeShortcut() + if start: + displayPoint(start) + else: + displayPoint(FreeCAD.Vector(0, 0, 0)) + + self.view = Draft.get3DView() + self.pointCbClick = self.view.addEventCallbackPivy(coin.SoMouseButtonEvent.getClassTypeId(), click) + self.pointCbMove = self.view.addEventCallbackPivy(coin.SoLocation2Event.getClassTypeId(), mouseMove) + + if self.buttonBox: + self.buttonBox.setEnabled(False) + FreeCADGui.Snapper.forceGridOff=True + + def pointFinish(self, ok, cleanup = True): + obj = FreeCADGui.Snapper.lastSnappedObject + + if cleanup: + self.removeGlobalCallbacks() + FreeCADGui.Snapper.off() + if self.buttonBox: + self.buttonBox.setEnabled(True) + self.removeEscapeShortcut() + self.formPoint.hide() + self.formOrig.show() + self.formOrig.setFocus() + + if ok: + self.pointWhenDone(self.pt, obj) + else: + self.pointWhenDone(None, None) + + def pointDone(self): + self.pointFinish(False) + + def pointReject(self): + self.pointFinish(False) + + def pointAccept(self): + self.pointFinish(True) + + def pointAcceptAndContinue(self): + self.pointFinish(True, False) + + def removeGlobalCallbacks(self): + if hasattr(self, 'view') and self.view: + if self.pointCbClick: + self.view.removeEventCallbackPivy(coin.SoMouseButtonEvent.getClassTypeId(), self.pointCbClick) + self.pointCbClick = None + if self.pointCbMove: + self.view.removeEventCallbackPivy(coin.SoLocation2Event.getClassTypeId(), self.pointCbMove) + self.pointCbMove = None + self.view = None + + def updatePoint(self): + x = FreeCAD.Units.Quantity(self.formPoint.ifValueX.text()).Value + y = FreeCAD.Units.Quantity(self.formPoint.ifValueY.text()).Value + z = FreeCAD.Units.Quantity(self.formPoint.ifValueZ.text()).Value + self.pt = FreeCAD.Vector(x, y, z) + diff --git a/src/Mod/Path/PathScripts/PathOp.py b/src/Mod/Path/PathScripts/PathOp.py index 1bf9151259..db293e659b 100644 --- a/src/Mod/Path/PathScripts/PathOp.py +++ b/src/Mod/Path/PathScripts/PathOp.py @@ -50,10 +50,11 @@ FeatureHeights = 0x0004 FeatureStartPoint = 0x0008 FeatureFinishDepth = 0x0010 FeatureStepDown = 0x0020 -FeatureBaseVertexes = 0x1000 -FeatureBaseEdges = 0x2000 -FeatureBaseFaces = 0x4000 -FeatureBasePanels = 0x8000 +FeatureBaseVertexes = 0x0100 +FeatureBaseEdges = 0x0200 +FeatureBaseFaces = 0x0400 +FeatureBasePanels = 0x0800 +FeatureLocations = 0x1000 FeatureBaseGeometry = FeatureBaseVertexes | FeatureBaseFaces | FeatureBaseEdges | FeatureBasePanels @@ -66,27 +67,32 @@ class ObjectOp(object): obj.addProperty("App::PropertyString", "Comment", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "An optional comment for this Operation")) obj.addProperty("App::PropertyString", "UserLabel", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "User Assigned Label")) - if FeatureBaseGeometry & self.opFeatures(obj): + features = self.opFeatures(obj) + + if FeatureBaseGeometry & features: obj.addProperty("App::PropertyLinkSubList", "Base", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "The base geometry for this operation")) - if FeatureTool & self.opFeatures(obj): + if FeatureLocations & features: + obj.addProperty("App::PropertyVectorList", "Locations", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "Base locations for this operation")) + + if FeatureTool & features: obj.addProperty("App::PropertyLink", "ToolController", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "The tool controller that will be used to calculate the path")) - if FeatureDepths & self.opFeatures(obj): + if FeatureDepths & features: obj.addProperty("App::PropertyDistance", "StartDepth", "Depth", QtCore.QT_TRANSLATE_NOOP("App::Property", "Starting Depth of Tool- first cut depth in Z")) obj.addProperty("App::PropertyDistance", "FinalDepth", "Depth", QtCore.QT_TRANSLATE_NOOP("App::Property", "Final Depth of Tool- lowest value in Z")) - if FeatureStepDown & self.opFeatures(obj): + if FeatureStepDown & features: obj.addProperty("App::PropertyDistance", "StepDown", "Depth", QtCore.QT_TRANSLATE_NOOP("App::Property", "Incremental Step Down of Tool")) - if FeatureFinishDepth & self.opFeatures(obj): + if FeatureFinishDepth & features: obj.addProperty("App::PropertyDistance", "FinishDepth", "Depth", QtCore.QT_TRANSLATE_NOOP("App::Property", "Maximum material removed on final pass.")) - if FeatureHeights & self.opFeatures(obj): + if FeatureHeights & features: obj.addProperty("App::PropertyDistance", "ClearanceHeight", "Depth", QtCore.QT_TRANSLATE_NOOP("App::Property", "The height needed to clear clamps and obstructions")) obj.addProperty("App::PropertyDistance", "SafeHeight", "Depth", QtCore.QT_TRANSLATE_NOOP("App::Property", "Rapid Safety Height between locations.")) - if FeatureStartPoint & self.opFeatures(obj): + if FeatureStartPoint & features: obj.addProperty("App::PropertyVector", "StartPoint", "Start Point", QtCore.QT_TRANSLATE_NOOP("App::Property", "The start point of this path")) obj.addProperty("App::PropertyBool", "UseStartPoint", "Start Point", QtCore.QT_TRANSLATE_NOOP("App::Property", "make True, if specifying a Start Point")) @@ -116,21 +122,23 @@ class ObjectOp(object): obj.Active = True - if FeatureTool & self.opFeatures(obj): + features = self.opFeatures(obj) + + if FeatureTool & features: obj.ToolController = PathUtils.findToolController(obj) - if FeatureDepths & self.opFeatures(obj): + if FeatureDepths & features: obj.StartDepth = 1.0 obj.FinalDepth = 0.0 - if FeatureStepDown & self.opFeatures(obj): + if FeatureStepDown & features: obj.StepDown = 1.0 - if FeatureHeights & self.opFeatures(obj): + if FeatureHeights & features: obj.ClearanceHeight = 10.0 obj.SafeHeight = 8.0 - if FeatureStartPoint & self.opFeatures(obj): + if FeatureStartPoint & features: obj.UseStartPoint = False self.opSetDefaultValues(obj) diff --git a/src/Mod/Path/PathScripts/PathOpGui.py b/src/Mod/Path/PathScripts/PathOpGui.py index 30cac5acf9..cacf200214 100644 --- a/src/Mod/Path/PathScripts/PathOpGui.py +++ b/src/Mod/Path/PathScripts/PathOpGui.py @@ -24,6 +24,7 @@ import FreeCAD import FreeCADGui +import PathScripts.PathGetPoint as PathGetPoint import PathScripts.PathLog as PathLog import PathScripts.PathSelection as PathSelection import PathScripts.PathOp as PathOp @@ -157,6 +158,8 @@ class TaskPanelPage(object): # subclass interface def initPage(self, obj): pass + def modifyStandardButtons(self, buttonBox): + pass def getForm(self): pass def getFields(self, obj): @@ -316,6 +319,108 @@ class TaskPanelBaseGeometryPage(TaskPanelPage): self.setFields(obj) +class TaskPanelBaseLocationPage(TaskPanelPage): + + DataLocation = QtCore.Qt.ItemDataRole.UserRole + + def getForm(self): + self.formLoc = FreeCADGui.PySideUic.loadUi(":/panels/PageBaseLocationEdit.ui") + self.formPts = FreeCADGui.PySideUic.loadUi(":/panels/PointEdit.ui") + form = QtGui.QWidget() + self.layout = QtGui.QVBoxLayout(form) + form.setWindowTitle(self.formLoc.windowTitle()) + form.setSizePolicy(self.formLoc.sizePolicy()) + self.formLoc.setParent(form) + self.formPts.setParent(form) + self.layout.addWidget(self.formLoc) + self.layout.addWidget(self.formPts) + self.formPts.hide() + self.getPoint = PathGetPoint.TaskPanel(self.formLoc, self.formPts) + return form + + def modifyStandardButtons(self, buttonBox): + self.getPoint.buttonBox = buttonBox + + def getTitle(self, obj): + return translate("PathOp", "Base Location") + def getFields(self, obj): + pass + + def setFields(self, obj): + self.formLoc.baseList.blockSignals(True) + self.formLoc.baseList.clearContents() + self.formLoc.baseList.setRowCount(0) + for location in self.obj.Locations: + self.formLoc.baseList.insertRow(self.formLoc.baseList.rowCount()) + + item = QtGui.QTableWidgetItem("%.2f" % location.x) + item.setData(self.DataLocation, location.x) + self.formLoc.baseList.setItem(self.formLoc.baseList.rowCount()-1, 0, item) + + item = QtGui.QTableWidgetItem("%.2f" % location.y) + item.setData(self.DataLocation, location.y) + self.formLoc.baseList.setItem(self.formLoc.baseList.rowCount()-1, 1, item) + self.formLoc.baseList.resizeColumnToContents(0) + self.formLoc.baseList.blockSignals(False) + + def removeLocation(self): + deletedRows = [] + selected = self.formLoc.baseList.selectedItems() + for item in selected: + row = self.formLoc.baseList.row(item) + if not row in deletedRows: + deletedRows.append(row) + self.formLoc.baseList.removeRow(row) + self.updateLocations() + FreeCAD.ActiveDocument.recompute() + + def updateLocations(self): + PathLog.track() + locations = [] + for i in range(self.formLoc.baseList.rowCount()): + x = self.formLoc.baseList.item(i, 0).data(self.DataLocation) + y = self.formLoc.baseList.item(i, 1).data(self.DataLocation) + location = FreeCAD.Vector(x, y, 0) + locations.append(location) + self.obj.Locations = locations + + def addLocation(self): + self.getPoint.getPoint(self.addLocationAt) + + def addLocationAt(self, point, obj): + if point: + locations = self.obj.Locations + locations.append(point) + self.obj.Locations = locations + FreeCAD.ActiveDocument.recompute() + + def editLocation(self): + selected = self.formLoc.baseList.selectedItems() + if selected: + row = self.formLoc.baseList.row(item) + self.editRow = row + x = self.formLoc.baseList.item(row, 0).data(self.DataLocation) + y = self.formLoc.baseList.item(row, 1).data(self.DataLocation) + start = FreeCAD.Vector(x, y, 0) + self.getPoint.getPoint(self.editLocationAt, start) + + def editLocationAt(self, point, obj): + if point: + self.formLoc.baseList.item(self.editRow, 0).setData(self.DataLocation, point.x) + self.formLoc.baseList.item(self.editRow, 1).setData(self.DataLocation, point.y) + self.updateLocations() + FreeCAD.ActiveDocument.recompute() + + def registerSignalHandlers(self, obj): + self.formLoc.addLocation.clicked.connect(self.addLocation) + self.formLoc.removeLocation.clicked.connect(self.removeLocation) + self.formLoc.editLocation.clicked.connect(self.editLocation) + + def pageUpdateData(self, obj, prop): + if prop in ['Locations']: + self.setFields(obj) + + class TaskPanelHeightsPage(TaskPanelPage): def getForm(self): return FreeCADGui.PySideUic.loadUi(":/panels/PageHeightsEdit.ui") @@ -400,6 +505,12 @@ class TaskPanel(object): else: self.featurePages.append(TaskPanelBaseGeometryPage(obj, features)) + if PathOp.FeatureLocations & features: + if hasattr(opPage, 'taskPanelBaseLocationPage'): + self.featurePages.append(opPage.taskPanelBaseLocationPage(obj, features)) + else: + self.featurePages.append(TaskPanelBaseLocationPage(obj, features)) + if PathOp.FeatureDepths & features: if hasattr(opPage, 'taskPanelDepthsPage'): self.featurePages.append(opPage.taskPanelDepthsPage(obj, features)) @@ -483,6 +594,10 @@ class TaskPanel(object): self.setClean() FreeCAD.ActiveDocument.recompute() + def modifyStandardButtons(self, buttonBox): + for page in self.featurePages: + page.modifyStandardButtons(buttonBox) + def panelGetFields(self): PathLog.track() for page in self.featurePages: