diff --git a/octoprint/cura/__init__.py b/octoprint/cura/__init__.py new file mode 100644 index 00000000..a9df95b7 --- /dev/null +++ b/octoprint/cura/__init__.py @@ -0,0 +1,57 @@ +__author__ = "Ross Hendrickson savorywatt" +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + +import logging + +from octoprint.settings import settings + +class CuraFactory(object): + + @staticmethod + def create_slicer(path=None): + if path: + return CuraEngine(path) + current_settings = settings(init=True) + path = current_settings.get(["curaEngine", "path"]) + + return CuraEngine(path) + + +class CuraEngine(object): + + def __init__(self, cura_path): + + if not cura_path: + raise Exception("Unable to create CuraEngine - no path specified") + + self.cura_path = cura_path + + logging.info('CuraEngine Created') + + + def process_file(self, config, gcode, file_path, call_back, call_back_args): + """Wraps around the main.cpp processFile method. + :param config: :class: `string` :path to a cura config file: + :param gcode: :class: `string :path to write out the gcode generated: + :param file_path: :class: `string :path to the STL to be sliced: + :note: This will spawn a thread to handle the subprocess call and allow + us to be able to have a call back + """ + import threading + + def start_thread(call_back, call_back_args, call_args): + import subprocess + logging.info("Starting SubProcess in Thread") + logging.info("Subprocess args: %s" % str(call_args)) + process = subprocess.call(call_args) + call_back(*call_back_args) + logging.info("Slicing call back complete") + + args = [self.cura_path, '-s', config, '-o', gcode, file_path] + logging.info('CuraEngine args:%s' % str(args)) + + thread = threading.Thread(target=start_thread, args=(call_back, + call_back_args, args)) + + thread.start() + logging.info('CuraEngine Slicing File:%s' % file_path) diff --git a/octoprint/cura/cura.py b/octoprint/cura/cura.py deleted file mode 100644 index d5c7db9e..00000000 --- a/octoprint/cura/cura.py +++ /dev/null @@ -1,48 +0,0 @@ -__author__ = "Ross Hendrickson savorywatt" -__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' - -import logging -import subprocess - -APPNAME="OctoPrint" - - -class CuraFactory(object): - - CURA_PATH = '/home/rosshendrickson/workspaces/opensource/CuraEngine/CuraEngine' - - @staticmethod - def create_slicer(path=None): - - if path: - return CuraEngine(path) - else: - return CuraEngine(CuraFactory.CURA_PATH) - - - -class CuraEngine(object): - - def __init__(self, cura_path): - - - self.cura_path = cura_path - - logging.info('CuraEngine Created') - - - def process_file(self, config, gcode, file_path): - """Wraps around the main.cpp processFile method. - - :param config: :class: `string` :path to a cura config file: - :param gcode: :class: `string :path to write out the gcode generated: - :param file_path: :class: `string :path to the STL to be sliced: - - :note This just uses subprocess at the moment. - """ - - args = [self.cura_path, '-s', config, '-o', gcode, file_path] - logging.info('CuraEngine args:%s' % str(args)) - - process = subprocess.call(args) - logging.info('CuraEngine Exit:%s' % str(process)) diff --git a/octoprint/cura/tests/test_cura.py b/octoprint/cura/tests/test_cura.py index ad3d4cc9..a75f507f 100644 --- a/octoprint/cura/tests/test_cura.py +++ b/octoprint/cura/tests/test_cura.py @@ -1,9 +1,10 @@ import unittest +from mock import patch -from cura import CuraFactory -from cura import CuraEngine +from octoprint.cura import CuraFactory +from octoprint.cura import CuraEngine class CuraFactoryTestCase(unittest.TestCase): @@ -15,14 +16,19 @@ class CuraFactoryTestCase(unittest.TestCase): self.assertEqual(fake_path, result.cura_path) - def test_cura_engine_process_file(self): - cura_engine = CuraFactory.create_slicer() + @patch('threading.Thread') + def test_cura_engine_process_file(self, thread): + path = 'rosshendrickson/workspaces/opensource/CuraEngine/' + + cura_engine = CuraFactory.create_slicer(path) + file_path = './cura/tests/test.stl' + config_path = './cura/tests/config' + gcode_filename= './cura/tests/output.gcode' - file_path = './cura/tests/test.stl' - config_path = './cura/tests/config' - gcode_filename= './cura/tests/output.gcode' - - cura_engine.process_file(config_path, gcode_filename, file_path) + args = [path, '-s', config_path, '-o', file_path] + + cura_engine.process_file(config_path, gcode_filename, file_path) + self.assertTrue(thread.called) diff --git a/octoprint/filemanager/__init__.py b/octoprint/filemanager/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/octoprint/filemanager/types.py b/octoprint/filemanager/types.py new file mode 100644 index 00000000..0c9d098e --- /dev/null +++ b/octoprint/filemanager/types.py @@ -0,0 +1,6 @@ + + +class FileTypes(object): + + STL = "stl" + GCODE = "gcode" diff --git a/octoprint/gcodefiles.py b/octoprint/gcodefiles.py index f4cfb416..177c419b 100644 --- a/octoprint/gcodefiles.py +++ b/octoprint/gcodefiles.py @@ -114,18 +114,55 @@ class GcodeManager: #~~ file handling def addFile(self, file): - if file: - absolutePath = self.getAbsolutePath(file.filename, mustExist=False) - if absolutePath is not None: - if file.filename in self._metadata.keys(): - # delete existing metadata entry, since the file is going to get overwritten - del self._metadata[file.filename] - self._metadataDirty = True - self._saveMetadata() - file.save(absolutePath) - self._metadataAnalyzer.addFileToQueue(os.path.basename(absolutePath)) - return self._getBasicFilename(absolutePath) - return None + from octoprint.filemanager.types import FileTypes + if not file: + return None + + absolutePath = self.getAbsolutePath(file.filename, mustExist=False) + logging.info("Abs Path %s" % absolutePath) + if absolutePath is None: + return None + + file.save(absolutePath) + fileType = file.filename.rsplit(".", 1)[1] + + if not fileType: + return None + + if fileType == FileTypes.GCODE: + return self.processGcode(file.filename, absolutePath) + + if fileType == FileTypes.STL: + return self.processSTL(file.filename, absolutePath) + + def processSTL(self, filename, absolutePath): + + from octoprint.cura import CuraFactory + + callBack = self.processGcode + gcodeFileName = util.genGcodeFileName(filename) + gcodePath = util.genGcodeFileName(absolutePath) + + callBackArgs = [gcodeFileName, gcodePath] + + curaEngine = CuraFactory.create_slicer() + current_settings = settings() + + config = current_settings.get(["curaEngine", "config"]) + + curaEngine.process_file( + config, gcodePath, absolutePath, callBack, callBackArgs) + + return self._getBasicFilename(absolutePath) + + def processGcode(self, filename, absolutePath): + if filename in self._metadata.keys(): + # delete existing metadata entry, since the file is going to get overwritten + del self._metadata[filename] + self._metadataDirty = True + self._saveMetadata() + self._metadataAnalyzer.addFileToQueue(os.path.basename(absolutePath)) + return self._getBasicFilename(absolutePath) def removeFile(self, filename): filename = self._getBasicFilename(filename) @@ -139,11 +176,11 @@ class GcodeManager: def getAbsolutePath(self, filename, mustExist=True): """ - Returns the absolute path of the given filename in the gcode upload folder. + Returns the absolute path of the given filename in the correct upload folder. Ensures that the file @@ -153,9 +190,10 @@ class GcodeManager: """ filename = self._getBasicFilename(filename) - if not util.isAllowedFile(filename, set(["gcode"])): + if not util.isAllowedFile(filename, set(["gcode", "stl"])): return None + # TODO: detect which type of file and add in the extra folder portion secure = os.path.join(self._uploadFolder, secure_filename(self._getBasicFilename(filename))) if mustExist and (not os.path.exists(secure) or not os.path.isfile(secure)): return None diff --git a/octoprint/server.py b/octoprint/server.py index d8e536d2..5984de73 100644 --- a/octoprint/server.py +++ b/octoprint/server.py @@ -308,6 +308,7 @@ def readGcodeFile(filename): @login_required def uploadGcodeFile(): filename = None + logging.info(str(request.files.keys())) if "gcode_file" in request.files.keys(): file = request.files["gcode_file"] filename = gcodeManager.addFile(file) diff --git a/octoprint/tests/__init__.py b/octoprint/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/octoprint/tests/test_file_manager.py b/octoprint/tests/test_file_manager.py new file mode 100644 index 00000000..50d804f9 --- /dev/null +++ b/octoprint/tests/test_file_manager.py @@ -0,0 +1,97 @@ +import unittest +from mock import Mock +from mock import patch + +import logging + +class FileManipulationTestCase(unittest.TestCase): + + def setUp(self): + + from octoprint.settings import settings + from octoprint.gcodefiles import GcodeManager + self.settings = settings(True) + self.manager = GcodeManager() + + self.filenames = [] + + def tearDown(self): + + for filename in self.filenames: + self.manager.removeFile(filename) + + logging.info("REMOVED %s filenames" % str(len(self.filenames))) + + @patch('octoprint.cura.CuraEngine.process_file') + def test_add_stl_file(self, process): + + fake = Mock() + fake.filename = "test_stl.stl" + self.filenames.append(fake.filename) + fake.__getitem__ = "SOMETHING" + + result = self.manager.addFile(fake) + + logging.info("RESULT:%s" % str(result)) + + self.assertTrue(fake.filename == result) + + self.assertTrue(process.called) + + def test_add_gcode_file(self): + fake = Mock() + fake.filename = "test_stl.gcode" + self.filenames.append(fake.filename) + fake.__getitem__ = "SOMETHING" + + result = self.manager.addFile(fake) + + logging.info("RESULT:%s" % str(result)) + + self.assertTrue(fake.filename == result) + + +class FileUtilTestCase(unittest.TestCase): + + def test_isGcode(self): + + from octoprint.util import isGcodeFileName + + filename = "/asdj/wefasdf/junk.stl" + + result = isGcodeFileName(filename) + + self.assertFalse(result) + + filename = "/asdj/wefasdf/junk.gcode" + + result = isGcodeFileName(filename) + + self.assertTrue(result) + + def test_isSTLFileName(self): + + from octoprint.util import isSTLFileName + filename = "/asdj/wefasdf/junk.stl" + + result = isSTLFileName(filename) + + self.assertTrue(result) + + filename = "/asdj/wefasdf/junk.gcode" + + result = isSTLFileName(filename) + + self.assertFalse(result) + + def test_genGcodeFileName(self): + + from octoprint.util import genGcodeFileName + + filename = "test.stl" + + expected = "test.gcode" + + result = genGcodeFileName(filename) + + self.assertEqual(result, expected) diff --git a/octoprint/tests/test_server.py b/octoprint/tests/test_server.py new file mode 100644 index 00000000..22589b4c --- /dev/null +++ b/octoprint/tests/test_server.py @@ -0,0 +1,9 @@ +import unittest + + +class FileManipulationTestCase(unittest.TestCase): + + + def test_simple(self): + + self.assertTrue(True) diff --git a/octoprint/util/__init__.py b/octoprint/util/__init__.py index 921ea9fb..cdd8ce4a 100644 --- a/octoprint/util/__init__.py +++ b/octoprint/util/__init__.py @@ -43,4 +43,21 @@ def getClass(name): return m def matchesGcode(line, gcode): - return re.search("^\s*%s\D" % gcode, line, re.I) \ No newline at end of file + return re.search("^\s*%s\D" % gcode, line, re.I) + +def isGcodeFileName(filename): + return "." in filename and filename.rsplit(".", 1)[1] in ["gcode", "GCODE"] + +def isSTLFileName(filename): + return "." in filename and filename.rsplit(".", 1)[1] in ["stl", "STL"] + +def genGcodeFileName(filename): + + if not filename: + return None + + if "." not in filename: + return filename + ".gcode" + + return filename.replace('.stl', '.gcode') +