From 75b8466065195cee8618fe818265ea8251dcd015 Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Tue, 3 Nov 2015 11:05:28 +0100 Subject: [PATCH 1/7] Speed up building of the layerdata mesh Use numpy copies rather than python iteration since it is far faster. Contributes to CURA-224 --- plugins/CuraEngineBackend/LayerData.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/plugins/CuraEngineBackend/LayerData.py b/plugins/CuraEngineBackend/LayerData.py index 8d20a009e0..e59abf597c 100644 --- a/plugins/CuraEngineBackend/LayerData.py +++ b/plugins/CuraEngineBackend/LayerData.py @@ -63,6 +63,7 @@ class LayerData(MeshData): offset = data.build(offset, vertices, colors, indices) self._element_counts[layer] = data.elementCount + self.clear() self.addVertices(vertices) self.addColors(colors) self.addIndices(indices.flatten()) @@ -198,18 +199,14 @@ class Polygon(): def build(self, offset, vertices, colors, indices): self._begin = offset + self._end = self._begin + len(self._data) - 1 color = self.getColor() color.setValues(color.r * 0.5, color.g * 0.5, color.b * 0.5, color.a) + color = numpy.array([color.r, color.g, color.b, color.a], numpy.float32) - for i in range(len(self._data)): - vertices[offset + i, :] = self._data[i, :] - colors[offset + i, 0] = color.r - colors[offset + i, 1] = color.g - colors[offset + i, 2] = color.b - colors[offset + i, 3] = color.a - - self._end = self._begin + len(self._data) - 1 + vertices[self._begin:self._end + 1, :] = self._data[:, :] + colors[self._begin:self._end + 1, :] = color for i in range(self._begin, self._end): indices[i, 0] = i From 653b46d8255ba3e5751c77cc30b4b7ec1413559b Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Wed, 4 Nov 2015 13:04:22 +0100 Subject: [PATCH 2/7] Properly report Layer data processing progress Contributes to CURA-224 --- .../ProcessSlicedObjectListJob.py | 35 +++++++++++++------ 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/plugins/CuraEngineBackend/ProcessSlicedObjectListJob.py b/plugins/CuraEngineBackend/ProcessSlicedObjectListJob.py index 02dcecb80b..0a9322b7cb 100644 --- a/plugins/CuraEngineBackend/ProcessSlicedObjectListJob.py +++ b/plugins/CuraEngineBackend/ProcessSlicedObjectListJob.py @@ -54,6 +54,20 @@ class ProcessSlicedObjectListJob(Job): mesh = MeshData() layer_data = LayerData.LayerData() + + #Add layerdata decorator to scene node to indicate that the node has layerdata + decorator = LayerDataDecorator.LayerDataDecorator() + decorator.setLayerData(layer_data) + new_node.addDecorator(decorator) + + new_node.setMeshData(mesh) + new_node.setParent(self._scene.getRoot()) + + layer_count = 0 + for object in self._message.objects: + layer_count += len(object.layers) + + current_layer = 0 for object in self._message.objects: try: node = objectIdMap[object.id] @@ -73,23 +87,24 @@ class ProcessSlicedObjectListJob(Job): points[:,2] *= -1 - points -= numpy.array(center) + points -= center layer_data.addPolygon(layer.id, polygon.type, points, polygon.line_width) + current_layer += 1 + progress = (current_layer / layer_count) * 100 + # TODO: Rebuild the layer data mesh once the layer has been processed. + # This needs some work in LayerData so we can add the new layers instead of recreating the entire mesh. + + if self._progress: + self._progress.setProgress(progress) # We are done processing all the layers we got from the engine, now create a mesh out of the data layer_data.build() - - #Add layerdata decorator to scene node to indicate that the node has layerdata - decorator = LayerDataDecorator.LayerDataDecorator() - decorator.setLayerData(layer_data) - new_node.addDecorator(decorator) - - new_node.setMeshData(mesh) - new_node.setParent(self._scene.getRoot()) - + if self._progress: + self._progress.setProgress(100) + view = Application.getInstance().getController().getActiveView() if view.getPluginId() == "LayerView": view.resetLayerData() From 0b2f0b26042d9c119262a8f5426c0253fc824b9e Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Wed, 4 Nov 2015 13:06:40 +0100 Subject: [PATCH 3/7] Add thread yields to several long running and heavy processing jobs Contributes to CURA-358 --- cura/ConvexHullJob.py | 2 ++ plugins/3MFReader/ThreeMFReader.py | 6 ++++++ plugins/CuraEngineBackend/ProcessSlicedObjectListJob.py | 4 +++- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/cura/ConvexHullJob.py b/cura/ConvexHullJob.py index 63eec87fb3..2388d1c9aa 100644 --- a/cura/ConvexHullJob.py +++ b/cura/ConvexHullJob.py @@ -31,6 +31,8 @@ class ConvexHullJob(Job): self._node.callDecoration("setConvexHullJob", None) return + Job.yieldThread() + else: if not self._node.getMeshData(): return diff --git a/plugins/3MFReader/ThreeMFReader.py b/plugins/3MFReader/ThreeMFReader.py index 9ca8875543..2b20954988 100644 --- a/plugins/3MFReader/ThreeMFReader.py +++ b/plugins/3MFReader/ThreeMFReader.py @@ -10,6 +10,7 @@ from UM.Scene.SceneNode import SceneNode from UM.Scene.GroupDecorator import GroupDecorator from UM.Math.Quaternion import Quaternion +from UM.Job import Job import os import struct @@ -53,6 +54,7 @@ class ThreeMFReader(MeshReader): #for vertex in object.mesh.vertices.vertex: for vertex in object.findall(".//3mf:vertex", self._namespaces): vertex_list.append([vertex.get("x"), vertex.get("y"), vertex.get("z")]) + Job.yieldThread() triangles = object.findall(".//3mf:triangle", self._namespaces) @@ -64,6 +66,8 @@ class ThreeMFReader(MeshReader): v2 = int(triangle.get("v2")) v3 = int(triangle.get("v3")) mesh.addFace(vertex_list[v1][0],vertex_list[v1][1],vertex_list[v1][2],vertex_list[v2][0],vertex_list[v2][1],vertex_list[v2][2],vertex_list[v3][0],vertex_list[v3][1],vertex_list[v3][2]) + Job.yieldThread() + #TODO: We currently do not check for normals and simply recalculate them. mesh.calculateNormals() node.setMeshData(mesh) @@ -116,6 +120,8 @@ class ThreeMFReader(MeshReader): node.rotate(rotation) result.addChild(node) + Job.yieldThread() + #If there is more then one object, group them. try: if len(objects) > 1: diff --git a/plugins/CuraEngineBackend/ProcessSlicedObjectListJob.py b/plugins/CuraEngineBackend/ProcessSlicedObjectListJob.py index 0a9322b7cb..67a24c368d 100644 --- a/plugins/CuraEngineBackend/ProcessSlicedObjectListJob.py +++ b/plugins/CuraEngineBackend/ProcessSlicedObjectListJob.py @@ -38,10 +38,10 @@ class ProcessSlicedObjectListJob(Job): for node in DepthFirstIterator(self._scene.getRoot()): if type(node) is SceneNode and node.getMeshData(): if node.callDecoration("getLayerData"): - #if hasattr(node.getMeshData(), "layerData"): self._scene.getRoot().removeChild(node) else: objectIdMap[id(node)] = node + Job.yieldThread() settings = Application.getInstance().getMachineManager().getActiveProfile() layerHeight = settings.getSettingValue("layer_height") @@ -91,6 +91,8 @@ class ProcessSlicedObjectListJob(Job): layer_data.addPolygon(layer.id, polygon.type, points, polygon.line_width) + Job.yieldThread() + current_layer += 1 progress = (current_layer / layer_count) * 100 # TODO: Rebuild the layer data mesh once the layer has been processed. From 20b828eceeeea3d7672838d77f6a2ba65bb68c0e Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Wed, 4 Nov 2015 13:08:03 +0100 Subject: [PATCH 4/7] Add a Job subclass that handles sending data to the engine This can be used by the CuraEngine backend to reduce lag when we start slicing. Contributes to CURA-358 --- plugins/CuraEngineBackend/StartSliceJob.py | 123 +++++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 plugins/CuraEngineBackend/StartSliceJob.py diff --git a/plugins/CuraEngineBackend/StartSliceJob.py b/plugins/CuraEngineBackend/StartSliceJob.py new file mode 100644 index 0000000000..1a2dacf38a --- /dev/null +++ b/plugins/CuraEngineBackend/StartSliceJob.py @@ -0,0 +1,123 @@ +# Copyright (c) 2015 Ultimaker B.V. +# Cura is released under the terms of the AGPLv3 or higher. + +import time +import numpy + +from UM.Job import Job +from UM.Application import Application +from UM.Logger import Logger + +from UM.Scene.SceneNode import SceneNode +from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator + +from cura.OneAtATimeIterator import OneAtATimeIterator + +from . import Cura_pb2 + +class StartSliceJob(Job): + def __init__(self, profile, socket): + super().__init__() + + self._scene = Application.getInstance().getController().getScene() + self._profile = profile + self._socket = socket + + def run(self): + self._scene.acquireLock() + + for node in DepthFirstIterator(self._scene.getRoot()): + if node.callDecoration("getLayerData"): + node.getParent().removeChild(node) + break + + object_groups = [] + if self._profile.getSettingValue("print_sequence") == "one_at_a_time": + for node in OneAtATimeIterator(self._scene.getRoot()): + temp_list = [] + + if getattr(node, "_outside_buildarea", False): + continue + + children = node.getAllChildren() + children.append(node) + for child_node in children: + if type(child_node) is SceneNode and child_node.getMeshData() and child_node.getMeshData().getVertices() is not None: + temp_list.append(child_node) + + object_groups.append(temp_list) + Job.yieldThread() + else: + temp_list = [] + for node in DepthFirstIterator(self._scene.getRoot()): + if type(node) is SceneNode and node.getMeshData() and node.getMeshData().getVertices() is not None: + if not getattr(node, "_outside_buildarea", False): + temp_list.append(node) + Job.yieldThread() + object_groups.append(temp_list) + + self._scene.releaseLock() + + if not object_groups: + return + + self._sendSettings(self._profile) + + slice_message = Cura_pb2.Slice() + + for group in object_groups: + group_message = slice_message.object_lists.add() + for object in group: + print(object) + mesh_data = object.getMeshData().getTransformed(object.getWorldTransformation()) + + obj = group_message.objects.add() + obj.id = id(object) + + verts = numpy.array(mesh_data.getVertices()) + verts[:,[1,2]] = verts[:,[2,1]] + verts[:,1] *= -1 + obj.vertices = verts.tostring() + + self._handlePerObjectSettings(object, obj) + + Job.yieldThread() + + # Hack to add per-object settings also to the "MeshGroup" in CuraEngine + # We really should come up with a better solution for this. + self._handlePerObjectSettings(group[0], group_message) + + Logger.log("d", "Sending data to engine for slicing.") + self._socket.sendMessage(slice_message) + + self.setResult(True) + + def _sendSettings(self, profile): + msg = Cura_pb2.SettingList() + for key, value in profile.getAllSettingValues(include_machine = True).items(): + s = msg.settings.add() + s.name = key + s.value = str(value).encode("utf-8") + + self._socket.sendMessage(msg) + + def _handlePerObjectSettings(self, node, message): + profile = node.callDecoration("getProfile") + if profile: + for key, value in profile.getChangedSettingValues().items(): + setting = message.settings.add() + setting.name = key + setting.value = str(value).encode() + + Job.yieldThread() + + object_settings = node.callDecoration("getAllSettingValues") + if not object_settings: + return + + for key, value in object_settings.items(): + setting = message.settings.add() + setting.name = key + setting.value = str(value).encode() + + Job.yieldThread() From cb05aee3914cb7ae39f04495adffd566c517fd9c Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Wed, 4 Nov 2015 14:08:20 +0100 Subject: [PATCH 5/7] Move the start of slicing to a proper job This way it can be properly threaded (with a generous sprinkling of "yieldThread") so we do not block the UI when slicing starts. Contributes to CURA-358 --- .../CuraEngineBackend/CuraEngineBackend.py | 197 +++++------------- plugins/CuraEngineBackend/StartSliceJob.py | 9 +- 2 files changed, 54 insertions(+), 152 deletions(-) diff --git a/plugins/CuraEngineBackend/CuraEngineBackend.py b/plugins/CuraEngineBackend/CuraEngineBackend.py index c612a6656c..7ce8cd33f6 100644 --- a/plugins/CuraEngineBackend/CuraEngineBackend.py +++ b/plugins/CuraEngineBackend/CuraEngineBackend.py @@ -17,6 +17,7 @@ from cura.OneAtATimeIterator import OneAtATimeIterator from . import Cura_pb2 from . import ProcessSlicedObjectListJob from . import ProcessGCodeJob +from . import StartSliceJob import os import sys @@ -67,12 +68,8 @@ class CuraEngineBackend(Backend): self._slicing = False self._restart = False - - self._save_gcode = True - self._save_polygons = True - self._report_progress = True - self._enabled = True + self._always_restart = True self._message = None @@ -97,24 +94,12 @@ class CuraEngineBackend(Backend): ## Emitted whne the slicing process is aborted forcefully. slicingCancelled = Signal() - ## Perform a slice of the scene with the given set of settings. - # - # \param kwargs Keyword arguments. - # Valid values are: - # - settings: The settings to use for the slice. The default is the active machine. - # - save_gcode: True if the generated gcode should be saved, False if not. True by default. - # - save_polygons: True if the generated polygon data should be saved, False if not. True by default. - # - force_restart: True if the slicing process should be forcefully restarted if it is already slicing. - # If False, this method will do nothing when already slicing. True by default. - # - report_progress: True if the slicing progress should be reported, False if not. Default is True. - def slice(self, **kwargs): + ## Perform a slice of the scene. + def slice(self): if not self._enabled: return if self._slicing: - if not kwargs.get("force_restart", True): - return - self._slicing = False self._restart = True if self._process is not None: @@ -123,41 +108,15 @@ class CuraEngineBackend(Backend): self._process.terminate() except: # terminating a process that is already terminating causes an exception, silently ignore this. pass - self.slicingCancelled.emit() - return - Logger.log("d", "Preparing to send slice data to engine.") - object_groups = [] - if self._profile.getSettingValue("print_sequence") == "one_at_a_time": - for node in OneAtATimeIterator(self._scene.getRoot()): - temp_list = [] - children = node.getAllChildren() - children.append(node) - for child_node in children: - if type(child_node) is SceneNode and child_node.getMeshData() and child_node.getMeshData().getVertices() is not None: - temp_list.append(child_node) - object_groups.append(temp_list) - else: - temp_list = [] - for node in DepthFirstIterator(self._scene.getRoot()): - if type(node) is SceneNode and node.getMeshData() and node.getMeshData().getVertices() is not None: - if not getattr(node, "_outside_buildarea", False): - temp_list.append(node) - if len(temp_list) == 0: - self.processingProgress.emit(0.0) - return - object_groups.append(temp_list) - #for node in DepthFirstIterator(self._scene.getRoot()): - # if type(node) is SceneNode and node.getMeshData() and node.getMeshData().getVertices() is not None: - # if not getattr(node, "_outside_buildarea", False): - # objects.append(node) - if len(object_groups) == 0: if self._message: self._message.hide() self._message = None - return #No point in slicing an empty build plate - if kwargs.get("profile", self._profile).hasErrorValue(): + self.slicingCancelled.emit() + return + + if self._profile.hasErrorValue(): Logger.log('w', "Profile has error values. Aborting slicing") if self._message: self._message.hide() @@ -165,62 +124,27 @@ class CuraEngineBackend(Backend): self._message = Message(catalog.i18nc("@info:status", "Unable to slice. Please check your setting values for errors.")) self._message.show() return #No slicing if we have error values since those are by definition illegal values. - # Remove existing layer data (if any) - for node in DepthFirstIterator(self._scene.getRoot()): - if type(node) is SceneNode and node.getMeshData(): - if node.callDecoration("getLayerData"): - Application.getInstance().getController().getScene().getRoot().removeChild(node) - break - Application.getInstance().getController().getScene().gcode_list = None + + self.processingProgress.emit(0.0) + if not self._message: + self._message = Message(catalog.i18nc("@info:status", "Slicing..."), 0, False, -1) + self._message.show() + else: + self._message.setProgress(-1) + + self._scene.gcode_list = [] self._slicing = True - self.slicingStarted.emit() - self._report_progress = kwargs.get("report_progress", True) - if self._report_progress: - self.processingProgress.emit(0.0) - if not self._message: - self._message = Message(catalog.i18nc("@info:status", "Slicing..."), 0, False, -1) - self._message.show() - else: - self._message.setProgress(-1) + job = StartSliceJob.StartSliceJob(self._profile, self._socket) + job.start() + job.finished.connect(self._onStartSliceCompleted) - self._sendSettings(kwargs.get("profile", self._profile)) - - self._scene.acquireLock() - - # Set the gcode as an empty list. This will be filled with strings by GCodeLayer messages. - # This is done so the gcode can be fragmented in memory and does not need a continues memory space. - # (AKA. This prevents MemoryErrors) - self._save_gcode = kwargs.get("save_gcode", True) - if self._save_gcode: - setattr(self._scene, "gcode_list", []) - - self._save_polygons = kwargs.get("save_polygons", True) - - slice_message = Cura_pb2.Slice() - - for group in object_groups: - group_message = slice_message.object_lists.add() - for object in group: - mesh_data = object.getMeshData().getTransformed(object.getWorldTransformation()) - - obj = group_message.objects.add() - obj.id = id(object) - - verts = numpy.array(mesh_data.getVertices()) - verts[:,[1,2]] = verts[:,[2,1]] - verts[:,1] *= -1 - obj.vertices = verts.tostring() - - self._handlePerObjectSettings(object, obj) - - # Hack to add per-object settings also to the "MeshGroup" in CuraEngine - # We really should come up with a better solution for this. - self._handlePerObjectSettings(group[0], group_message) - - self._scene.releaseLock() - Logger.log("d", "Sending data to engine for slicing.") - self._socket.sendMessage(slice_message) + def _onStartSliceCompleted(self, job): + if job.getError() or job.getResult() != True: + if self._message: + self._message.hide() + self._message = None + return def _onSceneChanged(self, source): if type(source) is not SceneNode: @@ -250,41 +174,42 @@ class CuraEngineBackend(Backend): self._onChanged() def _onSlicedObjectListMessage(self, message): - if self._save_polygons: - if self._layer_view_active: - job = ProcessSlicedObjectListJob.ProcessSlicedObjectListJob(message) - job.start() - else : - self._stored_layer_data = message + if self._layer_view_active: + job = ProcessSlicedObjectListJob.ProcessSlicedObjectListJob(message) + job.start() + else : + self._stored_layer_data = message def _onProgressMessage(self, message): - if message.amount >= 0.99: - self._slicing = False - - if self._message: - self._message.setProgress(100) - self._message.hide() - self._message = None - if self._message: self._message.setProgress(round(message.amount * 100)) - if self._report_progress: - self.processingProgress.emit(message.amount) + self.processingProgress.emit(message.amount) def _onGCodeLayerMessage(self, message): - if self._save_gcode: - job = ProcessGCodeJob.ProcessGCodeLayerJob(message) - job.start() + self._scene.gcode_list.append(message.data.decode("utf-8", "replace")) def _onGCodePrefixMessage(self, message): - if self._save_gcode: - self._scene.gcode_list.insert(0, message.data.decode("utf-8", "replace")) + self._scene.gcode_list.insert(0, message.data.decode("utf-8", "replace")) def _onObjectPrintTimeMessage(self, message): self.printDurationMessage.emit(message.time, message.material_amount) self.processingProgress.emit(1.0) + self._slicing = False + + if self._message: + self._message.setProgress(100) + self._message.hide() + self._message = None + + if self._always_restart: + try: + self._process.terminate() + self._createSocket() + except: # terminating a process that is already terminating causes an exception, silently ignore this. + pass + def _createSocket(self): super()._createSocket() @@ -306,15 +231,6 @@ class CuraEngineBackend(Backend): self._change_timer.start() - def _sendSettings(self, profile): - msg = Cura_pb2.SettingList() - for key, value in profile.getAllSettingValues(include_machine = True).items(): - s = msg.settings.add() - s.name = key - s.value = str(value).encode("utf-8") - - self._socket.sendMessage(msg) - def _onBackendConnected(self): if self._restart: self._onChanged() @@ -338,20 +254,3 @@ class CuraEngineBackend(Backend): self._stored_layer_data = None else: self._layer_view_active = False - - def _handlePerObjectSettings(self, node, message): - profile = node.callDecoration("getProfile") - if profile: - for key, value in profile.getChangedSettingValues().items(): - setting = message.settings.add() - setting.name = key - setting.value = str(value).encode() - - object_settings = node.callDecoration("getAllSettingValues") - if not object_settings: - return - - for key, value in object_settings.items(): - setting = message.settings.add() - setting.name = key - setting.value = str(value).encode() diff --git a/plugins/CuraEngineBackend/StartSliceJob.py b/plugins/CuraEngineBackend/StartSliceJob.py index 1a2dacf38a..f629819e8c 100644 --- a/plugins/CuraEngineBackend/StartSliceJob.py +++ b/plugins/CuraEngineBackend/StartSliceJob.py @@ -15,6 +15,7 @@ from cura.OneAtATimeIterator import OneAtATimeIterator from . import Cura_pb2 +## Job class that handles sending the current scene data to CuraEngine class StartSliceJob(Job): def __init__(self, profile, socket): super().__init__() @@ -45,7 +46,8 @@ class StartSliceJob(Job): if type(child_node) is SceneNode and child_node.getMeshData() and child_node.getMeshData().getVertices() is not None: temp_list.append(child_node) - object_groups.append(temp_list) + if temp_list: + object_groups.append(temp_list) Job.yieldThread() else: temp_list = [] @@ -54,7 +56,9 @@ class StartSliceJob(Job): if not getattr(node, "_outside_buildarea", False): temp_list.append(node) Job.yieldThread() - object_groups.append(temp_list) + + if temp_list: + object_groups.append(temp_list) self._scene.releaseLock() @@ -68,7 +72,6 @@ class StartSliceJob(Job): for group in object_groups: group_message = slice_message.object_lists.add() for object in group: - print(object) mesh_data = object.getMeshData().getTransformed(object.getWorldTransformation()) obj = group_message.objects.add() From 1140c853d143abc61655d8ae2ab43585ffc3c59c Mon Sep 17 00:00:00 2001 From: Arjen Hiemstra Date: Wed, 4 Nov 2015 16:42:07 +0100 Subject: [PATCH 6/7] Try to use Protobuf CPP implementation if it is available The C++ implementation is far faster so should always be used if available. If not, we log a warning since it makes a big difference. --- cura/CuraApplication.py | 3 +++ cura_app.py | 8 ++++++++ 2 files changed, 11 insertions(+) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 64551948dc..c194aedc61 100644 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -134,6 +134,9 @@ class CuraApplication(QtApplication): parser.add_argument("--debug", dest="debug-mode", action="store_true", default=False, help="Enable detailed crash reports.") def run(self): + if not "PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION" in os.environ or os.environ["PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION"] != "cpp": + Logger.log("w", "Using Python implementation of Protobuf, expect bad performance!") + self._i18n_catalog = i18nCatalog("cura"); i18nCatalog.setTagReplacements({ diff --git a/cura_app.py b/cura_app.py index e71fbd6515..92624be76f 100755 --- a/cura_app.py +++ b/cura_app.py @@ -4,6 +4,7 @@ # Cura is released under the terms of the AGPLv3 or higher. import sys +import os def exceptHook(type, value, traceback): import cura.CrashHandler @@ -11,6 +12,13 @@ def exceptHook(type, value, traceback): sys.excepthook = exceptHook +try: + from google.protobuf.pyext import _message +except ImportError: + pass +else: + os.environ["PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION"] = "cpp" + import cura.CuraApplication if sys.platform == "win32" and hasattr(sys, "frozen"): From 35ba7d59f4cbe24e30fec2848def7fbad552f5fe Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Wed, 4 Nov 2015 17:16:00 +0100 Subject: [PATCH 7/7] JSON: support bottom stair step height defaults changed so that the bottom distance to the model isn't violated too much --- resources/machines/fdmprinter.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/machines/fdmprinter.json b/resources/machines/fdmprinter.json index 1b34c71e0f..38d3ff3049 100644 --- a/resources/machines/fdmprinter.json +++ b/resources/machines/fdmprinter.json @@ -1100,7 +1100,7 @@ "unit": "mm", "min_value": "0", "max_value_warning": "10", - "default": 0.15, + "default": 0.1, "type": "float", "visible": false, "enabled": "support_enable" @@ -1141,7 +1141,7 @@ "description": "The height of the steps of the stair-like bottom of support resting on the model. Small steps can cause the support to be hard to remove from the top of the model.", "unit": "mm", "type": "float", - "default": 2, + "default": 0.3, "visible": false, "enabled": "support_enable" },