This PR fixes header uniformity across all Path files. It also removes all trailing whitespace.
349 lines
14 KiB
Python
349 lines
14 KiB
Python
# -*- coding: utf-8 -*
|
|
# ***************************************************************************
|
|
# * Copyright (c) 2018 sliptonic <shopinthewoods@gmail.com> *
|
|
# * *
|
|
# * 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 Path
|
|
import PathScripts.PathGeom as PathGeom
|
|
import PathScripts.PathLog as PathLog
|
|
import PathScripts.PathUtils as PathUtils
|
|
|
|
from PySide import QtCore, QtGui
|
|
|
|
# lazily loaded modules
|
|
from lazy_loader.lazy_loader import LazyLoader
|
|
Part = LazyLoader('Part', globals(), 'Part')
|
|
|
|
"""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
|
|
"""
|
|
|
|
LOGLEVEL = False
|
|
|
|
LOG_MODULE = PathLog.thisModule()
|
|
|
|
if LOGLEVEL:
|
|
PathLog.setLevel(PathLog.Level.DEBUG, LOG_MODULE)
|
|
PathLog.trackModule(PathLog.thisModule())
|
|
else:
|
|
PathLog.setLevel(PathLog.Level.NOTICE, LOG_MODULE)
|
|
|
|
|
|
# Qt translation 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.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():
|
|
if line == '\n':
|
|
continue
|
|
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.getOpenFileName(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')
|
|
FreeCADGui.doCommand('obj.ViewObject.Document.setEdit(obj.ViewObject, 0)')
|
|
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")
|