From 2e504ab3b6ccb2b78e6dda2059f5cdca9bd3a91f Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Wed, 29 Sep 2021 09:36:58 -0500 Subject: [PATCH] [OpenSCAD] Add ability to communicate via pipes In 2021.01 OpenSCAD added the ability to read from stdin and write output to stdout: this allows us to communicate with an OpenSCAD process that does not have read/write access to the same directories that FreeCAD does (for example, if one or the other is installed via a Snap package). This commit adds an additional preference to the OpenSCAD workbench allowing the user to choose between communication methods, as well as to optionally specify their own temporary directory for the data transfer, for use in cases where their version of OpenSCAD is installed via Snap, etc., but does not yet support the piped input and output. --- src/Mod/OpenSCAD/OpenSCADUtils.py | 59 +++++++++++- .../Resources/ui/openscadprefs-base.ui | 92 ++++++++++++++++++- 2 files changed, 144 insertions(+), 7 deletions(-) diff --git a/src/Mod/OpenSCAD/OpenSCADUtils.py b/src/Mod/OpenSCAD/OpenSCADUtils.py index de6987795d..aedbf6fd01 100644 --- a/src/Mod/OpenSCAD/OpenSCADUtils.py +++ b/src/Mod/OpenSCAD/OpenSCADUtils.py @@ -168,12 +168,22 @@ def callopenscad(inputfilename,outputfilename=None,outputext='csg',keepname=Fals FreeCAD.Console.PrintMessage(stdoutd+u'\n') return stdoutd - osfilename = FreeCAD.ParamGet(\ - "User parameter:BaseApp/Preferences/Mod/OpenSCAD").\ - GetString('openscadexecutable') + preferences = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/OpenSCAD") + osfilename = preferences.GetString('openscadexecutable') + transferMechanism = preferences.GetInt('transfermechanism',0) + if transferMechanism == 0: # Use the Python temp-directory creation function + transferDirectory = tempfile.gettempdir() + elif transferMechanism == 1: # Use a user-specified directory for the transfer + transferDirectory = preferences.GetString('transferdirectory') + elif transferMechanism == 2: # Use pipes instead of tempfiles + return call_openscad_with_pipes(inputfilename, outputfilename, outputext, keepname) + else: + raise OpenSCADError("Invalid transfer mechanism specified"); + if osfilename and os.path.isfile(osfilename): if not outputfilename: - dir1=tempfile.gettempdir() + + dir1 = transferDirectory if keepname: outputfilename=os.path.join(dir1,'%s.%s' % (os.path.split(\ inputfilename)[1].rsplit('.',1)[0],outputext)) @@ -185,6 +195,47 @@ def callopenscad(inputfilename,outputfilename=None,outputext='csg',keepname=Fals else: raise OpenSCADError('OpenSCAD executable unavailable') +def call_openscad_with_pipes(input_filename, output_filename, output_extension, keep_name): + ''' Call OpenSCAD by sending input data to stdin, and read the output from stdout. + Returns the tempfile the output is stored in on success, or None on failure. + NOTE: This feature was added to OpenSCAD in 2021.01''' + + # For testing purposes continue using temp files, but now OpenSCAD does not need + # read or write access to the files, only the FreeCAD process does. In the future + # this could be changed to keep everything in memory, if desired. + import subprocess,tempfile,os + transfer_directory = tempfile.gettempdir() + + # Load the data back in from our tempfile: + with open(input_filename) as datafile: + openscad_data = datafile.read() + # On the command line this looks like: + # $ cat myfile.scad | openscad --export-format csg -o - - + preferences = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/OpenSCAD") + openscad_executable = preferences.GetString('openscadexecutable') + p = subprocess.Popen([openscad_executable,"--export-format","csg", "-o", "-", "-"], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + stdoutd,stderrd = p.communicate (input = openscad_data.encode('utf8'), timeout=15) + stdoutd = stdoutd.decode("utf8") + stderrd = stderrd.decode("utf8") + if p.returncode != 0: + raise OpenSCADError('%s %s\n' % (stdoutd.strip(),stderrd.strip())) + + if not output_filename: + dir1 = transfer_directory + if keep_name: + output_filename=os.path.join(dir1,'%s.%s' % (os.path.split(\ + input_filename)[1].rsplit('.',1)[0],output_extension)) + else: + output_filename=os.path.join(dir1,'%s.%s' % \ + (next(tempfilenamegen),output_extension)) + with open(output_filename,"w") as outfile: + outfile.write(stdoutd); + return output_filename + return None + def callopenscadstring(scadstr,outputext='csg'): '''create a tempfile and call the open scad binary returns the filename of the result (or None), diff --git a/src/Mod/OpenSCAD/Resources/ui/openscadprefs-base.ui b/src/Mod/OpenSCAD/Resources/ui/openscadprefs-base.ui index 9c449fe4e7..93912ea157 100644 --- a/src/Mod/OpenSCAD/Resources/ui/openscadprefs-base.ui +++ b/src/Mod/OpenSCAD/Resources/ui/openscadprefs-base.ui @@ -17,7 +17,16 @@ 6 - + + 9 + + + 9 + + + 9 + + 9 @@ -36,7 +45,7 @@ - + 300 @@ -166,7 +175,79 @@ - + + + + + Send to OpenSCAD via: + + + + + + + + 0 + 0 + + + + The transfer mechanism for getting data to and from OpenSCAD + + + transfermechanism + + + Mod/OpenSCAD + + + + Standard temp directory + + + + + User-specified directory + + + + + stdout pipe (requires OpenSCAD >= 2021.1) + + + + + + + + + + + + Transfer directory + + + + + + + + 300 + 0 + + + + The path to the directory for transfering files to and from OpenSCAD + + + transferdirectory + + + Mod/OpenSCAD + + + + @@ -442,6 +523,11 @@ QDoubleSpinBox
Gui/PrefWidgets.h
+ + Gui::PrefComboBox + QComboBox +
Gui/PrefWidgets.h
+