Files
create/src/Mod/Path/PathScripts/PathToolLibraryManager.py
2017-09-26 10:51:17 -03:00

664 lines
26 KiB
Python

# -*- coding: utf-8 -*-
# ***************************************************************************
# * *
# * Copyright (c) 2014 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 *
# * *
# ***************************************************************************
from __future__ import print_function
import FreeCAD
import FreeCADGui
import Path
import PathScripts
import PathScripts.PathLog as PathLog
import PathScripts.PathUtils as PathUtils
import json
import os
import xml.sax
from PySide import QtCore, QtGui
LOG_MODULE = 'PathToolLibraryManager'
PathLog.setLevel(PathLog.Level.INFO, LOG_MODULE)
#PathLog.trackModule('PathToolLibraryManager')
def translate(context, text, disambig=None):
return QtCore.QCoreApplication.translate(context, text, disambig)
# Tooltable XML readers
class FreeCADTooltableHandler(xml.sax.ContentHandler):
# http://www.tutorialspoint.com/python/python_xml_processing.htm
def __init__(self):
self.tooltable = None
self.tool = None
self.number = None
# Call when an element is found
def startElement(self, tag, attributes):
if tag == "Tooltable":
self.tooltable = Path.Tooltable()
elif tag == "Toolslot":
self.number = int(attributes["number"])
elif tag == "Tool":
self.tool = Path.Tool()
self.tool.Name = str(attributes["name"])
self.tool.ToolType = str(attributes["type"])
self.tool.Material = str(attributes["mat"])
# for some reason without the following line I get an error
#print attributes["diameter"]
self.tool.Diameter = float(attributes["diameter"])
self.tool.LengthOffset = float(attributes["length"])
self.tool.FlatRadius = float(attributes["flat"])
self.tool.CornerRadius = float(attributes["corner"])
self.tool.CuttingEdgeAngle = float(attributes["angle"])
self.tool.CuttingEdgeHeight = float(attributes["height"])
# Call when an elements ends
def endElement(self, tag):
if tag == "Toolslot":
if self.tooltable and self.tool and self.number:
self.tooltable.setTool(self.number, self.tool)
self.number = None
self.tool = None
class HeeksTooltableHandler(xml.sax.ContentHandler):
def __init__(self):
self.tooltable = Path.Tooltable()
self.tool = None
self.number = None
# Call when an element is found
def startElement(self, tag, attributes):
if tag == "Tool":
self.tool = Path.Tool()
self.number = int(attributes["tool_number"])
self.tool.Name = str(attributes["title"])
elif tag == "params":
t = str(attributes["type"])
if t == "drill":
self.tool.ToolType = "Drill"
elif t == "center_drill_bit":
self.tool.ToolType = "CenterDrill"
elif t == "end_mill":
self.tool.ToolType = "EndMill"
elif t == "slot_cutter":
self.tool.ToolType = "SlotCutter"
elif t == "ball_end_mill":
self.tool.ToolType = "BallEndMill"
elif t == "chamfer":
self.tool.ToolType = "Chamfer"
elif t == "engraving_bit":
self.tool.ToolType = "Engraver"
m = str(attributes["material"])
if m == "0":
self.tool.Material = "HighSpeedSteel"
elif m == "1":
self.tool.Material = "Carbide"
# for some reason without the following line I get an error
#print attributes["diameter"]
self.tool.Diameter = float(attributes["diameter"])
self.tool.LengthOffset = float(attributes["tool_length_offset"])
self.tool.FlatRadius = float(attributes["flat_radius"])
self.tool.CornerRadius = float(attributes["corner_radius"])
self.tool.CuttingEdgeAngle = float(
attributes["cutting_edge_angle"])
self.tool.CuttingEdgeHeight = float(
attributes["cutting_edge_height"])
# Call when an elements ends
def endElement(self, tag):
if tag == "Tool":
if self.tooltable and self.tool and self.number:
self.tooltable.setTool(self.number, self.tool)
self.number = None
self.tool = None
class ToolLibraryManager():
'''
The Tool Library is a list of individual tool tables. Each
Tool Table can contain n tools. The tool library will be persisted to user
preferences and all or part of the library can be exported to other formats
'''
TooltableTypeJSON = translate("TooltableEditor", "Tooltable JSON (*.json)")
TooltableTypeXML = translate("TooltableEditor", "Tooltable XML (*.xml)")
TooltableTypeHeekscad = translate("TooltableEditor", "HeeksCAD tooltable (*.tooltable)")
TooltableTypeLinuxCNC = translate("TooltableEditor", "LinuxCNC tooltable (*.tbl)")
PreferenceMainLibraryXML = "ToolLibrary"
PreferenceMainLibraryJSON = "ToolLibrary-Main"
def __init__(self):
self.prefs = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Path")
return
def templateAttrs(self, tooltable):
attrs = {}
attrs['Version'] = 1
attrs['Tools'] = tooltable.templateAttrs()
return attrs
def tooltableFromAttrs(self, stringattrs):
if stringattrs.get('Version') and 1 == int(stringattrs['Version']):
attrs = {}
for key, val in stringattrs['Tools'].iteritems():
attrs[int(key)] = val
return Path.Tooltable(attrs)
else:
PathLog.error(translate('PathToolLibraryManager', "Unsupported Path tooltable template version %s") % stringattrs.get('Version'))
return None
def saveMainLibrary(self, tooltable):
'''Persists the permanent library to FreeCAD user preferences'''
tmpstring = json.dumps(self.templateAttrs(tooltable))
self.prefs.SetString(self.PreferenceMainLibraryJSON, tmpstring)
return True
def getLists(self):
'''Builds the list of all Tool Table lists'''
tablelist = []
toollist = "<Main>"
tablelist.append(toollist)
# Get ToolTables from any open CNC jobs
for o in FreeCAD.ActiveDocument.Objects:
if hasattr(o, "Proxy"):
if isinstance(o.Proxy, PathScripts.PathJob.ObjectJob):
tablelist.append(o.Label)
return tablelist
def _findList(self, listname):
tt = None
if listname == "<Main>":
tmpstring = self.prefs.GetString(self.PreferenceMainLibraryJSON, "")
if not tmpstring:
tmpstring = self.prefs.GetString(self.PreferenceMainLibraryXML, "")
if tmpstring:
if tmpstring[0] == '{':
tt = self.tooltableFromAttrs(json.loads(tmpstring))
elif tmpstring[0] == '<':
# legacy XML table
Handler = FreeCADTooltableHandler()
xml.sax.parseString(tmpstring, Handler)
tt = Handler.tooltable
# store new format
self.saveMainLibrary(tt)
else:
tt = Path.Tooltable()
else:
for o in FreeCAD.ActiveDocument.Objects:
if o.Label == listname:
tt = o.Tooltable
return tt
def getTool(self, listname, toolnum):
tt = self._findList(listname)
return tt.getTool(toolnum)
def getTools(self, tablename):
'''returns the tool data for a given table'''
tooldata = []
tt = self._findList(tablename)
headers = ["","Tool Num.","Name","Tool Type","Material","Diameter","Length Offset","Flat Radius","Corner Radius","Cutting Edge Angle","Cutting Edge Height"]
model = QtGui.QStandardItemModel()
model.setHorizontalHeaderLabels(headers)
def unitconv(ivalue):
val = FreeCAD.Units.Quantity(ivalue, FreeCAD.Units.Length)
displayed_val = val.UserString #just the displayed value-not the internal one
return displayed_val
if tt:
if len(tt.Tools) == 0:
tooldata.append([])
for number, t in tt.Tools.iteritems():
itemcheck = QtGui.QStandardItem()
itemcheck.setCheckable(True)
itemNumber = QtGui.QStandardItem(str(number))
itemName = QtGui.QStandardItem(t.Name)
itemToolType = QtGui.QStandardItem(t.ToolType)
itemMaterial = QtGui.QStandardItem(t.Material)
itemDiameter = QtGui.QStandardItem(unitconv(t.Diameter))
itemLengthOffset = QtGui.QStandardItem(unitconv(t.LengthOffset))
itemFlatRadius = QtGui.QStandardItem(unitconv(t.FlatRadius))
itmCornerRadius = QtGui.QStandardItem(unitconv(t.CornerRadius))
itemCuttingEdgeAngle = QtGui.QStandardItem(str(t.CuttingEdgeAngle))
itemCuttingEdgeHeight = QtGui.QStandardItem(unitconv(t.CuttingEdgeHeight))
row = [itemcheck, itemNumber, itemName, itemToolType, itemMaterial, itemDiameter, itemLengthOffset, itemFlatRadius, itmCornerRadius, itemCuttingEdgeAngle, itemCuttingEdgeHeight]
model.appendRow(row)
return model
# methods for importing and exporting
def read(self, filename, listname):
"imports a tooltable from a file"
try:
fileExtension = os.path.splitext(filename[0])[1].lower()
xmlHandler = None
if fileExtension == '.tooltable':
xmlHandler = HeeksTooltableHandler()
if fileExtension == '.xml':
xmlHandler = FreeCADTooltableHandler()
if xmlHandler:
parser = xml.sax.make_parser()
parser.setFeature(xml.sax.handler.feature_namespaces, 0)
parser.setContentHandler(xmlHandler)
parser.parse(unicode(filename[0]))
if not xmlHandler.tooltable:
return None
ht = xmlHandler.tooltable
else:
with open(unicode(filename[0]), "rb") as fp:
ht = self.tooltableFromAttrs(json.load(fp))
tt = self._findList(listname)
for t in ht.Tools:
newt = ht.getTool(t).copy()
tt.addTools(newt)
if listname == "<Main>":
self.saveMainLibrary(tt)
return True
except Exception as e:
print("could not parse file", e)
def write(self, filename, listname):
"exports the tooltable to a file"
tt = self._findList(listname)
if tt:
try:
def openFileWithExtension(name, ext):
fext = os.path.splitext(name)[1].lower()
if fext != ext:
name = "{}{}".format(name, ext)
return (open(unicode(name), 'wb'), name)
if filename[1] == self.TooltableTypeXML:
fp,fname = openFileWithExtension(filename[0], '.xml')
fp.write('<?xml version="1.0" encoding="UTF-8"?>\n')
fp.write(tt.Content)
elif filename[1] == self.TooltableTypeLinuxCNC:
fp,fname = openFileWithExtension(filename[0], '.tbl')
for key in tt.Tools:
t = tt.Tools[key]
fp.write("T{} P{} Y{} Z{} A{} B{} C{} U{} V{} W{} D{} I{} J{} Q{} ;{}\n".format(key,key,0,t.LengthOffset,0,0,0,0,0,0,t.Diameter,0,0,0,t.Name))
else:
fp,fname = openFileWithExtension(filename[0], '.json')
json.dump(self.templateAttrs(tt), fp, sort_keys=True, indent=2)
fp.close()
print("Written ", unicode(fname))
except Exception as e:
print("Could not write file:", e)
def addnew(self, listname, tool, position = None):
"adds a new tool at the end of the table"
print(listname, tool, position)
tt = self._findList(listname)
if not tt:
tt = Path.Tooltable()
if position is None:
tt.addTools(tool)
newID = tt.Tools.keys()[-1]
else:
tt.setTool(position, tool)
newID = position
if listname == "<Main>":
return self.saveMainLibrary(tt)
return newID
def updateTool(self, listname, toolnum, tool):
'''updates tool data'''
tt = self._findList(listname)
tt.deleteTool(toolnum)
tt.setTool(toolnum, tool)
if listname == "<Main>":
return self.saveMainLibrary(tt)
return True
def moveup(self, number, listname):
"moves a tool to a lower number, if possible"
if number < 2:
return False
target = number - 1
tt = self._findList(listname)
t1 = tt.getTool(number).copy()
tt.deleteTool(number)
if target in tt.Tools.keys():
t2 = tt.getTool(target).copy()
tt.deleteTool(target)
tt.setTool(number, t2)
tt.setTool(target, t1)
if listname == "<Main>":
self.saveMainLibrary(tt)
return True
def movedown(self, number, listname):
"moves a tool to a higher number, if possible"
tt = self._findList(listname)
target = number + 1
t1 = tt.getTool(number).copy()
tt.deleteTool(number)
if target in tt.Tools.keys():
t2 = tt.getTool(target).copy()
tt.deleteTool(target)
tt.setTool(number, t2)
tt.setTool(target, t1)
if listname == "<Main>":
self.saveMainLibrary(tt)
return True
def delete(self, number, listname):
'''deletes a tool from the current list'''
tt = self._findList(listname)
tt.deleteTool(number)
if listname == "<Main>":
self.saveMainLibrary(tt)
return True
class EditorPanel():
def __init__(self, job, cb):
#self.form = FreeCADGui.PySideUic.loadUi(FreeCAD.getHomePath() + "Mod/Path/ToolLibraryEditor.ui")
self.form = FreeCADGui.PySideUic.loadUi(":/panels/ToolLibraryEditor.ui")
#self.editform = FreeCADGui.PySideUic.loadUi(FreeCAD.getHomePath() + "Mod/Path/ToolEdit.ui")
self.editform = FreeCADGui.PySideUic.loadUi(":/panels/ToolEdit.ui")
self.TLM = ToolLibraryManager()
self.loadTable()
self.form.ToolsList.resizeColumnsToContents()
self.job = job
self.cb = cb
def accept(self):
pass
def reject(self):
FreeCADGui.Control.closeDialog()
FreeCAD.ActiveDocument.recompute()
def getFields(self):
pass
def getType(self, tooltype):
"gets a combobox index number for a given type or viceversa"
toolslist = ["Drill", "CenterDrill", "CounterSink", "CounterBore",
"Reamer", "Tap", "EndMill", "SlotCutter", "BallEndMill",
"ChamferMill", "CornerRound", "Engraver"]
if isinstance(tooltype, str):
if tooltype in toolslist:
return toolslist.index(tooltype)
else:
return 0
else:
return toolslist[tooltype]
def getMaterial(self, material):
"gets a combobox index number for a given material or viceversa"
matslist = ["HighSpeedSteel", "HighCarbonToolSteel", "CastAlloy",
"Carbide", "Ceramics", "Diamond", "Sialon"]
if isinstance(material, str):
if material in matslist:
return matslist.index(material)
else:
return 0
else:
return matslist[material]
def addTool(self):
t = Path.Tool()
editform = FreeCADGui.PySideUic.loadUi(":/panels/ToolEdit.ui")
r = editform.exec_()
if r:
if editform.NameField.text():
t.Name = str(editform.NameField.text()) #FIXME: not unicode safe!
t.ToolType = self.getType(editform.TypeField.currentIndex())
t.Material = self.getMaterial(editform.MaterialField.currentIndex())
t.Diameter = FreeCAD.Units.parseQuantity(editform.DiameterField.text())
t.LengthOffset = FreeCAD.Units.parseQuantity(editform.LengthOffsetField.text())
t.FlatRadius = FreeCAD.Units.parseQuantity(editform.FlatRadiusField.text())
t.CornerRadius = FreeCAD.Units.parseQuantity(editform.CornerRadiusField.text())
t.CuttingEdgeAngle = FreeCAD.Units.Quantity(editform.CuttingEdgeAngleField.text())
t.CuttingEdgeHeight = FreeCAD.Units.parseQuantity(editform.CuttingEdgeHeightField.text())
listname = "<Main>"
if self.TLM.addnew(listname, t) is True:
self.loadTable()
def setFields(self):
pass
def open(self):
pass
def loadTable(self):
#tooldata = self.TLM.getTools(curr.data())
tooldata = self.TLM.getTools("<Main>")
self.form.ToolsList.setModel(tooldata)
def moveUp(self):
"moves a tool to a lower number, if possible"
item = self.form.ToolsList.selectedIndexes()[1].data()
if item:
number = int(item)
listname = "<Main>"
#listname = self.form.listView.selectedIndexes()[0].data()
if self.TLM.moveup(number, listname) is True:
self.loadTable()
def moveDown(self):
"moves a tool to a higher number, if possible"
item = self.form.ToolsList.selectedIndexes()[1].data()
if item:
number = int(item)
listname = "<Main>"
#listname = self.form.listView.selectedIndexes()[0].data()
if self.TLM.movedown(number, listname) is True:
self.loadTable()
def delete(self):
'''deletes a tool'''
#listname = self.form.listView.selectedIndexes()[0].data()
listname = "<Main>"
model = self.form.ToolsList.model()
for i in range(model.rowCount()):
item = model.item(i, 0)
if item.checkState():
t = model.index(i, 1)
self.TLM.delete(int(t.data()) ,listname)
self.loadTable()
def editTool(self, currItem):
row = currItem.row()
value = currItem.sibling(row, 1).data()
#listname = self.form.listView.selectedIndexes()[0].data()
listname = "<Main>"
toolnum = int(value)
tool = self.TLM.getTool(listname, toolnum)
editform = FreeCADGui.PySideUic.loadUi(":/panels/ToolEdit.ui")
editform.NameField.setText(tool.Name)
editform.TypeField.setCurrentIndex(self.getType(tool.ToolType))
editform.MaterialField.setCurrentIndex(self.getMaterial(tool.Material))
editform.DiameterField.setText(FreeCAD.Units.Quantity(tool.Diameter, FreeCAD.Units.Length).UserString)
editform.LengthOffsetField.setText(FreeCAD.Units.Quantity(tool.LengthOffset, FreeCAD.Units.Length).UserString)
editform.FlatRadiusField.setText(FreeCAD.Units.Quantity(tool.FlatRadius, FreeCAD.Units.Length).UserString)
editform.CornerRadiusField.setText(FreeCAD.Units.Quantity(tool.CornerRadius, FreeCAD.Units.Length).UserString)
editform.CuttingEdgeAngleField.setText(FreeCAD.Units.Quantity(tool.CuttingEdgeAngle, FreeCAD.Units.Angle).UserString)
editform.CuttingEdgeHeightField.setText(FreeCAD.Units.Quantity(tool.CuttingEdgeHeight, FreeCAD.Units.Length).UserString)
r = editform.exec_()
if r:
if editform.NameField.text():
tool.Name = str(editform.NameField.text()) #FIXME: not unicode safe!
tool.ToolType = self.getType(editform.TypeField.currentIndex())
tool.Material = self.getMaterial(editform.MaterialField.currentIndex())
tool.Diameter = FreeCAD.Units.parseQuantity(editform.DiameterField.text())
tool.LengthOffset = FreeCAD.Units.parseQuantity(editform.LengthOffsetField.text())
tool.FlatRadius = FreeCAD.Units.parseQuantity(editform.FlatRadiusField.text())
tool.CornerRadius = FreeCAD.Units.parseQuantity(editform.CornerRadiusField.text())
tool.CuttingEdgeAngle = FreeCAD.Units.Quantity(editform.CuttingEdgeAngleField.text())
tool.CuttingEdgeHeight = FreeCAD.Units.parseQuantity(editform.CuttingEdgeHeightField.text())
if self.TLM.updateTool(listname, toolnum, tool) is True:
self.loadTable()
def importFile(self):
"imports a tooltable from a file"
filename = QtGui.QFileDialog.getOpenFileName(self.form, translate( "TooltableEditor", "Open tooltable", None), None, "{};;{};;{}".format(ToolLibraryManager.TooltableTypeJSON, ToolLibraryManager.TooltableTypeXML, ToolLibraryManager.TooltableTypeHeekscad))
if filename[0]:
listname = '<Main>'
if self.TLM.read(filename, listname):
self.loadTable()
def exportFile(self):
"export a tooltable to a file"
filename = QtGui.QFileDialog.getSaveFileName(self.form, translate("TooltableEditor", "Save tooltable", None), None, "{};;{};;{}".format(ToolLibraryManager.TooltableTypeJSON, ToolLibraryManager.TooltableTypeXML, ToolLibraryManager.TooltableTypeLinuxCNC))
if filename[0]:
#listname = self.form.listView.selectedIndexes()[0].data()
listname = '<Main>'
self.TLM.write(filename, listname)
def checkCopy(self):
self.form.btnCopyTools.setEnabled(False)
model = self.form.ToolsList.model()
for i in range(model.rowCount()):
item = model.item(i, 0)
if item.checkState():
self.form.btnCopyTools.setEnabled(True)
if len(PathUtils.GetJobs()) == 0:
self.form.btnCopyTools.setEnabled(False)
def copyTools(self):
tools = []
model = self.form.ToolsList.model()
for i in range(model.rowCount()):
item = model.item(i, 0)
if item.checkState():
item = model.index(i, 1)
tools.append(item.data())
if len(tools) == 0:
return
targets = self.TLM.getLists()
currList = "<Main>"
for target in targets:
if target == currList:
targets.remove(target)
if len(targets) == 0:
FreeCAD.Console.PrintWarning("no place to go")
return
elif len(targets) == 1:
targetlist = targets[0]
else:
form = FreeCADGui.PySideUic.loadUi(":/panels/DlgToolCopy.ui")
form.cboTarget.addItems(targets)
r = form.exec_()
if r is False:
return None
else:
targetlist = form.cboTarget.currentText()
for toolnum in tools:
tool = self.TLM.getTool(currList, int(toolnum))
PathLog.debug('tool: {}, toolnum: {}'.format(tool, toolnum))
if self.job:
label = "T{}: {}".format(toolnum, tool.Name)
tc = PathScripts.PathToolController.Create(label, tool=tool, toolNumber=int(toolnum))
self.job.Proxy.addToolController(tc)
else:
for job in FreeCAD.ActiveDocument.findObjects("Path::Feature"):
if isinstance(job.Proxy, PathScripts.PathJob.ObjectJob) and job.Label == targetlist:
label = "T{}: {}".format(toolnum, tool.Name)
tc = PathScripts.PathToolController.Create(label, tool=tool, toolNumber=int(toolnum))
job.Proxy.addToolController(tc)
if self.cb:
self.cb()
FreeCAD.ActiveDocument.recompute()
def getStandardButtons(self):
return int(QtGui.QDialogButtonBox.Ok)
def setupUi(self):
# Connect Signals and Slots
self.form.ButtonNewTool.clicked.connect(self.addTool)
self.form.ButtonImport.clicked.connect(self.importFile)
self.form.ButtonExport.clicked.connect(self.exportFile)
self.form.ButtonDown.clicked.connect(self.moveDown)
self.form.ButtonUp.clicked.connect(self.moveUp)
self.form.ButtonDelete.clicked.connect(self.delete)
self.form.ToolsList.doubleClicked.connect(self.editTool)
self.form.ToolsList.clicked.connect(self.checkCopy)
self.form.btnCopyTools.clicked.connect(self.copyTools)
self.form.btnCopyTools.setEnabled(False)
self.setFields()
class CommandToolLibraryEdit():
def edit(self, job=None, cb=None):
editor = EditorPanel(job, cb)
editor.setupUi()
r = editor.form.exec_()
if r:
pass
def GetResources(self):
return {'Pixmap' : 'Path-ToolTable',
'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_ToolTable","Tool Manager"),
'Accel': "P, T",
'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_ToolTable","Tool Manager")}
def IsActive(self):
return not FreeCAD.ActiveDocument is None
def Activated(self):
self.edit()
if FreeCAD.GuiUp:
# register the FreeCAD command
FreeCADGui.addCommand('Path_ToolLibraryEdit',CommandToolLibraryEdit())