diff --git a/cura/BlockSlicingDecorator.py b/cura/BlockSlicingDecorator.py new file mode 100644 index 0000000000..3fc0015836 --- /dev/null +++ b/cura/BlockSlicingDecorator.py @@ -0,0 +1,9 @@ +from UM.Scene.SceneNodeDecorator import SceneNodeDecorator + + +class BlockSlicingDecorator(SceneNodeDecorator): + def __init__(self): + super().__init__() + + def isBlockSlicing(self): + return True diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 2ab7837352..73cf50b214 100644 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -19,6 +19,8 @@ from UM.SaveFile import SaveFile from UM.Scene.Selection import Selection from UM.Scene.GroupDecorator import GroupDecorator from UM.Settings.Validator import Validator +from UM.Message import Message +from UM.i18n import i18nCatalog from UM.Operations.AddSceneNodeOperation import AddSceneNodeOperation from UM.Operations.RemoveSceneNodeOperation import RemoveSceneNodeOperation @@ -26,13 +28,13 @@ from UM.Operations.GroupedOperation import GroupedOperation from UM.Operations.SetTransformOperation import SetTransformOperation from UM.Operations.TranslateOperation import TranslateOperation from cura.SetParentOperation import SetParentOperation +from cura.SliceableObjectDecorator import SliceableObjectDecorator +from cura.BlockSlicingDecorator import BlockSlicingDecorator from UM.Settings.SettingDefinition import SettingDefinition, DefinitionPropertyType from UM.Settings.ContainerRegistry import ContainerRegistry from UM.Settings.SettingFunction import SettingFunction -from UM.i18n import i18nCatalog - from . import PlatformPhysics from . import BuildVolume from . import CameraAnimation @@ -135,6 +137,9 @@ class CuraApplication(QtApplication): } ) + self._currently_loading_files = [] + self._non_sliceable_extensions = [] + self._machine_action_manager = MachineActionManager.MachineActionManager() self._machine_manager = None # This is initialized on demand. self._setting_inheritance_manager = None @@ -580,9 +585,12 @@ class CuraApplication(QtApplication): def updatePlatformActivity(self, node = None): count = 0 scene_bounding_box = None + is_block_slicing_node = False for node in DepthFirstIterator(self.getController().getScene().getRoot()): - if type(node) is not SceneNode or not node.getMeshData(): + if type(node) is not SceneNode or (not node.getMeshData() and not node.callDecoration("getLayerData")): continue + if node.callDecoration("isBlockSlicing"): + is_block_slicing_node = True count += 1 if not scene_bounding_box: @@ -592,6 +600,10 @@ class CuraApplication(QtApplication): if other_bb is not None: scene_bounding_box = scene_bounding_box + node.getBoundingBox() + print_information = self.getPrintInformation() + if print_information: + print_information.setPreSliced(is_block_slicing_node) + if not scene_bounding_box: scene_bounding_box = AxisAlignedBox.Null @@ -712,7 +724,7 @@ class CuraApplication(QtApplication): for node in DepthFirstIterator(self.getController().getScene().getRoot()): if type(node) is not SceneNode: continue - if not node.getMeshData() and not node.callDecoration("isGroup"): + if (not node.getMeshData() and not node.callDecoration("getLayerData")) and not node.callDecoration("isGroup"): continue # Node that doesnt have a mesh and is not a group. if node.getParent() and node.getParent().callDecoration("isGroup"): continue # Grouped nodes don't need resetting as their parent (the group) is resetted) @@ -1007,3 +1019,78 @@ class CuraApplication(QtApplication): @pyqtSlot(str) def log(self, msg): Logger.log("d", msg) + + @pyqtSlot(QUrl) + def readLocalFile(self, file): + if not file.isValid(): + return + + scene = self.getController().getScene() + + for node in DepthFirstIterator(scene.getRoot()): + if node.callDecoration("isBlockSlicing"): + self.deleteAll() + break + + f = file.toLocalFile() + extension = os.path.splitext(f)[1] + filename = os.path.basename(f) + if len(self._currently_loading_files) > 0: + # If a non-slicable file is already being loaded, we prevent loading of any further non-slicable files + if extension.lower() in self._non_sliceable_extensions: + message = Message( + self._i18n_catalog.i18nc("@info:status", + "Only one G-code file can be loaded at a time. Skipped importing {0}", + filename)) + message.show() + return + # If file being loaded is non-slicable file, then prevent loading of any other files + extension = os.path.splitext(self._currently_loading_files[0])[1] + if extension.lower() in self._non_sliceable_extensions: + message = Message( + self._i18n_catalog.i18nc("@info:status", + "Can't open any other file if G-code is loading. Skipped importing {0}", + filename)) + message.show() + return + + self._currently_loading_files.append(f) + if extension in self._non_sliceable_extensions: + self.deleteAll() + + job = ReadMeshJob(f) + job.finished.connect(self._readMeshFinished) + job.start() + + def _readMeshFinished(self, job): + nodes = job.getResult() + filename = job.getFileName() + self._currently_loading_files.remove(filename) + + for node in nodes: + node.setSelectable(True) + node.setName(os.path.basename(filename)) + + extension = os.path.splitext(filename)[1] + if extension.lower() in self._non_sliceable_extensions: + self.getController().setActiveView("LayerView") + view = self.getController().getActiveView() + view.resetLayerData() + view.setLayer(9999999) + view.calculateMaxLayers() + + block_slicing_decorator = BlockSlicingDecorator() + node.addDecorator(block_slicing_decorator) + else: + sliceable_decorator = SliceableObjectDecorator() + node.addDecorator(sliceable_decorator) + + scene = self.getController().getScene() + + op = AddSceneNodeOperation(node, scene.getRoot()) + op.push() + + scene.sceneChanged.emit(node) + + def addNonSliceableExtension(self, extension): + self._non_sliceable_extensions.append(extension) diff --git a/cura/GCodeListDecorator.py b/cura/GCodeListDecorator.py new file mode 100644 index 0000000000..5738d0a7f2 --- /dev/null +++ b/cura/GCodeListDecorator.py @@ -0,0 +1,13 @@ +from UM.Scene.SceneNodeDecorator import SceneNodeDecorator + + +class GCodeListDecorator(SceneNodeDecorator): + def __init__(self): + super().__init__() + self._gcode_list = [] + + def getGCodeList(self): + return self._gcode_list + + def setGCodeList(self, list): + self._gcode_list = list diff --git a/cura/PrintInformation.py b/cura/PrintInformation.py index b65101ecc7..8fa7c48840 100644 --- a/cura/PrintInformation.py +++ b/cura/PrintInformation.py @@ -13,6 +13,9 @@ import math import os.path import unicodedata +from UM.i18n import i18nCatalog +catalog = i18nCatalog("cura") + ## A class for processing and calculating minimum, current and maximum print time as well as managing the job name # # This class contains all the logic relating to calculation and slicing for the @@ -49,6 +52,8 @@ class PrintInformation(QObject): self._material_lengths = [] self._material_weights = [] + self._pre_sliced = False + self._backend = Application.getInstance().getBackend() if self._backend: self._backend.printDurationMessage.connect(self._onPrintDurationMessage) @@ -61,6 +66,16 @@ class PrintInformation(QObject): currentPrintTimeChanged = pyqtSignal() + preSlicedChanged = pyqtSignal() + + @pyqtProperty(bool, notify=preSlicedChanged) + def preSliced(self): + return self._pre_sliced + + def setPreSliced(self, pre_sliced): + self._pre_sliced = pre_sliced + self.preSlicedChanged.emit() + @pyqtProperty(Duration, notify = currentPrintTimeChanged) def currentPrintTime(self): return self._current_print_time @@ -122,7 +137,9 @@ class PrintInformation(QObject): def createJobName(self, base_name): base_name = self._stripAccents(base_name) self._setAbbreviatedMachineName() - if Preferences.getInstance().getValue("cura/jobname_prefix"): + if self._pre_sliced: + return catalog.i18nc("@label", "Pre-sliced file {0}", base_name) + elif Preferences.getInstance().getValue("cura/jobname_prefix"): return self._abbr_machine + "_" + base_name else: return base_name @@ -150,4 +167,4 @@ class PrintInformation(QObject): ## Utility method that strips accents from characters (eg: รข -> a) def _stripAccents(self, str): - return ''.join(char for char in unicodedata.normalize('NFD', str) if unicodedata.category(char) != 'Mn') \ No newline at end of file + return ''.join(char for char in unicodedata.normalize('NFD', str) if unicodedata.category(char) != 'Mn') diff --git a/cura/SliceableObjectDecorator.py b/cura/SliceableObjectDecorator.py new file mode 100644 index 0000000000..1cb589d9c6 --- /dev/null +++ b/cura/SliceableObjectDecorator.py @@ -0,0 +1,12 @@ +from UM.Scene.SceneNodeDecorator import SceneNodeDecorator + + +class SliceableObjectDecorator(SceneNodeDecorator): + def __init__(self): + super().__init__() + + def isSliceable(self): + return True + + def __deepcopy__(self, memo): + return type(self)() diff --git a/plugins/3MFReader/ThreeMFReader.py b/plugins/3MFReader/ThreeMFReader.py index 397a63c194..e9f0e28511 100644 --- a/plugins/3MFReader/ThreeMFReader.py +++ b/plugins/3MFReader/ThreeMFReader.py @@ -1,22 +1,22 @@ # Copyright (c) 2015 Ultimaker B.V. # Cura is released under the terms of the AGPLv3 or higher. -from UM.Mesh.MeshReader import MeshReader -from UM.Mesh.MeshBuilder import MeshBuilder +import os.path +import zipfile + +from UM.Job import Job from UM.Logger import Logger from UM.Math.Matrix import Matrix from UM.Math.Vector import Vector -from UM.Scene.SceneNode import SceneNode +from UM.Mesh.MeshBuilder import MeshBuilder +from UM.Mesh.MeshReader import MeshReader from UM.Scene.GroupDecorator import GroupDecorator import UM.Application -from UM.Job import Job from cura.Settings.SettingOverrideDecorator import SettingOverrideDecorator from UM.Application import Application from cura.Settings.ExtruderManager import ExtruderManager from cura.QualityManager import QualityManager - -import os.path -import zipfile +from UM.Scene.SceneNode import SceneNode try: import xml.etree.cElementTree as ET @@ -263,4 +263,4 @@ class ThreeMFReader(MeshReader): Logger.log("w", "Unrecognised unit %s used. Assuming mm instead", unit) scale = 1 - return Vector(scale, scale, scale) \ No newline at end of file + return Vector(scale, scale, scale) diff --git a/plugins/CuraEngineBackend/CuraEngineBackend.py b/plugins/CuraEngineBackend/CuraEngineBackend.py index b2df2563d0..cf53475fb4 100644 --- a/plugins/CuraEngineBackend/CuraEngineBackend.py +++ b/plugins/CuraEngineBackend/CuraEngineBackend.py @@ -12,6 +12,8 @@ from UM.PluginRegistry import PluginRegistry from UM.Resources import Resources from UM.Settings.Validator import ValidatorState #To find if a setting is in an error state. We can't slice then. from UM.Platform import Platform +from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator + import cura.Settings @@ -69,6 +71,8 @@ class CuraEngineBackend(Backend): self._scene = Application.getInstance().getController().getScene() self._scene.sceneChanged.connect(self._onSceneChanged) + self._pause_slicing = False + # Workaround to disable layer view processing if layer view is not active. self._layer_view_active = False Application.getInstance().getController().activeViewChanged.connect(self._onActiveViewChanged) @@ -150,6 +154,8 @@ class CuraEngineBackend(Backend): ## Perform a slice of the scene. def slice(self): Logger.log("d", "Starting slice job...") + if self._pause_slicing: + return self._slice_start_time = time() if not self._enabled or not self._global_container_stack: # We shouldn't be slicing. # try again in a short time @@ -183,6 +189,17 @@ class CuraEngineBackend(Backend): self._start_slice_job.start() self._start_slice_job.finished.connect(self._onStartSliceCompleted) + + def pauseSlicing(self): + self.close() + self._pause_slicing = True + self.backendStateChange.emit(BackendState.Disabled) + + def continueSlicing(self): + if self._pause_slicing: + self._pause_slicing = False + self.backendStateChange.emit(BackendState.NotStarted) + ## Terminate the engine process. def _terminate(self): self._slicing = False @@ -298,6 +315,19 @@ class CuraEngineBackend(Backend): if source is self._scene.getRoot(): return + should_pause = False + for node in DepthFirstIterator(self._scene.getRoot()): + if node.callDecoration("isBlockSlicing"): + should_pause = True + gcode_list = node.callDecoration("getGCodeList") + if gcode_list is not None: + self._scene.gcode_list = gcode_list + + if should_pause: + self.pauseSlicing() + else: + self.continueSlicing() + if source.getMeshData() is None: return diff --git a/plugins/GCodeReader/GCodeReader.py b/plugins/GCodeReader/GCodeReader.py new file mode 100644 index 0000000000..34ea91a727 --- /dev/null +++ b/plugins/GCodeReader/GCodeReader.py @@ -0,0 +1,303 @@ +# Copyright (c) 2016 Aleph Objects, Inc. +# Cura is released under the terms of the AGPLv3 or higher. + +from UM.Application import Application +from UM.Logger import Logger +from UM.Math.AxisAlignedBox import AxisAlignedBox +from UM.Math.Vector import Vector +from UM.Mesh.MeshReader import MeshReader +from UM.Message import Message +from UM.Scene.SceneNode import SceneNode +from UM.i18n import i18nCatalog + +catalog = i18nCatalog("cura") + + +from cura import LayerDataBuilder +from cura import LayerDataDecorator +from cura.LayerPolygon import LayerPolygon +from cura.GCodeListDecorator import GCodeListDecorator + +import numpy +import math +import re +from collections import namedtuple + + +# Class for loading and parsing G-code files +class GCodeReader(MeshReader): + def __init__(self): + super(GCodeReader, self).__init__() + self._supported_extensions = [".gcode", ".g"] + Application.getInstance().hideMessageSignal.connect(self._onHideMessage) + self._cancelled = False + self._message = None + self._clearValues() + self._scene_node = None + self._position = namedtuple('Position', ['x', 'y', 'z', 'e']) + + def _clearValues(self): + self._extruder = 0 + self._layer_type = LayerPolygon.Inset0Type + self._layer = 0 + self._previous_z = 0 + self._layer_data_builder = LayerDataBuilder.LayerDataBuilder() + self._center_is_zero = False + + @staticmethod + def _getValue(line, code): + n = line.find(code) + if n < 0: + return None + n += len(code) + pattern = re.compile("[;\s]") + match = pattern.search(line, n) + m = match.start() if match is not None else -1 + try: + if m < 0: + return line[n:] + return line[n:m] + except: + return None + + def _getInt(self, line, code): + value = self._getValue(line, code) + try: + return int(value) + except: + return None + + def _getFloat(self, line, code): + value = self._getValue(line, code) + try: + return float(value) + except: + return None + + def _onHideMessage(self, message): + if message == self._message: + self._cancelled = True + + @staticmethod + def _getNullBoundingBox(): + return AxisAlignedBox(minimum=Vector(0, 0, 0), maximum=Vector(10, 10, 10)) + + def _createPolygon(self, current_z, path): + countvalid = 0 + for point in path: + if point[3] > 0: + countvalid += 1 + if countvalid < 2: + return False + try: + self._layer_data_builder.addLayer(self._layer) + self._layer_data_builder.setLayerHeight(self._layer, path[0][2]) + self._layer_data_builder.setLayerThickness(self._layer, math.fabs(current_z - self._previous_z)) + this_layer = self._layer_data_builder.getLayer(self._layer) + except ValueError: + return False + count = len(path) + line_types = numpy.empty((count - 1, 1), numpy.int32) + line_widths = numpy.empty((count - 1, 1), numpy.float32) + # TODO: need to calculate actual line width based on E values + line_widths[:, 0] = 0.4 + points = numpy.empty((count, 3), numpy.float32) + i = 0 + for point in path: + points[i, 0] = point[0] + points[i, 1] = point[2] + points[i, 2] = -point[1] + if i > 0: + line_types[i - 1] = point[3] + if point[3] in [LayerPolygon.MoveCombingType, LayerPolygon.MoveRetractionType]: + line_widths[i - 1] = 0.2 + i += 1 + + this_poly = LayerPolygon(self._layer_data_builder, self._extruder, line_types, points, line_widths) + this_poly.buildCache() + + this_layer.polygons.append(this_poly) + return True + + def _gCode0(self, position, params, path): + x, y, z, e = position + x = params.x if params.x is not None else x + y = params.y if params.y is not None else y + z_changed = False + if params.z is not None: + if z != params.z: + z_changed = True + self._previous_z = z + z = params.z + if params.e is not None: + if params.e > e[self._extruder]: + path.append([x, y, z, self._layer_type]) # extrusion + else: + path.append([x, y, z, LayerPolygon.MoveRetractionType]) # retraction + e[self._extruder] = params.e + else: + path.append([x, y, z, LayerPolygon.MoveCombingType]) + if z_changed: + if not self._is_layers_in_file: + if len(path) > 1 and z > 0: + if self._createPolygon(z, path): + self._layer += 1 + path.clear() + else: + path.clear() + return self._position(x, y, z, e) + + def _gCode28(self, position, params, path): + return self._position( + params.x if params.x is not None else position.x, + params.y if params.y is not None else position.y, + 0, + position.e) + + def _gCode92(self, position, params, path): + if params.e is not None: + position.e[self._extruder] = params.e + return self._position( + params.x if params.x is not None else position.x, + params.y if params.y is not None else position.y, + params.z if params.z is not None else position.z, + position.e) + + _gCode1 = _gCode0 + + def _processGCode(self, G, line, position, path): + func = getattr(self, "_gCode%s" % G, None) + x = self._getFloat(line, "X") + y = self._getFloat(line, "Y") + z = self._getFloat(line, "Z") + e = self._getFloat(line, "E") + if func is not None: + if (x is not None and x < 0) or (y is not None and y < 0): + self._center_is_zero = True + params = self._position(x, y, z, e) + return func(position, params, path) + return position + + def _processTCode(self, T, line, position, path): + self._extruder = T + if self._extruder + 1 > len(position.e): + position.e.extend([0] * (self._extruder - len(position.e) + 1)) + if not self._is_layers_in_file: + if len(path) > 1 and position[2] > 0: + if self._createPolygon(position[2], path): + self._layer += 1 + path.clear() + else: + path.clear() + return position + + _type_keyword = ";TYPE:" + _layer_keyword = ";LAYER:" + + def read(self, file_name): + Logger.log("d", "Preparing to load %s" % file_name) + self._cancelled = False + + scene_node = SceneNode() + scene_node.getBoundingBox = self._getNullBoundingBox # Manually set bounding box, because mesh doesn't have mesh data + + glist = [] + self._is_layers_in_file = False + + + Logger.log("d", "Opening file %s" % file_name) + + with open(file_name, "r") as file: + file_lines = 0 + current_line = 0 + for line in file: + file_lines += 1 + glist.append(line) + if not self._is_layers_in_file and line[:len(self._layer_keyword)] == self._layer_keyword: + self._is_layers_in_file = True + file.seek(0) + + file_step = max(math.floor(file_lines / 100), 1) + + self._clearValues() + + self._message = Message(catalog.i18nc("@info:status", "Parsing G-code"), lifetime=0) + self._message.setProgress(0) + self._message.show() + + Logger.log("d", "Parsing %s" % file_name) + + current_position = self._position(0, 0, 0, [0]) + current_path = [] + + for line in file: + if self._cancelled: + Logger.log("d", "Parsing %s cancelled" % file_name) + return None + current_line += 1 + if current_line % file_step == 0: + self._message.setProgress(math.floor(current_line / file_lines * 100)) + if len(line) == 0: + continue + if line.find(self._type_keyword) == 0: + type = line[len(self._type_keyword):].strip() + if type == "WALL-INNER": + self._layer_type = LayerPolygon.InsetXType + elif type == "WALL-OUTER": + self._layer_type = LayerPolygon.Inset0Type + elif type == "SKIN": + self._layer_type = LayerPolygon.SkinType + elif type == "SKIRT": + self._layer_type = LayerPolygon.SkirtType + elif type == "SUPPORT": + self._layer_type = LayerPolygon.SupportType + elif type == "FILL": + self._layer_type = LayerPolygon.InfillType + if self._is_layers_in_file and line[:len(self._layer_keyword)] == self._layer_keyword: + try: + layer_number = int(line[len(self._layer_keyword):]) + self._createPolygon(current_position[2], current_path) + current_path.clear() + self._layer = layer_number + except: + pass + if line[0] == ";": + continue + + G = self._getInt(line, "G") + if G is not None: + current_position = self._processGCode(G, line, current_position, current_path) + T = self._getInt(line, "T") + if T is not None: + current_position = self._processTCode(T, line, current_position, current_path) + + if not self._is_layers_in_file and len(current_path) > 1 and current_position[2] > 0: + if self._createPolygon(current_position[2], current_path): + self._layer += 1 + current_path.clear() + + layer_mesh = self._layer_data_builder.build() + decorator = LayerDataDecorator.LayerDataDecorator() + decorator.setLayerData(layer_mesh) + scene_node.addDecorator(decorator) + + gcode_list_decorator = GCodeListDecorator() + gcode_list_decorator.setGCodeList(glist) + scene_node.addDecorator(gcode_list_decorator) + + Logger.log("d", "Finished parsing %s" % file_name) + self._message.hide() + + if self._layer == 0: + Logger.log("w", "File %s doesn't contain any valid layers" % file_name) + + settings = Application.getInstance().getGlobalContainerStack() + machine_width = settings.getProperty("machine_width", "value") + machine_depth = settings.getProperty("machine_depth", "value") + + if not self._center_is_zero: + scene_node.setPosition(Vector(-machine_width / 2, 0, machine_depth / 2)) + + Logger.log("d", "Loaded %s" % file_name) + + return scene_node diff --git a/plugins/GCodeReader/__init__.py b/plugins/GCodeReader/__init__.py new file mode 100644 index 0000000000..2ff412e757 --- /dev/null +++ b/plugins/GCodeReader/__init__.py @@ -0,0 +1,33 @@ +# Copyright (c) 2016 Aleph Objects, Inc. +# Cura is released under the terms of the AGPLv3 or higher. + +from . import GCodeReader + +from UM.i18n import i18nCatalog +i18n_catalog = i18nCatalog("cura") + +def getMetaData(): + return { + "plugin": { + "name": i18n_catalog.i18nc("@label", "G-code Reader"), + "author": "Victor Larchenko", + "version": "1.0", + "description": i18n_catalog.i18nc("@info:whatsthis", "Allows loading and displaying G-code files."), + "api": 3 + }, + "mesh_reader": [ + { + "extension": "gcode", + "description": i18n_catalog.i18nc("@item:inlistbox", "G-code File") + }, + { + "extension": "g", + "description": i18n_catalog.i18nc("@item:inlistbox", "G File") + } + ] + } + +def register(app): + app.addNonSliceableExtension(".gcode") + app.addNonSliceableExtension(".g") + return { "mesh_reader": GCodeReader.GCodeReader() } diff --git a/plugins/LayerView/LayerPass.py b/plugins/LayerView/LayerPass.py index 8ff2eb16ec..9bc67efc58 100644 --- a/plugins/LayerView/LayerPass.py +++ b/plugins/LayerView/LayerPass.py @@ -45,10 +45,11 @@ class LayerPass(RenderPass): tool_handle_batch = RenderBatch(self._tool_handle_shader, type = RenderBatch.RenderType.Overlay) for node in DepthFirstIterator(self._scene.getRoot()): + if isinstance(node, ToolHandle): tool_handle_batch.addItem(node.getWorldTransformation(), mesh = node.getSolidMesh()) - elif isinstance(node, SceneNode) and node.getMeshData() and node.isVisible(): + elif isinstance(node, SceneNode) and (node.getMeshData() or node.callDecoration("isBlockSlicing")) and node.isVisible(): layer_data = node.callDecoration("getLayerData") if not layer_data: continue diff --git a/plugins/LayerView/LayerView.py b/plugins/LayerView/LayerView.py index 50c13194f7..0bae9c891c 100644 --- a/plugins/LayerView/LayerView.py +++ b/plugins/LayerView/LayerView.py @@ -119,7 +119,7 @@ class LayerView(View): continue if not node.render(renderer): - if node.getMeshData() and node.isVisible(): + if (node.getMeshData()) and node.isVisible(): renderer.queueNode(node, transparent = True, shader = self._ghost_shader) def setLayer(self, value): diff --git a/plugins/X3DReader/X3DReader.py b/plugins/X3DReader/X3DReader.py index ba31c9ea86..0a81e98d0d 100644 --- a/plugins/X3DReader/X3DReader.py +++ b/plugins/X3DReader/X3DReader.py @@ -1,15 +1,17 @@ # Contributed by Seva Alekseyev with National Institutes of Health, 2016 # Cura is released under the terms of the AGPLv3 or higher. -from UM.Mesh.MeshReader import MeshReader -from UM.Mesh.MeshBuilder import MeshBuilder +from math import pi, sin, cos, sqrt + +import numpy + +from UM.Job import Job from UM.Logger import Logger from UM.Math.Matrix import Matrix from UM.Math.Vector import Vector +from UM.Mesh.MeshBuilder import MeshBuilder +from UM.Mesh.MeshReader import MeshReader from UM.Scene.SceneNode import SceneNode -from UM.Job import Job -from math import pi, sin, cos, sqrt -import numpy try: import xml.etree.cElementTree as ET diff --git a/resources/qml/Cura.qml b/resources/qml/Cura.qml index 314f3b7443..1383338144 100644 --- a/resources/qml/Cura.qml +++ b/resources/qml/Cura.qml @@ -269,16 +269,16 @@ UM.MainWindow if(drop.urls.length > 0) { // Import models + var imported_model = -1; for(var i in drop.urls) { // There is no endsWith in this version of JS... if ((drop.urls[i].length <= 12) || (drop.urls[i].substring(drop.urls[i].length-12) !== ".curaprofile")) { // Drop an object - UM.MeshFileHandler.readLocalFile(drop.urls[i]); - if (i == drop.urls.length - 1) + Printer.readLocalFile(drop.urls[i]); + if (imported_model == -1) { - var meshName = backgroundItem.getMeshName(drop.urls[i].toString()); - backgroundItem.hasMesh(decodeURIComponent(meshName)); + imported_model = i; } } } @@ -297,6 +297,11 @@ UM.MainWindow } messageDialog.open() } + if (imported_model != -1) + { + var meshName = backgroundItem.getMeshName(drop.urls[imported_model].toString()) + backgroundItem.hasMesh(decodeURIComponent(meshName)) + } } } } @@ -406,7 +411,8 @@ UM.MainWindow iconSource: UM.Theme.getIcon("viewmode"); style: UM.Theme.styles.tool_button; - tooltip: ''; + tooltip: ""; + enabled: !PrintInformation.preSliced menu: ViewMenu { } } @@ -731,14 +737,11 @@ UM.MainWindow for(var i in fileUrls) { - UM.MeshFileHandler.readLocalFile(fileUrls[i]) - - if (i == fileUrls.length - 1) - { - var meshName = backgroundItem.getMeshName(fileUrls.toString()) - backgroundItem.hasMesh(decodeURIComponent(meshName)) - } + Printer.readLocalFile(fileUrls[i]) } + + var meshName = backgroundItem.getMeshName(fileUrls[0].toString()) + backgroundItem.hasMesh(decodeURIComponent(meshName)) } } diff --git a/resources/qml/Menus/RecentFilesMenu.qml b/resources/qml/Menus/RecentFilesMenu.qml index c47fc5715b..866b06ccbb 100644 --- a/resources/qml/Menus/RecentFilesMenu.qml +++ b/resources/qml/Menus/RecentFilesMenu.qml @@ -26,7 +26,7 @@ Menu return (index + 1) + ". " + path.slice(path.lastIndexOf("/") + 1); } onTriggered: { - UM.MeshFileHandler.readLocalFile(modelData); + Printer.readLocalFile(modelData); var meshName = backgroundItem.getMeshName(modelData.toString()) backgroundItem.hasMesh(decodeURIComponent(meshName)) } diff --git a/resources/qml/Menus/ViewMenu.qml b/resources/qml/Menus/ViewMenu.qml index 74579932e0..859620692e 100644 --- a/resources/qml/Menus/ViewMenu.qml +++ b/resources/qml/Menus/ViewMenu.qml @@ -11,6 +11,7 @@ Menu { title: catalog.i18nc("@title:menu menubar:toplevel", "&View"); id: menu + enabled: !PrintInformation.preSliced Instantiator { model: UM.ViewModel { } diff --git a/resources/qml/SaveButton.qml b/resources/qml/SaveButton.qml index 9b63fccf94..323123e9a7 100644 --- a/resources/qml/SaveButton.qml +++ b/resources/qml/SaveButton.qml @@ -34,6 +34,8 @@ Rectangle { return catalog.i18nc("@label:PrintjobStatus %1 is target operation","Ready to %1").arg(UM.OutputDeviceManager.activeDeviceShortDescription); case 4: return catalog.i18nc("@label:PrintjobStatus", "Unable to Slice"); + case 5: + return catalog.i18nc("@label:PrintjobStatus", "Slicing unavailable"); default: return ""; } @@ -104,7 +106,7 @@ Rectangle { id: saveToButton tooltip: UM.OutputDeviceManager.activeDeviceDescription; - enabled: base.backendState == 3 && base.activity == true + enabled: (base.backendState == 3 || base.backendState == 5) && base.activity == true height: UM.Theme.getSize("save_button_save_to_button").height anchors.top: parent.top @@ -179,7 +181,7 @@ Rectangle { anchors.rightMargin: UM.Theme.getSize("default_margin").width width: UM.Theme.getSize("save_button_save_to_button").height height: UM.Theme.getSize("save_button_save_to_button").height - enabled: base.backendState == 3 && base.activity == true + enabled: (base.backendState == 3 || base.backendState == 5) && base.activity == true visible: devicesModel.deviceCount > 1 diff --git a/resources/qml/Sidebar.qml b/resources/qml/Sidebar.qml index d97b69e801..a3f792d137 100644 --- a/resources/qml/Sidebar.qml +++ b/resources/qml/Sidebar.qml @@ -16,6 +16,7 @@ Rectangle property int currentModeIndex; property bool monitoringPrint: false + property bool hideSettings: PrintInformation.preSliced Connections { target: Printer @@ -288,7 +289,7 @@ Rectangle Label { id: settingsModeLabel - text: catalog.i18nc("@label:listbox", "Print Setup"); + text: !hideSettings ? catalog.i18nc("@label:listbox", "Print Setup") : catalog.i18nc("@label:listbox","Print Setup disabled\nG-code files cannot be modified"); anchors.left: parent.left anchors.leftMargin: UM.Theme.getSize("default_margin").width; anchors.top: headerSeparator.bottom @@ -308,7 +309,7 @@ Rectangle anchors.rightMargin: UM.Theme.getSize("default_margin").width anchors.top: headerSeparator.bottom anchors.topMargin: UM.Theme.getSize("default_margin").height - visible: !monitoringPrint + visible: !monitoringPrint && !hideSettings Component{ id: wizardDelegate Button { @@ -384,7 +385,7 @@ Rectangle height: settingsModeSelection.height width: visible ? height : 0 - visible: !monitoringPrint && modesListModel.get(base.currentModeIndex) != undefined && modesListModel.get(base.currentModeIndex).showFilterButton + visible: !monitoringPrint && !hideSettings && modesListModel.get(base.currentModeIndex) != undefined && modesListModel.get(base.currentModeIndex).showFilterButton opacity: visible ? 1 : 0 onClicked: sidebarContents.currentItem.toggleFilterField() @@ -432,7 +433,7 @@ Rectangle anchors.topMargin: UM.Theme.getSize("default_margin").height anchors.left: base.left anchors.right: base.right - visible: !monitoringPrint + visible: !monitoringPrint && !hideSettings delegate: StackViewDelegate {