Browse Source

Merge branch 'master' of github.com:Ultimaker/Cura

Jaime van Kessel 8 years ago
parent
commit
f52a3c1b9d

+ 76 - 18
cura/BuildVolume.py

@@ -3,6 +3,7 @@
 
 from cura.Settings.ExtruderManager import ExtruderManager
 from UM.i18n import i18nCatalog
+from UM.Scene.Platform import Platform
 from UM.Scene.SceneNode import SceneNode
 from UM.Application import Application
 from UM.Resources import Resources
@@ -18,12 +19,32 @@ from UM.View.GL.OpenGL import OpenGL
 catalog = i18nCatalog("cura")
 
 import numpy
+import copy
 
 
 # Setting for clearance around the prime
 PRIME_CLEARANCE = 10
 
 
+def approximatedCircleVertices(r):
+    """
+    Return vertices from an approximated circle.
+    :param r: radius
+    :return: numpy 2-array with the vertices
+    """
+
+    return numpy.array([
+        [-r, 0],
+        [-r * 0.707, r * 0.707],
+        [0, r],
+        [r * 0.707, r * 0.707],
+        [r, 0],
+        [r * 0.707, -r * 0.707],
+        [0, -r],
+        [-r * 0.707, -r * 0.707]
+    ], numpy.float32)
+
+
 ##  Build volume is a special kind of node that is responsible for rendering the printable area & disallowed areas.
 class BuildVolume(SceneNode):
     VolumeOutlineColor = Color(12, 169, 227, 255)
@@ -46,6 +67,11 @@ class BuildVolume(SceneNode):
         self.setCalculateBoundingBox(False)
         self._volume_aabb = None
 
+        self._raft_thickness = 0.0
+        self._adhesion_type = None
+        self._raft_mesh = None
+        self._platform = Platform(self)
+
         self._active_container_stack = None
         Application.getInstance().globalContainerStackChanged.connect(self._onGlobalContainerStackChanged)
         self._onGlobalContainerStackChanged()
@@ -77,6 +103,9 @@ class BuildVolume(SceneNode):
         renderer.queueNode(self, mesh = self._grid_mesh, shader = self._grid_shader, backface_cull = True)
         if self._disallowed_area_mesh:
             renderer.queueNode(self, mesh = self._disallowed_area_mesh, shader = self._shader, transparent = True, backface_cull = True, sort = -9)
+        if self._raft_mesh and self._adhesion_type == "raft":
+            renderer.queueNode(self, mesh=self._raft_mesh, transparent=True, backface_cull=True, sort=-9)
+
         return True
 
     ##  Recalculates the build volume & disallowed areas.
@@ -93,6 +122,7 @@ class BuildVolume(SceneNode):
 
         mb = MeshBuilder()
 
+        # Outline 'cube' of the build volume
         mb.addLine(Vector(min_w, min_h, min_d), Vector(max_w, min_h, min_d), color = self.VolumeOutlineColor)
         mb.addLine(Vector(min_w, min_h, min_d), Vector(min_w, max_h, min_d), color = self.VolumeOutlineColor)
         mb.addLine(Vector(min_w, max_h, min_d), Vector(max_w, max_h, min_d), color = self.VolumeOutlineColor)
@@ -117,11 +147,23 @@ class BuildVolume(SceneNode):
             Vector(max_w, min_h - 0.2, max_d),
             Vector(min_w, min_h - 0.2, max_d)
         )
+
         for n in range(0, 6):
             v = mb.getVertex(n)
             mb.setVertexUVCoordinates(n, v[0], v[2])
         self._grid_mesh = mb.build()
 
+        # Build raft mesh: a plane on the height of the raft.
+        mb = MeshBuilder()
+        mb.addQuad(
+            Vector(min_w, self._raft_thickness, min_d),
+            Vector(max_w, self._raft_thickness, min_d),
+            Vector(max_w, self._raft_thickness, max_d),
+            Vector(min_w, self._raft_thickness, max_d),
+            color=Color(128, 128, 128, 64)
+        )
+        self._raft_mesh = mb.build()
+
         disallowed_area_height = 0.1
         disallowed_area_size = 0
         if self._disallowed_areas:
@@ -177,6 +219,21 @@ class BuildVolume(SceneNode):
             " \"Print Sequence\" setting to prevent the gantry from colliding"
             " with printed objects."), lifetime=10).show()
 
