Browse Source

Merge branch 'master' of github.com:ultimaker/Cura into per_object_settings

* 'master' of github.com:ultimaker/Cura: (49 commits)
  15.10 restyling of the sidebar header
  15.10 restyling of the sidebar header
  15.10 restyling of the sidebar
  split magic_mesh)surface_mode into Normal, Surface, Both
  Added preference to disable automatic scale
  Removed unused import
  Added changeLog plugin
  Added missing )
  Merging of mine and Jaimes work
  Removed font from rectangle
  JSON: git diff! removed triangles and grid top/bottom skin options (though they are available)
  Code style & switch to translation catalog
  15.10 re-alignment of the toolbar
  15.10 New Icons
  15.10 restyling of the savebutton Area
  Added message asking about sending data to server
  Added exception handling for checking overlap.
  Fixed default button for general and view page
  Fixed double ID in qml
  Removed unused import
  ...
Arjen Hiemstra 9 years ago
parent
commit
57b2ce4f3e

+ 7 - 2
cura/CuraApplication.py

@@ -46,7 +46,6 @@ import platform
 import sys
 import os
 import os.path
-import configparser
 import numpy
 numpy.seterr(all="ignore")
 
@@ -96,6 +95,7 @@ class CuraApplication(QtApplication):
         Preferences.getInstance().addPreference("cura/recent_files", "")
         Preferences.getInstance().addPreference("cura/categories_expanded", "")
         Preferences.getInstance().addPreference("view/center_on_select", True)
+        Preferences.getInstance().addPreference("mesh/scale_to_fit", True)
 
         JobQueue.getInstance().jobFinished.connect(self._onJobFinished)
 
@@ -191,6 +191,9 @@ class CuraApplication(QtApplication):
 
         return super().event(event)
 
+    def getPrintInformation(self):
+        return self._print_information
+
     def registerObjects(self, engine):
         engine.rootContext().setContextProperty("Printer", self)
         self._print_information = PrintInformation.PrintInformation()
@@ -269,8 +272,10 @@ class CuraApplication(QtApplication):
             for i in range(count):
                 new_node = SceneNode()
                 new_node.setMeshData(node.getMeshData())
+
+                new_node.translate(Vector((i + 1) * node.getBoundingBox().width, node.getPosition().y, 0))
+                new_node.setOrientation(node.getOrientation())
                 new_node.setScale(node.getScale())
-                new_node.translate(Vector((i + 1) * node.getBoundingBox().width, 0, 0))
                 new_node.setSelectable(True)
                 op.addOperation(AddSceneNodeOperation(new_node, node.getParent()))
             op.push()

+ 7 - 4
cura/PlatformPhysics.py

@@ -6,7 +6,6 @@ from PyQt5.QtCore import QTimer
 from UM.Scene.SceneNode import SceneNode
 from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator
 from UM.Operations.TranslateOperation import TranslateOperation
-from UM.Operations.ScaleToBoundsOperation import ScaleToBoundsOperation
 from UM.Math.Float import Float
 from UM.Math.Vector import Vector
 from UM.Math.AxisAlignedBox import AxisAlignedBox
@@ -107,11 +106,15 @@ class PlatformPhysics:
                         continue
 
                     # Check to see if the bounding boxes intersect. If not, we can ignore the node as there is no way the hull intersects.
-                    if node.getBoundingBox().intersectsBox(other_node.getBoundingBox()) == AxisAlignedBox.IntersectionResult.NoIntersection:
-                        continue
+                    #if node.getBoundingBox().intersectsBox(other_node.getBoundingBox()) == AxisAlignedBox.IntersectionResult.NoIntersection:
+                    #    continue
 
                     # Get the overlap distance for both convex hulls. If this returns None, there is no intersection.
-                    overlap = node.callDecoration("getConvexHull").intersectsPolygon(other_node.callDecoration("getConvexHull"))
+                    try:
+                        overlap = node.callDecoration("getConvexHull").intersectsPolygon(other_node.callDecoration("getConvexHull"))
+                    except:
+                        overlap = None #It can sometimes occur that the caclulated convex hull has no size, in which case there is no overlap.
+
                     if overlap is None:
                         continue
                     

