diff --git a/src/Mod/Path/CMakeLists.txt b/src/Mod/Path/CMakeLists.txt index 098bb114dd..1a51ebc872 100644 --- a/src/Mod/Path/CMakeLists.txt +++ b/src/Mod/Path/CMakeLists.txt @@ -46,6 +46,7 @@ SET(PathScripts_SRCS PathScripts/PathDressupTag.py PathScripts/PathDressupTagGui.py PathScripts/PathDressupTagPreferences.py + PathScripts/PathDressupZCorrect.py PathScripts/PathDrilling.py PathScripts/PathDrillingGui.py PathScripts/PathEngrave.py @@ -82,6 +83,8 @@ SET(PathScripts_SRCS PathScripts/PathPreferences.py PathScripts/PathPreferencesPathDressup.py PathScripts/PathPreferencesPathJob.py + PathScripts/PathProbe.py + PathScripts/PathProbeGui.py PathScripts/PathProfileBase.py PathScripts/PathProfileBaseGui.py PathScripts/PathProfileContour.py diff --git a/src/Mod/Path/Gui/Resources/Path.qrc b/src/Mod/Path/Gui/Resources/Path.qrc index b461c8cdf6..ca09e140b8 100644 --- a/src/Mod/Path/Gui/Resources/Path.qrc +++ b/src/Mod/Path/Gui/Resources/Path.qrc @@ -37,6 +37,7 @@ icons/Path-Plane.svg icons/Path-Pocket.svg icons/Path-Post.svg + icons/Path-Probe.svg icons/Path-Profile-Edges.svg icons/Path-Profile-Face.svg icons/Path-Profile.svg @@ -102,6 +103,7 @@ panels/PageOpHelixEdit.ui panels/PageOpPocketExtEdit.ui panels/PageOpPocketFullEdit.ui + panels/PageOpProbeEdit.ui panels/PageOpProfileFullEdit.ui panels/PageOpSurfaceEdit.ui panels/PathEdit.ui @@ -114,6 +116,7 @@ panels/ToolEditor.ui panels/ToolLibraryEditor.ui panels/TaskPathSimulator.ui + panels/ZCorrectEdit.ui preferences/PathDressupHoldingTags.ui preferences/PathJob.ui translations/Path_af.qm diff --git a/src/Mod/Path/Gui/Resources/icons/Path-Probe.svg b/src/Mod/Path/Gui/Resources/icons/Path-Probe.svg new file mode 100644 index 0000000000..63eae06c30 --- /dev/null +++ b/src/Mod/Path/Gui/Resources/icons/Path-Probe.svg @@ -0,0 +1,666 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + Path-Drilling + 2015-07-04 + http://www.freecadweb.org/wiki/index.php?title=Artwork + + + FreeCAD + + + FreeCAD/src/Mod/Path/Gui/Resources/icons/Path-Drilling.svg + + + FreeCAD LGPL2+ + + + https://www.gnu.org/copyleft/lesser.html + + + [agryson] Alexander Gryson + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Mod/Path/Gui/Resources/panels/PageOpProbeEdit.ui b/src/Mod/Path/Gui/Resources/panels/PageOpProbeEdit.ui new file mode 100644 index 0000000000..f222080379 --- /dev/null +++ b/src/Mod/Path/Gui/Resources/panels/PageOpProbeEdit.ui @@ -0,0 +1,186 @@ + + + Form + + + + 0 + 0 + 424 + 376 + + + + Form + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + + + ToolController + + + + + + + <html><head/><body><p>The tool and its settings to be used for this operation.</p></body></html> + + + + + + + + + + Probe Grid Points + + + + + + 3 + + + 1000 + + + + + + + 3 + + + 1000 + + + + + + + X: + + + + + + + Y: + + + + + + + + + + Probe + + + + + + Y Offset + + + + + + + + + + + + + + X Offset + + + + + + + + + + + + + + + + + Output + + + + + + File Name + + + + + + + <html><head/><body><p>Enter the filename where the probe points should be written.</p></body></html> + + + ProbePoints.txt + + + + + + + ... + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + Gui::InputField + QLineEdit +
Gui/InputField.h
+
+
+ + +
diff --git a/src/Mod/Path/Gui/Resources/panels/ZCorrectEdit.ui b/src/Mod/Path/Gui/Resources/panels/ZCorrectEdit.ui new file mode 100644 index 0000000000..36d58f0c6f --- /dev/null +++ b/src/Mod/Path/Gui/Resources/panels/ZCorrectEdit.ui @@ -0,0 +1,92 @@ + + + TaskPanel + + + + 0 + 0 + 376 + 387 + + + + Z Depth Correction + + + + + + QFrame::NoFrame + + + 0 + + + + + 0 + 0 + 358 + 340 + + + + Dressup + + + + + + Probe Points File + + + + + + ... + + + + + + + File Name + + + + + + + <html><head/><body><p>Enter the filename containing the probe data</p></body></html> + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + + + diff --git a/src/Mod/Path/InitGui.py b/src/Mod/Path/InitGui.py index 0657fac577..dc638a9299 100644 --- a/src/Mod/Path/InitGui.py +++ b/src/Mod/Path/InitGui.py @@ -87,12 +87,12 @@ class PathWorkbench (Workbench): # build commands list projcmdlist = ["Path_Job", "Path_Post"] toolcmdlist = ["Path_Inspect", "Path_Simulator", "Path_ToolLibraryEdit", "Path_SelectLoop", "Path_OpActiveToggle"] - prepcmdlist = ["Path_Fixture", "Path_Comment", "Path_Stop", "Path_Custom"] + prepcmdlist = ["Path_Fixture", "Path_Comment", "Path_Stop", "Path_Custom", "Path_Probe"] twodopcmdlist = ["Path_Contour", "Path_Profile_Faces", "Path_Profile_Edges", "Path_Pocket_Shape", "Path_Drilling", "Path_MillFace", "Path_Helix", "Path_Adaptive" ] threedopcmdlist = ["Path_Pocket_3D"] engravecmdlist = ["Path_Engrave", "Path_Deburr"] modcmdlist = ["Path_OperationCopy", "Path_Array", "Path_SimpleCopy" ] - dressupcmdlist = ["Path_DressupAxisMap", "Path_DressupPathBoundary", "Path_DressupDogbone", "Path_DressupDragKnife", "Path_DressupLeadInOut", "Path_DressupRampEntry", "Path_DressupTag"] + dressupcmdlist = ["Path_DressupAxisMap", "Path_DressupPathBoundary", "Path_DressupDogbone", "Path_DressupDragKnife", "Path_DressupLeadInOut", "Path_DressupRampEntry", "Path_DressupTag", "Path_DressupZCorrect"] extracmdlist = [] #modcmdmore = ["Path_Hop",] #remotecmdlist = ["Path_Remote"] diff --git a/src/Mod/Path/PathScripts/PathDressupZCorrect.py b/src/Mod/Path/PathScripts/PathDressupZCorrect.py new file mode 100644 index 0000000000..c035451829 --- /dev/null +++ b/src/Mod/Path/PathScripts/PathDressupZCorrect.py @@ -0,0 +1,343 @@ +# -*- coding: utf-8 -*- + +# *************************************************************************** +# * * +# * Copyright (c) 2018 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 * +# * * +# * Bilinear interpolation code modified heavily from the interpolation * +# * library https://github.com/pmav99/interpolation * +# * Copyright (c) 2013 by Panagiotis Mavrogiorgos * +# * * +# *************************************************************************** +import FreeCAD +import FreeCADGui +import Part +import Path +import PathScripts.PathGeom as PathGeom +import PathScripts.PathLog as PathLog +import PathScripts.PathUtils as PathUtils + +from PySide import QtCore, QtGui + +"""Z Depth Correction Dressup. This dressup takes a probe file as input and does bilinear interpolation of the Zdepths to correct for a surface which is not parallel to the milling table/bed. The probe file should conform to the format specified by the linuxcnc G38 probe logging: 9-number coordinate consisting of XYZABCUVW http://linuxcnc.org/docs/html/gcode/g-code.html#gcode:g38 +""" + +LOG_MODULE = PathLog.thisModule() + +if False: + PathLog.setLevel(PathLog.Level.DEBUG, LOG_MODULE) + PathLog.setLevel(PathLog.Level.DEBUG, LOG_MODULE) +else: + PathLog.setLevel(PathLog.Level.NOTICE, LOG_MODULE) + + +# Qt tanslation handling +def translate(context, text, disambig=None): + return QtCore.QCoreApplication.translate(context, text, disambig) + + +movecommands = ['G1', 'G01', 'G2', 'G02', 'G3', 'G03'] +rapidcommands = ['G0', 'G00'] +arccommands = ['G2', 'G3', 'G02', 'G03'] + + +class ObjectDressup: + + def __init__(self, obj): + obj.addProperty("App::PropertyLink", "Base", "Path", QtCore.QT_TRANSLATE_NOOP("Path_DressupAxisMap", "The base path to modify")) + obj.addProperty("App::PropertyFile", "probefile", "ProbeData", QtCore.QT_TRANSLATE_NOOP("Path_DressupZCorrect", "The point file from the surface probing.")) + obj.Proxy = self + obj.addProperty("Part::PropertyPartShape", "interpSurface", "Path") + obj.setEditorMode('interpSurface', 2) # hide + obj.addProperty("App::PropertyDistance", "ArcInterpolate", "Interpolate", QtCore.QT_TRANSLATE_NOOP("Path_DressupZCorrect", "Deflection distance for arc interpolation")) + obj.addProperty("App::PropertyDistance", "SegInterpolate", "Interpolate", QtCore.QT_TRANSLATE_NOOP("Path_DressupZCorrectp", "break segments into smaller segments of this length.")) + obj.ArcInterpolate = 0.1 + obj.SegInterpolate = 1.0 + + def __getstate__(self): + return None + + def __setstate__(self, state): + return None + + def onChanged(self, fp, prop): + if str(prop) == "probefile": + self._loadFile(fp, fp.probefile) + + def _bilinearInterpolate(self, surface, x, y): + + p1 = FreeCAD.Vector(x, y, 100.0) + p2 = FreeCAD.Vector(x, y, -100.0) + + vertical_line = Part.Line(p1, p2) + points, curves = vertical_line.intersectCS(surface) + return points[0].Z + + def _loadFile(self, obj, filename): + if filename == "": + return + + f1 = open(filename, 'r') + + try: + pointlist = [] + for line in f1.readlines(): + w = line.split() + xval = round(float(w[0]), 2) + yval = round(float(w[1]), 2) + zval = round(float(w[2]), 2) + + pointlist.append([xval, yval, zval]) + PathLog.debug(pointlist) + + cols = list(zip(*pointlist)) + PathLog.debug("cols: {}".format(cols)) + yindex = list(sorted(set(cols[1]))) + PathLog.debug("yindex: {}".format(yindex)) + + array = [] + for y in yindex: + points = sorted([p for p in pointlist if p[1] == y]) + inner = [] + for p in points: + inner.append(FreeCAD.Vector(p[0], p[1], p[2])) + array.append(inner) + + intSurf = Part.BSplineSurface() + intSurf.interpolate(array) + + obj.interpSurface = intSurf.toShape() + except Exception: + raise ValueError("File does not contain appropriate point data") + + def execute(self, obj): + + sampleD = obj.SegInterpolate.Value + curveD = obj.ArcInterpolate.Value + + if obj.interpSurface.isNull(): # No valid probe data. return unchanged path + obj.Path = obj.Base.Path + return + + surface = obj.interpSurface.toNurbs().Faces[0].Surface + + if obj.Base: + if obj.Base.isDerivedFrom("Path::Feature"): + if obj.Base.Path: + if obj.Base.Path.Commands: + pathlist = obj.Base.Path.Commands + + newcommandlist = [] + currLocation = {'X': 0, 'Y': 0, 'Z': 0, 'F': 0} + + for c in pathlist: + PathLog.debug(c) + PathLog.debug(" curLoc:{}".format(currLocation)) + newparams = dict(c.Parameters) + zval = newparams.get("Z", currLocation['Z']) + if c.Name in movecommands: + curVec = FreeCAD.Vector(currLocation['X'], currLocation['Y'], currLocation['Z']) + arcwire = PathGeom.edgeForCmd(c, curVec) + if arcwire is None: + continue + if c.Name in arccommands: + pointlist = arcwire.discretize(Deflection=curveD) + else: + disc_number = int(arcwire.Length / sampleD) + if disc_number > 1: + pointlist = arcwire.discretize(Number=int(arcwire.Length / sampleD)) + else: + pointlist = [v.Point for v in arcwire.Vertexes] + for point in pointlist: + offset = self._bilinearInterpolate(surface, point.x, point.y) + newcommand = Path.Command("G1", {'X': point.x, 'Y': point.y, 'Z': point.z + offset}) + newcommandlist.append(newcommand) + currLocation.update(newcommand.Parameters) + currLocation['Z'] = zval + + else: + # Non Feed Command + newcommandlist.append(c) + currLocation.update(c.Parameters) + path = Path.Path(newcommandlist) + obj.Path = path + + +class TaskPanel: + + def __init__(self, obj): + self.obj = obj + self.form = FreeCADGui.PySideUic.loadUi(":/panels/ZCorrectEdit.ui") + FreeCAD.ActiveDocument.openTransaction(translate("Path_DressupZCorrect", "Edit Z Correction Dress-up")) + self.interpshape = FreeCAD.ActiveDocument.addObject("Part::Feature", "InterpolationSurface") + self.interpshape.Shape = obj.interpSurface + self.interpshape.ViewObject.Transparency = 60 + self.interpshape.ViewObject.ShapeColor = (1.00000, 1.00000, 0.01961) + self.interpshape.ViewObject.Selectable = False + stock = PathUtils.findParentJob(obj).Stock + self.interpshape.Placement.Base.z = stock.Shape.BoundBox.ZMax + + def reject(self): + FreeCAD.ActiveDocument.abortTransaction() + FreeCADGui.Control.closeDialog() + FreeCAD.ActiveDocument.recompute() + + def accept(self): + self.getFields() + FreeCAD.ActiveDocument.commitTransaction() + FreeCAD.ActiveDocument.removeObject(self.interpshape.Name) + FreeCADGui.ActiveDocument.resetEdit() + FreeCADGui.Control.closeDialog() + FreeCAD.ActiveDocument.recompute() + FreeCAD.ActiveDocument.recompute() + + def getFields(self): + self.obj.Proxy.execute(self.obj) + + def updateUI(self): + + if PathLog.getLevel(LOG_MODULE) == PathLog.Level.DEBUG: + for obj in FreeCAD.ActiveDocument.Objects: + if obj.Name.startswith('Shape'): + FreeCAD.ActiveDocument.removeObject(obj.Name) + print('object name %s' % self.obj.Name) + if hasattr(self.obj.Proxy, "shapes"): + PathLog.info("showing shapes attribute") + for shapes in self.obj.Proxy.shapes.itervalues(): + for shape in shapes: + Part.show(shape) + else: + PathLog.info("no shapes attribute found") + + def updateModel(self): + self.getFields() + self.updateUI() + FreeCAD.ActiveDocument.recompute() + + def setFields(self): + self.form.ProbePointFileName.setText(self.obj.probefile) + + self.updateUI() + + def open(self): + pass + + def setupUi(self): + self.setFields() + # now that the form is filled, setup the signal handlers + self.form.ProbePointFileName.editingFinished.connect(self.updateModel) + self.form.SetProbePointFileName.clicked.connect(self.SetProbePointFileName) + + def SetProbePointFileName(self): + filename = QtGui.QFileDialog.getSaveFileName(self.form, translate("Path_Probe", "Select Probe Point File"), None, translate("Path_Probe", "All Files (*.*)")) + if filename and filename[0]: + self.obj.probefile = str(filename[0]) + self.setFields() + + +class ViewProviderDressup: + + def __init__(self, vobj): + vobj.Proxy = self + + def attach(self, vobj): + self.obj = vobj.Object + if self.obj and self.obj.Base: + for i in self.obj.Base.InList: + if hasattr(i, "Group"): + group = i.Group + for g in group: + if g.Name == self.obj.Base.Name: + group.remove(g) + i.Group = group + return + + def claimChildren(self): + return [self.obj.Base] + + def setEdit(self, vobj, mode=0): + FreeCADGui.Control.closeDialog() + panel = TaskPanel(vobj.Object) + FreeCADGui.Control.showDialog(panel) + panel.setupUi() + return True + + def __getstate__(self): + return None + + def __setstate__(self, state): + return None + + def onDelete(self, arg1=None, arg2=None): + '''this makes sure that the base operation is added back to the project and visible''' + FreeCADGui.ActiveDocument.getObject(arg1.Object.Base.Name).Visibility = True + job = PathUtils.findParentJob(arg1.Object) + job.Proxy.addOperation(arg1.Object.Base) + arg1.Object.Base = None + return True + + +class CommandPathDressup: + + def GetResources(self): + return {'Pixmap': 'Path-Dressup', + 'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_DressupZCorrect", "Z Depth Correction Dress-up"), + 'Accel': "", + 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_DressupZCorrect", "Use Probe Map to correct Z depth")} + + def IsActive(self): + if FreeCAD.ActiveDocument is not None: + for o in FreeCAD.ActiveDocument.Objects: + if o.Name[:3] == "Job": + return True + return False + + def Activated(self): + + # check that the selection contains exactly what we want + selection = FreeCADGui.Selection.getSelection() + if len(selection) != 1: + FreeCAD.Console.PrintError(translate("Path_Dressup", "Please select one path object\n")) + return + if not selection[0].isDerivedFrom("Path::Feature"): + FreeCAD.Console.PrintError(translate("Path_Dressup", "The selected object is not a path\n")) + return + if selection[0].isDerivedFrom("Path::FeatureCompoundPython"): + FreeCAD.Console.PrintError(translate("Path_Dressup", "Please select a Path object")) + return + + # everything ok! + FreeCAD.ActiveDocument.openTransaction(translate("Path_DressupZCorrect", "Create Dress-up")) + FreeCADGui.addModule("PathScripts.PathDressupZCorrect") + FreeCADGui.addModule("PathScripts.PathUtils") + FreeCADGui.doCommand('obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", "ZCorrectDressup")') + FreeCADGui.doCommand('PathScripts.PathDressupZCorrect.ObjectDressup(obj)') + FreeCADGui.doCommand('obj.Base = FreeCAD.ActiveDocument.' + selection[0].Name) + FreeCADGui.doCommand('PathScripts.PathDressupZCorrect.ViewProviderDressup(obj.ViewObject)') + FreeCADGui.doCommand('PathScripts.PathUtils.addToJob(obj)') + FreeCADGui.doCommand('Gui.ActiveDocument.getObject(obj.Base.Name).Visibility = False') + FreeCAD.ActiveDocument.commitTransaction() + FreeCAD.ActiveDocument.recompute() + + +if FreeCAD.GuiUp: + # register the FreeCAD command + FreeCADGui.addCommand('Path_DressupZCorrect', CommandPathDressup()) + +FreeCAD.Console.PrintLog("Loading PathDressup... done\n") diff --git a/src/Mod/Path/PathScripts/PathGeom.py b/src/Mod/Path/PathScripts/PathGeom.py index 2fbaae4a40..fba8d1ddb1 100644 --- a/src/Mod/Path/PathScripts/PathGeom.py +++ b/src/Mod/Path/PathScripts/PathGeom.py @@ -313,6 +313,9 @@ def edgeForCmd(cmd, startPoint): """edgeForCmd(cmd, startPoint). Returns an Edge representing the given command, assuming a given startPoint.""" + PathLog.debug("cmd: {}".format(cmd)) + PathLog.debug("startpoint {}".format(startPoint)) + endPoint = commandEndPoint(cmd, startPoint) if (cmd.Name in CmdMoveStraight) or (cmd.Name in CmdMoveRapid): if pointsCoincide(startPoint, endPoint): @@ -343,6 +346,10 @@ def edgeForCmd(cmd, startPoint): if isRoughly(startPoint.z, endPoint.z): midPoint = center + Vector(math.cos(angle), math.sin(angle), 0) * R PathLog.debug("arc: (%.2f, %.2f) -> (%.2f, %.2f) -> (%.2f, %.2f)" % (startPoint.x, startPoint.y, midPoint.x, midPoint.y, endPoint.x, endPoint.y)) + PathLog.debug("StartPoint:{}".format(startPoint)) + PathLog.debug("MidPoint:{}".format(midPoint)) + PathLog.debug("EndPoint:{}".format(endPoint)) + return Part.Edge(Part.Arc(startPoint, midPoint, endPoint)) # It's a Helix diff --git a/src/Mod/Path/PathScripts/PathGuiInit.py b/src/Mod/Path/PathScripts/PathGuiInit.py index 57f302fc3a..29272c0382 100644 --- a/src/Mod/Path/PathScripts/PathGuiInit.py +++ b/src/Mod/Path/PathScripts/PathGuiInit.py @@ -51,6 +51,7 @@ def Startup(): from PathScripts import PathDressupPathBoundaryGui from PathScripts import PathDressupTagGui from PathScripts import PathDressupLeadInOut + from PathScripts import PathDressupZCorrect from PathScripts import PathDrillingGui from PathScripts import PathEngraveGui from PathScripts import PathFixture @@ -61,6 +62,7 @@ def Startup(): from PathScripts import PathPocketGui from PathScripts import PathPocketShapeGui from PathScripts import PathPost + from PathScripts import PathProbeGui from PathScripts import PathProfileContourGui from PathScripts import PathProfileEdgesGui from PathScripts import PathProfileFacesGui diff --git a/src/Mod/Path/PathScripts/PathProbe.py b/src/Mod/Path/PathScripts/PathProbe.py new file mode 100644 index 0000000000..c5e8b34f81 --- /dev/null +++ b/src/Mod/Path/PathScripts/PathProbe.py @@ -0,0 +1,107 @@ +# -*- coding: utf-8 -*- + +# *************************************************************************** +# * * +# * Copyright (c) 2018 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 * +# * * +# *************************************************************************** + +from __future__ import print_function + +import FreeCAD +import Path +import PathScripts.PathLog as PathLog +import PathScripts.PathOp as PathOp +import PathScripts.PathUtils as PathUtils + +from PySide import QtCore + +__title__ = "Path Probing Operation" +__author__ = "sliptonic (Brad Collette)" +__url__ = "http://www.freecadweb.org" +__doc__ = "Path Probing operation." + +if False: + PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) + PathLog.trackModule(PathLog.thisModule()) +else: + PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + + +# Qt tanslation handling +def translate(context, text, disambig=None): + return QtCore.QCoreApplication.translate(context, text, disambig) + + +class ObjectProbing(PathOp.ObjectOp): + '''Proxy object for Probing operation.''' + + def opFeatures(self, obj): + '''opFeatures(obj) ... Probing works on the stock object.''' + return PathOp.FeatureDepths | PathOp.FeatureHeights | PathOp.FeatureTool + + def initOperation(self, obj): + obj.addProperty("App::PropertyLength", "Xoffset", "Probe", QtCore.QT_TRANSLATE_NOOP("App::Property", "X offset between tool and probe")) + obj.addProperty("App::PropertyLength", "Yoffset", "Probe", QtCore.QT_TRANSLATE_NOOP("App::Property", "Y offset between tool and probe")) + obj.addProperty("App::PropertyInteger", "PointCountX", "Probe", QtCore.QT_TRANSLATE_NOOP("App::Property", "Number of points to probe in X direction")) + obj.addProperty("App::PropertyInteger", "PointCountY", "Probe", QtCore.QT_TRANSLATE_NOOP("App::Property", "Number of points to probe in Y direction")) + obj.addProperty("App::PropertyFile", "OutputFileName", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "The output location for the probe data to be written")) + + def nextpoint(self, startpoint=0.0, endpoint=0.0, count=3): + curstep = 0 + dist = (endpoint - startpoint) / (count - 1) + while curstep <= count-1: + yield startpoint + (curstep * dist) + curstep += 1 + + def opExecute(self, obj): + '''opExecute(obj) ... generate probe locations.''' + PathLog.track() + self.commandlist.append(Path.Command("(Begin Probing)")) + + stock = PathUtils.findParentJob(obj).Stock + bb = stock.Shape.BoundBox + + openstring = '(PROBEOPEN {})'.format(obj.OutputFileName) + self.commandlist.append(Path.Command(openstring)) + self.commandlist.append(Path.Command("G0", {"Z": obj.ClearanceHeight.Value})) + + for y in self.nextpoint(bb.YMin, bb.YMax, obj.PointCountY): + for x in self.nextpoint(bb.XMin, bb.XMax, obj.PointCountX): + self.commandlist.append(Path.Command("G0", {"X": x + obj.Xoffset.Value, "Y": y + obj.Yoffset.Value, "Z": obj.SafeHeight.Value})) + self.commandlist.append(Path.Command("G38.2", {"Z": obj.FinalDepth.Value, "F": obj.ToolController.VertFeed.Value})) + self.commandlist.append(Path.Command("G0", {"Z": obj.SafeHeight.Value})) + + self.commandlist.append(Path.Command("(PROBECLOSE)")) + + def opSetDefaultValues(self, obj, job): + '''opSetDefaultValues(obj, job) ... set default value for RetractHeight''' + + +def SetupProperties(): + setup = ['Xoffset', 'Yoffset', 'PointCountX', 'PointCountY', 'OutputFileName'] + return setup + + +def Create(name, obj=None): + '''Create(name) ... Creates and returns a Probing operation.''' + if obj is None: + obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name) + proxy = ObjectProbing(obj, name) + return obj diff --git a/src/Mod/Path/PathScripts/PathProbeGui.py b/src/Mod/Path/PathScripts/PathProbeGui.py new file mode 100644 index 0000000000..9da563fa48 --- /dev/null +++ b/src/Mod/Path/PathScripts/PathProbeGui.py @@ -0,0 +1,94 @@ +# -*- 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 FreeCAD +import FreeCADGui +import PathScripts.PathProbe as PathProbe +import PathScripts.PathOpGui as PathOpGui +import PathScripts.PathGui as PathGui + +from PySide import QtCore, QtGui + +__title__ = "Path Probing Operation UI" +__author__ = "sliptonic (Brad Collette)" +__url__ = "http://www.freecadweb.org" +__doc__ = "Probing operation page controller and command implementation." + + +# Qt tanslation handling +def translate(context, text, disambig=None): + return QtCore.QCoreApplication.translate(context, text, disambig) + + +class TaskPanelOpPage(PathOpGui.TaskPanelPage): + '''Page controller class for the Probing operation.''' + + def getForm(self): + '''getForm() ... returns UI''' + return FreeCADGui.PySideUic.loadUi(":/panels/PageOpProbeEdit.ui") + + def getFields(self, obj): + '''getFields(obj) ... transfers values from UI to obj's proprties''' + self.updateToolController(obj, self.form.toolController) + PathGui.updateInputField(obj, 'Xoffset', self.form.Xoffset) + PathGui.updateInputField(obj, 'Yoffset', self.form.Yoffset) + obj.PointCountX = self.form.PointCountX.value() + obj.PointCountY = self.form.PointCountY.value() + obj.OutputFileName = str(self.form.OutputFileName.text()) + + def setFields(self, obj): + '''setFields(obj) ... transfers obj's property values to UI''' + self.setupToolController(obj, self.form.toolController) + self.form.Xoffset.setText(FreeCAD.Units.Quantity(obj.Xoffset.Value, FreeCAD.Units.Length).UserString) + self.form.Yoffset.setText(FreeCAD.Units.Quantity(obj.Yoffset.Value, FreeCAD.Units.Length).UserString) + self.form.OutputFileName.setText(obj.OutputFileName) + self.form.PointCountX.setValue(obj.PointCountX) + self.form.PointCountY.setValue(obj.PointCountY) + + def getSignalsForUpdate(self, obj): + '''getSignalsForUpdate(obj) ... return list of signals for updating obj''' + signals = [] + signals.append(self.form.toolController.currentIndexChanged) + signals.append(self.form.PointCountX.valueChanged) + signals.append(self.form.PointCountY.valueChanged) + signals.append(self.form.OutputFileName.editingFinished) + signals.append(self.form.Xoffset.valueChanged) + signals.append(self.form.Yoffset.valueChanged) + self.form.SetOutputFileName.clicked.connect(self.SetOutputFileName) + return signals + + def SetOutputFileName(self): + filename = QtGui.QFileDialog.getSaveFileName(self.form, translate("Path_Probe", "Select Output File"), None, translate("Path_Probe", "All Files (*.*)")) + if filename and filename[0]: + self.obj.OutputFileName = str(filename[0]) + self.setFields(self.obj) + + +Command = PathOpGui.SetupOperation('Probe', PathProbe.Create, TaskPanelOpPage, + 'Path-Probe', + QtCore.QT_TRANSLATE_NOOP("Probe", "Probe"), + QtCore.QT_TRANSLATE_NOOP("Probe", "Create a Probing Grid from a job stock"), + PathProbe.SetupProperties) + +FreeCAD.Console.PrintLog("Loading PathProbeGui... done\n") diff --git a/src/Mod/Path/PathScripts/PathSelection.py b/src/Mod/Path/PathScripts/PathSelection.py index acb9c473e2..386ff1c29c 100644 --- a/src/Mod/Path/PathScripts/PathSelection.py +++ b/src/Mod/Path/PathScripts/PathSelection.py @@ -167,13 +167,17 @@ class ADAPTIVEGate(PathBaseGate): obj = obj.Shape except Exception: # pylint: disable=broad-except return False - + return adaptive class CONTOURGate(PathBaseGate): def allow(self, doc, obj, sub): # pylint: disable=unused-argument pass +class PROBEGate: + def allow(self, doc, obj, sub): + pass + def contourselect(): FreeCADGui.Selection.addSelectionGate(CONTOURGate()) FreeCAD.Console.PrintWarning("Contour Select Mode\n") @@ -215,6 +219,10 @@ def surfaceselect(): # FreeCADGui.Selection.addSelectionGate(PROFILEGate()) # Added for face selection FreeCAD.Console.PrintWarning("Surfacing Select Mode\n") +def probeselect(): + FreeCADGui.Selection.addSelectionGate(PROBEGate()) + FreeCAD.Console.PrintWarning("Probe Select Mode\n") + def select(op): opsel = {} opsel['Contour'] = contourselect @@ -230,6 +238,7 @@ def select(op): opsel['Profile Faces'] = profileselect opsel['Surface'] = surfaceselect opsel['Adaptive'] = adaptiveselect + opsel['Probe'] = probeselect return opsel[op] def clear(): diff --git a/src/Mod/Path/PathScripts/PathSetupSheetOpPrototype.py b/src/Mod/Path/PathScripts/PathSetupSheetOpPrototype.py index fa99cbc72b..d089dd7f7a 100644 --- a/src/Mod/Path/PathScripts/PathSetupSheetOpPrototype.py +++ b/src/Mod/Path/PathScripts/PathSetupSheetOpPrototype.py @@ -149,6 +149,7 @@ class OpPrototype(object): 'App::PropertyBool': PropertyBool, 'App::PropertyDistance': PropertyDistance, 'App::PropertyEnumeration': PropertyEnumeration, + 'App::PropertyFile': PropertyString, 'App::PropertyFloat': PropertyFloat, 'App::PropertyFloatConstraint': Property, 'App::PropertyFloatList': Property, diff --git a/src/Mod/Path/PathScripts/PathSetupSheetOpPrototypeGui.py b/src/Mod/Path/PathScripts/PathSetupSheetOpPrototypeGui.py index f89b7b40ed..d648ca6a62 100644 --- a/src/Mod/Path/PathScripts/PathSetupSheetOpPrototypeGui.py +++ b/src/Mod/Path/PathScripts/PathSetupSheetOpPrototypeGui.py @@ -188,6 +188,18 @@ class _PropertyFloatEditor(_PropertyEditor): def setModelData(self, widget): self.prop.setValue(widget.value()) +class _PropertyFileEditor(_PropertyEditor): + + def widget(self, parent): + return QtGui.QLineEdit(parent) + + def setEditorData(self, widget): + text = '' if self.prop.getValue() is None else self.prop.getValue() + widget.setText(text) + + def setModelData(self, widget): + self.prop.setValue(widget.text()) + _EditorFactory = { PathSetupSheetOpPrototype.Property: None, PathSetupSheetOpPrototype.PropertyAngle: _PropertyAngleEditor,