diff --git a/CHANGES b/CHANGES index e62253066b..f33001afd5 100644 --- a/CHANGES +++ b/CHANGES @@ -7,6 +7,24 @@ 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.93 +---------------------- + +* Fixed: No shortcuts for moving up/down layers in layer view. +* Fixed: Last view layers could not be scrolled through in layer view. +* Fixed: Files provided on command line would not actually show up on the build + platform. +* Fixed: Render a ghost of the selection in Layer view to make the actual object + position clear. +* Fixed: Showing a menu would clear the selection. +* Fixed: Size and scaling factor display for scale tool. +* Fixed: Missing background for additional tool controls. +* Fixed: Loading message times out when loading large files. +* Fixed: Show recent files in the file menu. +* Fixed: Windows installer will now install MSVC 2010 redistributable, to + prevent issues with missing DLL's. +* Fixed: Collapsed/expanded state of setting categories not stored. + Changes since 15.05.91 ---------------------- @@ -26,7 +44,8 @@ Changes since 15.05.91 * Fixed: Camera panning now works correctly instead of doing nothing. * Fixed: Camera would flip around center point at maximum rotation. * Fixed: Build platform grid blocked view from below objects. -* Fixed: Viewport on MacOSX with high-DPI screens was only taking 1/4th of the window +* Fixed: Viewport on MacOSX with high-DPI screens was only taking 1/4th of the +window Changes since 15.05.90 ---------------------- diff --git a/README.md b/README.md index f311976ca1..acd87f1b0e 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@ Cura ==== -This is the source code of Cura. +This is the new, shiny, unreleased frontend for Cura. [daid/Cura](https://github.com/daid/Cura.git) is the old legacy Cura that everyone knows and loves/hates. + +We re-worked the whole GUI code at Ultimaker, because my old code started to become an unmaintainable ball of poo. Dependencies ------------ @@ -12,3 +14,8 @@ Dependencies This will be needed at runtime to perform the actual slicing. * PySerial Only required for USB printing support. + +Build scripts +------------- + +Please checkout [cura-build](https://github.com/Ultimaker/cura-build) diff --git a/cura/ConvexHullNode.py b/cura/ConvexHullNode.py index a94ae60897..5be5820982 100644 --- a/cura/ConvexHullNode.py +++ b/cura/ConvexHullNode.py @@ -48,6 +48,9 @@ class ConvexHullNode(SceneNode): self.setMeshData(mesh) + def getWatchedNode(self): + return self._node + def render(self, renderer): if not self._material: self._material = renderer.createMaterial(Resources.getPath(Resources.ShadersLocation, "basic.vert"), Resources.getPath(Resources.ShadersLocation, "color.frag")) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index ca146b22d6..8afbcab050 100644 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -17,6 +17,7 @@ from UM.Logger import Logger from UM.Preferences import Preferences from UM.Message import Message from UM.PluginRegistry import PluginRegistry +from UM.JobQueue import JobQueue from UM.Scene.BoxRenderer import BoxRenderer from UM.Scene.Selection import Selection @@ -71,6 +72,18 @@ class CuraApplication(QtApplication): Preferences.getInstance().addPreference("cura/active_machine", "") Preferences.getInstance().addPreference("cura/active_mode", "simple") + Preferences.getInstance().addPreference("cura/recent_files", "") + Preferences.getInstance().addPreference("cura/categories_expanded", "") + + JobQueue.getInstance().jobFinished.connect(self._onJobFinished) + + self._recent_files = [] + files = Preferences.getInstance().getValue("cura/recent_files").split(";") + for f in files: + if not os.path.isfile(f): + continue + + self._recent_files.append(f) ## Handle loading of all plugin types (and the backend explicitly) # \sa PluginRegistery @@ -305,6 +318,25 @@ class CuraApplication(QtApplication): return log + recentFilesChanged = pyqtSignal() + @pyqtProperty("QStringList", notify = recentFilesChanged) + def recentFiles(self): + return self._recent_files + + @pyqtSlot("QStringList") + def setExpandedCategories(self, categories): + categories = list(set(categories)) + categories.sort() + joined = ";".join(categories) + if joined != Preferences.getInstance().getValue("cura/categories_expanded"): + Preferences.getInstance().setValue("cura/categories_expanded", joined) + self.expandedCategoriesChanged.emit() + + expandedCategoriesChanged = pyqtSignal() + @pyqtProperty("QStringList", notify = expandedCategoriesChanged) + def expandedCategories(self): + return Preferences.getInstance().getValue("cura/categories_expanded").split(";") + outputDevicesChanged = pyqtSignal() @pyqtProperty("QVariantMap", notify = outputDevicesChanged) @@ -460,3 +492,18 @@ class CuraApplication(QtApplication): op = AddSceneNodeOperation(node, self.getController().getScene().getRoot()) op.push() + + def _onJobFinished(self, job): + if type(job) is not ReadMeshJob: + return + + f = job.getFileName() + if f in self._recent_files: + self._recent_files.remove(f) + + self._recent_files.insert(0, f) + if len(self._recent_files) > 10: + del self._recent_files[10] + + Preferences.getInstance().setValue("cura/recent_files", ";".join(self._recent_files)) + self.recentFilesChanged.emit() diff --git a/installer.nsi b/installer.nsi index 5bd18a929b..1259aeba7a 100644 --- a/installer.nsi +++ b/installer.nsi @@ -99,6 +99,15 @@ Function LaunchLink Exec '"$WINDIR\explorer.exe" "$SMPROGRAMS\Cura ${VERSION}\Cura ${VERSION}.lnk"' FunctionEnd +Section "Install Visual Studio 2010 Redistributable" + SetOutPath "$INSTDIR" + File "vcredist_2010_x86.exe" + + IfSilent +2 + ExecWait '"$INSTDIR\vcredist_2010_x86.exe"' + +SectionEnd + ;Section "Install Arduino Drivers" ; ; Set output path to the driver directory. ; SetOutPath "$INSTDIR\drivers\" diff --git a/plugins/CuraEngineBackend/Cura_pb2.py b/plugins/CuraEngineBackend/Cura_pb2.py index cf95bd2832..f97aaafc4d 100644 --- a/plugins/CuraEngineBackend/Cura_pb2.py +++ b/plugins/CuraEngineBackend/Cura_pb2.py @@ -1,6 +1,3 @@ -# Copyright (c) 2015 Ultimaker B.V. -# Cura is released under the terms of the AGPLv3 or higher. - # Generated by the protocol buffer compiler. DO NOT EDIT! # source: Cura.proto @@ -21,7 +18,7 @@ _sym_db = _symbol_database.Default() DESCRIPTOR = _descriptor.FileDescriptor( name='Cura.proto', package='Cura', - serialized_pb=_b('\n\nCura.proto\x12\x04\x43ura\"+\n\nObjectList\x12\x1d\n\x07objects\x18\x01 \x03(\x0b\x32\x0c.Cura.Object\"i\n\x06Object\x12\n\n\x02id\x18\x01 \x01(\x03\x12\x10\n\x08vertices\x18\x02 \x01(\x0c\x12\x0f\n\x07normals\x18\x03 \x01(\x0c\x12\x0f\n\x07indices\x18\x04 \x01(\x0c\x12\x1f\n\x08settings\x18\x05 \x03(\x0b\x32\r.Cura.Setting\"\x1a\n\x08Progress\x12\x0e\n\x06\x61mount\x18\x01 \x01(\x02\"7\n\x10SlicedObjectList\x12#\n\x07objects\x18\x01 \x03(\x0b\x32\x12.Cura.SlicedObject\"7\n\x0cSlicedObject\x12\n\n\x02id\x18\x01 \x01(\x03\x12\x1b\n\x06layers\x18\x02 \x03(\x0b\x32\x0b.Cura.Layer\"4\n\x05Layer\x12\n\n\x02id\x18\x01 \x01(\x05\x12\x1f\n\x08polygons\x18\x02 \x03(\x0b\x32\r.Cura.Polygon\"\x9f\x01\n\x07Polygon\x12 \n\x04type\x18\x01 \x01(\x0e\x32\x12.Cura.Polygon.Type\x12\x0e\n\x06points\x18\x02 \x01(\x0c\"b\n\x04Type\x12\x0c\n\x08NoneType\x10\x00\x12\x0e\n\nInset0Type\x10\x01\x12\x0e\n\nInsetXType\x10\x02\x12\x0c\n\x08SkinType\x10\x03\x12\x0f\n\x0bSupportType\x10\x04\x12\r\n\tSkirtType\x10\x05\"&\n\nGCodeLayer\x12\n\n\x02id\x18\x01 \x01(\x03\x12\x0c\n\x04\x64\x61ta\x18\x02 \x01(\x0c\"D\n\x0fObjectPrintTime\x12\n\n\x02id\x18\x01 \x01(\x03\x12\x0c\n\x04time\x18\x02 \x01(\x02\x12\x17\n\x0fmaterial_amount\x18\x03 \x01(\x02\".\n\x0bSettingList\x12\x1f\n\x08settings\x18\x01 \x03(\x0b\x32\r.Cura.Setting\"&\n\x07Setting\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x0c\"\x1b\n\x0bGCodePrefix\x12\x0c\n\x04\x64\x61ta\x18\x02 \x01(\x0c\x62\x06proto3') + serialized_pb=_b('\n\nCura.proto\x12\x04\x43ura\"+\n\nObjectList\x12\x1d\n\x07objects\x18\x01 \x03(\x0b\x32\x0c.Cura.Object\"i\n\x06Object\x12\n\n\x02id\x18\x01 \x01(\x03\x12\x10\n\x08vertices\x18\x02 \x01(\x0c\x12\x0f\n\x07normals\x18\x03 \x01(\x0c\x12\x0f\n\x07indices\x18\x04 \x01(\x0c\x12\x1f\n\x08settings\x18\x05 \x03(\x0b\x32\r.Cura.Setting\"\x1a\n\x08Progress\x12\x0e\n\x06\x61mount\x18\x01 \x01(\x02\"7\n\x10SlicedObjectList\x12#\n\x07objects\x18\x01 \x03(\x0b\x32\x12.Cura.SlicedObject\"7\n\x0cSlicedObject\x12\n\n\x02id\x18\x01 \x01(\x03\x12\x1b\n\x06layers\x18\x02 \x03(\x0b\x32\x0b.Cura.Layer\"W\n\x05Layer\x12\n\n\x02id\x18\x01 \x01(\x05\x12\x0e\n\x06height\x18\x02 \x01(\x02\x12\x11\n\tthickness\x18\x03 \x01(\x02\x12\x1f\n\x08polygons\x18\x04 \x03(\x0b\x32\r.Cura.Polygon\"\xdb\x01\n\x07Polygon\x12 \n\x04type\x18\x01 \x01(\x0e\x32\x12.Cura.Polygon.Type\x12\x0e\n\x06points\x18\x02 \x01(\x0c\x12\x12\n\nline_width\x18\x03 \x01(\x02\"\x89\x01\n\x04Type\x12\x0c\n\x08NoneType\x10\x00\x12\x0e\n\nInset0Type\x10\x01\x12\x0e\n\nInsetXType\x10\x02\x12\x0c\n\x08SkinType\x10\x03\x12\x0f\n\x0bSupportType\x10\x04\x12\r\n\tSkirtType\x10\x05\x12\x0e\n\nInfillType\x10\x06\x12\x15\n\x11SupportInfillType\x10\x07\"&\n\nGCodeLayer\x12\n\n\x02id\x18\x01 \x01(\x03\x12\x0c\n\x04\x64\x61ta\x18\x02 \x01(\x0c\"D\n\x0fObjectPrintTime\x12\n\n\x02id\x18\x01 \x01(\x03\x12\x0c\n\x04time\x18\x02 \x01(\x02\x12\x17\n\x0fmaterial_amount\x18\x03 \x01(\x02\".\n\x0bSettingList\x12\x1f\n\x08settings\x18\x01 \x03(\x0b\x32\r.Cura.Setting\"&\n\x07Setting\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x0c\"\x1b\n\x0bGCodePrefix\x12\x0c\n\x04\x64\x61ta\x18\x02 \x01(\x0c\x62\x06proto3') ) _sym_db.RegisterFileDescriptor(DESCRIPTOR) @@ -57,11 +54,19 @@ _POLYGON_TYPE = _descriptor.EnumDescriptor( name='SkirtType', index=5, number=5, options=None, type=None), + _descriptor.EnumValueDescriptor( + name='InfillType', index=6, number=6, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='SupportInfillType', index=7, number=7, + options=None, + type=None), ], containing_type=None, options=None, - serialized_start=430, - serialized_end=528, + serialized_start=486, + serialized_end=623, ) _sym_db.RegisterEnumDescriptor(_POLYGON_TYPE) @@ -266,8 +271,22 @@ _LAYER = _descriptor.Descriptor( is_extension=False, extension_scope=None, options=None), _descriptor.FieldDescriptor( - name='polygons', full_name='Cura.Layer.polygons', index=1, - number=2, type=11, cpp_type=10, label=3, + name='height', full_name='Cura.Layer.height', index=1, + number=2, type=2, cpp_type=6, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='thickness', full_name='Cura.Layer.thickness', index=2, + number=3, type=2, cpp_type=6, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='polygons', full_name='Cura.Layer.polygons', index=3, + number=4, type=11, cpp_type=10, label=3, has_default_value=False, default_value=[], message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, @@ -284,7 +303,7 @@ _LAYER = _descriptor.Descriptor( oneofs=[ ], serialized_start=314, - serialized_end=366, + serialized_end=401, ) @@ -309,6 +328,13 @@ _POLYGON = _descriptor.Descriptor( message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None), + _descriptor.FieldDescriptor( + name='line_width', full_name='Cura.Polygon.line_width', index=2, + number=3, type=2, cpp_type=6, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), ], extensions=[ ], @@ -321,8 +347,8 @@ _POLYGON = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=369, - serialized_end=528, + serialized_start=404, + serialized_end=623, ) @@ -358,8 +384,8 @@ _GCODELAYER = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=530, - serialized_end=568, + serialized_start=625, + serialized_end=663, ) @@ -402,8 +428,8 @@ _OBJECTPRINTTIME = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=570, - serialized_end=638, + serialized_start=665, + serialized_end=733, ) @@ -432,8 +458,8 @@ _SETTINGLIST = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=640, - serialized_end=686, + serialized_start=735, + serialized_end=781, ) @@ -469,8 +495,8 @@ _SETTING = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=688, - serialized_end=726, + serialized_start=783, + serialized_end=821, ) @@ -499,8 +525,8 @@ _GCODEPREFIX = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=728, - serialized_end=755, + serialized_start=823, + serialized_end=850, ) _OBJECTLIST.fields_by_name['objects'].message_type = _OBJECT diff --git a/plugins/CuraEngineBackend/LayerData.py b/plugins/CuraEngineBackend/LayerData.py index 546397e460..b129942c36 100644 --- a/plugins/CuraEngineBackend/LayerData.py +++ b/plugins/CuraEngineBackend/LayerData.py @@ -2,7 +2,9 @@ # Cura is released under the terms of the AGPLv3 or higher. from UM.Mesh.MeshData import MeshData +from UM.Mesh.MeshBuilder import MeshBuilder from UM.Math.Color import Color +from UM.Math.Vector import Vector import numpy import math @@ -13,12 +15,19 @@ class LayerData(MeshData): self._layers = {} self._element_counts = {} - def addPolygon(self, layer, type, data): + def addLayer(self, layer): if layer not in self._layers: - self._layers[layer] = [] + self._layers[layer] = Layer(layer) - p = Polygon(self, type, data) - self._layers[layer].append(p) + def addPolygon(self, layer, type, data, line_width): + if layer not in self._layers: + self.addLayer(layer) + + p = Polygon(self, type, data, line_width) + self._layers[layer].polygons.append(p) + + def getLayer(self, layer): + return self._layers[layer] def getLayers(self): return self._layers @@ -26,14 +35,112 @@ class LayerData(MeshData): def getElementCounts(self): return self._element_counts + def setLayerHeight(self, layer, height): + if layer not in self._layers: + self.addLayer(layer) + + self._layers[layer].setHeight(height) + + def setLayerThickness(self, layer, thickness): + if layer not in self._layers: + self.addLayer(layer) + + self._layers[layer].setThickness(thickness) + def build(self): for layer, data in self._layers.items(): - if layer not in self._element_counts: - self._element_counts[layer] = [] + data.build() - for polygon in data: - polygon.build() - self._element_counts[layer].append(polygon.elementCount) + self._element_counts[layer] = data.elementCount + +class Layer(): + def __init__(self, id): + self._id = id + self._height = 0.0 + self._thickness = 0.0 + self._polygons = [] + self._element_count = 0 + + @property + def height(self): + return self._height + + @property + def thickness(self): + return self._thickness + + @property + def polygons(self): + return self._polygons + + @property + def elementCount(self): + return self._element_count + + def setHeight(self, height): + self._height = height + + def setThickness(self, thickness): + self._thickness = thickness + + def build(self): + for polygon in self._polygons: + if polygon._type == Polygon.InfillType or polygon._type == Polygon.SupportInfillType: + continue + + polygon.build() + self._element_count += polygon.elementCount + + 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: + points[:,1] -= 0.01 + + # Calculate normals for the entire polygon using numpy. + normals = numpy.copy(points) + normals[:,1] = 0.0 # We are only interested in 2D normals + + # Calculate the edges between points. + # The call to numpy.roll shifts the entire array by one so that + # we end up subtracting each next point from the current, wrapping + # around. This gives us the edges from the next point to the current + # point. + normals[:] = normals[:] - numpy.roll(normals, -1, axis = 0) + # Calculate the length of each edge using standard Pythagoras + lengths = numpy.sqrt(normals[:,0] ** 2 + normals[:,2] ** 2) + # The normal of a 2D vector is equal to its x and y coordinates swapped + # and then x inverted. This code does that. + normals[:,[0, 2]] = normals[:,[2, 0]] + normals[:,0] *= -1 + + # Normalize the normals. + normals[:,0] /= lengths + normals[:,2] /= lengths + + # Scale all by the line width of the polygon so we can easily offset. + normals *= (polygon.lineWidth / 2) + + #TODO: Use numpy magic to perform the vertex creation to speed up things. + for i in range(len(points)): + start = points[i - 1] + end = points[i] + + normal = normals[i - 1] + + point1 = Vector(data = start - normal) + point2 = Vector(data = start + normal) + point3 = Vector(data = end + normal) + point4 = Vector(data = end - normal) + + builder.addQuad(point1, point2, point3, point4, color = poly_color) + + return builder.getData() class Polygon(): NoneType = 0 @@ -42,34 +149,26 @@ class Polygon(): SkinType = 3 SupportType = 4 SkirtType = 5 + InfillType = 6 + SupportInfillType = 7 - def __init__(self, mesh, type, data): + def __init__(self, mesh, type, data, line_width): super().__init__() self._mesh = mesh self._type = type 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 - color = None - if self._type == self.Inset0Type: - color = [1, 0, 0, 1] - elif self._type == self.InsetXType: - color = [0, 1, 0, 1] - elif self._type == self.SkinType: - color = [1, 1, 0, 1] - elif self._type == self.SupportType: - color = [0, 1, 1, 1] - elif self._type == self.SkirtType: - color = [0, 1, 1, 1] - else: - color = [1, 1, 1, 1] + color = self.getColor() + color[3] = 2.0 colors = [color for i in range(len(self._data))] - self._mesh.addColors(numpy.array(colors, dtype=numpy.float32)) + self._mesh.addColors(numpy.array(colors, dtype=numpy.float32) * 0.5) indices = [] for i in range(self._begin, self._end): @@ -80,6 +179,24 @@ class Polygon(): indices.append(self._begin) self._mesh.addIndices(numpy.array(indices, dtype=numpy.int32)) + def getColor(self): + if self._type == self.Inset0Type: + return [1.0, 0.0, 0.0, 1.0] + elif self._type == self.InsetXType: + return [0.0, 1.0, 0.0, 1.0] + elif self._type == self.SkinType: + return [1.0, 1.0, 0.0, 1.0] + elif self._type == self.SupportType: + return [0.0, 1.0, 1.0, 1.0] + elif self._type == self.SkirtType: + return [0.0, 1.0, 1.0, 1.0] + elif self._type == self.InfillType: + return [1.0, 1.0, 0.0, 1.0] + elif self._type == self.SupportInfillType: + return [0.0, 1.0, 1.0, 1.0] + else: + return [1.0, 1.0, 1.0, 1.0] + @property def type(self): return self._type @@ -90,4 +207,8 @@ class Polygon(): @property def elementCount(self): - return (self._end - self._begin) * 2 #The range of vertices multiplied by 2 since each vertex is used twice + return ((self._end - self._begin) + 1) * 2 #The range of vertices multiplied by 2 since each vertex is used twice + + @property + def lineWidth(self): + return self._line_width diff --git a/plugins/CuraEngineBackend/ProcessSlicedObjectListJob.py b/plugins/CuraEngineBackend/ProcessSlicedObjectListJob.py index 804338e4dc..6113da78a0 100644 --- a/plugins/CuraEngineBackend/ProcessSlicedObjectListJob.py +++ b/plugins/CuraEngineBackend/ProcessSlicedObjectListJob.py @@ -32,22 +32,24 @@ class ProcessSlicedObjectListJob(Job): settings = Application.getInstance().getActiveMachine() layerHeight = settings.getSettingValueByKey("layer_height") + mesh = MeshData() for object in self._message.objects: - try: + try: node = objectIdMap[object.id] except KeyError: continue - - mesh = MeshData() layerData = LayerData.LayerData() for layer in object.layers: + layerData.addLayer(layer.id) + layerData.setLayerHeight(layer.id, layer.height) + layerData.setLayerThickness(layer.id, layer.thickness) for polygon in layer.polygons: points = numpy.fromstring(polygon.points, dtype="i8") # Convert bytearray to numpy array points = points.reshape((-1,2)) # We get a linear list of pairs that make up the points, so make numpy interpret them correctly. points = numpy.asarray(points, dtype=numpy.float32) points /= 1000 - points = numpy.insert(points, 1, layer.id * layerHeight, axis = 1) + points = numpy.insert(points, 1, (layer.height / 1000), axis = 1) points[:,2] *= -1 @@ -55,16 +57,11 @@ class ProcessSlicedObjectListJob(Job): center = [settings.getSettingValueByKey("machine_width") / 2, 0.0, -settings.getSettingValueByKey("machine_depth") / 2] points -= numpy.array(center) - #points = numpy.pad(points, ((0,0), (0,1)), "constant", constant_values=(0.0, 1.0)) - #inverse = node.getWorldTransformation().getInverse().getData() - #points = points.dot(inverse) - #points = points[:,0:3] + layerData.addPolygon(layer.id, polygon.type, points, polygon.line_width) - layerData.addPolygon(layer.id, polygon.type, points) + # 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 - # 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 - new_node.setMeshData(mesh) new_node.setParent(self._scene.getRoot()) diff --git a/plugins/LayerView/LayerView.py b/plugins/LayerView/LayerView.py index f3580039c5..17cea9988c 100644 --- a/plugins/LayerView/LayerView.py +++ b/plugins/LayerView/LayerView.py @@ -9,6 +9,10 @@ from UM.Event import Event, KeyEvent from UM.Signal import Signal from UM.Scene.Selection import Selection from UM.Math.Color import Color +from UM.Mesh.MeshData import MeshData + +from cura.ConvexHullNode import ConvexHullNode + from . import LayerViewProxy ## View used to display g-code paths. @@ -22,6 +26,9 @@ class LayerView(View): self._controller.getScene().sceneChanged.connect(self._onSceneChanged) self._max_layers = 10 self._current_layer_num = 10 + self._current_layer_mesh = None + + self._solid_layers = 5 def getCurrentLayer(self): return self._current_layer_num @@ -45,6 +52,11 @@ class LayerView(View): self._selection_material.setUniformValue("u_color", Color(35, 35, 35, 128)) for node in DepthFirstIterator(scene.getRoot()): + # We do not want to render ConvexHullNode as it conflicts with the bottom layers. + # However, it is somewhat relevant when the node is selected, so do render it then. + if type(node) is ConvexHullNode and not Selection.isSelected(node.getWatchedNode()): + continue + if not node.render(renderer): if node.getMeshData() and node.isVisible(): if Selection.isSelected(node): @@ -55,19 +67,39 @@ class LayerView(View): except AttributeError: continue - start = 0 - end = 0 + # Render all layers below a certain number as line mesh instead of vertices. + if self._current_layer_num - self._solid_layers > -1: + start = 0 + end = 0 + element_counts = layer_data.getElementCounts() + for layer, counts in element_counts.items(): + if layer + self._solid_layers > self._current_layer_num: + break + end += counts - element_counts = layer_data.getElementCounts() - for layer, counts in element_counts.items(): - end += sum(counts) - ## Hack to ensure the end is correct. Not quite sure what causes this - end += 2 * len(counts) + # This uses glDrawRangeElements internally to only draw a certain range of lines. + renderer.queueNode(node, mesh = layer_data, material = self._material, mode = Renderer.RenderLines, start = start, end = end) - if layer >= self._current_layer_num: - break + # We currently recreate the current "solid" layers every time a + if not self._current_layer_mesh: + self._current_layer_mesh = MeshData() + for i in range(self._solid_layers): + layer = self._current_layer_num - i + if layer < 0: + continue - renderer.queueNode(node, mesh = layer_data, material = self._material, mode = Renderer.RenderLines, start = start, end = end) + layer_mesh = layer_data.getLayer(layer).createMesh() + if not layer_mesh or layer_mesh.getVertices() is None: + continue + + self._current_layer_mesh.addVertices(layer_mesh.getVertices()) + + # Scale layer color by a brightness factor based on the current layer number + # This will result in a range of 0.5 - 1.0 to multiply colors by. + brightness = (2.0 - (i / self._solid_layers)) / 2.0 + self._current_layer_mesh.addColors(layer_mesh.getColors() * brightness) + + renderer.queueNode(node, mesh = self._current_layer_mesh, material = self._material) def setLayer(self, value): if self._current_layer_num != value: @@ -76,6 +108,8 @@ class LayerView(View): self._current_layer_num = 0 if self._current_layer_num > self._max_layers: self._current_layer_num = self._max_layers + + self._current_layer_mesh = None self.currentLayerNumChanged.emit() currentLayerNumChanged = Signal() @@ -96,7 +130,7 @@ class LayerView(View): except AttributeError: continue if new_max_layers < len(layer_data.getLayers()): - new_max_layers = len(layer_data.getLayers()) + new_max_layers = len(layer_data.getLayers()) - 1 if new_max_layers > 0 and new_max_layers != self._old_max_layers: self._max_layers = new_max_layers diff --git a/resources/qml/AboutDialog.qml b/resources/qml/AboutDialog.qml index df2ecf4172..1ed9f5dd32 100644 --- a/resources/qml/AboutDialog.qml +++ b/resources/qml/AboutDialog.qml @@ -3,7 +3,6 @@ import QtQuick 2.2 import QtQuick.Controls 1.1 -import QtQuick.Layouts 1.1 import QtQuick.Window 2.1 import UM 1.0 as UM @@ -12,48 +11,53 @@ UM.Dialog { id: base //: About dialog title - title: qsTr("About Cura"); + title: qsTr("About Cura") + minimumWidth: 400 + minimumHeight: 300 - ColumnLayout { - anchors.fill: parent; + Image { + id: logo + width: parent.width * 0.75 + height: width * (1/4.25) - Item { - Layout.fillWidth: true; - Layout.fillHeight: true; - } + source: UM.Theme.images.logo - Image { - Layout.alignment: Qt.AlignHCenter; - Layout.preferredWidth: parent.width * 0.75; - Layout.preferredHeight: width * (1/4.25); + sourceSize.width: width + sourceSize.height: height + anchors.centerIn: parent + anchors.verticalCenterOffset : -(height * 0.5) + } - source: UM.Theme.images.logo; + Label { + id: version - sourceSize.width: width; - sourceSize.height: height; - } + text: "Cura 15.06 Beta" + font: UM.Theme.fonts.large + anchors.horizontalCenter : logo.horizontalCenter + anchors.horizontalCenterOffset : (logo.width * 0.25) + anchors.top: logo.bottom + anchors.topMargin : 5 + } - Label { - Layout.alignment: Qt.AlignHCenter; + Label { + id: description + width: parent.width - text: "Cura 15.06 Beta"; - font: UM.Theme.fonts.large; - } + //: About dialog application description + text: qsTr("End-to-end solution for fused filament 3D printing.") + wrapMode: Text.WordWrap + anchors.top: version.bottom + anchors.topMargin : 10 + } - Label { - //: About dialog application description - text: qsTr("End-to-end solution for fused filament 3D printing.") - } + Label { + id: author_note + width: parent.width - Label { - //: About dialog application author note - text: qsTr("Cura has been developed by Ultimaker B.V. in cooperation with the community.") - } - - Item { - Layout.fillWidth: true; - Layout.fillHeight: true; - } + //: About dialog application author note + text: qsTr("Cura has been developed by Ultimaker B.V. in cooperation with the community.") + wrapMode: Text.WordWrap + anchors.top: description.bottom } rightButtons: Button { diff --git a/resources/qml/Cura.qml b/resources/qml/Cura.qml index a2eeba59cd..7067c88c12 100644 --- a/resources/qml/Cura.qml +++ b/resources/qml/Cura.qml @@ -25,6 +25,7 @@ UM.MainWindow { window: base Menu { + id: fileMenu //: File menu title: qsTr("&File"); @@ -33,6 +34,19 @@ UM.MainWindow { MenuSeparator { } + Instantiator { + model: Printer.recentFiles + MenuItem { + property url filePath: modelData; + text: (index + 1) + ". " + modelData.slice(modelData.lastIndexOf("/") + 1); + onTriggered: UM.MeshFileHandler.readLocalFile(filePath); + } + onObjectAdded: fileMenu.insertItem(index, object) + onObjectRemoved: fileMenu.removeItem(object) + } + + MenuSeparator { } + MenuItem { action: actions.quit; } } @@ -178,8 +192,8 @@ UM.MainWindow { id: openFileButton; iconSource: UM.Theme.icons.open; - style: UM.Theme.styles.tool_button; - + style: UM.Backend.progress < 0 ? UM.Theme.styles.open_file_button : UM.Theme.styles.tool_button; + tooltip: ''; anchors { top: parent.top; topMargin: UM.Theme.sizes.window_margin.height; @@ -218,7 +232,7 @@ UM.MainWindow { iconSource: UM.Theme.icons.viewmode; style: UM.Theme.styles.tool_button; - + tooltip: ''; menu: Menu { id: viewMenu; Instantiator { @@ -419,3 +433,4 @@ UM.MainWindow { Component.onCompleted: UM.Theme.load(UM.Resources.getPath(UM.Resources.ThemesLocation, "cura")) } + diff --git a/resources/qml/SidebarAdvanced.qml b/resources/qml/SidebarAdvanced.qml index 7d0e391768..8a231aa53d 100644 --- a/resources/qml/SidebarAdvanced.qml +++ b/resources/qml/SidebarAdvanced.qml @@ -1,6 +1,13 @@ // Copyright (c) 2015 Ultimaker B.V. // Cura is released under the terms of the AGPLv3 or higher. +import QtQuick 2.0 + +import QtQuick.Controls 1.2 + import UM 1.0 as UM -UM.SettingView { } +UM.SettingView { + expandedCategories: Printer.expandedCategories; + onExpandedCategoriesChanged: Printer.setExpandedCategories(expandedCategories); +} diff --git a/resources/qml/Toolbar.qml b/resources/qml/Toolbar.qml index e7cb0788c5..d5126db481 100644 --- a/resources/qml/Toolbar.qml +++ b/resources/qml/Toolbar.qml @@ -47,7 +47,6 @@ Item { Button { text: model.name; iconSource: UM.Theme.icons[model.icon]; - tooltip: model.description; checkable: true; checked: model.active; diff --git a/resources/settings/ultimaker_original_plus.json b/resources/settings/ultimaker_original_plus.json index d0b04879db..9e5cf6370d 100644 --- a/resources/settings/ultimaker_original_plus.json +++ b/resources/settings/ultimaker_original_plus.json @@ -1,6 +1,6 @@ { "id": "ultimaker_original_plus", - "name": "Ultimaker Original Plus", + "name": "Ultimaker Original+", "icon": "icon_ultimaker.png", "platform": "ultimaker2_platform.obj", "platform_texture": "UltimakerPlusbackplate.png", diff --git a/resources/themes/cura/styles.qml b/resources/themes/cura/styles.qml index b7db374da1..43bbb8c400 100644 --- a/resources/themes/cura/styles.qml +++ b/resources/themes/cura/styles.qml @@ -35,47 +35,42 @@ QtObject { } } - property Component tool_button: Component { + property Component open_file_button: Component { ButtonStyle { - background: UM.AngledCornerRectangle { - property bool down: control.pressed || (control.checkable && control.checked); - + background: Item { implicitWidth: UM.Theme.sizes.button.width; implicitHeight: UM.Theme.sizes.button.height; - color: { - if(!control.enabled) { - return UM.Theme.colors.button_disabled; - } else if(control.checkable && control.checked && control.hovered) { - return UM.Theme.colors.button_active_hover; - } else if(control.pressed || (control.checkable && control.checked)) { - return UM.Theme.colors.button_active; - } else if(control.hovered) { - return UM.Theme.colors.button_hover; - } else { - return UM.Theme.colors.button; - } - } - Behavior on color { ColorAnimation { duration: 50; } } - cornerSize: UM.Theme.sizes.default_margin.width; Rectangle { - anchors.bottom: parent.top; - + anchors.bottom: parent.verticalCenter; width: parent.width; - height: control.hovered ? label.height : 0; - Behavior on height { NumberAnimation { duration: 75; } } + height: control.hovered ? parent.height / 2 + label.height : 0; + Behavior on height { NumberAnimation { duration: 100; } } opacity: control.hovered ? 1.0 : 0.0; - Behavior on opacity { NumberAnimation { duration: 75; } } + Behavior on opacity { NumberAnimation { duration: 100; } } Label { - id: label + id: label; anchors.horizontalCenter: parent.horizontalCenter; - text: control.text; + text: control.text.replace("&", ""); font: UM.Theme.fonts.button_tooltip; color: UM.Theme.colors.button_tooltip_text; } } + + UM.AngledCornerRectangle { + anchors.fill: parent; + color: { + if(control.hovered) { + return UM.Theme.colors.button_active_hover; + } else { + return UM.Theme.colors.button_active; + } + } + Behavior on color { ColorAnimation { duration: 50; } } + cornerSize: UM.Theme.sizes.default_margin.width; + } } label: Item { @@ -92,6 +87,113 @@ QtObject { } } + property Component tool_button: Component { + ButtonStyle { + background: Item { + implicitWidth: UM.Theme.sizes.button.width; + implicitHeight: UM.Theme.sizes.button.height; + + Rectangle { + anchors.bottom: parent.verticalCenter; + + width: parent.width; + height: control.hovered ? parent.height / 2 + label.height : 0; + Behavior on height { NumberAnimation { duration: 100; } } + + opacity: control.hovered ? 1.0 : 0.0; + Behavior on opacity { NumberAnimation { duration: 100; } } + + Label { + id: label + anchors.horizontalCenter: parent.horizontalCenter; + text: control.text.replace("&", ""); + font: UM.Theme.fonts.button_tooltip; + color: UM.Theme.colors.button_tooltip_text; + } + } + + UM.AngledCornerRectangle { + id: buttonFace; + + anchors.fill: parent; + + property bool down: control.pressed || (control.checkable && control.checked); + + color: { + if(!control.enabled) { + return UM.Theme.colors.button_disabled; + } else if(control.checkable && control.checked && control.hovered) { + return UM.Theme.colors.button_active_hover; + } else if(control.pressed || (control.checkable && control.checked)) { + return UM.Theme.colors.button_active; + } else if(control.hovered) { + return UM.Theme.colors.button_hover; + } else { + return UM.Theme.colors.button; + } + } + Behavior on color { ColorAnimation { duration: 50; } } + cornerSize: UM.Theme.sizes.default_margin.width; + } + } + + label: Item { + Image { + anchors.centerIn: parent; + + source: control.iconSource; + width: UM.Theme.sizes.button_icon.width; + height: UM.Theme.sizes.button_icon.height; + + sourceSize: UM.Theme.sizes.button_icon; + } + } + } + } + + + property Component progressbar: Component{ + ProgressBarStyle { + background: UM.AngledCornerRectangle { + anchors.fill: parent + anchors.left: parent.left + implicitWidth: UM.Theme.sizes.progressbar.width + implicitHeight: UM.Theme.sizes.progressbar.height + color: "transparent" + } + progress: UM.AngledCornerRectangle { + anchors.left: parent.left + anchors.fill: parent + cornerSize: UM.Theme.sizes.progressbar_control.height + color: UM.Theme.colors.progressbar_background + Item { + anchors.fill: parent + anchors.margins: UM.Theme.sizes.progressbar_margin.width + visible: control.indeterminate + Row { + Repeater { + UM.AngledCornerRectangle { + cornerSize: UM.Theme.sizes.progressbar_control.height + color: UM.Theme.colors.progressbar_control + width: UM.Theme.sizes.progressbar_control.width + height: UM.Theme.sizes.progressbar_control.height + } + model: 1 + } + SequentialAnimation on x { + id: xAnim + property int animEndPoint: UM.Theme.sizes.progressbar.width - UM.Theme.sizes.progressbar_control.width + running: control.indeterminate + loops: Animation.Infinite + NumberAnimation { from: 0; to: xAnim.animEndPoint; duration: 2000;} + NumberAnimation { from: xAnim.animEndPoint; to: 0; duration: 2000;} + } + } + } + } + } + } + property Component sidebar_category: Component { ButtonStyle { background: UM.AngledCornerRectangle { diff --git a/resources/themes/cura/theme.json b/resources/themes/cura/theme.json index eda2badd71..1e34028046 100644 --- a/resources/themes/cura/theme.json +++ b/resources/themes/cura/theme.json @@ -52,15 +52,15 @@ "border": [205, 202, 201, 255], "secondary": [205, 202, 201, 255], - "text": [174, 174, 174, 255], - "text_inactive": [205, 202, 201, 255], + "text": [140, 144, 154, 255], + "text_inactive": [174, 174, 174, 255], "text_hover": [35, 35, 35, 255], "text_pressed": [12, 169, 227, 255], - "button": [205, 202, 201, 255], - "button_hover": [174, 174, 174, 255], + "button": [160, 163, 171, 255], + "button_hover": [140, 144, 154, 255], "button_active": [12, 169, 227, 255], - "button_active_hover": [34, 150, 190, 255], + "button_active_hover": [34, 150, 199, 255], "button_text": [255, 255, 255, 255], "button_disabled": [245, 245, 245, 255], "button_tooltip_text": [35, 35, 35, 255], @@ -77,7 +77,7 @@ "setting_category_active_hover": [34, 150, 190, 255], "setting_category_text": [255, 255, 255, 255], - "setting_label": [174, 174, 174, 255], + "setting_label": [140, 144, 154, 255], "setting_control": [255, 255, 255, 255], "setting_control_highlight": [245, 245, 245, 255], "setting_control_border": [174, 174, 174, 255], @@ -88,6 +88,9 @@ "setting_validation_warning": [255, 186, 15, 255], "setting_validation_ok": [255, 255, 255, 255], + "progressbar_background": [245, 245, 245, 255], + "progressbar_control": [12, 169, 227, 255], + "slider_groove": [245, 245, 245, 255], "slider_groove_border": [205, 202, 201, 255], "slider_groove_fill": [205, 202, 201, 255], @@ -98,7 +101,7 @@ "checkbox_hover": [245, 245, 245, 255], "checkbox_border": [174, 174, 174, 255], "checkbox_mark": [35, 35, 35, 255], - "checkbox_text": [174, 174, 174, 255], + "checkbox_text": [140, 144, 154, 255], "tooltip": [255, 225, 146, 255], @@ -133,7 +136,11 @@ "setting_unit_margin": [0.5, 0.5], "button": [4.25, 4.25], - "button_icon": [3.57, 3.57], + "button_icon": [2.9, 2.9], + + "progressbar": [26.0, 0.5], + "progressbar_control": [8.0, 0.5], + "progressbar_padding": [0.0, 1.0], "scrollbar": [0.5, 0.5],