diff --git a/src/Mod/Path/Gui/Resources/panels/JobEdit.ui b/src/Mod/Path/Gui/Resources/panels/JobEdit.ui
index 76180f9cb..1b1c7921d 100644
--- a/src/Mod/Path/Gui/Resources/panels/JobEdit.ui
+++ b/src/Mod/Path/Gui/Resources/panels/JobEdit.ui
@@ -42,8 +42,8 @@
0
0
- 363
- 443
+ 378
+ 409
@@ -146,7 +146,7 @@
0
0
378
- 391
+ 409
@@ -216,7 +216,7 @@
0
0
378
- 391
+ 409
@@ -229,7 +229,7 @@
-
-
+
-
@@ -237,16 +237,6 @@
- -
-
-
-
- 0
- 0
-
-
-
-
-
@@ -260,6 +250,64 @@
+ -
+
+
+
+ 0
+ 0
+
+
+
+ <html><head/><body><p>Enter a path and optionally file name (see below) to be used as the default for the post processor export.</p><p>The following substitutions are performed before the name is resolved at the time of the post processing:</p><p>%D ... directory of the active document<br/>%d ... name of the active document (with extension)<br/>%M ... user macro directory<br/>%j ... name of the active Job object</p><p>The following example store all files with the same name as the document the directory /home/freecad (please remove quotes):</p><p>"/home/cnc/%d.g-code"</p><p>See the file save policy below on how to deal with name conflicts.</p></body></html>
+
+
+
+ -
+
+
+ File Save Policy
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ <html><head/><body><p>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.</p></body></html>
+
+
-
+
+ Use default
+
+
+ -
+
+ Open File Dialog
+
+
+ -
+
+ Open File Dialog on conflict
+
+
+ -
+
+ Overwrite existing file
+
+
+ -
+
+ Append Unique ID on conflict
+
+
+
+
diff --git a/src/Mod/Path/Gui/Resources/preferences/PathJob.ui b/src/Mod/Path/Gui/Resources/preferences/PathJob.ui
index 17e82cab4..16efd86c1 100644
--- a/src/Mod/Path/Gui/Resources/preferences/PathJob.ui
+++ b/src/Mod/Path/Gui/Resources/preferences/PathJob.ui
@@ -22,32 +22,115 @@
-
-
- General Path settings
+
+
+ 0
+ 0
+
-
-
-
-
-
-
-
-
- If this option is enabled, new paths will automatically be placed in the active project, which will be created if necessary.
-
-
- Automatic project handling
-
-
- true
-
-
- pathAutoProject
-
-
- Mod/Path
-
-
-
-
+
+ Output File
+
+
+ -
+
+
+
-
+
+
+ Default Path
+
+
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
-
+
+
+ <html><head/><body><p>Enter a path and optionally file name (see below) to be used as the default for the post processor export.</p><p>The following substitutions are performed before the name is resolved at the time of the post processing:</p><p>%D ... directory of the active document<br/>%d ... name of the active document (with extension)<br/>%M ... user macro directory<br/>%j ... name of the active Job object</p><p>The following example store all files with the same name as the document the directory /home/freecad (please remove quotes):</p><p>"/home/cnc/%d.g-code"</p><p>See the file save policy below on how to deal with name conflicts.</p></body></html>
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ <html><head/><body><p>Open a file system browser to select default path for output files.</p></body></html>
+
+
+ ...
+
+
+
+
+
+
+ -
+
+
+
-
+
+
+ File Save Policy
+
+
+
+
+
+
+ -
+
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ <html><head/><body><p>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.</p></body></html>
+
+
-
+
+ Open File Dialog
+
+
+ -
+
+ Open File Dialog on conflict
+
+
+ -
+
+ Overwrite existing file
+
+
+ -
+
+ Append Unique ID on conflict
+
+
+
+
+
+
@@ -61,6 +144,23 @@
QFormLayout::AllNonFixedFieldsGrow
+ -
+
+
+ Post Processors Selection
+
+
+
+ -
+
+
+ true
+
+
+ <html><head/><body><p>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 "_post.py".</p></body></html>
+
+
+
-
@@ -101,23 +201,6 @@
- -
-
-
- Enabled Post Processors
-
-
-
- -
-
-
- true
-
-
- <html><head/><body><p>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 "_post.py".</p></body></html>
-
-
-
diff --git a/src/Mod/Path/InitGui.py b/src/Mod/Path/InitGui.py
index f620d22f2..9894b2201 100644
--- a/src/Mod/Path/InitGui.py
+++ b/src/Mod/Path/InitGui.py
@@ -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):
diff --git a/src/Mod/Path/PathScripts/PathJob.py b/src/Mod/Path/PathScripts/PathJob.py
index 881aae939..dc1ddb361 100644
--- a/src/Mod/Path/PathScripts/PathJob.py
+++ b/src/Mod/Path/PathScripts/PathJob.py
@@ -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)
diff --git a/src/Mod/Path/PathScripts/PathPost.py b/src/Mod/Path/PathScripts/PathPost.py
index 67afad23b..cae311c93 100644
--- a/src/Mod/Path/PathScripts/PathPost.py
+++ b/src/Mod/Path/PathScripts/PathPost.py
@@ -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:
diff --git a/src/Mod/Path/PathScripts/PathPreferencesPathJob.py b/src/Mod/Path/PathScripts/PathPreferencesPathJob.py
index d9581de29..2cba8164c 100644
--- a/src/Mod/Path/PathScripts/PathPreferencesPathJob.py
+++ b/src/Mod/Path/PathScripts/PathPreferencesPathJob.py
@@ -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)