+ 98 - 0
plugins/ChangeLogPlugin/ChangeLog.py

@@ -0,0 +1,98 @@
+# Copyright (c) 2015 Ultimaker B.V.
+# Cura is released under the terms of the AGPLv3 or higher.
+
+from UM.i18n import i18nCatalog
+from UM.Extension import Extension
+from UM.Preferences import Preferences
+from UM.Application import Application
+from UM.PluginRegistry import PluginRegistry
+from UM.Version import Version
+
+from PyQt5.QtQuick import QQuickView
+from PyQt5.QtQml import QQmlComponent, QQmlContext
+from PyQt5.QtCore import QUrl, pyqtSlot, QObject
+
+import os.path
+
+catalog = i18nCatalog("cura")
+
+class ChangeLog(Extension, QObject,):
+    def __init__(self, parent = None):
+        QObject.__init__(self, parent)
+        Extension.__init__(self)
+        self._changelog_window = None
+        self._changelog_context = None
+        version_string = Application.getInstance().getVersion()
+        if version_string is not "master":
+            self._version = Version(version_string)
+        else:
+            self._version = None
+        self._change_logs = None
+        Application.getInstance().engineCreatedSignal.connect(self._onEngineCreated)
+        Preferences.getInstance().addPreference("general/latest_version_changelog_shown", "15.05.90") #First version of CURA with uranium
+        #self.showChangelog()
+
+    def getChangeLogs(self):
+        if not self._change_logs:
+            self.loadChangeLogs()
+        return self._change_logs
+
+    @pyqtSlot(result = str)
+    def getChangeLogString(self):
+        logs = self.getChangeLogs()
+        latest_version = Version(Preferences.getInstance().getValue("general/latest_version_changelog_shown"))
+        result = ""
+        for version in logs:
+            result += "<h1>" + str(version) + "</h1><br>"
+            result += ""
+            for change in logs[version]:
+                result += "<b>" + str(change) + "</b><br>"
+                for line in logs[version][change]:
+                    result += str(line) + "<br>"
+                result += "<br>"
+
+        pass
+        return result
+
+    def loadChangeLogs(self):
+        self._change_logs = {}
+        with open(os.path.join(PluginRegistry.getInstance().getPluginPath("ChangeLogPlugin"), "ChangeLog.txt"), 'r') as f:
+            open_version = None
+            open_header = None
+            for line in f:
+                line = line.replace("\n","")
+                if "[" in line and "]" in line:
+                    line = line.replace("[","")
+                    line = line.replace("]","")
+                    open_version = Version(line)
+                    self._change_logs[Version(line)] = {}
+                elif line.startswith("*"):
+                    open_header = line.replace("*","")
+                    self._change_logs[open_version][open_header] = []
+                else:
+                    if line != "":
+                        self._change_logs[open_version][open_header].append(line)
+
+    def _onEngineCreated(self):
+        if not self._version:
+            return #We're on dev branch.
+        if self._version > Preferences.getInstance().getValue("general/latest_version_changelog_shown"):
+            self.showChangelog()
+
+    def showChangelog(self):
+        if not self._changelog_window:
+            self.createChangelogWindow()
+        self._changelog_window.show()
+        Preferences.getInstance().setValue("general/latest_version_changelog_shown", Application.getInstance().getVersion())
+
+    def hideChangelog(self):
+        if self._changelog_window:
+            self._changelog_window.hide()
+
+    def createChangelogWindow(self):
+        path = QUrl.fromLocalFile(os.path.join(PluginRegistry.getInstance().getPluginPath("ChangeLogPlugin"), "ChangeLog.qml"))
+        component = QQmlComponent(Application.getInstance()._engine, path)
+        self._changelog_context = QQmlContext(Application.getInstance()._engine.rootContext())
+        self._changelog_context.setContextProperty("manager", self)
+        self._changelog_window = component.create(self._changelog_context)
+        #print(self._changelog_window)

