Added suppor for default OutputFile with templates and a policy on how to deal with file name conflicts.

This commit is contained in:
ml 2016-10-23 22:23:38 -07:00 committed by wmayer
parent a3c4903d9b
commit e1a5b9a9e1
6 changed files with 327 additions and 84 deletions

View File

@ -42,8 +42,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>363</width>
<height>443</height>
<width>378</width>
<height>409</height>
</rect>
</property>
<attribute name="icon">
@ -146,7 +146,7 @@
<x>0</x>
<y>0</y>
<width>378</width>
<height>391</height>
<height>409</height>
</rect>
</property>
<attribute name="icon">
@ -216,7 +216,7 @@
<x>0</x>
<y>0</y>
<width>378</width>
<height>391</height>
<height>409</height>
</rect>
</property>
<attribute name="icon">
@ -229,7 +229,7 @@
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QWidget" name="widget_4" native="true">
<layout class="QGridLayout" name="gridLayout_6">
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
@ -237,16 +237,6 @@
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLineEdit" name="leOutputFile">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QPushButton" name="btnSelectFile">
<property name="sizePolicy">
@ -260,6 +250,64 @@
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLineEdit" name="leOutputFile">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Enter a path and optionally file name (see below) to be used as the default for the post processor export.&lt;/p&gt;&lt;p&gt;The following substitutions are performed before the name is resolved at the time of the post processing:&lt;/p&gt;&lt;p&gt;%D ... directory of the active document&lt;br/&gt;%d ... name of the active document (with extension)&lt;br/&gt;%M ... user macro directory&lt;br/&gt;%j ... name of the active Job object&lt;/p&gt;&lt;p&gt;The following example store all files with the same name as the document the directory /home/freecad (please remove quotes):&lt;/p&gt;&lt;p&gt;&amp;quot;/home/cnc/%d.g-code&amp;quot;&lt;/p&gt;&lt;p&gt;See the file save policy below on how to deal with name conflicts.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_9">
<property name="text">
<string>File Save Policy</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QComboBox" name="cboOutputPolicy">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Choose how to deal with potential file name conflicts. Always open a dialog, only open a dialog if the output file already exists, overwrite any existing file or add a unique (3 digit) sequential ID to the file name.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<item>
<property name="text">
<string>Use default</string>
</property>
</item>
<item>
<property name="text">
<string>Open File Dialog</string>
</property>
</item>
<item>
<property name="text">
<string>Open File Dialog on conflict</string>
</property>
</item>
<item>
<property name="text">
<string>Overwrite existing file</string>
</property>
</item>
<item>
<property name="text">
<string>Append Unique ID on conflict</string>
</property>
</item>
</widget>
</item>
</layout>
</widget>
</item>

View File

@ -22,32 +22,115 @@
</property>
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>General Path settings</string>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_12">
<item>
<widget class="QCheckBox" name="pathTestSetting1">
<property name="toolTip">
<string>If this option is enabled, new paths will automatically be placed in the active project, which will be created if necessary.</string>
</property>
<property name="text">
<string>Automatic project handling</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
<property name="prefEntry" stdset="0">
<cstring>pathAutoProject</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Path</cstring>
</property>
</widget>
</item>
</layout>
<property name="title">
<string>Output File</string>
</property>
<layout class="QFormLayout" name="formLayout_2">
<item row="0" column="0">
<widget class="QWidget" name="widget_2" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLabel" name="label_4">
<property name="text">
<string>Default Path</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="0" column="1">
<widget class="QWidget" name="widget" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLineEdit" name="leOutputFile">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Enter a path and optionally file name (see below) to be used as the default for the post processor export.&lt;/p&gt;&lt;p&gt;The following substitutions are performed before the name is resolved at the time of the post processing:&lt;/p&gt;&lt;p&gt;%D ... directory of the active document&lt;br/&gt;%d ... name of the active document (with extension)&lt;br/&gt;%M ... user macro directory&lt;br/&gt;%j ... name of the active Job object&lt;/p&gt;&lt;p&gt;The following example store all files with the same name as the document the directory /home/freecad (please remove quotes):&lt;/p&gt;&lt;p&gt;&amp;quot;/home/cnc/%d.g-code&amp;quot;&lt;/p&gt;&lt;p&gt;See the file save policy below on how to deal with name conflicts.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="browseFileSystem">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Open a file system browser to select default path for output files.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>...</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="1" column="0">
<widget class="QWidget" name="widget_3" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QLabel" name="label_5">
<property name="text">
<string>File Save Policy</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="1" column="1">
<widget class="QWidget" name="widget_4" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<widget class="QComboBox" name="cboOutputPolicy">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Choose how to deal with potential file name conflicts. Always open a dialog, only open a dialog if the output file already exists, overwrite any existing file or add a unique (3 digit) sequential ID to the file name.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<item>
<property name="text">
<string>Open File Dialog</string>
</property>
</item>
<item>
<property name="text">
<string>Open File Dialog on conflict</string>
</property>
</item>
<item>
<property name="text">
<string>Overwrite existing file</string>
</property>
</item>
<item>
<property name="text">
<string>Append Unique ID on conflict</string>
</property>
</item>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
@ -61,6 +144,23 @@
<property name="fieldGrowthPolicy">
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
</property>
<item row="0" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Post Processors Selection </string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QListWidget" name="postProcessorList">
<property name="mouseTracking">
<bool>true</bool>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;It doesn't seem there are any post processor scripts installed. Pleas add some into your macro directory and make sure the file name ends with &amp;quot;_post.py&amp;quot;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label">
<property name="text">
@ -101,23 +201,6 @@
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Enabled Post Processors </string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QListWidget" name="postProcessorList">
<property name="mouseTracking">
<bool>true</bool>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;It doesn't seem there are any post processor scripts installed. Pleas add some into your macro directory and make sure the file name ends with &amp;quot;_post.py&amp;quot;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>

