Merge branch '15.06' of https://github.com/Ultimaker/Cura into 15.06

Someone else pushed changes before I did
This commit is contained in:
Tamara Hogenhout 2015-06-24 13:45:39 +02:00
commit 1acd3ee2a5
16 changed files with 190 additions and 51 deletions

26
CHANGES
View file

@ -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.

View file

@ -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)

View file

@ -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()

View file

@ -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()

BIN
icons/cura-128.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
icons/cura-32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 625 B

BIN
icons/cura-48.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 KiB

BIN
icons/cura-64.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
icons/cura.icns Normal file

Binary file not shown.

BIN
icons/cura.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View file

@ -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()

View file

@ -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):

View file

@ -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()

View file

@ -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()

View file

@ -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)

View file

@ -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;