+ 28 - 0
plugins/ChangeLogPlugin/ChangeLog.qml

@@ -0,0 +1,28 @@
+// Copyright (c) 2015 Ultimaker B.V.
+// Cura is released under the terms of the AGPLv3 or higher.
+
+import QtQuick 2.1
+import QtQuick.Controls 1.1
+import QtQuick.Layouts 1.1
+import QtQuick.Window 2.1
+
+import UM 1.1 as UM
+
+UM.Dialog
+{
+    id: base
+    width: 300 * Screen.devicePixelRatio;
+    height: 500 * Screen.devicePixelRatio;
+    title: "Changelog"
+    ScrollView
+    {
+        anchors.fill:parent
+        Text
+        {
+            text: manager.getChangeLogString()
+            width:base.width - 35
+            wrapMode: Text.Wrap;
+            //Component.onCompleted: console.log()
+        }
+    }
+}

+ 41 - 0
plugins/ChangeLogPlugin/ChangeLog.txt

@@ -0,0 +1,41 @@
+[15.10.0]
+*All at Once/One at a Time
+Cura’s default mode is set to All At Once. You can print multiple objects faster with the option print objects One At A Time. This can be changed in Advanced Settings. Please note that in One At A Time mode, grouped objects will still be printed as a single object.
+
+*Setting Profiles
+Now you can create preferred setting favourites and share them with others.
+
+
+*Post-Processing Plugin
+This  plugin supports post-processing on the GCode generated by the engine – allowing for custom scripts. For example, Pause At Height and Tweak At Z.
+
+*Support for Bed Levelling and other wizards
+We have restored the Bed Levelling function and several other wizards that were previously available for the Ultimaker Original. Additionally, these are ready to be used with machines from other vendors (BQ, Rep Rap neo).
+
+*Third-Party Printer Profiles
+We received printer profiles for third-party vendors (BQ, Rep Rap neo) from the community (thanks guys!). These have been included in this release.
+
+
+*3MF File Loading Support (New)
+We’re happy to report we now support loading 3MF files. This is a new file format similar to AMF, but freely available.
+
+*Output Device API for Developers (New)
+The Storage Device API has now been replaced with the Output Device API for saving files. It’s designed to make it easier for anyone that wants to write a plugin giving them some form of output device, whether it’s a printer or a web service.
+
+*Improved Cut-Off Object Bottom (New)
+We’ve added a feature than allows you to move objects below the build plate. You can either correct a model with a rough bottom, or print only a part of an object. Please note that the implementation greatly differs from the old one where it was a setting.
+
+*Improved File Saving (new)
+We’re happy to report that the way file saving is handled has received a huge overhaul. Now the default action is to save everything on the build plate to a file.
+
+*Select Multiple Objects (New)
+You now have the freedom to select and manipulate multiple objects at the same time.
+
+*Grouping (New)
+You can now group objects together to make it easier to manipulate multiple objects.
+
+*Per-Object Settings (New)
+You can now select different profiles for different objects and in advance mode override individual settings.
+
+*64-bit Windows Builds (New)
+Cura now allows 64-bit Windows builds in addition to the 32-bit builds. For users running the 64-bit version of Windows, you can now load models in more detail.

+ 21 - 0
plugins/ChangeLogPlugin/__init__.py

@@ -0,0 +1,21 @@
+# Copyright (c) 2015 Ultimaker B.V.
+# Cura is released under the terms of the AGPLv3 or higher.
+from UM.i18n import i18nCatalog
+
+from . import ChangeLog
+
+catalog = i18nCatalog("cura")
+
+def getMetaData():
+    return {
+        "plugin": {
+            "name": "Change log",
+            "author": "Ultimaker",
+            "version": "1.0",
+            "description": catalog.i18nc("Change log plugin description", "Shows changes since latest checked version"),
+            "api": 2
+        }
+    }
+
+def register(app):
+    return {"extension": ChangeLog.ChangeLog()}

