Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/Ultimaker/Cura.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorArjen Hiemstra <ahiemstra@heimr.nl>2015-06-24 13:06:39 +0300
committerArjen Hiemstra <ahiemstra@heimr.nl>2015-06-24 13:06:39 +0300
commita429e362ad979a5c8fb9f21d780c05f59132649d (patch)
tree44d6e84ede60d2acb3e18e380579e940dd9936d3
parentc387cd9420bab49e0ed7ac4f58b45470022537d2 (diff)
parent074ebb92430e5a35e23f12ae4d882c249b393f4e (diff)
Merge branch '15.06'
* 15.06: Update changelog Correct the bottom offset we add when setting the volume for scale to max Display progress information during processing of layer data If findObject returns none but object_id != 0 use the selected object Offset the displayed rotation angle so it does not overlap the mouse cursor Abort attempts to connect if an error is thrown when connecting to the serial port Fix recent files on Windows Defer opening the webbrowser until the next run of the event loop Disable slicing and platform physics when an operation is being performed Rework LayerData mesh generation for improved performance Performance: Only calculate the platform center once, not for every poly Add application icons for all three platforms
-rw-r--r--CHANGES26
-rw-r--r--cura/CuraActions.py18
-rw-r--r--cura/CuraApplication.py25
-rw-r--r--cura/PlatformPhysics.py14
-rw-r--r--icons/cura-128.pngbin0 -> 2145 bytes
-rw-r--r--icons/cura-32.pngbin0 -> 625 bytes
-rw-r--r--icons/cura-48.pngbin0 -> 1030 bytes
-rw-r--r--icons/cura-64.pngbin0 -> 1133 bytes
-rw-r--r--icons/cura.icnsbin0 -> 27026 bytes
-rw-r--r--icons/cura.icobin0 -> 31206 bytes
-rw-r--r--plugins/CuraEngineBackend/CuraEngineBackend.py12
-rw-r--r--plugins/CuraEngineBackend/LayerData.py78
-rw-r--r--plugins/CuraEngineBackend/ProcessSlicedObjectListJob.py49
-rw-r--r--plugins/LayerView/LayerView.py3
-rw-r--r--plugins/USBPrinting/PrinterConnection.py4
-rw-r--r--resources/qml/Cura.qml12
16 files changed, 190 insertions, 51 deletions
diff --git a/CHANGES b/CHANGES
index 64858ff8ea..bd9c26751c 100644
--- a/CHANGES
+++ b/CHANGES
@@ -7,6 +7,22 @@ Cura 15.06 is a new release built from the ground up on a completely new
framework called Uranium. This framework has been designed to make it easier to
extend Cura with additional functionality as well as provide a cleaner UI.
+Changes since 15.05.95
+----------------------
+
+* Fixed: Selection ghost remains visible after deleting an object
+* Fixed: Window does not show up immediately after starting application on OSX
+* Fixed: Added display of rotation angle during rotation
+* Fixed: Object changes position while rotating/scaling
+* Fixed: Loading improvements in the layer view
+* Fixed: Added application icons
+* Fixed: Improved feedback when loading models
+* Fixed: Eject device on MacOSX now provides proper feedback
+* Fixed: Make it possible to show retraction settings for UM2
+* Fixed: Opening the machine preferences page will switch to the first available machine
+* Fixed: Improved tool handle hit area size
+* Fixed: Render lines with a thickness based on screen DPI
+
Changes since 15.05.94
----------------------
@@ -150,18 +166,8 @@ For an up to date list of all known issues, please see
https://github.com/Ultimaker/Cura/issues and
https://github.com/Ultimaker/Uranium/issues .
-* The application has no application icon yet.
-* The Windows version starts a console before starting the
- application. This is intentional for the beta and it will be
- removed for the final version.
-* Opening the machine preferences page will switch to the first
- available machine instead of keeping the current machine
- selected.
* Some OBJ files are rendered as black objects due to missing
normals.
-* The developer documentation for Uranium (available at
- http://software.ultimaker.com/uranium/index.html) is not yet
- complete.
* Disabling plugins does not work correctly yet.
* Unicorn occasionally still requires feeding. Do not feed it
after midnight.
diff --git a/cura/CuraActions.py b/cura/CuraActions.py
index ee75665e0b..e585b261d0 100644
--- a/cura/CuraActions.py
+++ b/cura/CuraActions.py
@@ -1,4 +1,8 @@
-from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot, pyqtProperty
+from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot, pyqtProperty, QUrl
+from PyQt5.QtGui import QDesktopServices
+
+from UM.Event import CallFunctionEvent
+from UM.Application import Application
import webbrowser
@@ -8,8 +12,16 @@ class CuraActions(QObject):
@pyqtSlot()
def openDocumentation(self):
- webbrowser.open("http://ultimaker.com/en/support/software")
+ # Starting a web browser from a signal handler connected to a menu will crash on windows.
+ # So instead, defer the call to the next run of the event loop, since that does work.
+ # Note that weirdly enough, only signal handlers that open a web browser fail like that.
+ event = CallFunctionEvent(self._openUrl, [QUrl("http://ultimaker.com/en/support/software")], {})
+ Application.getInstance().functionEvent(event)
@pyqtSlot()
def openBugReportPage(self):
- webbrowser.open("http://github.com/Ultimaker/Cura/issues")
+ event = CallFunctionEvent(self._openUrl, [QUrl("http://github.com/Ultimaker/Cura/issues")], {})
+ Application.getInstance().functionEvent(event)
+
+ def _openUrl(self, url):
+ QDesktopServices.openUrl(url) \ No newline at end of file
diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py
index b7ac5f96c8..78ef677556 100644
--- a/cura/CuraApplication.py
+++ b/cura/CuraApplication.py
@@ -85,7 +85,7 @@ class CuraApplication(QtApplication):
if not os.path.isfile(f):
continue
- self._recent_files.append(f)
+ self._recent_files.append(QUrl.fromLocalFile(f))
## Handle loading of all plugin types (and the backend explicitly)
# \sa PluginRegistery
@@ -215,6 +215,9 @@ class CuraApplication(QtApplication):
def deleteObject(self, object_id):
object = self.getController().getScene().findObject(object_id)
+ if not object and object_id != 0: #Workaround for tool handles overlapping the selected object
+ object = Selection.getSelectedObject(0)
+
if object:
op = RemoveSceneNodeOperation(object)
op.push()
@@ -224,6 +227,9 @@ class CuraApplication(QtApplication):
def multiplyObject(self, object_id, count):
node = self.getController().getScene().findObject(object_id)
+ if not node and object_id != 0: #Workaround for tool handles overlapping the selected object
+ node = Selection.getSelectedObject(0)
+
if node:
op = GroupedOperation()
for i in range(count):
@@ -240,6 +246,9 @@ class CuraApplication(QtApplication):
def centerObject(self, object_id):
node = self.getController().getScene().findObject(object_id)
+ if not node and object_id != 0: #Workaround for tool handles overlapping the selected object
+ node = Selection.getSelectedObject(0)
+
if node:
op = SetTransformOperation(node, Vector())
op.push()
@@ -330,7 +339,7 @@ class CuraApplication(QtApplication):
return log
recentFilesChanged = pyqtSignal()
- @pyqtProperty("QStringList", notify = recentFilesChanged)
+ @pyqtProperty("QVariantList", notify = recentFilesChanged)
def recentFiles(self):
return self._recent_files
@@ -468,7 +477,9 @@ class CuraApplication(QtApplication):
self._volume.rebuild()
if self.getController().getTool("ScaleTool"):
- self.getController().getTool("ScaleTool").setMaximumBounds(self._volume.getBoundingBox())
+ bbox = self._volume.getBoundingBox()
+ bbox.setBottom(0.0)
+ self.getController().getTool("ScaleTool").setMaximumBounds(bbox)
offset = machine.getSettingValueByKey("machine_platform_offset")
if offset:
@@ -508,7 +519,7 @@ class CuraApplication(QtApplication):
if type(job) is not ReadMeshJob:
return
- f = job.getFileName()
+ f = QUrl.fromLocalFile(job.getFileName())
if f in self._recent_files:
self._recent_files.remove(f)
@@ -516,5 +527,9 @@ class CuraApplication(QtApplication):
if len(self._recent_files) > 10:
del self._recent_files[10]
- Preferences.getInstance().setValue("cura/recent_files", ";".join(self._recent_files))
+ pref = ""
+ for path in self._recent_files:
+ pref += path.toLocalFile() + ";"
+
+ Preferences.getInstance().setValue("cura/recent_files", pref)
self.recentFilesChanged.emit()
diff --git a/cura/PlatformPhysics.py b/cura/PlatformPhysics.py
index 5e4bd5a415..d7276c9773 100644
--- a/cura/PlatformPhysics.py
+++ b/cura/PlatformPhysics.py
@@ -24,8 +24,12 @@ class PlatformPhysics:
super().__init__()
self._controller = controller
self._controller.getScene().sceneChanged.connect(self._onSceneChanged)
+ self._controller.toolOperationStarted.connect(self._onToolOperationStarted)
+ self._controller.toolOperationStopped.connect(self._onToolOperationStopped)
self._build_volume = volume
+ self._enabled = True
+
self._change_timer = QTimer()
self._change_timer.setInterval(100)
self._change_timer.setSingleShot(True)
@@ -35,6 +39,9 @@ class PlatformPhysics:
self._change_timer.start()
def _onChangeTimerFinished(self):
+ if not self._enabled:
+ return
+
root = self._controller.getScene().getRoot()
for node in BreadthFirstIterator(root):
if node is root or type(node) is not SceneNode:
@@ -93,3 +100,10 @@ class PlatformPhysics:
if node.getBoundingBox().intersectsBox(self._build_volume.getBoundingBox()) == AxisAlignedBox.IntersectionResult.FullIntersection:
op = ScaleToBoundsOperation(node, self._build_volume.getBoundingBox())
op.push()
+
+ def _onToolOperationStarted(self, tool):
+ self._enabled = False
+
+ def _onToolOperationStopped(self, tool):
+ self._enabled = True
+ self._onChangeTimerFinished()
diff --git a/icons/cura-128.png b/icons/cura-128.png
new file mode 100644
index 0000000000..ecfe0d1e4e
--- /dev/null
+++ b/icons/cura-128.png
Binary files differ
diff --git a/icons/cura-32.png b/icons/cura-32.png
new file mode 100644
index 0000000000..2ad22313e9
--- /dev/null
+++ b/icons/cura-32.png
Binary files differ
diff --git a/icons/cura-48.png b/icons/cura-48.png
new file mode 100644
index 0000000000..5fadb70f0f
--- /dev/null
+++ b/icons/cura-48.png
Binary files differ
diff --git a/icons/cura-64.png b/icons/cura-64.png
new file mode 100644
index 0000000000..b6abad9ac1
--- /dev/null
+++ b/icons/cura-64.png
Binary files differ
diff --git a/icons/cura.icns b/icons/cura.icns
new file mode 100644
index 0000000000..eac1092e0e
--- /dev/null
+++ b/icons/cura.icns
Binary files differ
diff --git a/icons/cura.ico b/icons/cura.ico
new file mode 100644
index 0000000000..c6b554301c
--- /dev/null
+++ b/icons/cura.ico
Binary files differ
diff --git a/plugins/CuraEngineBackend/CuraEngineBackend.py b/plugins/CuraEngineBackend/CuraEngineBackend.py
index 45a2148892..9fbd7a47a1 100644
--- a/plugins/CuraEngineBackend/CuraEngineBackend.py
+++ b/plugins/CuraEngineBackend/CuraEngineBackend.py
@@ -59,6 +59,8 @@ class CuraEngineBackend(Backend):
self._save_polygons = True
self._report_progress = True
+ self._enabled = True
+
self.backendConnected.connect(self._onBackendConnected)
def getEngineCommand(self):
@@ -86,6 +88,9 @@ class CuraEngineBackend(Backend):
# 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):
+ if not self._enabled:
+ return
+
if self._slicing:
if not kwargs.get("force_restart", True):
return
@@ -235,3 +240,10 @@ class CuraEngineBackend(Backend):
if self._restart:
self._onChanged()
self._restart = False
+
+ def _onToolOperationStarted(self, tool):
+ self._enabled = False
+
+ def _onToolOperationStopped(self, tool):
+ self._enabled = True
+ self._onChanged()
diff --git a/plugins/CuraEngineBackend/LayerData.py b/plugins/CuraEngineBackend/LayerData.py
index b129942c36..c793c17504 100644
--- a/plugins/CuraEngineBackend/LayerData.py
+++ b/plugins/CuraEngineBackend/LayerData.py
@@ -8,6 +8,7 @@ from UM.Math.Vector import Vector
import numpy
import math
+import copy
class LayerData(MeshData):
def __init__(self):
@@ -48,11 +49,23 @@ class LayerData(MeshData):
self._layers[layer].setThickness(thickness)
def build(self):
+ vertex_count = 0
for layer, data in self._layers.items():
- data.build()
+ vertex_count += data.vertexCount()
+ vertices = numpy.empty((vertex_count, 3), numpy.float32)
+ colors = numpy.empty((vertex_count, 4), numpy.float32)
+ indices = numpy.empty((vertex_count, 2), numpy.int32)
+
+ offset = 0
+ for layer, data in self._layers.items():
+ offset = data.build(offset, vertices, colors, indices)
self._element_counts[layer] = data.elementCount
+ self.addVertices(vertices)
+ self.addColors(colors)
+ self.addIndices(indices.flatten())
+
class Layer():
def __init__(self, id):
self._id = id
@@ -83,20 +96,30 @@ class Layer():
def setThickness(self, thickness):
self._thickness = thickness
- def build(self):
+ def vertexCount(self):
+ result = 0
+ for polygon in self._polygons:
+ result += polygon.vertexCount()
+
+ return result
+
+ def build(self, offset, vertices, colors, indices):
+ result = offset
for polygon in self._polygons:
if polygon._type == Polygon.InfillType or polygon._type == Polygon.SupportInfillType:
continue
- polygon.build()
+ polygon.build(result, vertices, colors, indices)
+ result += polygon.vertexCount()
self._element_count += polygon.elementCount
+ return result
+
def createMesh(self):
builder = MeshBuilder()
for polygon in self._polygons:
poly_color = polygon.getColor()
- poly_color = Color(poly_color[0], poly_color[1], poly_color[2], poly_color[3])
points = numpy.copy(polygon.data)
if polygon.type == Polygon.InfillType or polygon.type == Polygon.SkinType or polygon.type == Polygon.SupportInfillType:
@@ -159,43 +182,48 @@ class Polygon():
self._data = data
self._line_width = line_width / 1000
- def build(self):
- self._begin = self._mesh._vertex_count
- self._mesh.addVertices(self._data)
- self._end = self._begin + len(self._data) - 1
+ def build(self, offset, vertices, colors, indices):
+ self._begin = offset
color = self.getColor()
- color[3] = 2.0
+ color.setValues(color.r * 0.5, color.g * 0.5, color.b * 0.5, color.a)
- colors = [color for i in range(len(self._data))]
- self._mesh.addColors(numpy.array(colors, dtype=numpy.float32) * 0.5)
+ 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
- indices = []
for i in range(self._begin, self._end):
- indices.append(i)
- indices.append(i + 1)
+ indices[i, 0] = i
+ indices[i, 1] = i + 1
- indices.append(self._end)
- indices.append(self._begin)
- self._mesh.addIndices(numpy.array(indices, dtype=numpy.int32))
+ indices[self._end, 0] = self._end
+ indices[self._end, 1] = self._begin
def getColor(self):
if self._type == self.Inset0Type:
- return [1.0, 0.0, 0.0, 1.0]
+ return Color(1.0, 0.0, 0.0, 1.0)
elif self._type == self.InsetXType:
- return [0.0, 1.0, 0.0, 1.0]
+ return Color(0.0, 1.0, 0.0, 1.0)
elif self._type == self.SkinType:
- return [1.0, 1.0, 0.0, 1.0]
+ return Color(1.0, 1.0, 0.0, 1.0)
elif self._type == self.SupportType:
- return [0.0, 1.0, 1.0, 1.0]
+ return Color(0.0, 1.0, 1.0, 1.0)
elif self._type == self.SkirtType:
- return [0.0, 1.0, 1.0, 1.0]
+ return Color(0.0, 1.0, 1.0, 1.0)
elif self._type == self.InfillType:
- return [1.0, 1.0, 0.0, 1.0]
+ return Color(1.0, 1.0, 0.0, 1.0)
elif self._type == self.SupportInfillType:
- return [0.0, 1.0, 1.0, 1.0]
+ return Color(0.0, 1.0, 1.0, 1.0)
else:
- return [1.0, 1.0, 1.0, 1.0]
+ return Color(1.0, 1.0, 1.0, 1.0)
+
+ def vertexCount(self):
+ return len(self._data)
@property
def type(self):
diff --git a/plugins/CuraEngineBackend/ProcessSlicedObjectListJob.py b/plugins/CuraEngineBackend/ProcessSlicedObjectListJob.py
index 6113da78a0..4c6e0fd2ea 100644
--- a/plugins/CuraEngineBackend/ProcessSlicedObjectListJob.py
+++ b/plugins/CuraEngineBackend/ProcessSlicedObjectListJob.py
@@ -7,18 +7,30 @@ from UM.Scene.SceneNode import SceneNode
from UM.Application import Application
from UM.Mesh.MeshData import MeshData
+from UM.Message import Message
+from UM.i18n import i18nCatalog
+
from . import LayerData
import numpy
import struct
+catalog = i18nCatalog("cura")
+
class ProcessSlicedObjectListJob(Job):
def __init__(self, message):
super().__init__()
self._message = message
self._scene = Application.getInstance().getController().getScene()
+ self._progress = None
+ Application.getInstance().getController().activeViewChanged.connect(self._onActiveViewChanged)
+
def run(self):
+ if Application.getInstance().getController().getActiveView().getPluginId() == "LayerView":
+ self._progress = Message(catalog.i18nc("Layers View mode", "Layers"), 0, False, 0)
+ self._progress.show()
+
objectIdMap = {}
new_node = SceneNode()
## Put all nodes in a dict identified by ID
@@ -32,6 +44,15 @@ class ProcessSlicedObjectListJob(Job):
settings = Application.getInstance().getActiveMachine()
layerHeight = settings.getSettingValueByKey("layer_height")
+ center = None
+ if not settings.getSettingValueByKey("machine_center_is_zero"):
+ center = numpy.array([settings.getSettingValueByKey("machine_width") / 2, 0.0, -settings.getSettingValueByKey("machine_depth") / 2])
+ else:
+ center = numpy.array([0.0, 0.0, 0.0])
+
+ if self._progress:
+ self._progress.setProgress(2)
+
mesh = MeshData()
for object in self._message.objects:
try:
@@ -53,15 +74,37 @@ class ProcessSlicedObjectListJob(Job):
points[:,2] *= -1
- if not settings.getSettingValueByKey("machine_center_is_zero"):
- center = [settings.getSettingValueByKey("machine_width") / 2, 0.0, -settings.getSettingValueByKey("machine_depth") / 2]
- points -= numpy.array(center)
+ points -= numpy.array(center)
layerData.addPolygon(layer.id, polygon.type, points, polygon.line_width)
+ if self._progress:
+ self._progress.setProgress(50)
+
# We are done processing all the layers we got from the engine, now create a mesh out of the data
layerData.build()
mesh.layerData = layerData
+ if self._progress:
+ self._progress.setProgress(100)
+
new_node.setMeshData(mesh)
new_node.setParent(self._scene.getRoot())
+
+ view = Application.getInstance().getController().getActiveView()
+ if view.getPluginId() == "LayerView":
+ view.resetLayerData()
+
+ if self._progress:
+ self._progress.hide()
+
+ def _onActiveViewChanged(self):
+ if self.isRunning():
+ if Application.getInstance().getController().getActiveView().getPluginId() == "LayerView":
+ if not self._progress:
+ self._progress = Message(catalog.i18nc("Layers View mode", "Layers"), 0, False, 0)
+ self._progress.show()
+ else:
+ if self._progress:
+ self._progress.hide()
+
diff --git a/plugins/LayerView/LayerView.py b/plugins/LayerView/LayerView.py
index 17cea9988c..617dda411a 100644
--- a/plugins/LayerView/LayerView.py
+++ b/plugins/LayerView/LayerView.py
@@ -39,6 +39,9 @@ class LayerView(View):
def getMaxLayers(self):
return self._max_layers
+ def resetLayerData(self):
+ self._current_layer_mesh = None
+
def beginRendering(self):
scene = self.getController().getScene()
renderer = self.getRenderer()
diff --git a/plugins/USBPrinting/PrinterConnection.py b/plugins/USBPrinting/PrinterConnection.py
index 4a9a73bb7b..2ac8dbab0f 100644
--- a/plugins/USBPrinting/PrinterConnection.py
+++ b/plugins/USBPrinting/PrinterConnection.py
@@ -173,6 +173,10 @@ class PrinterConnection(SignalEmitter):
Logger.log("i", "Could not establish connection on %s: %s. Device is not arduino based." %(self._serial_port,str(e)))
except Exception as e:
Logger.log("i", "Could not establish connection on %s, unknown reasons. Device is not arduino based." % self._serial_port)
+
+ if not self._serial or not programmer.serial:
+ self._is_connecting = False
+ return
# If the programmer connected, we know its an atmega based version. Not all that usefull, but it does give some debugging information.
for baud_rate in self._getBaudrateList(): # Cycle all baud rates (auto detect)
diff --git a/resources/qml/Cura.qml b/resources/qml/Cura.qml
index 830470c9c6..9756b3550f 100644
--- a/resources/qml/Cura.qml
+++ b/resources/qml/Cura.qml
@@ -37,9 +37,11 @@ UM.MainWindow {
Instantiator {
model: Printer.recentFiles
MenuItem {
- property url filePath: modelData;
- text: (index + 1) + ". " + modelData.slice(modelData.lastIndexOf("/") + 1);
- onTriggered: UM.MeshFileHandler.readLocalFile(filePath);
+ text: {
+ var path = modelData.toString()
+ return (index + 1) + ". " + path.slice(path.lastIndexOf("/") + 1);
+ }
+ onTriggered: UM.MeshFileHandler.readLocalFile(modelData);
}
onObjectAdded: fileMenu.insertItem(index, object)
onObjectRemoved: fileMenu.removeItem(object)
@@ -281,8 +283,8 @@ UM.MainWindow {
}
Rectangle {
- x: base.mouseX;
- y: base.mouseY;
+ x: base.mouseX + UM.Theme.sizes.default_margin.width;
+ y: base.mouseY + UM.Theme.sizes.default_margin.height;
width: childrenRect.width;
height: childrenRect.height;