Merge branch 'master' into python_type_hinting

This commit is contained in:
Simon Edwards 2017-01-17 08:42:55 +01:00
commit fb70eb6813
124 changed files with 65675 additions and 49200 deletions

View file

@ -1,21 +1,21 @@
# Copyright (c) 2015 Ultimaker B.V.
# Cura is released under the terms of the AGPLv3 or higher.
from UM.Mesh.MeshReader import MeshReader
from UM.Mesh.MeshBuilder import MeshBuilder
import os.path
import zipfile
from UM.Job import Job
from UM.Logger import Logger
from UM.Math.Matrix import Matrix
from UM.Math.Vector import Vector
from UM.Scene.SceneNode import SceneNode
from UM.Mesh.MeshBuilder import MeshBuilder
from UM.Mesh.MeshReader import MeshReader
from UM.Scene.GroupDecorator import GroupDecorator
from UM.Job import Job
from cura.Settings.SettingOverrideDecorator import SettingOverrideDecorator
from UM.Application import Application
from cura.Settings.ExtruderManager import ExtruderManager
from cura.QualityManager import QualityManager
import os.path
import zipfile
from UM.Scene.SceneNode import SceneNode
try:
import xml.etree.cElementTree as ET
@ -262,4 +262,4 @@ class ThreeMFReader(MeshReader):
Logger.log("w", "Unrecognised unit %s used. Assuming mm instead", unit)
scale = 1
return Vector(scale, scale, scale)
return Vector(scale, scale, scale)

View file

@ -54,7 +54,12 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
else:
Logger.log("w", "Could not find reader that was able to read the scene data for 3MF workspace")
return WorkspaceReader.PreReadResult.failed
machine_name = ""
machine_type = ""
variant_type_name = i18n_catalog.i18nc("@label", "Nozzle")
num_extruders = 0
# Check if there are any conflicts, so we can ask the user.
archive = zipfile.ZipFile(file_name, "r")
cura_file_names = [name for name in archive.namelist() if name.startswith("Cura/")]
@ -76,6 +81,30 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
machine_conflict = True
Job.yieldThread()
definition_container_files = [name for name in cura_file_names if name.endswith(self._definition_container_suffix)]
for definition_container_file in definition_container_files:
container_id = self._stripFileToId(definition_container_file)
definitions = self._container_registry.findDefinitionContainers(id=container_id)
if not definitions:
definition_container = DefinitionContainer(container_id)
definition_container.deserialize(archive.open(definition_container_file).read().decode("utf-8"))
else:
definition_container = definitions[0]
if definition_container.getMetaDataEntry("type") != "extruder":
machine_type = definition_container.getName()
variant_type_name = definition_container.getMetaDataEntry("variants_name", variant_type_name)
else:
num_extruders += 1
Job.yieldThread()
if num_extruders == 0:
num_extruders = 1 # No extruder stacks found, which means there is one extruder
extruders = num_extruders * [""]
material_labels = []
material_conflict = False
xml_material_profile = self._getXmlProfileClass()
@ -95,6 +124,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
quality_name = ""
quality_type = ""
num_settings_overriden_by_quality_changes = 0 # How many settings are changed by the quality changes
num_user_settings = 0
for instance_container_file in instance_container_files:
container_id = self._stripFileToId(instance_container_file)
instance_container = InstanceContainer(container_id)
@ -117,6 +147,9 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
if quality_name == "":
quality_name = instance_container.getName()
quality_type = instance_container.getName()
elif container_type == "user":
num_user_settings += len(instance_container._instances)
Job.yieldThread()
num_visible_settings = 0
try:
@ -142,9 +175,13 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
self._dialog.setQualityName(quality_name)
self._dialog.setQualityType(quality_type)
self._dialog.setNumSettingsOverridenByQualityChanges(num_settings_overriden_by_quality_changes)
self._dialog.setNumUserSettings(num_user_settings)
self._dialog.setActiveMode(active_mode)
self._dialog.setMachineName(machine_name)
self._dialog.setMaterialLabels(material_labels)
self._dialog.setMachineType(machine_type)
self._dialog.setExtruders(extruders)
self._dialog.setVariantType(variant_type_name)
self._dialog.setHasObjectsOnPlate(Application.getInstance().getPlatformActivity)
self._dialog.show()

View file