+ 5 - 0
plugins/CuraEngineBackend/CuraEngineBackend.py

@@ -132,6 +132,7 @@ class CuraEngineBackend(Backend):
                     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()):
@@ -250,6 +251,10 @@ class CuraEngineBackend(Backend):
         self._socket.registerMessageType(6, Cura_pb2.SettingList)
         self._socket.registerMessageType(7, Cura_pb2.GCodePrefix)
 
+    ##  Manually triggers a reslice
+    def forceSlice(self):
+        self._change_timer.start()
+
     def _onChanged(self):
         if not self._profile:
             return

+ 2 - 1
plugins/CuraEngineBackend/LayerData.py

@@ -28,7 +28,8 @@ class LayerData(MeshData):
         self._layers[layer].polygons.append(p)
 
     def getLayer(self, layer):
-        return self._layers[layer]
+        if layer in self._layers:
+            return self._layers[layer]
 
     def getLayers(self):
         return self._layers

+ 6 - 4
plugins/LayerView/LayerView.qml

@@ -19,7 +19,7 @@ Item
         width: 10
         height: 250
         anchors.right : parent.right
-        anchors.rightMargin: UM.Theme.sizes.slider_layerview_margin.width
+        anchors.rightMargin: UM.Theme.sizes.slider_layerview_margin.width/2
         orientation: Qt.Vertical
         minimumValue: 0;
         maximumValue: UM.LayerView.numLayers;
@@ -38,14 +38,16 @@ Item
         height: UM.Theme.sizes.slider_layerview_background_extension.height
         color: UM.Theme.colors.slider_text_background
     }