View File

@ -31,6 +31,10 @@ class PathWorkbench (Workbench):
self.__class__.ToolTip = "Path workbench"
def Initialize(self):
# Add preferences pages - before loading PathGui to properly order pages of Path group
from PathScripts import PathPreferencesPathJob
FreeCADGui.addPreferencePage(PathPreferencesPathJob.Page, "Path")
# load the builtin modules
import Path
import PathGui
@ -70,7 +74,6 @@ class PathWorkbench (Workbench):
from PathScripts import PathContour
from PathScripts import PathProfileEdges
from PathScripts import DogboneDressup
from PathScripts import PathPreferencesPathJob
import PathCommands
# build commands list
@ -115,9 +118,6 @@ class PathWorkbench (Workbench):
# "Path", "Remote Operations")], remotecmdlist)
self.appendMenu([translate("Path", "&Path")], extracmdlist)
# Add preferences pages
FreeCADGui.addPreferencePage(PathPreferencesPathJob.Page, "Path")
Log('Loading Path workbench... done\n')
def GetClassName(self):

View File

@ -28,6 +28,7 @@ from PySide import QtCore, QtGui
import os
import glob
from PathScripts.PathPostProcessor import PostProcessor
from PathScripts.PathPost import CommandPathPost as PathPost
import Draft
@ -47,12 +48,24 @@ except AttributeError:
def translate(context, text, disambig=None):
return QtGui.QApplication.translate(context, text, disambig)
class OutputPolicy:
Default = 'Use default'
Dialog = 'Open File Dialog'
DialogOnConflict = 'Open File Dialog on conflict'
Overwrite = 'Overwrite existing file'
AppendID = 'Append Unique ID on conflict'
All = [Default, Dialog, DialogOnConflict, Overwrite, AppendID]
class ObjectPathJob:
def __init__(self, obj):
# obj.addProperty("App::PropertyFile", "PostProcessor", "CodeOutput", "Select the Post Processor file for this project")
obj.addProperty("App::PropertyFile", "OutputFile", "CodeOutput", QtCore.QT_TRANSLATE_NOOP("App::Property","The NC output file for this project"))
obj.OutputFile = PathPost.defaultOutputFile()
obj.setEditorMode("OutputFile", 0) # set to default mode
obj.addProperty("App::PropertyEnumeration", "OutputPolicy", "CodeOutput", QtCore.QT_TRANSLATE_NOOP("App::Property","The policy on how to save output files and resolve name conflicts"))
obj.OutputPolicy = OutputPolicy.All
obj.OutputPolicy = OutputPolicy.Default
obj.addProperty("App::PropertyString", "Description", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property","An optional description for this job"))
obj.addProperty("App::PropertyEnumeration", "PostProcessor", "Output", QtCore.QT_TRANSLATE_NOOP("App::Property","Select the Post Processor"))
@ -270,6 +283,8 @@ class TaskPanel:
self.obj.Label = str(self.form.leLabel.text())
if hasattr(self.obj, "OutputFile"):
self.obj.OutputFile = str(self.form.leOutputFile.text())
if hasattr(self.obj, "OutputPolicy"):
self.obj.OutputPolicy = str(self.form.cboOutputPolicy.currentText())
oldlist = self.obj.Group
newlist = []
@ -291,21 +306,23 @@ class TaskPanel:
self.obj.Proxy.execute(self.obj)
def selectComboBoxText(self, widget, text):
index = widget.findText(text, QtCore.Qt.MatchFixedString)
if index >= 0:
widget.blockSignals(True)
widget.setCurrentIndex(index)
widget.blockSignals(False)
def setFields(self):
'''sets fields in the form to match the object'''
self.form.leLabel.setText(self.obj.Label)
self.form.leOutputFile.setText(self.obj.OutputFile)
self.selectComboBoxText(self.form.cboOutputPolicy, self.obj.OutputPolicy)
postindex = self.form.cboPostProcessor.findText(
self.obj.PostProcessor, QtCore.Qt.MatchFixedString)
if postindex >= 0:
self.form.cboPostProcessor.blockSignals(True)
self.form.cboPostProcessor.setCurrentIndex(postindex)
self.form.cboPostProcessor.blockSignals(False)
# make sure the proxy loads post processor script values and settings
self.obj.Proxy.onChanged(self.obj, "PostProcessor")
self.updateTooltips()
self.selectComboBoxText(self.form.cboPostProcessor, self.obj.PostProcessor)
self.obj.Proxy.onChanged(self.obj, "PostProcessor")
self.updateTooltips()
for child in self.obj.Group:
self.form.PathsList.addItem(child.Name)
@ -335,6 +352,7 @@ class TaskPanel:
# Connect Signals and Slots
self.form.cboPostProcessor.currentIndexChanged.connect(self.getFields)
self.form.leOutputFile.editingFinished.connect(self.getFields)
self.form.cboOutputPolicy.currentIndexChanged.connect(self.getFields)
self.form.leLabel.editingFinished.connect(self.getFields)
self.form.btnSelectFile.clicked.connect(self.setFile)
self.form.PathsList.indexesMoved.connect(self.getFields)

View File

@ -42,6 +42,85 @@ except AttributeError:
class CommandPathPost:
DefaultOutputFile = "DefaultOutputFile"
DefaultOutputPolicy = "DefaultOutputPolicy"
@classmethod
def saveDefaults(cls, path, policy):
preferences = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Path")
preferences.SetString(cls.DefaultOutputFile, path)
preferences.SetString(cls.DefaultOutputPolicy, policy)
@classmethod
def defaultOutputFile(cls):
preferences = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Path")
return preferences.GetString(cls.DefaultOutputFile, "")
@classmethod
def defaultOutputPolicy(cls):
preferences = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Path")
return preferences.GetString(cls.DefaultOutputPolicy, "")
def resolveFileName(self, job):
path = "tmp.tap"
if job.OutputFile:
path = job.OutputFile
filename = path
if '%D' in filename:
D = FreeCAD.ActiveDocument.FileName
if D:
D = os.path.dirname(D)
else:
FreeCAD.Console.PrintError("Please save document in order to resolve output path!\n")
return None
filename = filename.replace('%D', D)
if '%d' in filename:
d = FreeCAD.ActiveDocument.Label
filename = filename.replace('%d', d)
if '%j' in filename:
j = job.Label
filename = filename.replace('%j', j)
if '%M' in filename:
pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Macro")
M = pref.GetString("MacroPath", FreeCAD.getUserAppDataDir())
filename = filename.replace('%M', M)
policy = job.OutputPolicy
if not policy or policy == 'Use default':
policy = self.defaultOutputPolicy()
openDialog = policy == 'Open File Dialog'
if os.path.isdir(filename) or not os.path.isdir(os.path.dirname(filename)):
# Either the entire filename resolves into a directory or the parent directory doesn't exist.
# Either way I don't know what to do - ask for help
openDialog = True
if os.path.isfile(filename) and not openDialog:
if policy == 'Open File Dialog on conflict':
openDialog = True
elif policy == 'Append Unique ID on conflict':
fn, ext = os.path.splitext(filename)
nr = fn[-3:]
n = 1
if nr.isdigit():
n = int(nr)
while os.path.isfile("%s%03d%s" % (fn, n, ext)):
n = n + 1
filename = "%s%03d%s" % (fn, n, ext)
if openDialog:
foo = QtGui.QFileDialog.getSaveFileName(QtGui.qApp.activeWindow(), "Output File", filename)
if foo:
filename = foo[0]
else:
filename = None
#print("resolveFileName(%s, %s) -> '%s'" % (path, policy, filename))
return filename
def GetResources(self):
return {'Pixmap': 'Path-Post',
'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_Post", "Post Process"),
@ -64,7 +143,6 @@ class CommandPathPost:
# default to the dumper post and default .tap file
postname = "dumper"
filename = "tmp.tap"
postArgs = ""
print "in activated %s" %(obj)
@ -73,7 +151,7 @@ class CommandPathPost:
# output filename
if hasattr(obj[0], "Group") and hasattr(obj[0], "Path"):
# # Check for a selected post post processor if it's set
proj = obj[0]
job = obj[0]
if hasattr(obj[0], "PostProcessor"):
postobj = obj[0]
@ -85,15 +163,17 @@ class CommandPathPost:
lessextn = os.path.splitext(postobj.PostProcessor)[0]
postname = os.path.split(lessextn)[1]
if proj.OutputFile:
filename = proj.OutputFile
if hasattr(postobj, "PostProcessorArgs"):
postArgs = postobj.PostProcessorArgs
processor = PostProcessor.load(postname)
processor.export(obj, filename, postArgs)
filename = self.resolveFileName(job)
if filename:
processor = PostProcessor.load(postname)
processor.export(obj, filename, postArgs)
FreeCAD.ActiveDocument.commitTransaction()
FreeCAD.ActiveDocument.commitTransaction()
else:
FreeCAD.ActiveDocument.abortTransaction()
FreeCAD.ActiveDocument.recompute()
if FreeCAD.GuiUp:

View File

@ -26,6 +26,7 @@ import FreeCAD
import FreeCADGui
from PySide import QtCore, QtGui
from PathScripts.PathPostProcessor import PostProcessor
from PathScripts.PathPost import CommandPathPost as PathPost
class Page:
@ -46,6 +47,17 @@ class Page:
blacklist.append(item.text())
PostProcessor.saveDefaults(processor, args, blacklist)
path = str(self.form.leOutputFile.text())
policy = str(self.form.cboOutputPolicy.currentText())
PathPost.saveDefaults(path, policy)
def selectComboEntry(self, widget, text):
index = widget.findText(text, QtCore.Qt.MatchFixedString)
if index >= 0:
widget.blockSignals(True)
widget.setCurrentIndex(index)
widget.blockSignals(False)
def loadSettings(self):
self.form.defaultPostProcessor.addItem("")
blacklist = PostProcessor.blacklist()
@ -58,18 +70,15 @@ class Page:
item.setCheckState(QtCore.Qt.CheckState.Checked)
item.setFlags( QtCore.Qt.ItemFlag.ItemIsSelectable | QtCore.Qt.ItemFlag.ItemIsEnabled | QtCore.Qt.ItemFlag.ItemIsUserCheckable)
self.form.postProcessorList.addItem(item)
postindex = self.form.defaultPostProcessor.findText(PostProcessor.default(), QtCore.Qt.MatchFixedString)
if postindex >= 0:
self.form.defaultPostProcessor.blockSignals(True)
self.form.defaultPostProcessor.setCurrentIndex(postindex)
self.form.defaultPostProcessor.blockSignals(False)
self.selectComboEntry(self.form.defaultPostProcessor, PostProcessor.default())
self.form.defaultPostProcessorArgs.setText(PostProcessor.defaultArgs())
self.form.leOutputFile.setText(PathPost.defaultOutputFile())
self.selectComboEntry(self.form.cboOutputPolicy, PathPost.defaultOutputPolicy())
self.form.postProcessorList.itemEntered.connect(self.setProcessorListTooltip)
self.form.defaultPostProcessor.currentIndexChanged.connect(self.updateDefaultPostProcessorToolTip)
self.form.browseFileSystem.clicked.connect(self.browseFileSystem)
def getPostProcessor(self, name):
if not name in self.processor.keys():
@ -100,3 +109,8 @@ class Page:
else:
self.form.defaultPostProcessor.setToolTip(self.postProcessorDefaultTooltip)
self.form.defaultPostProcessorArgs.setToolTip(self.postProcessorArgsDefaultTooltip)
def browseFileSystem(self):
foo = QtGui.QFileDialog.getExistingDirectory(QtGui.qApp.activeWindow(), "Path - Output File/Directory", self.form.defaultOutputPath.text())
if foo:
self.form.defaultOutputPath.setText(foo)