@ -1,7 +1,8 @@
# Copyright (c) 2016 Ultimaker B.V.
# Cura is released under the terms of the AGPLv3 or higher.
from PyQt5.QtCore import Qt, QUrl, pyqtSignal, pyqtSlot, QObject, pyqtProperty, QCoreApplication
from PyQt5.QtCore import Qt, QUrl, pyqtSignal, QObject, pyqtProperty, QCoreApplication
from UM.FlameProfiler import pyqtSlot
from PyQt5.QtQml import QQmlComponent, QQmlContext
from UM.PluginRegistry import PluginRegistry
from UM.Application import Application
@ -36,12 +37,16 @@ class WorkspaceDialog(QObject):
self._has_machine_conflict = False
self._has_material_conflict = False
self._num_visible_settings = 0
self._num_user_settings = 0
self._active_mode = ""
self._quality_name = ""
self._num_settings_overriden_by_quality_changes = 0
self._quality_type = ""
self._machine_name = ""
self._machine_type = ""
self._variant_type = ""
self._material_labels = []
self._extruders = []
self._objects_on_plate = False
machineConflictChanged = pyqtSignal()
@ -55,6 +60,34 @@ class WorkspaceDialog(QObject):
machineNameChanged = pyqtSignal()
materialLabelsChanged = pyqtSignal()
objectsOnPlateChanged = pyqtSignal()
numUserSettingsChanged = pyqtSignal()
machineTypeChanged = pyqtSignal()
variantTypeChanged = pyqtSignal()
extrudersChanged = pyqtSignal()
@pyqtProperty(str, notify=variantTypeChanged)
def variantType(self):
return self._variant_type
def setVariantType(self, variant_type):
self._variant_type = variant_type
self.variantTypeChanged.emit()
@pyqtProperty(str, notify=machineTypeChanged)
def machineType(self):
return self._machine_type
def setMachineType(self, machine_type):
self._machine_type = machine_type
self.machineTypeChanged.emit()
def setNumUserSettings(self, num_user_settings):
self._num_user_settings = num_user_settings
self.numVisibleSettingsChanged.emit()
@pyqtProperty(int, notify=numUserSettingsChanged)
def numUserSettings(self):
return self._num_user_settings
@pyqtProperty(bool, notify=objectsOnPlateChanged)
def hasObjectsOnPlate(self):
@ -72,6 +105,14 @@ class WorkspaceDialog(QObject):
self._material_labels = material_labels
self.materialLabelsChanged.emit()
@pyqtProperty("QVariantList", notify=extrudersChanged)
def extruders(self):
return self._extruders
def setExtruders(self, extruders):
self._extruders = extruders
self.extrudersChanged.emit()
@pyqtProperty(str, notify = machineNameChanged)
def machineName(self):
return self._machine_name
@ -144,6 +185,11 @@ class WorkspaceDialog(QObject):
if key in self._result:
self._result[key] = strategy
## Close the backend: otherwise one could end up with "Slicing..."
@pyqtSlot()
def closeBackend(self):
Application.getInstance().getBackend().close()
def setMaterialConflict(self, material_conflict):
self._has_material_conflict = material_conflict
self.materialConflictChanged.emit()

View file

@ -16,9 +16,9 @@ UM.Dialog
minimumWidth: 550
maximumWidth: 550
height: 350
minimumHeight: 350
maximumHeight: 350
height: 400
minimumHeight: 400
maximumHeight: 400
property int comboboxHeight: 15
property int spacerHeight: 10
onClosing: manager.notifyClosed()
@ -28,12 +28,20 @@ UM.Dialog
{
machineResolveComboBox.currentIndex = 0
qualityChangesResolveComboBox.currentIndex = 0
materialConflictComboBox.currentIndex = 0
materialResolveComboBox.currentIndex = 0
}
}
Item
{
anchors.fill: parent
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.topMargin: 20
anchors.bottomMargin: 20
anchors.leftMargin:20
anchors.rightMargin: 20
UM.I18nCatalog
{
@ -77,27 +85,22 @@ UM.Dialog
width: height
}
Label
{
text: catalog.i18nc("@action:label", "Printer settings")
font.bold: true
}
Row
{
width: parent.width
height: childrenRect.height
width: parent.width
Label
{
text: catalog.i18nc("@action:label", "Name")
width: parent.width / 3
text: catalog.i18nc("@action:label", "Printer settings")
font.bold: true
width: parent.width /3
}
Label
Item
{
text: manager.machineName
// spacer
height: spacerHeight
width: parent.width / 3
}
UM.TooltipArea
{
id: machineResolveTooltip
@ -118,16 +121,20 @@ UM.Dialog
}
}
}
Item // Spacer
Row
{
height: spacerHeight
width: height
}
Label
{
text: catalog.i18nc("@action:label", "Profile settings")
font.bold: true
width: parent.width
height: childrenRect.height
Label
{
text: catalog.i18nc("@action:label", "Type")
width: parent.width / 3
}
Label
{
text: manager.machineType
width: parent.width / 3
}
}
Row
@ -141,10 +148,32 @@ UM.Dialog
}
Label
{
text: manager.qualityName
text: manager.machineName
width: parent.width / 3
}
}
Item // Spacer
{
height: spacerHeight
width: height
}
Row
{
height: childrenRect.height
width: parent.width
Label
{
text: catalog.i18nc("@action:label", "Profile settings")
font.bold: true
width: parent.width / 3
}
Item
{
// spacer
height: spacerHeight
width: parent.width / 3
}
UM.TooltipArea
{
id: qualityChangesResolveTooltip
@ -170,13 +199,44 @@ UM.Dialog
width: parent.width
height: childrenRect.height
Label
{
text: catalog.i18nc("@action:label", "Name")
width: parent.width / 3
}
Label
{
text: manager.qualityName
width: parent.width / 3
}
}
Row
{
width: parent.width
height: manager.numUserSettings != 0 ? childrenRect.height : 0
Label
{
text: catalog.i18nc("@action:label", "Not in profile")
width: parent.width / 3
}
Label
{
text: catalog.i18ncp("@action:label", "%1 override", "%1 overrides", manager.numUserSettings).arg(manager.numUserSettings)
width: parent.width / 3
}
visible: manager.numUserSettings != 0
}
Row
{
width: parent.width
height: manager.numSettingsOverridenByQualityChanges != 0 ? childrenRect.height : 0
Label
{
text: catalog.i18nc("@action:label", "Derivative from")
width: parent.width / 3
}
Label
{
text: catalog.i18nc("@action:label", "%1, %2 override(s)" ).arg(manager.qualityType).arg(manager.numSettingsOverridenByQualityChanges)
text: catalog.i18ncp("@action:label", "%1, %2 override", "%1, %2 overrides", manager.numSettingsOverridenByQualityChanges).arg(manager.qualityType).arg(manager.numSettingsOverridenByQualityChanges)
width: parent.width / 3
}
visible: manager.numSettingsOverridenByQualityChanges != 0
@ -186,11 +246,41 @@ UM.Dialog
height: spacerHeight
width: height
}
Label
Row
{
text: catalog.i18nc("@action:label", "Material settings")
font.bold: true
height: childrenRect.height
width: parent.width
Label
{
text: catalog.i18nc("@action:label", "Material settings")
font.bold: true
width: parent.width / 3
}
Item
{
// spacer
height: spacerHeight
width: parent.width / 3
}
UM.TooltipArea
{
id: materialResolveTooltip
width: parent.width / 3
height: visible ? comboboxHeight : 0
visible: manager.materialConflict
text: catalog.i18nc("@info:tooltip", "How should the conflict in the material be resolved?")
ComboBox
{
model: resolveStrategiesModel
textRole: "label"
id: materialResolveComboBox
width: parent.width
onActivated:
{
manager.setResolveStrategy("material", resolveStrategiesModel.get(index).key)
}
}
}
}
Repeater
@ -213,37 +303,6 @@ UM.Dialog
}
}
Row
{
width: parent.width
height: childrenRect.height
visible: manager.materialConflict
Item
{
width: parent.width / 3 * 2
height: comboboxHeight
}
UM.TooltipArea
{
id: materialResolveTooltip
width: parent.width / 3
height: visible ? comboboxHeight : 0
text: catalog.i18nc("@info:tooltip", "How should the conflict in the material be resolved?")
ComboBox
{
model: resolveStrategiesModel
textRole: "label"
id: materialResolveComboBox
width: parent.width
onActivated:
{
manager.setResolveStrategy("material", resolveStrategiesModel.get(index).key)
}
}
}
}
Item // Spacer
{
height: spacerHeight
@ -290,30 +349,47 @@ UM.Dialog
height: spacerHeight
width: height
}
Label
Row
{
text: catalog.i18nc("@action:warning", "Loading a project will clear all models on the buildplate")
visible: manager.hasObjectsOnPlate
color: "red"
width: parent.width
wrapMode: Text.Wrap
height: childrenRect.height
visible: manager.hasObjectsOnPlate
UM.RecolorImage
{
width: warningLabel.height
height: width
source: UM.Theme.getIcon("notice")
color: "black"
}
Label
{
id: warningLabel
text: catalog.i18nc("@action:warning", "Loading a project will clear all models on the buildplate")
wrapMode: Text.Wrap
}
}
}
}
rightButtons: [
Button
{
id: ok_button
text: catalog.i18nc("@action:button","OK");
onClicked: { manager.onOkButtonClicked() }
enabled: true
},
Button
{
id: cancel_button
text: catalog.i18nc("@action:button","Cancel");
onClicked: { manager.onCancelButtonClicked() }
enabled: true
anchors.bottom: parent.bottom
anchors.right: ok_button.left
anchors.bottomMargin: - 0.5 * height
anchors.rightMargin:2
}
]
Button
{
id: ok_button
text: catalog.i18nc("@action:button","Open");
onClicked: { manager.closeBackend(); manager.onOkButtonClicked() }
anchors.bottomMargin: - 0.5 * height
anchors.bottom: parent.bottom
anchors.right: parent.right
}
}
}