-    UM.AngledCornerRectangle {
+    Rectangle {
         anchors.right : parent.right
         anchors.verticalCenter: parent.verticalCenter
         z: slider.z - 1
-        cornerSize: UM.Theme.sizes.default_margin.width;
         width: UM.Theme.sizes.slider_layerview_background.width
         height: slider.height + UM.Theme.sizes.default_margin.height * 2
-        color: UM.Theme.colors.slider_text_background
+        color: UM.Theme.colors.tool_panel_background;
+        border.width: UM.Theme.sizes.default_lining.width
+        border.color: UM.Theme.colors.lining
+
         MouseArea {
             id: sliderMouseArea
             property double manualStepSize: slider.maximumValue / 11

+ 122 - 0
plugins/SliceInfoPlugin/SliceInfo.py

@@ -0,0 +1,122 @@
+# Copyright (c) 2015 Ultimaker B.V.
+# Cura is released under the terms of the AGPLv3 or higher.
+
+from UM.Extension import Extension
+from UM.Application import Application
+from UM.Preferences import Preferences
+from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
+from UM.Scene.SceneNode import SceneNode
+from UM.Message import Message
+from UM.i18n import i18nCatalog
+
+import collections
+import json
+import os.path
+import copy
+import platform
+import math
+import urllib.request
+import urllib.parse
+
+catalog = i18nCatalog("cura")
+
+
+##      This Extension runs in the background and sends several bits of information to the Ultimaker servers.
+#       The data is only sent when the user in question gave permission to do so. All data is anonymous and
+#       no model files are being sent (Just a SHA256 hash of the model).
+class SliceInfo(Extension):
+    def __init__(self):
+        super().__init__()
+        Application.getInstance().getOutputDeviceManager().writeStarted.connect(self._onWriteStarted)
+        Preferences.getInstance().addPreference("info/send_slice_info", True)
+        Preferences.getInstance().addPreference("info/asked_send_slice_info", False)
+
+        if not Preferences.getInstance().getValue("info/asked_send_slice_info"):
+            self.send_slice_info_message = Message(catalog.i18nc("", "Cura automatically sends slice info. You can disable this in preferences"), lifetime = 0, dismissable = False)
+            self.send_slice_info_message.addAction("Dismiss","Dismiss", None, "Dismiss")
+            self.send_slice_info_message.actionTriggered.connect(self.messageActionTriggered)
+            self.send_slice_info_message.show()
+
+    def messageActionTriggered(self, message_id, action_id):
+        self.send_slice_info_message.hide()
+        Preferences.getInstance().setValue("info/asked_send_slice_info", True)
+
+    def _onWriteStarted(self, output_device):
+        if not Preferences.getInstance().getValue("info/send_slice_info"):
+            return # Do nothing, user does not want to send data
+
+        settings = Application.getInstance().getActiveMachine()
+        profile = settings.getActiveProfile()
+        profile_values = None
+
+        # Load all machine definitions and put them in machine_settings dict
+        setting_file_name = Application.getInstance().getActiveMachine()._json_file
+        machine_settings = {}
+        with open(setting_file_name, "rt", -1, "utf-8") as f:
+            data = json.load(f, object_pairs_hook = collections.OrderedDict)
+        machine_settings[os.path.basename(setting_file_name)] = copy.deepcopy(data)
+        # Loop through inherited json files
+        while True:
+            if "inherits" in data:
+                inherited_setting_file_name = os.path.dirname(setting_file_name) + "/" + data["inherits"]
+                with open(inherited_setting_file_name, "rt", -1, "utf-8") as f:
+                    data = json.load(f, object_pairs_hook = collections.OrderedDict)
+                machine_settings[os.path.basename(inherited_setting_file_name)] = copy.deepcopy(data)
+                print("Inherited:", os.path.basename(inherited_setting_file_name))
+            else:
+                break
+
+        if profile:
+            profile_values = profile.getChangedSettings()
+
+        # Get total material used (in mm^3)
+        print_information = Application.getInstance().getPrintInformation()
+        material_radius = 0.5 * settings.getSettingValueByKey("material_diameter")
+        material_used = math.pi * material_radius * material_radius * print_information.materialAmount #Volume of material used
+
+        # Get model information (bounding boxes, hashes and transformation matrix)
+        models_info = []
+        for node in DepthFirstIterator(Application.getInstance().getController().getScene().getRoot()):
+            if type(node) is SceneNode and node.getMeshData() and node.getMeshData().getVertices() is not None:
+                if not getattr(node, "_outside_buildarea", False):
+                    model_info = {}
+                    model_info["hash"] = node.getMeshData().getHash()
+                    model_info["bounding_box"] = {}
+                    model_info["bounding_box"]["minimum"] = {}
+                    model_info["bounding_box"]["minimum"]["x"] = node.getBoundingBox().minimum.x
+                    model_info["bounding_box"]["minimum"]["y"] = node.getBoundingBox().minimum.y
+                    model_info["bounding_box"]["minimum"]["z"] = node.getBoundingBox().minimum.z
+
+                    model_info["bounding_box"]["maximum"] = {}
+                    model_info["bounding_box"]["maximum"]["x"] = node.getBoundingBox().maximum.x
+                    model_info["bounding_box"]["maximum"]["y"] = node.getBoundingBox().maximum.y
+                    model_info["bounding_box"]["maximum"]["z"] = node.getBoundingBox().maximum.z
+                    model_info["transformation"] = str(node.getWorldTransformation().getData())
+
+                    models_info.append(model_info)
+
+        # Bundle the collected data
+        submitted_data = {
+            "processor": platform.processor(),
+            "machine": platform.machine(),
+            "platform": platform.platform(),
+            "machine_settings": json.dumps(machine_settings),
+            "version": Application.getInstance().getVersion(),
+            "modelhash": "None",
+            "printtime": str(print_information.currentPrintTime),
+            "filament": material_used,
+            "language": Preferences.getInstance().getValue("general/language"),
+            "materials_profiles ": {}
+        }
+
+        # Convert data to bytes
+        submitted_data = urllib.parse.urlencode(submitted_data)
+        binary_data = submitted_data.encode('utf-8')
+
+        # Submit data
+        try:
+            f = urllib.request.urlopen("https://stats.youmagine.com/curastats/slice", data = binary_data, timeout = 1)
+        except Exception as e:
+            print("Exception occured", e)
+
+        f.close()

Some files were not shown because too many files changed in this diff