Files
create/src/Mod/Path/PathScripts/PathDressupZCorrect.py
luz.paz 1663fbb1cd [skip ci] Fix typos in Path WB
Found via 
```
codespell -q 3 -L aci,ake,aline,alle,alledges,alocation,als,ang,anid,ba,beginn,behaviour,bloaded,byteorder,calculater,cancelled,cancelling,cas,cascade,centimetre,childs,colour,colours,commen,connexion,currenty,dof,doubleclick,dum,eiter,elemente,ende,feld,finde,findf,freez,hist,iff,indicies,initialisation,initialise,initialised,initialises,initialisiert,ist,kilometre,lod,mantatory,methode,metres,millimetre,modell,nd,noe,normale,normaly,nto,numer,oder,orgin,orginx,orginy,ot,pard,pres,programm,que,recurrance,rougly,seperator,serie,sinc,strack,substraction,te,thist,thru,tread,uint,unter,vertexes,wallthickness,whitespaces -S ./.git,*.po,*.ts,./ChangeLog.txt,./src/3rdParty,./src/Mod/Assembly/App/opendcm,./src/CXX,./src/zipios++,./src/Base/swig*,./src/Mod/Robot/App/kdl_cp,./src/Mod/Import/App/SCL,./src/WindowsInstaller,./src/Doc/FreeCAD.uml
```
2020-04-01 13:28:48 +02:00

344 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 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 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.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")