View file

@ -4,10 +4,16 @@
from . import ThreeMFReader
from . import ThreeMFWorkspaceReader
from UM.i18n import i18nCatalog
import UM.Platform
catalog = i18nCatalog("cura")
def getMetaData():
# Workarround for osx not supporting double file extensions correclty.
if UM.Platform.isOSX():
workspace_extension = "3mf"
else:
workspace_extension = "curaproject.3mf"
return {
"plugin": {
"name": catalog.i18nc("@label", "3MF Reader"),
@ -25,7 +31,7 @@ def getMetaData():
"workspace_reader":
[
{
"extension": "curaproject.3mf",
"extension": workspace_extension,
"description": catalog.i18nc("@item:inlistbox", "3MF File")
}
]

View file

@ -1,3 +1,128 @@
[2.4.0]
*Project saving & opening
You can now save your build plate configuration - with all your active machines meshes and settings. When you reopen the project file, youll find that the build plate configuration and all settings will be exactly as you last left them when you saved the project.
*Setting search
You can now search the custom settings directly from the side panel, which means you can easily locate the setting you need to tweak. Thanks to community member Aldo Hoeben & LulzBot for this feature.
*Editing start g-code and end g-code
Aldo Hoeben also added this feature, enabling you to alter both start and end code g-code settings for single extrusion machines.
*Multiply object function
By right-clicking on an object, you can multiply it by a variable amount, rather than duplicating multiple times. Thanks again to Aldo Hoeben for this feature.
*Ultimaker 3 single extrusion prints
Dual extrusion printers now allow for single extrusion prints in a larger printable area.
*Streaming printer monitor view
Ultimaker 3s camera views no longer only show snapshots. They now show a live stream.
*Explain why slicing is disabled
When slicing is blocked by settings with error values, a message now appears, clearly indicating which settings need to be changed.
*Ultimaker 3 print profiles
The initial and final printing temperatures reduce the amount of oozing during PLA-PLA, PLA-PVA and Nylon-PVA prints. This means printing a prime tower is now optional (except for CPE and ABS at the moment). The new Ultimaker 3 printing profiles ensure increased reliability and shorter print time.
*Initial Layer Printing Temperature
Initial and final printing temperature settings have been tuned for higher quality results.
*Printing temperature of the materials
The printing temperature of the materials in the material profiles is now the same as the printing temperature for the Normal Quality profile.
*Improved PLA-PVA layer adhesion
The PVA jerk and acceleration have been optimized to improve the layer adhesion between PVA and PLA.
*Default build plate adhesion type for Nylon
The default build plate adhesion type for Nylon prints has been changed from raft to brim.
*Support Interface Thickness
The Support Roof Thickness is now 0.8 mm and PVA support infill has been slightly decreased to lower the printing time.
*Ultimaker 2+ PC prints
In the polycarbonate profiles, the raft settings for the 0.25 mm and 0.4 mm nozzles are tweaked for less warping.
*Hollow prime tower
Print the prime tower hollow to minimize material use while maintaining stability. Wiping the oozed material on the prime tower is now done from the inside, which means the excess material is contained within the prime tower.
*Precooling and prewarming
Printing now starts at a lower temperature, before increasing swiftly to the normal printing temperature. Cooling also starts earlier than the last extrusion (with that print core). This minimizes the materials heat absorption, which decreases the amount of degradation of the PVA material. This reduces the risk of clogging your nozzles.
*Remove Mesh Intersection
You are now able to turn off resolving of overlapping meshes. Models can now overlap, so you can perform build plate color mixing, by placing meshes over one another and lowering their flow.
*Alternate Mesh Removal
For areas where two models overlap, let each layer of the overlapping volume alternate (depending on which object the overlapping area of that layer belongs to). This improves the bonding between dual color models and allows for more controlled build plate color mixing.
*Hollow Object
Remove the infill from a mesh and treat internal cavities as overhangs, so as to create support in the models interior. This experimental setting greatly reduces the amount of material needed on the inside of the print.
*Fill Gaps Between Walls
Fill up small gaps between consecutive walls, making thin pieces in your model dense, rather than hollow. This feature makes the thin pieces stronger.
*Cubic subdivision infill
This experimental new infill pattern is similar to cubic infill, but generates bigger cubes farther inside the mesh. This greatly reduces print times and material use, while maintaining structural integrity. Thanks to community members Martin Boerwinckle and Nicholas Seward for this feature.
*Concentric 3D infill
This new infill pattern is similar to concentric infill, but touches the shell every X layers, creating better support for the top layers.
* Printing Temperature Initial Layer
Nozzle temperature to be used during the first layer.
*Build Plate Temperature Initial Layer
Bed temperature to be used during the first layer.
*Initial Fan Speed
Fan speed to be used during the first layer.
*Retract at Layer Change
Retract each time the printer progresses to the next layer.
*Outer Wall Wipe Distance
Wipe the nozzle after printing the outer wall.
*Set X-Y coordinate of z-seam
Select where to place the Z seam.
*Start Layers with the Same Part
Start each layer with the part closest to a given location.
*Turn off nozzle after last use
Turn off the nozzle after its last use, while other nozzles are still in use.
*Option for no build plate adhesion
Select not to print any build plate adhesion helper parts.
*Anti-overhang and support meshes
Use a mesh to specify a volume within which to classify nothing as overhang for support or specify a volume within which to print support.
*Delta printer support
This release adds support for printers with elliptic buildplates. This feature has not been extensively tested so please let us know if it works or get involved in improving it.
*bugfixes
The user is now notified when a new version of Cura is available.
When searching in the setting visibility preferences, the category for each setting is always displayed.
3MF files are now saved and loaded correctly.
Dragging a profile onto Cura now loads it automatically.
You can now view which print cores and materials are currently in your Ultimaker 3, via the machine manager.
You can now add the heated bed upgrade etc. from the machine manager.
Print core and material is now arranged under extruder tabs.
Cura now remembers all printers and profiles when you open just after closing it.
You can now duplicate the standard profiles.
Layer view now doesnt use as much RAM.
Its now quicker to change the value of the Support Enable setting.
Changing a setting updates all dependent settings more quickly.
Having errors in your setting values now always blocks slicing.
Selecting a model with any active tool no longer causes a reslice.
The prime poop now introduces a separate area where you cannot print.
Support Extruder setting is now near the support settings.
Build Plate Adhesion Extruder setting is now near the build plate adhesion settings.
Z hop settings have been moved to the Travel category.
Inactive nozzle wiping on the prime tower is re-enabled.
There are no more unnecessary retractions in support.
Each layer now has less extruder switches than the machine has extruders.
Concentric infill doesnt generate the first infill perimeter next to the walls.
Extruder priming now always happens on the first layer.
[2.3.1]
*Layer Height in Profile Selection
Added the layer height to the profile selection menu.

View file

@ -12,6 +12,8 @@ from UM.PluginRegistry import PluginRegistry
from UM.Resources import Resources
from UM.Settings.Validator import ValidatorState #To find if a setting is in an error state. We can't slice then.
from UM.Platform import Platform
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from cura.Settings.ExtruderManager import ExtruderManager
from . import ProcessSlicedLayersJob
@ -65,6 +67,8 @@ class CuraEngineBackend(Backend):
self._scene = Application.getInstance().getController().getScene()
self._scene.sceneChanged.connect(self._onSceneChanged)
self._pause_slicing = False
# Workaround to disable layer view processing if layer view is not active.
self._layer_view_active = False
Application.getInstance().getController().activeViewChanged.connect(self._onActiveViewChanged)
@ -145,6 +149,9 @@ class CuraEngineBackend(Backend):
## Perform a slice of the scene.
def slice(self):
Logger.log("d", "Starting slice job...")
if self._pause_slicing:
return
self._slice_start_time = time()
if not self._enabled or not self._global_container_stack: # We shouldn't be slicing.
# try again in a short time
@ -178,6 +185,17 @@ class CuraEngineBackend(Backend):
self._start_slice_job.start()
self._start_slice_job.finished.connect(self._onStartSliceCompleted)
def pauseSlicing(self):
self.close()
self._pause_slicing = True
self.backendStateChange.emit(BackendState.Disabled)
def continueSlicing(self):
if self._pause_slicing:
self._pause_slicing = False
self.backendStateChange.emit(BackendState.NotStarted)
## Terminate the engine process.
def _terminate(self):
self._slicing = False
@ -293,6 +311,19 @@ class CuraEngineBackend(Backend):
if source is self._scene.getRoot():
return
should_pause = False
for node in DepthFirstIterator(self._scene.getRoot()):
if node.callDecoration("isBlockSlicing"):
should_pause = True
gcode_list = node.callDecoration("getGCodeList")
if gcode_list is not None:
self._scene.gcode_list = gcode_list
if should_pause:
self.pauseSlicing()
else:
self.continueSlicing()
if source.getMeshData() is None:
return

View file

@ -253,7 +253,7 @@ class StartSliceJob(Job):
for key, value in settings.items(): #Add all submessages for each individual setting.
setting_message = self._slice_message.getMessage("global_settings").addRepeatedMessage("settings")
setting_message.name = key
if key == "machine_start_gcode" or key == "machine_end_gcode": #If it's a g-code message, use special formatting.
if key == "machine_start_gcode" or key == "machine_end_gcode" or key == "machine_extruder_start_code" or key == "machine_extruder_end_code": #If it's a g-code message, use special formatting.
setting_message.value = self._expandGcodeTokens(key, value, settings)
else:
setting_message.value = str(value).encode("utf-8")

View file

@ -0,0 +1,303 @@
# Copyright (c) 2016 Aleph Objects, Inc.
# Cura is released under the terms of the AGPLv3 or higher.
from UM.Application import Application
from UM.Logger import Logger
from UM.Math.AxisAlignedBox import AxisAlignedBox
from UM.Math.Vector import Vector
from UM.Mesh.MeshReader import MeshReader
from UM.Message import Message
from UM.Scene.SceneNode import SceneNode
from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")
from cura import LayerDataBuilder
from cura import LayerDataDecorator
from cura.LayerPolygon import LayerPolygon
from cura.GCodeListDecorator import GCodeListDecorator
import numpy
import math
import re
from collections import namedtuple
# Class for loading and parsing G-code files
class GCodeReader(MeshReader):
def __init__(self):
super(GCodeReader, self).__init__()
self._supported_extensions = [".gcode", ".g"]
Application.getInstance().hideMessageSignal.connect(self._onHideMessage)
self._cancelled = False
self._message = None
self._clearValues()
self._scene_node = None
self._position = namedtuple('Position', ['x', 'y', 'z', 'e'])
def _clearValues(self):
self._extruder = 0
self._layer_type = LayerPolygon.Inset0Type
self._layer = 0
self._previous_z = 0
self._layer_data_builder = LayerDataBuilder.LayerDataBuilder()
self._center_is_zero = False
@staticmethod
def _getValue(line, code):
n = line.find(code)
if n < 0:
return None
n += len(code)
pattern = re.compile("[;\s]")
match = pattern.search(line, n)
m = match.start() if match is not None else -1
try:
if m < 0:
return line[n:]
return line[n:m]
except:
return None
def _getInt(self, line, code):
value = self._getValue(line, code)
try:
return int(value)
except:
return None
def _getFloat(self, line, code):
value = self._getValue(line, code)
try:
return float(value)
except:
return None
def _onHideMessage(self, message):
if message == self._message:
self._cancelled = True
@staticmethod
def _getNullBoundingBox():
return AxisAlignedBox(minimum=Vector(0, 0, 0), maximum=Vector(10, 10, 10))
def _createPolygon(self, current_z, path):
countvalid = 0
for point in path:
if point[3] > 0:
countvalid += 1
if countvalid < 2:
return False
try:
self._layer_data_builder.addLayer(self._layer)
self._layer_data_builder.setLayerHeight(self._layer, path[0][2])
self._layer_data_builder.setLayerThickness(self._layer, math.fabs(current_z - self._previous_z))
this_layer = self._layer_data_builder.getLayer(self._layer)
except ValueError:
return False
count = len(path)
line_types = numpy.empty((count - 1, 1), numpy.int32)
line_widths = numpy.empty((count - 1, 1), numpy.float32)
# TODO: need to calculate actual line width based on E values
line_widths[:, 0] = 0.4
points = numpy.empty((count, 3), numpy.float32)
i = 0
for point in path:
points[i, 0] = point[0]
points[i, 1] = point[2]
points[i, 2] = -point[1]
if i > 0:
line_types[i - 1] = point[3]
if point[3] in [LayerPolygon.MoveCombingType, LayerPolygon.MoveRetractionType]:
line_widths[i - 1] = 0.2
i += 1
this_poly = LayerPolygon(self._layer_data_builder, self._extruder, line_types, points, line_widths)
this_poly.buildCache()
this_layer.polygons.append(this_poly)
return True
def _gCode0(self, position, params, path):
x, y, z, e = position
x = params.x if params.x is not None else x
y = params.y if params.y is not None else y
z_changed = False
if params.z is not None:
if z != params.z:
z_changed = True
self._previous_z = z
z = params.z
if params.e is not None:
if params.e > e[self._extruder]:
path.append([x, y, z, self._layer_type]) # extrusion
else:
path.append([x, y, z, LayerPolygon.MoveRetractionType]) # retraction
e[self._extruder] = params.e
else:
path.append([x, y, z, LayerPolygon.MoveCombingType])
if z_changed:
if not self._is_layers_in_file:
if len(path) > 1 and z > 0:
if self._createPolygon(z, path):
self._layer += 1
path.clear()
else:
path.clear()
return self._position(x, y, z, e)
def _gCode28(self, position, params, path):
return self._position(
params.x if params.x is not None else position.x,
params.y if params.y is not None else position.y,
0,
position.e)
def _gCode92(self, position, params, path):
if params.e is not None:
position.e[self._extruder] = params.e
return self._position(
params.x if params.x is not None else position.x,
params.y if params.y is not None else position.y,
params.z if params.z is not None else position.z,
position.e)
_gCode1 = _gCode0
def _processGCode(self, G, line, position, path):
func = getattr(self, "_gCode%s" % G, None)
x = self._getFloat(line, "X")
y = self._getFloat(line, "Y")
z = self._getFloat(line, "Z")
e = self._getFloat(line, "E")
if func is not None:
if (x is not None and x < 0) or (y is not None and y < 0):
self._center_is_zero = True
params = self._position(x, y, z, e)
return func(position, params, path)
return position
def _processTCode(self, T, line, position, path):
self._extruder = T
if self._extruder + 1 > len(position.e):
position.e.extend([0] * (self._extruder - len(position.e) + 1))
if not self._is_layers_in_file:
if len(path) > 1 and position[2] > 0:
if self._createPolygon(position[2], path):
self._layer += 1
path.clear()
else:
path.clear()
return position
_type_keyword = ";TYPE:"
_layer_keyword = ";LAYER:"
def read(self, file_name):
Logger.log("d", "Preparing to load %s" % file_name)
self._cancelled = False
scene_node = SceneNode()
scene_node.getBoundingBox = self._getNullBoundingBox # Manually set bounding box, because mesh doesn't have mesh data
glist = []
self._is_layers_in_file = False
Logger.log("d", "Opening file %s" % file_name)
with open(file_name, "r") as file:
file_lines = 0
current_line = 0
for line in file:
file_lines += 1
glist.append(line)
if not self._is_layers_in_file and line[:len(self._layer_keyword)] == self._layer_keyword:
self._is_layers_in_file = True
file.seek(0)
file_step = max(math.floor(file_lines / 100), 1)
self._clearValues()
self._message = Message(catalog.i18nc("@info:status", "Parsing G-code"), lifetime=0)
self._message.setProgress(0)
self._message.show()
Logger.log("d", "Parsing %s" % file_name)
current_position = self._position(0, 0, 0, [0])
current_path = []
for line in file:
if self._cancelled:
Logger.log("d", "Parsing %s cancelled" % file_name)
return None
current_line += 1
if current_line % file_step == 0:
self._message.setProgress(math.floor(current_line / file_lines * 100))
if len(line) == 0:
continue
if line.find(self._type_keyword) == 0:
type = line[len(self._type_keyword):].strip()
if type == "WALL-INNER":
self._layer_type = LayerPolygon.InsetXType
elif type == "WALL-OUTER":
self._layer_type = LayerPolygon.Inset0Type
elif type == "SKIN":
self._layer_type = LayerPolygon.SkinType
elif type == "SKIRT":
self._layer_type = LayerPolygon.SkirtType
elif type == "SUPPORT":
self._layer_type = LayerPolygon.SupportType
elif type == "FILL":
self._layer_type = LayerPolygon.InfillType
if self._is_layers_in_file and line[:len(self._layer_keyword)] == self._layer_keyword:
try:
layer_number = int(line[len(self._layer_keyword):])
self._createPolygon(current_position[2], current_path)
current_path.clear()
self._layer = layer_number
except:
pass
if line[0] == ";":
continue
G = self._getInt(line, "G")
if G is not None:
current_position = self._processGCode(G, line, current_position, current_path)
T = self._getInt(line, "T")
if T is not None:
current_position = self._processTCode(T, line, current_position, current_path)
if not self._is_layers_in_file and len(current_path) > 1 and current_position[2] > 0:
if self._createPolygon(current_position[2], current_path):
self._layer += 1
current_path.clear()
layer_mesh = self._layer_data_builder.build()
decorator = LayerDataDecorator.LayerDataDecorator()
decorator.setLayerData(layer_mesh)
scene_node.addDecorator(decorator)
gcode_list_decorator = GCodeListDecorator()
gcode_list_decorator.setGCodeList(glist)
scene_node.addDecorator(gcode_list_decorator)
Logger.log("d", "Finished parsing %s" % file_name)
self._message.hide()
if self._layer == 0:
Logger.log("w", "File %s doesn't contain any valid layers" % file_name)
settings = Application.getInstance().getGlobalContainerStack()
machine_width = settings.getProperty("machine_width", "value")
machine_depth = settings.getProperty("machine_depth", "value")
if not self._center_is_zero:
scene_node.setPosition(Vector(-machine_width / 2, 0, machine_depth / 2))
Logger.log("d", "Loaded %s" % file_name)
return scene_node

View file

@ -0,0 +1,33 @@
# Copyright (c) 2016 Aleph Objects, Inc.
# Cura is released under the terms of the AGPLv3 or higher.
from . import GCodeReader
from UM.i18n import i18nCatalog
i18n_catalog = i18nCatalog("cura")
def getMetaData():
return {
"plugin": {
"name": i18n_catalog.i18nc("@label", "G-code Reader"),
"author": "Victor Larchenko",
"version": "1.0",
"description": i18n_catalog.i18nc("@info:whatsthis", "Allows loading and displaying G-code files."),
"api": 3
},
"mesh_reader": [
{
"extension": "gcode",
"description": i18n_catalog.i18nc("@item:inlistbox", "G-code File")
},
{
"extension": "g",
"description": i18n_catalog.i18nc("@item:inlistbox", "G File")
}
]
}
def register(app):
app.addNonSliceableExtension(".gcode")
app.addNonSliceableExtension(".g")
return { "mesh_reader": GCodeReader.GCodeReader() }

View file

@ -4,9 +4,9 @@
import os
import threading
from PyQt5.QtCore import Qt, QUrl, pyqtSignal, pyqtSlot, QObject
from PyQt5.QtCore import Qt, QUrl, pyqtSignal, QObject
from PyQt5.QtQml import QQmlComponent, QQmlContext
from UM.FlameProfiler import pyqtSlot
from UM.Application import Application
from UM.PluginRegistry import PluginRegistry
from UM.Logger import Logger

View file

@ -45,10 +45,11 @@ class LayerPass(RenderPass):
tool_handle_batch = RenderBatch(self._tool_handle_shader, type = RenderBatch.RenderType.Overlay)
for node in DepthFirstIterator(self._scene.getRoot()):
if isinstance(node, ToolHandle):
tool_handle_batch.addItem(node.getWorldTransformation(), mesh = node.getSolidMesh())
elif isinstance(node, SceneNode) and node.getMeshData() and node.isVisible():
elif isinstance(node, SceneNode) and (node.getMeshData() or node.callDecoration("isBlockSlicing")) and node.isVisible():
layer_data = node.callDecoration("getLayerData")
if not layer_data:
continue

View file

@ -119,7 +119,7 @@ class LayerView(View):
continue
if not node.render(renderer):
if node.getMeshData() and node.isVisible():
if (node.getMeshData()) and node.isVisible():
renderer.queueNode(node, transparent = True, shader = self._ghost_shader)
def setLayer(self, value):

View file

@ -1,4 +1,5 @@
from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot, pyqtProperty
from PyQt5.QtCore import QObject, pyqtSignal, pyqtProperty
from UM.FlameProfiler import pyqtSlot
from UM.Application import Application
import LayerView

View file

@ -1,7 +1,8 @@
# Copyright (c) 2016 Ultimaker B.V.
# Cura is released under the terms of the AGPLv3 or higher.
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot
from PyQt5.QtCore import pyqtProperty, pyqtSignal
from UM.FlameProfiler import pyqtSlot
from cura.MachineAction import MachineAction

View file

@ -322,7 +322,7 @@ Item {
id: settingPickDialog
title: catalog.i18nc("@title:window", "Select Settings to Customize for this model")
width: screenScaleFactor * 360;
width: Screen.devicePixelRatio * 360;
property string labelFilter: ""

View file

@ -1,7 +1,7 @@
# Copyright (c) 2015 Ultimaker B.V.
# Uranium is released under the terms of the AGPLv3 or higher.
# Cura is released under the terms of the AGPLv3 or higher.
import platform
from UM.Platform import Platform
from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")
@ -18,15 +18,15 @@ def getMetaData():
}
def register(app):
if platform.system() == "Windows":
if Platform.isWindows():
from . import WindowsRemovableDrivePlugin
return { "output_device": WindowsRemovableDrivePlugin.WindowsRemovableDrivePlugin() }
elif platform.system() == "Darwin":
elif Platform.isOSX():
from . import OSXRemovableDrivePlugin
return { "output_device": OSXRemovableDrivePlugin.OSXRemovableDrivePlugin() }
elif platform.system() == "Linux":
elif Platform.isLinux():
from . import LinuxRemovableDrivePlugin
return { "output_device": LinuxRemovableDrivePlugin.LinuxRemovableDrivePlugin() }
else:
Logger.log("e", "Unsupported system %s, no removable device hotplugging support available.", platform.system())
Logger.log("e", "Unsupported system, thus no removable device hotplugging support available.")
return { }

View file

@ -87,8 +87,10 @@ class SolidView(View):
extruder_id = node.callDecoration("getActiveExtruder")
if extruder_id:
extruder_index = max(0, self._extruders_model.find("id", extruder_id))
material_color = self._extruders_model.getItem(extruder_index)["color"]
try:
material_color = self._extruders_model.getItem(extruder_index)["color"]
except KeyError:
material_color = self._extruders_model.defaultColors[0]
if extruder_index != ExtruderManager.getInstance().activeExtruderIndex:
# Shade objects that are printed with the non-active extruder 25% darker

View file

@ -1016,7 +1016,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice):
## Let the user decide if the hotends and/or material should be synced with the printer
def materialHotendChangedMessage(self, callback):
Application.getInstance().messageBox(i18n_catalog.i18nc("@window:title", "Changes on the Printer"),
Application.getInstance().messageBox(i18n_catalog.i18nc("@window:title", "Sync with your printer"),
i18n_catalog.i18nc("@label",
"Would you like to use your current printer configuration in Cura?"),
i18n_catalog.i18nc("@label",

View file

@ -313,6 +313,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
if self._serial is None:
try:
self._serial = serial.Serial(str(self._serial_port), baud_rate, timeout = 3, writeTimeout = 10000)
time.sleep(10)
except serial.SerialException:
Logger.log("d", "Could not open port %s" % self._serial_port)
continue
@ -468,7 +469,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
break # None is only returned when something went wrong. Stop listening
if time.time() > temperature_request_timeout:
if self._num_extruders > 0:
if self._num_extruders > 1:
self._temperature_requested_extruder_index = (self._temperature_requested_extruder_index + 1) % self._num_extruders
self.sendCommand("M105 T%d" % (self._temperature_requested_extruder_index))
else:
@ -524,7 +525,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
# Request the temperature on comm timeout (every 2 seconds) when we are not printing.)
if line == b"":
if self._num_extruders > 0:
if self._num_extruders > 1:
self._temperature_requested_extruder_index = (self._temperature_requested_extruder_index + 1) % self._num_extruders
self.sendCommand("M105 T%d" % self._temperature_requested_extruder_index)
else:

View file

@ -43,17 +43,20 @@ class Stk500v2(ispBase.IspBase):
self.serial.flushInput()
self.serial.flushOutput()
if self.sendMessage([0x10, 0xc8, 0x64, 0x19, 0x20, 0x00, 0x53, 0x03, 0xac, 0x53, 0x00, 0x00]) != [0x10, 0x00]:
try:
if self.sendMessage([0x10, 0xc8, 0x64, 0x19, 0x20, 0x00, 0x53, 0x03, 0xac, 0x53, 0x00, 0x00]) != [0x10, 0x00]:
raise ispBase.IspError("Failed to enter programming mode")
self.sendMessage([0x06, 0x80, 0x00, 0x00, 0x00])
if self.sendMessage([0xEE])[1] == 0x00:
self._has_checksum = True
else:
self._has_checksum = False
except ispBase.IspError:
self.close()
raise ispBase.IspError("Failed to enter programming mode")
self.sendMessage([0x06, 0x80, 0x00, 0x00, 0x00])
if self.sendMessage([0xEE])[1] == 0x00:
self._has_checksum = True
else:
self._has_checksum = False
raise
self.serial.timeout = 5
def close(self):
if self.serial is not None:
self.serial.close()

View file

@ -1,7 +1,7 @@
from cura.MachineAction import MachineAction
from cura.PrinterOutputDevice import PrinterOutputDevice
from PyQt5.QtCore import pyqtSlot
from UM.FlameProfiler import pyqtSlot
from UM.Application import Application
from UM.i18n import i18nCatalog

View file

@ -8,6 +8,7 @@ import io
from UM.Resources import Resources
from UM.VersionUpgrade import VersionUpgrade # Superclass of the plugin.
import UM.VersionUpgrade
class VersionUpgrade22to24(VersionUpgrade):
@ -19,6 +20,10 @@ class VersionUpgrade22to24(VersionUpgrade):
config = configparser.ConfigParser(interpolation = None)
config.read_string(serialised) # Read the input string as config file.
if config.get("metadata", "type") == "definition_changes":
# This is not a container stack, don't upgrade it here
return
config.set("general", "version", "3")
container_list = []
@ -44,9 +49,11 @@ class VersionUpgrade22to24(VersionUpgrade):
# Change the name of variant and insert empty_variant into the stack.
new_container_list = []
for item in container_list:
if not item: # the last item may be an empty string
continue
if item == variant_name:
new_container_list.append(config_name)
new_container_list.append("empty_variant")
new_container_list.append(config_name)
else:
new_container_list.append(item)
@ -58,7 +65,7 @@ class VersionUpgrade22to24(VersionUpgrade):
config.remove_option("general", "containers")
for index in range(len(container_list)):
config.set("containers", index, container_list[index])
config.set("containers", str(index), container_list[index])
output = io.StringIO()
config.write(output)
@ -114,6 +121,26 @@ class VersionUpgrade22to24(VersionUpgrade):
config.write(output)
return [filename], [output.getvalue()]
def upgradePreferences(self, serialised, filename):
config = configparser.ConfigParser(interpolation = None)
config.read_string(serialised)
if not config.has_section("general"):
raise UM.VersionUpgrade.FormatException("No \"general\" section.")
# Make z_seam_x and z_seam_y options visible. In a clean 2.4 they are visible by default.
if config.has_option("general", "visible_settings"):
visible_settings = config.get("general", "visible_settings")
visible_set = set(visible_settings.split(";"))
visible_set.add("z_seam_x")
visible_set.add("z_seam_y")
config.set("general", "visible_settings", ";".join(visible_set))
config.set("general", "version", value="4")
output = io.StringIO()
config.write(output)
return [filename], [output.getvalue()]
def getCfgVersion(self, serialised):
parser = configparser.ConfigParser(interpolation = None)
parser.read_string(serialised)

View file

@ -20,8 +20,10 @@ def getMetaData():
"version_upgrade": {
# From To Upgrade function
("machine_instance", 2): ("machine_stack", 3, upgrade.upgradeMachineInstance),
("extruder_train", 2): ("extruder_train", 3, upgrade.upgradeExtruderTrain)
},
("extruder_train", 2): ("extruder_train", 3, upgrade.upgradeExtruderTrain),
("preferences", 3): ("preferences", 4, upgrade.upgradePreferences)
},
"sources": {
"machine_stack": {
"get_version": upgrade.getCfgVersion,

View file

@ -1,15 +1,17 @@
# Contributed by Seva Alekseyev <sevaa@nih.gov> with National Institutes of Health, 2016
# Cura is released under the terms of the AGPLv3 or higher.
from UM.Mesh.MeshReader import MeshReader
from UM.Mesh.MeshBuilder import MeshBuilder
from math import pi, sin, cos, sqrt
import numpy
from UM.Job import Job
from UM.Logger import Logger
from UM.Math.Matrix import Matrix
from UM.Math.Vector import Vector
from UM.Mesh.MeshBuilder import MeshBuilder
from UM.Mesh.MeshReader import MeshReader
from UM.Scene.SceneNode import SceneNode
from UM.Job import Job
from math import pi, sin, cos, sqrt
import numpy
try:
import xml.etree.cElementTree as ET

View file

@ -593,6 +593,8 @@ class XmlMaterialProfile(InstanceContainer):
# Map XML file product names to internal ids
# TODO: Move this to definition's metadata
__product_id_map = {
"Ultimaker 3": "ultimaker3",
"Ultimaker 3 Extended": "ultimaker3_extended",
"Ultimaker 2": "ultimaker2",
"Ultimaker 2+": "ultimaker2_plus",
"Ultimaker 2 Go": "ultimaker2_go",