+    def _updateRaftThickness(self):
+        old_raft_thickness = self._raft_thickness
+        self._adhesion_type = self._active_container_stack.getProperty("adhesion_type", "value")
+        self._raft_thickness = 0.0
+        if self._adhesion_type == "raft":
+            self._raft_thickness = (
+                self._active_container_stack.getProperty("raft_base_thickness", "value") +
+                self._active_container_stack.getProperty("raft_interface_thickness", "value") +
+                self._active_container_stack.getProperty("raft_surface_layers", "value") *
+                    self._active_container_stack.getProperty("raft_surface_thickness", "value") +
+                self._active_container_stack.getProperty("raft_airgap", "value"))
+        # Rounding errors do not matter, we check if raft_thickness has changed at all
+        if old_raft_thickness != self._raft_thickness:
+            self.setPosition(Vector(0, -self._raft_thickness, 0), SceneNode.TransformSpace.World)
+
     def _onGlobalContainerStackChanged(self):
         if self._active_container_stack:
             self._active_container_stack.propertyChanged.disconnect(self._onSettingPropertyChanged)
@@ -195,6 +252,7 @@ class BuildVolume(SceneNode):
             self._depth = self._active_container_stack.getProperty("machine_depth", "value")
 
             self._updateDisallowedAreas()
+            self._updateRaftThickness()
 
             self.rebuild()
 
@@ -202,32 +260,40 @@ class BuildVolume(SceneNode):
         if property_name != "value":
             return
 
+        rebuild_me = False
         if setting_key == "print_sequence":
             if Application.getInstance().getGlobalContainerStack().getProperty("print_sequence", "value") == "one_at_a_time":
                 self._height = self._active_container_stack.getProperty("gantry_height", "value")
                 self._buildVolumeMessage()
             else:
                 self._height = self._active_container_stack.getProperty("machine_height", "value")
-            self.rebuild()
+            rebuild_me = True
+
         if setting_key in self._skirt_settings:
             self._updateDisallowedAreas()
+            rebuild_me = True
+
+        if setting_key in self._raft_settings:
+            self._updateRaftThickness()
+            rebuild_me = True
+
+        if rebuild_me:
             self.rebuild()
 
     def _updateDisallowedAreas(self):
         if not self._active_container_stack:
             return
 
-        disallowed_areas = self._active_container_stack.getProperty("machine_disallowed_areas", "value")
+        disallowed_areas = copy.deepcopy(
+            self._active_container_stack.getProperty("machine_disallowed_areas", "value"))
         areas = []
 
         # Add extruder prime locations as disallowed areas.
         # Probably needs some rework after coordinate system change.
-        machine_definition = self._active_container_stack.getBottom()
-        current_machine_id = machine_definition.getId()
         extruder_manager = ExtruderManager.getInstance()
-        extruders = extruder_manager.getMachineExtruders(current_machine_id)
-        machine_width = machine_definition.getProperty("machine_width", "value")
-        machine_depth = machine_definition.getProperty("machine_depth", "value")
+        extruders = extruder_manager.getMachineExtruders(self._active_container_stack.getId())
+        machine_width = self._active_container_stack.getProperty("machine_width", "value")
+        machine_depth = self._active_container_stack.getProperty("machine_depth", "value")
         for single_extruder in extruders:
             extruder_prime_pos_x = single_extruder.getProperty("extruder_prime_pos_x", "value")
             extruder_prime_pos_y = single_extruder.getProperty("extruder_prime_pos_y", "value")
@@ -249,16 +315,7 @@ class BuildVolume(SceneNode):
             # Extend every area already in the disallowed_areas with the skirt size.
             for area in disallowed_areas:
                 poly = Polygon(numpy.array(area, numpy.float32))
-                poly = poly.getMinkowskiHull(Polygon(numpy.array([
-                    [-skirt_size, 0],
-                    [-skirt_size * 0.707, skirt_size * 0.707],
-                    [0, skirt_size],
-                    [skirt_size * 0.707, skirt_size * 0.707],
-                    [skirt_size, 0],
-                    [skirt_size * 0.707, -skirt_size * 0.707],
-                    [0, -skirt_size],
-                    [-skirt_size * 0.707, -skirt_size * 0.707]
-                ], numpy.float32)))
+                poly = poly.getMinkowskiHull(Polygon(approximatedCircleVertices(skirt_size)))
 
                 areas.append(poly)
 
@@ -297,7 +354,7 @@ class BuildVolume(SceneNode):
 
         self._disallowed_areas = areas
 
-    ##  Convenience function to calculate the size of the bed adhesion.
+    ##  Convenience function to calculate the size of the bed adhesion in directions x, y.
     def _getSkirtSize(self, container_stack):
         skirt_size = 0.0
 
@@ -323,3 +380,4 @@ class BuildVolume(SceneNode):
         return max(min(value, max_value), min_value)
 
     _skirt_settings = ["adhesion_type", "skirt_gap", "skirt_line_count", "skirt_line_width", "brim_width", "brim_line_count", "raft_margin", "draft_shield_enabled", "draft_shield_dist", "xy_offset"]
