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 @@
+
+
+
+
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
+
+
+
+
+
+
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,