lazy_loader is copied to Ext now, modified external imports to lazy_load add a few more imports to be lazy loaded, think the install path is correct now [TD]"<" symbol embedded in html revert changes to path modules for testing use lazyloader in PathAreaOp.py add back in deferred loading temp change to print error message in tests temp change to print error message in tests add _init__.py to lazy_loader make install in CMakeLists.txt one line
347 lines
14 KiB
Python
347 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
|
|
"""
|
|
|
|
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")
|