+    _raft_settings = ["adhesion_type", "raft_base_thickness", "raft_interface_thickness", "raft_surface_layers", "raft_surface_thickness", "raft_airgap"]

+ 22 - 7
cura/CuraApplication.py

@@ -4,7 +4,6 @@
 from UM.Qt.QtApplication import QtApplication
 from UM.Scene.SceneNode import SceneNode
 from UM.Scene.Camera import Camera
-from UM.Scene.Platform import Platform as Scene_Platform
 from UM.Math.Vector import Vector
 from UM.Math.Quaternion import Quaternion
 from UM.Math.AxisAlignedBox import AxisAlignedBox
@@ -144,7 +143,6 @@ class CuraApplication(QtApplication):
         ])
         self._physics = None
         self._volume = None
-        self._platform = None
         self._output_devices = {}
         self._print_information = None
         self._previous_active_tool = None
@@ -196,6 +194,14 @@ class CuraApplication(QtApplication):
         Preferences.getInstance().addPreference("view/center_on_select", True)
         Preferences.getInstance().addPreference("mesh/scale_to_fit", True)
         Preferences.getInstance().addPreference("mesh/scale_tiny_meshes", True)
+
+        for key in [
+            "dialog_load_path",  # dialog_save_path is in LocalFileOutputDevicePlugin
+            "dialog_profile_path",
+            "dialog_material_path"]:
+
+            Preferences.getInstance().addPreference("local_file/%s" % key, "~/")
+
         Preferences.getInstance().setDefault("local_file/last_used_type", "text/x-gcode")
 
         Preferences.getInstance().setDefault("general/visible_settings", """
@@ -334,10 +340,16 @@ class CuraApplication(QtApplication):
                     f.write(data)
 
 
-    @pyqtSlot(result = QUrl)
-    def getDefaultPath(self):
-        return QUrl.fromLocalFile(os.path.expanduser("~/"))
-    
+    @pyqtSlot(str, result = QUrl)
+    def getDefaultPath(self, key):
+        #return QUrl.fromLocalFile(os.path.expanduser("~/"))
+        default_path = Preferences.getInstance().getValue("local_file/%s" % key)
+        return QUrl.fromLocalFile(default_path)
+
+    @pyqtSlot(str, str)
+    def setDefaultPath(self, key, default_path):
+        Preferences.getInstance().setValue("local_file/%s" % key, default_path)
+
     ##  Handle loading of all plugin types (and the backend explicitly)
     #   \sa PluginRegistery
     def _loadPlugins(self):
@@ -377,8 +389,8 @@ class CuraApplication(QtApplication):
         Selection.selectionChanged.connect(self.onSelectionChanged)
 
         root = controller.getScene().getRoot()
-        self._platform = Scene_Platform(root)
 
+        # The platform is a child of BuildVolume
         self._volume = BuildVolume.BuildVolume(root)
 
         self.getRenderer().setBackgroundColor(QColor(245, 245, 245))
@@ -857,3 +869,6 @@ class CuraApplication(QtApplication):
     @pyqtSlot("QSize")
     def setMinimumWindowSize(self, size):
         self.getMainWindow().setMinimumSize(size)
+
+    def getBuildVolume(self):
+        return self._volume

+ 2 - 4
cura/PlatformPhysics.py

@@ -40,6 +40,7 @@ class PlatformPhysics:
             return
 
         root = self._controller.getScene().getRoot()
+
         for node in BreadthFirstIterator(root):
             if node is root or type(node) is not SceneNode or node.getBoundingBox() is None:
                 continue
@@ -58,10 +59,7 @@ class PlatformPhysics:
             move_vector = Vector()
             if not (node.getParent() and node.getParent().callDecoration("isGroup")): #If an object is grouped, don't move it down
                 z_offset = node.callDecoration("getZOffset") if node.getDecorator(ZOffsetDecorator.ZOffsetDecorator) else 0
-                if bbox.bottom > 0:
-                    move_vector = move_vector.set(y=-bbox.bottom + z_offset)
-                elif bbox.bottom < z_offset:
-                    move_vector = move_vector.set(y=(-bbox.bottom) - z_offset)
+                move_vector = move_vector.set(y=-bbox.bottom + z_offset)
 
             # If there is no convex hull for the node, start calculating it and continue.
             if not node.getDecorator(ConvexHullDecorator):

+ 4 - 1
plugins/CuraEngineBackend/ProcessSlicedLayersJob.py

@@ -130,7 +130,10 @@ class ProcessSlicedLayersJob(Job):
         new_node.addDecorator(decorator)
 
         new_node.setMeshData(mesh)
-        new_node.setParent(self._scene.getRoot())  # Note: After this we can no longer abort!
+        # Set build volume as parent, the build volume can move as a result of raft settings.
+        # It makes sense to set the build volume as parent: the print is actually printed on it.
+        new_node_parent = Application.getInstance().getBuildVolume()
+        new_node.setParent(new_node_parent)  # Note: After this we can no longer abort!
 
         settings = Application.getInstance().getGlobalContainerStack()
         if not settings.getProperty("machine_center_is_zero", "value"):

+ 1 - 1
resources/definitions/fdmprinter.def.json

@@ -1000,7 +1000,7 @@
                     "type": "int",
                     "minimum_value": "0",
                     "maximum_value_warning": "4",
-                    "maximum_value": "17",
+                    "maximum_value": "20 - math.log(infill_line_distance) / math.log(2)",
                     "settable_per_mesh": true
                 },
                 "gradual_infill_step_height":

+ 2 - 1
resources/qml/Cura.qml

@@ -638,7 +638,7 @@ UM.MainWindow
         //TODO: Support multiple file selection, workaround bug in KDE file dialog
         //selectMultiple: true
         nameFilters: UM.MeshFileHandler.supportedReadFileTypes;
-        folder: Printer.getDefaultPath()
+        folder: CuraApplication.getDefaultPath("dialog_load_path")
         onAccepted:
         {
             //Because several implementations of the file dialog only update the folder
@@ -646,6 +646,7 @@ UM.MainWindow
             var f = folder;
             folder = f;
 
+            CuraApplication.setDefaultPath("dialog_load_path", folder);
             UM.MeshFileHandler.readLocalFile(fileUrl)
             var meshName = backgroundItem.getMeshName(fileUrl.toString())
             backgroundItem.hasMesh(decodeURIComponent(meshName))

+ 4 - 2
resources/qml/Preferences/MaterialsPage.qml

@@ -200,7 +200,7 @@ UM.ManagementPage
             title: catalog.i18nc("@title:window", "Import Material");
             selectExisting: true;
             nameFilters: Cura.ContainerManager.getContainerNameFilters("material")
-            folder: CuraApplication.getDefaultPath()
+            folder: CuraApplication.getDefaultPath("dialog_material_path")
             onAccepted:
             {
                 var result = Cura.ContainerManager.importContainer(fileUrl)
@@ -221,6 +221,7 @@ UM.ManagementPage
                     messageDialog.icon = StandardIcon.Critical
                 }
                 messageDialog.open()
+                CuraApplication.setDefaultPath("dialog_material_path", folder)
             }
         }
 
@@ -230,7 +231,7 @@ UM.ManagementPage
             title: catalog.i18nc("@title:window", "Export Material");
             selectExisting: false;
             nameFilters: Cura.ContainerManager.getContainerNameFilters("material")
-            folder: CuraApplication.getDefaultPath()
+            folder: CuraApplication.getDefaultPath("dialog_material_path")
             onAccepted:
             {
                 if(base.currentItem.metadata.base_file)
@@ -255,6 +256,7 @@ UM.ManagementPage
                     messageDialog.text = catalog.i18nc("@info:status", "Successfully exported material to <filename>%1</filename>").arg(fileUrl)
                     messageDialog.open()
                 }
+                CuraApplication.setDefaultPath("dialog_material_path", folder)
             }
         }
 

+ 4 - 2
resources/qml/Preferences/ProfilesPage.qml

@@ -291,7 +291,7 @@ UM.ManagementPage
             title: catalog.i18nc("@title:window", "Import Profile");
             selectExisting: true;
             nameFilters: base.model.getFileNameFilters("profile_reader")
-            folder: base.model.getDefaultPath()
+            folder: CuraApplication.getDefaultPath("dialog_profile_path")
             onAccepted:
             {
                 var result = base.model.importProfile(fileUrl)
@@ -309,6 +309,7 @@ UM.ManagementPage
                     messageDialog.icon = StandardIcon.Critical
                 }
                 messageDialog.open()
+                CuraApplication.setDefaultPath("dialog_profile_path", folder)
             }
         }
 
@@ -318,7 +319,7 @@ UM.ManagementPage
             title: catalog.i18nc("@title:window", "Export Profile");
             selectExisting: false;
             nameFilters: base.model.getFileNameFilters("profile_writer")
-            folder: base.model.getDefaultPath()
+            folder: CuraApplication.getDefaultPath("dialog_profile_path")
             onAccepted:
             {
                 var result = base.model.exportProfile(base.currentItem.id, fileUrl, selectedNameFilter)
@@ -329,6 +330,7 @@ UM.ManagementPage
                     messageDialog.open()
                 }
                 // else pop-up Message thing from python code
+                CuraApplication.setDefaultPath("dialog_profile_path", folder)
             }
         }
     }