Merge branch 'master' into feature_arrange

This commit is contained in:
Jack Ha 2017-03-23 15:02:04 +01:00
commit 462f3abead
84 changed files with 2411 additions and 266 deletions

1
.gitignore vendored
View file

@ -10,6 +10,7 @@ resources/i18n/x-test
resources/firmware
resources/materials
LC_MESSAGES
.cache
# Editors and IDEs.
*kdev*

View file

@ -1,14 +1,16 @@
project(cura NONE)
cmake_minimum_required(VERSION 2.8.12)
set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/
${CMAKE_MODULE_PATH})
include(GNUInstallDirs)
set(URANIUM_SCRIPTS_DIR "${CMAKE_SOURCE_DIR}/../uranium/scripts" CACHE DIRECTORY "The location of the scripts directory of the Uranium repository")
set(URANIUM_DIR "${CMAKE_SOURCE_DIR}/../Uranium" CACHE DIRECTORY "The location of the Uranium repository")
set(URANIUM_SCRIPTS_DIR "${URANIUM_DIR}/scripts" CACHE DIRECTORY "The location of the scripts directory of the Uranium repository")
# Tests
# Note that we use exit 0 here to not mark the build as a failure on test failure
add_custom_target(tests)
add_custom_command(TARGET tests POST_BUILD COMMAND "PYTHONPATH=${CMAKE_SOURCE_DIR}/../Uranium/:${CMAKE_SOURCE_DIR}" ${PYTHON_EXECUTABLE} -m pytest -r a --junitxml=${CMAKE_BINARY_DIR}/junit.xml ${CMAKE_SOURCE_DIR} || exit 0)
include(CuraTests)
option(CURA_DEBUGMODE "Enable debug dialog and other debug features" OFF)
if(CURA_DEBUGMODE)
@ -21,6 +23,7 @@ configure_file(${CMAKE_SOURCE_DIR}/cura.desktop.in ${CMAKE_BINARY_DIR}/cura.desk
configure_file(cura/CuraVersion.py.in CuraVersion.py @ONLY)
if(NOT ${URANIUM_SCRIPTS_DIR} STREQUAL "")
list(APPEND CMAKE_MODULE_PATH ${URANIUM_DIR}/cmake)
include(UraniumTranslationTools)
# Extract Strings
add_custom_target(extract-messages ${URANIUM_SCRIPTS_DIR}/extract-messages ${CMAKE_SOURCE_DIR} cura)

40
Jenkinsfile vendored Normal file
View file

@ -0,0 +1,40 @@
parallel_nodes(['linux && cura', 'windows && cura']) {
// Prepare building
stage('Prepare') {
// Ensure we start with a clean build directory.
step([$class: 'WsCleanup'])
// Checkout whatever sources are linked to this pipeline.
checkout scm
}
// If any error occurs during building, we want to catch it and continue with the "finale" stage.
catchError {
// Building and testing should happen in a subdirectory.
dir('build') {
// Perform the "build". Since Uranium is Python code, this basically only ensures CMake is setup.
stage('Build') {
// Ensure CMake is setup. Note that since this is Python code we do not really "build" it.
def uranium_dir = get_workspace_dir("Ultimaker/Uranium/master")
cmake("..", "-DCMAKE_PREFIX_PATH=${env.CURA_ENVIRONMENT_PATH} -DCMAKE_BUILD_TYPE=Release -DURANIUM_DIR=${uranium_dir}")
}
// Try and run the unit tests. If this stage fails, we consider the build to be "unstable".
stage('Unit Test') {
try {
make('test')
} catch(e) {
currentBuild.result = "UNSTABLE"
}
}
}
}
// Perform any post-build actions like notification and publishing of unit tests.
stage('Finalize') {
// Publish the test results to Jenkins.
junit allowEmptyResults: true, testResults: 'build/junit*.xml'
notify_build_result(env.CURA_EMAIL_RECIPIENTS, '#cura-dev', ['master', '2.'])
}
}

48
cmake/CuraTests.cmake Normal file
View file

@ -0,0 +1,48 @@
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the AGPLv3 or higher.
enable_testing()
include(CMakeParseArguments)
find_package(PythonInterp 3.5.0 REQUIRED)
function(cura_add_test)
set(_single_args NAME DIRECTORY PYTHONPATH)
cmake_parse_arguments("" "" "${_single_args}" "" ${ARGN})
if(NOT _NAME)
message(FATAL_ERROR "cura_add_test requires a test name argument")
endif()
if(NOT _DIRECTORY)
message(FATAL_ERROR "cura_add_test requires a directory to test")
endif()
if(NOT _PYTHONPATH)
set(_PYTHONPATH ${_DIRECTORY})
endif()
if(WIN32)
string(REPLACE "|" "\\;" _PYTHONPATH ${_PYTHONPATH})
else()
string(REPLACE "|" ":" _PYTHONPATH ${_PYTHONPATH})
endif()
add_test(
NAME ${_NAME}
COMMAND ${PYTHON_EXECUTABLE} -m pytest --junitxml=${CMAKE_BINARY_DIR}/junit-${_NAME}.xml ${_DIRECTORY}
)
set_tests_properties(${_NAME} PROPERTIES ENVIRONMENT LANG=C)
set_tests_properties(${_NAME} PROPERTIES ENVIRONMENT "PYTHONPATH=${_PYTHONPATH}")
endfunction()
cura_add_test(NAME pytest-main DIRECTORY ${CMAKE_SOURCE_DIR}/tests PYTHONPATH "${CMAKE_SOURCE_DIR}|${URANIUM_DIR}")
file(GLOB_RECURSE _plugins plugins/*/__init__.py)
foreach(_plugin ${_plugins})
get_filename_component(_plugin_directory ${_plugin} DIRECTORY)
if(EXISTS ${_plugin_directory}/tests)
get_filename_component(_plugin_name ${_plugin_directory} NAME)
cura_add_test(NAME pytest-${_plugin_name} DIRECTORY ${_plugin_directory} PYTHONPATH "${_plugin_directory}|${CMAKE_SOURCE_DIR}|${URANIUM_DIR}")
endif()
endforeach()

View file

@ -629,11 +629,12 @@ class BuildVolume(SceneNode):
if not self._global_container_stack.getProperty("machine_center_is_zero", "value"):
prime_x = prime_x - machine_width / 2 #Offset by half machine_width and _depth to put the origin in the front-left.
prime_y = prime_x + machine_depth / 2
prime_y = prime_y + machine_depth / 2
prime_polygon = Polygon.approximatedCircle(PRIME_CLEARANCE)
prime_polygon = prime_polygon.translate(prime_x, prime_y)
prime_polygon = prime_polygon.getMinkowskiHull(Polygon.approximatedCircle(border_size))
prime_polygon = prime_polygon.translate(prime_x, prime_y)
result[extruder.getId()] = [prime_polygon]
return result

View file

@ -24,7 +24,7 @@ class ConvexHullNode(SceneNode):
self._original_parent = parent
# Color of the drawn convex hull
self._color = None
self._color = Color(*Application.getInstance().getTheme().getColor("convex_hull").getRgb())
# The y-coordinate of the convex hull mesh. Must not be 0, to prevent z-fighting.
self._mesh_height = 0.1
@ -73,8 +73,6 @@ class ConvexHullNode(SceneNode):
return True
def _onNodeDecoratorsChanged(self, node):
self._color = Color(*Application.getInstance().getTheme().getColor("convex_hull").getRgb())
convex_hull_head = self._node.callDecoration("getConvexHullHead")
if convex_hull_head:
convex_hull_head_builder = MeshBuilder()

View file

@ -4,6 +4,7 @@ from PyQt5.QtNetwork import QLocalServer
from PyQt5.QtNetwork import QLocalSocket
from UM.Qt.QtApplication import QtApplication
from UM.FileHandler.ReadFileJob import ReadFileJob
from UM.Scene.SceneNode import SceneNode
from UM.Scene.Camera import Camera
from UM.Math.Vector import Vector
@ -25,6 +26,7 @@ from UM.Settings.InstanceContainer import InstanceContainer
from UM.Settings.Validator import Validator
from UM.Message import Message
from UM.i18n import i18nCatalog
from UM.Workspace.WorkspaceReader import WorkspaceReader
from UM.Operations.AddSceneNodeOperation import AddSceneNodeOperation
from UM.Operations.RemoveSceneNodeOperation import RemoveSceneNodeOperation
@ -199,7 +201,7 @@ class CuraApplication(QtApplication):
self._message_box_callback = None
self._message_box_callback_arguments = []
self._preferred_mimetype = ""
self._i18n_catalog = i18nCatalog("cura")
self.getController().getScene().sceneChanged.connect(self.updatePlatformActivity)
@ -224,7 +226,7 @@ class CuraApplication(QtApplication):
ContainerRegistry.getInstance().addContainer(empty_material_container)
empty_quality_container = copy.deepcopy(empty_container)
empty_quality_container._id = "empty_quality"
empty_quality_container.setName("Not supported")
empty_quality_container.setName("Not Supported")
empty_quality_container.addMetaDataEntry("quality_type", "normal")
empty_quality_container.addMetaDataEntry("type", "quality")
ContainerRegistry.getInstance().addContainer(empty_quality_container)
@ -245,11 +247,14 @@ class CuraApplication(QtApplication):
Preferences.getInstance().addPreference("mesh/scale_tiny_meshes", True)
Preferences.getInstance().addPreference("cura/dialog_on_project_save", True)
Preferences.getInstance().addPreference("cura/asked_dialog_on_project_save", False)
Preferences.getInstance().addPreference("cura/choice_on_profile_override", 0)
Preferences.getInstance().addPreference("cura/choice_on_profile_override", "always_ask")
Preferences.getInstance().addPreference("cura/choice_on_open_project", "always_ask")
Preferences.getInstance().addPreference("cura/currency", "")
Preferences.getInstance().addPreference("cura/material_settings", "{}")
Preferences.getInstance().addPreference("view/invert_zoom", False)
for key in [
"dialog_load_path", # dialog_save_path is in LocalFileOutputDevicePlugin
"dialog_profile_path",
@ -314,6 +319,9 @@ class CuraApplication(QtApplication):
self.applicationShuttingDown.connect(self.saveSettings)
self.engineCreatedSignal.connect(self._onEngineCreated)
self.globalContainerStackChanged.connect(self._onGlobalContainerChanged)
self._onGlobalContainerChanged()
self._recent_files = []
files = Preferences.getInstance().getValue("cura/recent_files").split(";")
for f in files:
@ -338,10 +346,10 @@ class CuraApplication(QtApplication):
def discardOrKeepProfileChanges(self):
choice = Preferences.getInstance().getValue("cura/choice_on_profile_override")
if choice == 1:
if choice == "always_discard":
# don't show dialog and DISCARD the profile
self.discardOrKeepProfileChangesClosed("discard")
elif choice == 2:
elif choice == "always_keep":
# don't show dialog and KEEP the profile
self.discardOrKeepProfileChangesClosed("keep")
else:
@ -731,14 +739,30 @@ class CuraApplication(QtApplication):
self._camera_animation.setTarget(Selection.getSelectedObject(0).getWorldPosition())
self._camera_animation.start()
def _onGlobalContainerChanged(self):
if self._global_container_stack is not None:
machine_file_formats = [file_type.strip() for file_type in self._global_container_stack.getMetaDataEntry("file_formats").split(";")]
new_preferred_mimetype = ""
if machine_file_formats:
new_preferred_mimetype = machine_file_formats[0]
if new_preferred_mimetype != self._preferred_mimetype:
self._preferred_mimetype = new_preferred_mimetype
self.preferredOutputMimetypeChanged.emit()
requestAddPrinter = pyqtSignal()
activityChanged = pyqtSignal()
sceneBoundingBoxChanged = pyqtSignal()
preferredOutputMimetypeChanged = pyqtSignal()
@pyqtProperty(bool, notify = activityChanged)
def platformActivity(self):
return self._platform_activity
@pyqtProperty(str, notify=preferredOutputMimetypeChanged)
def preferredOutputMimetype(self):
return self._preferred_mimetype
@pyqtProperty(str, notify = sceneBoundingBoxChanged)
def getSceneBoundingBoxString(self):
return self._i18n_catalog.i18nc("@info", "%(width).1f x %(depth).1f x %(height).1f mm") % {'width' : self._scene_bounding_box.width.item(), 'depth': self._scene_bounding_box.depth.item(), 'height' : self._scene_bounding_box.height.item()}
@ -1146,7 +1170,7 @@ class CuraApplication(QtApplication):
fileLoaded = pyqtSignal(str)
def _onJobFinished(self, job):
if type(job) is not ReadMeshJob or not job.getResult():
if (not isinstance(job, ReadMeshJob) and not isinstance(job, ReadFileJob)) or not job.getResult():
return
f = QUrl.fromLocalFile(job.getFileName())
@ -1285,3 +1309,16 @@ class CuraApplication(QtApplication):
def addNonSliceableExtension(self, extension):
self._non_sliceable_extensions.append(extension)
@pyqtSlot(str, result=bool)
def checkIsValidProjectFile(self, file_url):
"""
Checks if the given file URL is a valid project file.
"""
file_path = QUrl(file_url).toLocalFile()
workspace_reader = self.getWorkspaceFileHandler().getReaderForFile(file_path)
if workspace_reader is None:
return False # non-project files won't get a reader
result = workspace_reader.preRead(file_path, show_dialog=False)
return result == WorkspaceReader.PreReadResult.accepted

View file

@ -1,10 +1,8 @@
from .LayerPolygon import LayerPolygon
from UM.Math.Vector import Vector
from UM.Mesh.MeshBuilder import MeshBuilder
import numpy
class Layer:
def __init__(self, layer_id):
self._id = layer_id
@ -81,7 +79,6 @@ class Layer:
for polygon in self._polygons:
line_count += polygon.jumpCount
# Reserve the neccesary space for the data upfront
builder.reserveFaceAndVertexCount(2 * line_count, 4 * line_count)
@ -106,13 +103,14 @@ class Layer:
# Scale all normals by the line width of the current line so we can easily offset.
normals *= (polygon.lineWidths[index_mask.ravel()] / 2)
# Create 4 points to draw each line segment, points +- normals results in 2 points each. Reshape to one point per line
# Create 4 points to draw each line segment, points +- normals results in 2 points each.
# After this we reshape to one point per line.
f_points = numpy.concatenate((points-normals, points+normals), 1).reshape((-1, 3))
# __index_pattern defines which points to use to draw the two faces for each lines egment, the following linesegment is offset by 4
f_indices = ( self.__index_pattern + numpy.arange(0, 4 * len(normals), 4, dtype=numpy.int32).reshape((-1, 1)) ).reshape((-1, 3))
f_colors = numpy.repeat(polygon.mapLineTypeToColor(line_types), 4, 0)
builder.addFacesWithColor(f_points, f_indices, f_colors)
return builder.build()

View file

@ -2,6 +2,7 @@
# Cura is released under the terms of the AGPLv3 or higher.
from UM.Mesh.MeshData import MeshData
## Class to holds the layer mesh and information about the layers.
# Immutable, use LayerDataBuilder to create one of these.
class LayerData(MeshData):

View file

@ -8,6 +8,7 @@ from .LayerData import LayerData
import numpy
## Builder class for constructing a LayerData object
class LayerDataBuilder(MeshBuilder):
def __init__(self):

View file

@ -171,6 +171,10 @@ class PlatformPhysics:
self._enabled = False
def _onToolOperationStopped(self, tool):
# Selection tool should not trigger an update.
if tool.getPluginId() == "SelectionTool":
return
if tool.getPluginId() == "TranslateTool":
for node in Selection.getAllSelectedObjects():
if node.getBoundingBox().bottom < 0:

View file

@ -5,6 +5,7 @@ from PyQt5.QtCore import QObject, pyqtSignal, pyqtProperty
from UM.FlameProfiler import pyqtSlot
from UM.Application import Application
from UM.Logger import Logger
from UM.Qt.Duration import Duration
from UM.Preferences import Preferences
from UM.Settings.ContainerRegistry import ContainerRegistry
@ -109,13 +110,21 @@ class PrintInformation(QObject):
return self._material_costs
def _onPrintDurationMessage(self, total_time, material_amounts):
if total_time != total_time: # Check for NaN. Engine can sometimes give us weird values.
Logger.log("w", "Received NaN for print duration message")
self._current_print_time.setDuration(0)
else:
self._current_print_time.setDuration(total_time)
self.currentPrintTimeChanged.emit()
self._material_amounts = material_amounts
self._calculateInformation()
def _calculateInformation(self):
if Application.getInstance().getGlobalContainerStack() is None:
return
# Material amount is sent as an amount of mm^3, so calculate length from that
r = Application.getInstance().getGlobalContainerStack().getProperty("material_diameter", "value") / 2
self._material_lengths = []
@ -177,7 +186,7 @@ class PrintInformation(QObject):
self._active_material_container = active_material_containers[0]
self._active_material_container.metaDataChanged.connect(self._onMaterialMetaDataChanged)
def _onMaterialMetaDataChanged(self):
def _onMaterialMetaDataChanged(self, *args, **kwargs):
self._calculateInformation()
@pyqtSlot(str)

7
cura/Settings/ExtruderManager.py Normal file → Executable file
View file

@ -135,6 +135,13 @@ class ExtruderManager(QObject):
return self._extruder_trains[global_container_stack.getId()][str(index)]
return None
## Get all extruder stacks
def getExtruderStacks(self):
result = []
for i in range(self.extruderCount):
result.append(self.getExtruderStack(i))
return result
## Adds all extruders of a specific machine definition to the extruder
# manager.
#

30
cura/Settings/MachineManager.py Normal file → Executable file
View file

@ -731,6 +731,9 @@ class MachineManager(QObject):
else:
self._material_incompatible_message.hide()
quality_type = None
new_quality_id = None
if old_quality:
new_quality_id = old_quality.getId()
quality_type = old_quality.getMetaDataEntry("quality_type")
if old_quality_changes:
@ -740,16 +743,20 @@ class MachineManager(QObject):
# See if the requested quality type is available in the new situation.
machine_definition = self._active_container_stack.getBottom()
quality_manager = QualityManager.getInstance()
candidate_quality = None
if quality_type:
candidate_quality = quality_manager.findQualityByQualityType(quality_type,
quality_manager.getWholeMachineDefinition(machine_definition),
[material_container])
if not candidate_quality or candidate_quality.getId() == "empty_quality":
# Fall back to a quality
new_quality = quality_manager.findQualityByQualityType(None,
quality_manager.getWholeMachineDefinition(machine_definition),
[material_container])
if new_quality:
new_quality_id = new_quality.getId()
# Fall back to a quality (which must be compatible with all other extruders)
new_qualities = quality_manager.findAllUsableQualitiesForMachineAndExtruders(
self._global_container_stack, ExtruderManager.getInstance().getExtruderStacks())
if new_qualities:
new_quality_id = new_qualities[0].getId() # Just pick the first available one
else:
Logger.log("w", "No quality profile found that matches the current machine and extruders.")
else:
if not old_quality_changes:
new_quality_id = candidate_quality.getId()
@ -806,6 +813,10 @@ class MachineManager(QObject):
Logger.log("e", "Tried to set quality to a container that is not of the right type")
return
# Check if it was at all possible to find new settings
if new_quality_settings_list is None:
return
name_changed_connect_stacks = [] # Connect these stacks to the name changed callback
for setting_info in new_quality_settings_list:
stack = setting_info["stack"]
@ -882,7 +893,12 @@ class MachineManager(QObject):
quality_changes_profiles = quality_manager.findQualityChangesByName(quality_changes_name,
global_machine_definition)
global_quality_changes = [qcp for qcp in quality_changes_profiles if qcp.getMetaDataEntry("extruder") is None][0]
global_quality_changes = [qcp for qcp in quality_changes_profiles if qcp.getMetaDataEntry("extruder") is None]
if global_quality_changes:
global_quality_changes = global_quality_changes[0]
else:
Logger.log("e", "Could not find the global quality changes container with name %s", quality_changes_name)
return None
material = global_container_stack.findContainer(type="material")
# For the global stack, find a quality which matches the quality_type in

View file

@ -7,12 +7,14 @@ from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.i18n import i18nCatalog
from UM.Settings.SettingFunction import SettingFunction
from collections import OrderedDict
import os
class UserChangesModel(ListModel):
KeyRole = Qt.UserRole + 1
LabelRole = Qt.UserRole + 2
ExtruderRole = Qt.UserRole +3
ExtruderRole = Qt.UserRole + 3
OriginalValueRole = Qt.UserRole + 4
UserValueRole = Qt.UserRole + 6
CategoryRole = Qt.UserRole + 7
@ -35,7 +37,8 @@ class UserChangesModel(ListModel):
self._update()
def _update(self):
items = []
item_dict = OrderedDict()
item_list = []
global_stack = Application.getInstance().getGlobalContainerStack()
if not global_stack:
return
@ -111,5 +114,9 @@ class UserChangesModel(ListModel):
if stack != global_stack:
item_to_add["extruder"] = stack.getName()
items.append(item_to_add)
self.setItems(items)
if category_label not in item_dict:
item_dict[category_label] = []
item_dict[category_label].append(item_to_add)
for each_item_list in item_dict.values():
item_list += each_item_list
self.setItems(item_list)

View file

@ -50,7 +50,6 @@ sys.excepthook = exceptHook
# first seems to prevent Sip from going into a state where it
# tries to create PyQt objects on a non-main thread.
import Arcus #@UnusedImport
from UM.Platform import Platform
import cura.CuraApplication
import cura.Settings.CuraContainerRegistry

15
plugins/3MFReader/ThreeMFReader.py Normal file → Executable file
View file

@ -16,6 +16,7 @@ from UM.Application import Application
from cura.Settings.ExtruderManager import ExtruderManager
from cura.QualityManager import QualityManager
from UM.Scene.SceneNode import SceneNode
from cura.SliceableObjectDecorator import SliceableObjectDecorator
MYPY = False
@ -92,7 +93,12 @@ class ThreeMFReader(MeshReader):
um_node.setMeshData(mesh_data)
for child in savitar_node.getChildren():
um_node.addChild(self._convertSavitarNodeToUMNode(child))
child_node = self._convertSavitarNodeToUMNode(child)
if child_node:
um_node.addChild(child_node)
if um_node.getMeshData() is None and len(um_node.getChildren()) == 0:
return None
settings = savitar_node.getSettings()
@ -139,6 +145,11 @@ class ThreeMFReader(MeshReader):
group_decorator = GroupDecorator()
um_node.addDecorator(group_decorator)
um_node.setSelectable(True)
if um_node.getMeshData():
# Assuming that all nodes with mesh data are printable objects
# affects (auto) slicing
sliceable_decorator = SliceableObjectDecorator()
um_node.addDecorator(sliceable_decorator)
return um_node
def read(self, file_name):
@ -152,6 +163,8 @@ class ThreeMFReader(MeshReader):
self._unit = scene_3mf.getUnit()
for node in scene_3mf.getSceneNodes():
um_node = self._convertSavitarNodeToUMNode(node)
if um_node is None:
continue
# compensate for original center position, if object(s) is/are not around its zero position
transform_matrix = Matrix()

View file

@ -47,7 +47,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
self._id_mapping[old_id] = self._container_registry.uniqueName(old_id)
return self._id_mapping[old_id]
def preRead(self, file_name):
def preRead(self, file_name, show_dialog=True, *args, **kwargs):
self._3mf_mesh_reader = Application.getInstance().getMeshFileHandler().getReaderForFile(file_name)
if self._3mf_mesh_reader and self._3mf_mesh_reader.preRead(file_name) == WorkspaceReader.PreReadResult.accepted:
pass
@ -167,6 +167,10 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
Logger.log("w", "File %s is not a valid workspace.", file_name)
return WorkspaceReader.PreReadResult.failed
# In case we use preRead() to check if a file is a valid project file, we don't want to show a dialog.
if not show_dialog:
return WorkspaceReader.PreReadResult.accepted
# Show the dialog, informing the user what is about to happen.
self._dialog.setMachineConflict(machine_conflict)
self._dialog.setQualityChangesConflict(quality_changes_conflict)

View file

@ -1,9 +1,31 @@
[2.5.0]
*Layerview double slider.
The layerview now has a nice slider with double handles where you can drag maximum layer, minimum layer and the layer range. Thansk to community member Aldo Hoeben for this feature.
*Improved speed
Weve made changing printers, profiles, materials, and print cores even faster. 3MF processing is also much faster now. Opening a 3MF file now takes one tenth of the time.
*Speedup engine Multithreading
Cura can process multiple operations at the same time during slicing. Supported by Windows and Linux operating systems only.
*Preheat the build plate (with a connected printer)
Users can now set the Ultimaker 3 to preheat the build plate, which reduces the downtime, allowing to manually speed up the printing workflow.
*Better layout for 3D layer view options
An improved layer view has been implemented for computers that support OpenGL 4.1. For OpenGL 2.0 to 4.0, we will automatically switch to the old layer view.
*Disable automatic slicing
An option to disable auto-slicing has been added for the better user experience.
*Auto-scale off by default
This change speaks for itself.
*Print cost calculation
The latest version of Cura now contains code to help users calculate the cost of their prints. To do so, users need to enter a cost per spool and an amount of materials per spool. It is also possible to set the cost per material and gain better control of the expenses. Thanks to our community member Aldo Hoeben for adding this feature.
*G-code reader
The g-code reader has been reintroduced, which means users can load g-code from file and display it in layer view. Users can also print saved g-code files with Cura, share and re-use them, as well as preview the printed object via the g-code viewer. Thanks to AlephObjects for this feature.
*Discard or Keep Changes popup
Weve changed the popup that appears when a user changes a printing profile after setting custom printing settings. It is now more informative and helpful.
*Included PauseBackendPlugin.
This enables pausing the backend and manually start the backend. Thanks to community member Aldo Hoeben for this feature.
[2.4.0]
*Project saving & opening

180
plugins/GCodeReader/GCodeReader.py Normal file → Executable file
View file

@ -2,6 +2,7 @@
# Cura is released under the terms of the AGPLv3 or higher.
from UM.Application import Application
from UM.Job import Job
from UM.Logger import Logger
from UM.Math.AxisAlignedBox import AxisAlignedBox
from UM.Math.Vector import Vector
@ -9,6 +10,7 @@ from UM.Mesh.MeshReader import MeshReader
from UM.Message import Message
from UM.Scene.SceneNode import SceneNode
from UM.i18n import i18nCatalog
from UM.Preferences import Preferences
catalog = i18nCatalog("cura")
@ -17,6 +19,7 @@ from cura import LayerDataBuilder
from cura import LayerDataDecorator
from cura.LayerPolygon import LayerPolygon
from cura.GCodeListDecorator import GCodeListDecorator
from cura.Settings.ExtruderManager import ExtruderManager
import numpy
import math
@ -32,14 +35,22 @@ class GCodeReader(MeshReader):
Application.getInstance().hideMessageSignal.connect(self._onHideMessage)
self._cancelled = False
self._message = None
self._layer_number = 0
self._extruder_number = 0
self._layer_type = LayerPolygon.Inset0Type
self._clearValues()
self._scene_node = None
self._position = namedtuple('Position', ['x', 'y', 'z', 'e'])
self._is_layers_in_file = False # Does the Gcode have the layers comment?
self._extruder_offsets = {} # Offsets for multi extruders. key is index, value is [x-offset, y-offset]
self._current_layer_thickness = 0.2 # default
Preferences.getInstance().addPreference("gcodereader/show_caution", True)
def _clearValues(self):
self._extruder = 0
self._extruder_number = 0
self._layer_type = LayerPolygon.Inset0Type
self._layer = 0
self._layer_number = 0
self._previous_z = 0
self._layer_data_builder = LayerDataBuilder.LayerDataBuilder()
self._center_is_zero = False
@ -82,18 +93,21 @@ class GCodeReader(MeshReader):
def _getNullBoundingBox():
return AxisAlignedBox(minimum=Vector(0, 0, 0), maximum=Vector(10, 10, 10))
def _createPolygon(self, current_z, path):
def _createPolygon(self, layer_thickness, path, extruder_offsets):
countvalid = 0
for point in path:
if point[3] > 0:
countvalid += 1
if countvalid >= 2:
# we know what to do now, no need to count further
continue
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)
self._layer_data_builder.addLayer(self._layer_number)
self._layer_data_builder.setLayerHeight(self._layer_number, path[0][2])
self._layer_data_builder.setLayerThickness(self._layer_number, layer_thickness)
this_layer = self._layer_data_builder.getLayer(self._layer_number)
except ValueError:
return False
count = len(path)
@ -101,22 +115,19 @@ class GCodeReader(MeshReader):
line_widths = numpy.empty((count - 1, 1), numpy.float32)
line_thicknesses = numpy.empty((count - 1, 1), numpy.float32)
# TODO: need to calculate actual line width based on E values
line_widths[:, 0] = 0.4
# TODO: need to calculate actual line heights
line_thicknesses[:, 0] = 0.2
line_widths[:, 0] = 0.35 # Just a guess
line_thicknesses[:, 0] = layer_thickness
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]
points[i, :] = [point[0] + extruder_offsets[0], point[2], -point[1] - extruder_offsets[1]]
if i > 0:
line_types[i - 1] = point[3]
if point[3] in [LayerPolygon.MoveCombingType, LayerPolygon.MoveRetractionType]:
line_widths[i - 1] = 0.2
line_widths[i - 1] = 0.1
i += 1
this_poly = LayerPolygon(self._extruder, line_types, points, line_widths, line_thicknesses)
this_poly = LayerPolygon(self._extruder_number, line_types, points, line_widths, line_thicknesses)
this_poly.buildCache()
this_layer.polygons.append(this_poly)
@ -126,30 +137,29 @@ class GCodeReader(MeshReader):
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
z = params.z if params.z is not None else position.z
if params.e is not None:
if params.e > e[self._extruder]:
if params.e > e[self._extruder_number]:
path.append([x, y, z, self._layer_type]) # extrusion
else:
path.append([x, y, z, LayerPolygon.MoveRetractionType]) # retraction
e[self._extruder] = params.e
e[self._extruder_number] = params.e
# Only when extruding we can determine the latest known "layer height" which is the difference in height between extrusions
# Also, 1.5 is a heuristic for any priming or whatsoever, we skip those.
if z > self._previous_z and (z - self._previous_z < 1.5):
self._current_layer_thickness = z - self._previous_z + 0.05 # allow a tiny overlap
self._previous_z = z
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)
# G0 and G1 should be handled exactly the same.
_gCode1 = _gCode0
## Home the head.
def _gCode28(self, position, params, path):
return self._position(
params.x if params.x is not None else position.x,
@ -157,24 +167,36 @@ class GCodeReader(MeshReader):
0,
position.e)
## Reset the current position to the values specified.
# For example: G92 X10 will set the X to 10 without any physical motion.
def _gCode92(self, position, params, path):
if params.e is not None:
position.e[self._extruder] = params.e
position.e[self._extruder_number] = 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:
s = line.upper().split(" ")
x, y, z, e = None, None, None, None
for item in s[1:]:
if len(item) <= 1:
continue
if item.startswith(";"):
continue
if item[0] == "X":
x = float(item[1:])
if item[0] == "Y":
y = float(item[1:])
if item[0] == "Z":
z = float(item[1:])
if item[0] == "E":
e = float(item[1:])
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)
@ -182,40 +204,46 @@ class GCodeReader(MeshReader):
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()
self._extruder_number = T
if self._extruder_number + 1 > len(position.e):
position.e.extend([0] * (self._extruder_number - len(position.e) + 1))
return position
_type_keyword = ";TYPE:"
_layer_keyword = ";LAYER:"
## For showing correct x, y offsets for each extruder
def _extruderOffsets(self):
result = {}
for extruder in ExtruderManager.getInstance().getExtruderStacks():
result[int(extruder.getMetaData().get("position", "0"))] = [
extruder.getProperty("machine_nozzle_offset_x", "value"),
extruder.getProperty("machine_nozzle_offset_y", "value")]
return result
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
# Override getBoundingBox function of the sceneNode, as this node should return a bounding box, but there is no
# real data to calculate it from.
scene_node.getBoundingBox = self._getNullBoundingBox
glist = []
gcode_list = []
self._is_layers_in_file = False
Logger.log("d", "Opening file %s" % file_name)
self._extruder_offsets = self._extruderOffsets() # dict with index the extruder number. can be empty
last_z = 0
with open(file_name, "r") as file:
file_lines = 0
current_line = 0
for line in file:
file_lines += 1
glist.append(line)
gcode_list.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)
@ -228,7 +256,7 @@ class GCodeReader(MeshReader):
self._message.setProgress(0)
self._message.show()
Logger.log("d", "Parsing %s" % file_name)
Logger.log("d", "Parsing %s..." % file_name)
current_position = self._position(0, 0, 0, [0])
current_path = []
@ -238,10 +266,14 @@ class GCodeReader(MeshReader):
Logger.log("d", "Parsing %s cancelled" % file_name)
return None
current_line += 1
last_z = current_position.z
if current_line % file_step == 0:
self._message.setProgress(math.floor(current_line / file_lines * 100))
Job.yieldThread()
if len(line) == 0:
continue
if line.find(self._type_keyword) == 0:
type = line[len(self._type_keyword):].strip()
if type == "WALL-INNER":
@ -256,27 +288,47 @@ class GCodeReader(MeshReader):
self._layer_type = LayerPolygon.SupportType
elif type == "FILL":
self._layer_type = LayerPolygon.InfillType
else:
Logger.log("w", "Encountered a unknown type (%s) while parsing g-code.", type)
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)
self._createPolygon(self._current_layer_thickness, current_path, self._extruder_offsets.get(self._extruder_number, [0, 0]))
current_path.clear()
self._layer = layer_number
self._layer_number = layer_number
except:
pass
if line[0] == ";":
# This line is a comment. Ignore it (except for the layer_keyword)
if line.startswith(";"):
continue
G = self._getInt(line, "G")
if G is not None:
current_position = self._processGCode(G, line, current_position, current_path)
# < 2 is a heuristic for a movement only, that should not be counted as a layer
if current_position.z > last_z and abs(current_position.z - last_z) < 2:
if self._createPolygon(self._current_layer_thickness, current_path, self._extruder_offsets.get(self._extruder_number, [0, 0])):
current_path.clear()
if not self._is_layers_in_file:
self._layer_number += 1
continue
if line.startswith("T"):
T = self._getInt(line, "T")
if T is not None:
self._createPolygon(self._current_layer_thickness, current_path, self._extruder_offsets.get(self._extruder_number, [0, 0]))
current_path.clear()
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
# "Flush" leftovers
if not self._is_layers_in_file and len(current_path) > 1:
if self._createPolygon(self._current_layer_thickness, current_path, self._extruder_offsets.get(self._extruder_number, [0, 0])):
self._layer_number += 1
current_path.clear()
material_color_map = numpy.zeros((10, 4), dtype = numpy.float32)
@ -288,13 +340,13 @@ class GCodeReader(MeshReader):
scene_node.addDecorator(decorator)
gcode_list_decorator = GCodeListDecorator()
gcode_list_decorator.setGCodeList(glist)
gcode_list_decorator.setGCodeList(gcode_list)
scene_node.addDecorator(gcode_list_decorator)
Logger.log("d", "Finished parsing %s" % file_name)
self._message.hide()
if self._layer == 0:
if self._layer_number == 0:
Logger.log("w", "File %s doesn't contain any valid layers" % file_name)
settings = Application.getInstance().getGlobalContainerStack()
@ -306,4 +358,10 @@ class GCodeReader(MeshReader):
Logger.log("d", "Loaded %s" % file_name)
if Preferences.getInstance().getValue("gcodereader/show_caution"):
caution_message = Message(catalog.i18nc(
"@info:generic",
"Make sure the g-code is suitable for your printer and printer configuration before sending the file to it. The g-code representation may not be accurate."), lifetime=0)
caution_message.show()
return scene_node

View file

@ -21,7 +21,7 @@ class ImageReader(MeshReader):
self._supported_extensions = [".jpg", ".jpeg", ".bmp", ".gif", ".png"]
self._ui = ImageReaderUI(self)
def preRead(self, file_name):
def preRead(self, file_name, *args, **kwargs):
img = QImage(file_name)
if img.isNull():

View file

@ -37,7 +37,8 @@ class RemovableDriveOutputDevice(OutputDevice):
# meshes.
# \param limit_mimetypes Should we limit the available MIME types to the
# MIME types available to the currently active machine?
def requestWrite(self, nodes, file_name = None, filter_by_machine = False, file_handler = None):
#
def requestWrite(self, nodes, file_name = None, filter_by_machine = False, file_handler = None, **kwargs):
filter_by_machine = True # This plugin is indended to be used by machine (regardless of what it was told to do)
if self._writing:
raise OutputDeviceError.DeviceBusyError()

View file

@ -101,6 +101,7 @@ class DiscoverUM3Action(MachineAction):
if "um_network_key" in meta_data:
global_container_stack.setMetaDataEntry("um_network_key", key)
# Delete old authentication data.
Logger.log("d", "Removing old authentication id %s for device %s", global_container_stack.getMetaDataEntry("network_authentication_id", None), key)
global_container_stack.removeMetaDataEntry("network_authentication_id")
global_container_stack.removeMetaDataEntry("network_authentication_key")
else:

View file

@ -601,7 +601,8 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice):
# This is ignored.
# \param filter_by_machine Whether to filter MIME types by machine. This
# is ignored.
def requestWrite(self, nodes, file_name = None, filter_by_machine = False, file_handler = None):
# \param kwargs Keyword arguments.
def requestWrite(self, nodes, file_name = None, filter_by_machine = False, file_handler = None, **kwargs):
if self._printer_state != "idle":
self._error_message = Message(
i18n_catalog.i18nc("@info:status", "Unable to start a new print job, printer is busy. Current printer status is %s.") % self._printer_state)
@ -725,7 +726,12 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice):
## Check if this machine was authenticated before.
self._authentication_id = Application.getInstance().getGlobalContainerStack().getMetaDataEntry("network_authentication_id", None)
self._authentication_key = Application.getInstance().getGlobalContainerStack().getMetaDataEntry("network_authentication_key", None)
if self._authentication_id is None and self._authentication_key is None:
Logger.log("d", "No authentication found in metadata.")
else:
Logger.log("d", "Loaded authentication id %s from the metadata entry", self._authentication_id)
self._update_timer.start()
## Stop requesting data from printer

View file

@ -443,7 +443,8 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
# This is ignored.
# \param filter_by_machine Whether to filter MIME types by machine. This
# is ignored.
def requestWrite(self, nodes, file_name = None, filter_by_machine = False, file_handler = None):
# \param kwargs Keyword arguments.
def requestWrite(self, nodes, file_name = None, filter_by_machine = False, file_handler = None, **kwargs):
container_stack = Application.getInstance().getGlobalContainerStack()
if container_stack.getProperty("machine_gcode_flavor", "value") == "UltiGCode":

View file

@ -0,0 +1,77 @@
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the AGPLv3 or higher.
import configparser #To parse the files we need to upgrade and write the new files.
import io #To serialise configparser output to a string.
from UM.VersionUpgrade import VersionUpgrade
_removed_settings = { #Settings that were removed in 2.5.
"start_layers_at_same_position"
}
## A collection of functions that convert the configuration of the user in Cura
# 2.4 to a configuration for Cura 2.5.
#
# All of these methods are essentially stateless.
class VersionUpgrade24to25(VersionUpgrade):
## Gets the version number from a CFG file in Uranium's 2.4 format.
#
# Since the format may change, this is implemented for the 2.4 format only
# and needs to be included in the version upgrade system rather than
# globally in Uranium.
#
# \param serialised The serialised form of a CFG file.
# \return The version number stored in the CFG file.
# \raises ValueError The format of the version number in the file is
# incorrect.
# \raises KeyError The format of the file is incorrect.
def getCfgVersion(self, serialised):
parser = configparser.ConfigParser(interpolation = None)
parser.read_string(serialised)
return int(parser.get("general", "version")) #Explicitly give an exception when this fails. That means that the file format is not recognised.
## Upgrades the preferences file from version 2.4 to 2.5.
#
# \param serialised The serialised form of a preferences file.
# \param filename The name of the file to upgrade.
def upgradePreferences(self, serialised, filename):
parser = configparser.ConfigParser(interpolation = None)
parser.read_string(serialised)
#Remove settings from the visible_settings.
if parser.has_section("general") and "visible_settings" in parser["general"]:
visible_settings = parser["general"]["visible_settings"].split(";")
visible_settings = filter(lambda setting: setting not in _removed_settings, visible_settings)
parser["general"]["visible_settings"] = ";".join(visible_settings)
#Change the version number in the file.
if parser.has_section("general"): #It better have!
parser["general"]["version"] = "5"
#Re-serialise the file.
output = io.StringIO()
parser.write(output)
return [filename], [output.getvalue()]
## Upgrades an instance container from version 2.4 to 2.5.
#
# \param serialised The serialised form of a quality profile.
# \param filename The name of the file to upgrade.
def upgradeInstanceContainer(self, serialised, filename):
parser = configparser.ConfigParser(interpolation = None)
parser.read_string(serialised)
#Remove settings from the [values] section.
if parser.has_section("values"):
for removed_setting in (_removed_settings & parser["values"].keys()): #Both in keys that need to be removed and in keys present in the file.
del parser["values"][removed_setting]
#Change the version number in the file.
if parser.has_section("general"):
parser["general"]["version"] = "3"
#Re-serialise the file.
output = io.StringIO()
parser.write(output)
return [filename], [output.getvalue()]

View file

@ -0,0 +1,45 @@
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the AGPLv3 or higher.
from . import VersionUpgrade24to25
from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")
upgrade = VersionUpgrade24to25.VersionUpgrade24to25()
def getMetaData():
return {
"plugin": {
"name": catalog.i18nc("@label", "Version Upgrade 2.4 to 2.5"),
"author": "Ultimaker",
"version": "1.0",
"description": catalog.i18nc("@info:whatsthis", "Upgrades configurations from Cura 2.4 to Cura 2.5."),
"api": 3
},
"version_upgrade": {
# From To Upgrade function
("preferences", 4): ("preferences", 5, upgrade.upgradePreferences),
("quality", 2): ("quality", 3, upgrade.upgradeInstanceContainer),
("variant", 2): ("variant", 3, upgrade.upgradeInstanceContainer), #We can re-use upgradeContainerStack since there is nothing specific to quality, variant or user profiles being changed.
("user", 2): ("user", 3, upgrade.upgradeInstanceContainer)
},
"sources": {
"quality": {
"get_version": upgrade.getCfgVersion,
"location": {"./quality"}
},
"preferences": {
"get_version": upgrade.getCfgVersion,
"location": {"."}
},
"user": {
"get_version": upgrade.getCfgVersion,
"location": {"./user"}
}
}
}
def register(app):
return {}
return { "version_upgrade": upgrade }

View file

@ -0,0 +1,190 @@
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the AGPLv3 or higher.
import configparser #To check whether the appropriate exceptions are raised.
import pytest #To register tests with.
import VersionUpgrade24to25 #The module we're testing.
## Creates an instance of the upgrader to test with.
@pytest.fixture
def upgrader():
return VersionUpgrade24to25.VersionUpgrade24to25()
test_cfg_version_good_data = [
{
"test_name": "Simple",
"file_data": """[general]
version = 1
""",
"version": 1
},
{
"test_name": "Other Data Around",
"file_data": """[nonsense]
life = good
[general]
version = 3
[values]
layer_height = 0.12
infill_sparse_density = 42
""",
"version": 3
},
{
"test_name": "Negative Version", #Why not?
"file_data": """[general]
version = -20
""",
"version": -20
}
]
## Tests the technique that gets the version number from CFG files.
#
# \param data The parametrised data to test with. It contains a test name
# to debug with, the serialised contents of a CFG file and the correct
# version number in that CFG file.
# \param upgrader The instance of the upgrade class to test.
@pytest.mark.parametrize("data", test_cfg_version_good_data)
def test_cfgVersionGood(data, upgrader):
version = upgrader.getCfgVersion(data["file_data"])
assert version == data["version"]
test_cfg_version_bad_data = [
{
"test_name": "Empty",
"file_data": "",
"exception": configparser.Error #Explicitly not specified further which specific error we're getting, because that depends on the implementation of configparser.
},
{
"test_name": "No General",
"file_data": """[values]
layer_height = 0.1337
""",
"exception": configparser.Error
},
{
"test_name": "No Version",
"file_data": """[general]
true = false
""",
"exception": configparser.Error
},
{
"test_name": "Not a Number",
"file_data": """[general]
version = not-a-text-version-number
""",
"exception": ValueError
}
]
## Tests whether getting a version number from bad CFG files gives an
# exception.
#
# \param data The parametrised data to test with. It contains a test name
# to debug with, the serialised contents of a CFG file and the class of
# exception it needs to throw.
# \param upgrader The instance of the upgrader to test.
@pytest.mark.parametrize("data", test_cfg_version_bad_data)
def test_cfgVersionBad(data, upgrader):
with pytest.raises(data["exception"]):
upgrader.getCfgVersion(data["file_data"])
test_upgrade_preferences_removed_settings_data = [
{
"test_name": "Removed Setting",
"file_data": """[general]
visible_settings = baby;you;know;how;I;like;to;start_layers_at_same_position
""",
},
{
"test_name": "No Removed Setting",
"file_data": """[general]
visible_settings = baby;you;now;how;I;like;to;eat;chocolate;muffins
"""
},
{
"test_name": "No Visible Settings Key",
"file_data": """[general]
cura = cool
"""
},
{
"test_name": "No General Category",
"file_data": """[foos]
foo = bar
"""
}
]
## Tests whether the settings that should be removed are removed for the 2.5
# version of preferences.
@pytest.mark.parametrize("data", test_upgrade_preferences_removed_settings_data)
def test_upgradePreferencesRemovedSettings(data, upgrader):
#Get the settings from the original file.
original_parser = configparser.ConfigParser(interpolation = None)
original_parser.read_string(data["file_data"])
settings = set()
if original_parser.has_section("general") and "visible_settings" in original_parser["general"]:
settings = set(original_parser["general"]["visible_settings"].split(";"))
#Perform the upgrade.
_, upgraded_preferences = upgrader.upgradePreferences(data["file_data"], "<string>")
upgraded_preferences = upgraded_preferences[0]
#Find whether the removed setting is removed from the file now.
settings -= VersionUpgrade24to25._removed_settings
parser = configparser.ConfigParser(interpolation = None)
parser.read_string(upgraded_preferences)
assert (parser.has_section("general") and "visible_settings" in parser["general"]) == (len(settings) > 0) #If there are settings, there must also be a preference.
if settings:
assert settings == set(parser["general"]["visible_settings"].split(";"))
test_upgrade_instance_container_removed_settings_data = [
{
"test_name": "Removed Setting",
"file_data": """[values]
layer_height = 0.1337
start_layers_at_same_position = True
"""
},
{
"test_name": "No Removed Setting",
"file_data": """[values]
oceans_number = 11
"""
},
{
"test_name": "No Values Category",
"file_data": """[general]
type = instance_container
"""
}
]
## Tests whether the settings that should be removed are removed for the 2.5
# version of instance containers.
@pytest.mark.parametrize("data", test_upgrade_instance_container_removed_settings_data)
def test_upgradeInstanceContainerRemovedSettings(data, upgrader):
#Get the settings from the original file.
original_parser = configparser.ConfigParser(interpolation = None)
original_parser.read_string(data["file_data"])
settings = set()
if original_parser.has_section("values"):
settings = set(original_parser["values"])
#Perform the upgrade.
_, upgraded_container = upgrader.upgradeInstanceContainer(data["file_data"], "<string>")
upgraded_container = upgraded_container[0]
#Find whether the forbidden setting is still in the container.
settings -= VersionUpgrade24to25._removed_settings
parser = configparser.ConfigParser(interpolation = None)
parser.read_string(upgraded_container)
assert parser.has_section("values") == (len(settings) > 0) #If there are settings, there must also be the values category.
if settings:
assert settings == set(parser["values"])

View file

@ -248,11 +248,12 @@ class XmlMaterialProfile(InstanceContainer):
root = builder.close()
_indent(root)
stream = io.StringIO()
stream = io.BytesIO()
tree = ET.ElementTree(root)
tree.write(stream, encoding="unicode", xml_declaration=True)
# this makes sure that the XML header states encoding="utf-8"
tree.write(stream, encoding="utf-8", xml_declaration=True)
return stream.getvalue()
return stream.getvalue().decode('utf-8')
# Recursively resolve loading inherited files
def _resolveInheritance(self, file_name):

17
resources/definitions/fdmprinter.def.json Normal file → Executable file
View file

@ -1087,7 +1087,7 @@
"default_value": 2,
"minimum_value": "0",
"minimum_value_warning": "infill_line_width",
"value": "0 if infill_sparse_density == 0 else (infill_line_width * 100) / infill_sparse_density * (2 if infill_pattern == 'grid' else (3 if infill_pattern == 'triangles' or infill_pattern == 'cubic' or infill_pattern == 'cubicsubdiv' else (4 if infill_pattern == 'tetrahedral' else 1)))",
"value": "0 if infill_sparse_density == 0 else (infill_line_width * 100) / infill_sparse_density * (2 if infill_pattern == 'grid' else (3 if infill_pattern == 'triangles' or infill_pattern == 'cubic' or infill_pattern == 'cubicsubdiv' else (2 if infill_pattern == 'tetrahedral' else 1)))",
"settable_per_mesh": true
}
}
@ -1318,10 +1318,10 @@
"enabled": "expand_upper_skins or expand_lower_skins",
"settable_per_mesh": true
},
"min_skin_angle_for_expansion":
"max_skin_angle_for_expansion":
{
"label": "Minimum Skin Angle for Expansion",
"description": "Top and or bottom surfaces of your object with an angle larger than this setting, won't have their top/bottom skin expanded. This avoids expanding the narrow skin areas that are created when the model surface has a near vertical slope.",
"label": "Maximum Skin Angle for Expansion",
"description": "Top and or bottom surfaces of your object with an angle larger than this setting, won't have their top/bottom skin expanded. This avoids expanding the narrow skin areas that are created when the model surface has a near vertical slope. An angle of 0° is horizontal, while an angle of 90° is vertical.",
"unit": "°",
"type": "float",
"minimum_value": "0",
@ -1340,7 +1340,7 @@
"unit": "mm",
"type": "float",
"default_value": 2.24,
"value": "top_layers * layer_height / math.tan(math.radians(min_skin_angle_for_expansion))",
"value": "top_layers * layer_height / math.tan(math.radians(max_skin_angle_for_expansion))",
"minimum_value": "0",
"enabled": "expand_upper_skins or expand_lower_skins",
"settable_per_mesh": true
@ -2562,6 +2562,7 @@
"description": "In each layer start with printing the object near the same point, so that we don't start a new layer with printing the piece which the previous layer ended with. This makes for better overhangs and small parts, but increases printing time.",
"type": "bool",
"default_value": false,
"enabled": false,
"settable_per_mesh": false,
"settable_per_extruder": false,
"settable_per_meshgroup": true
@ -2574,9 +2575,8 @@
"type": "float",
"default_value": 0.0,
"minimum_value": "0",
"enabled": "start_layers_at_same_position",
"settable_per_mesh": false,
"settable_per_extruder": false,
"settable_per_extruder": true,
"settable_per_meshgroup": true
},
"layer_start_y":
@ -2587,9 +2587,8 @@
"type": "float",
"default_value": 0.0,
"minimum_value": "0",
"enabled": "start_layers_at_same_position",
"settable_per_mesh": false,
"settable_per_extruder": false,
"settable_per_extruder": true,
"settable_per_meshgroup": true
},
"retraction_hop_enabled": {

View file

@ -0,0 +1,129 @@
{
"id": "makeit_pro_l",
"version": 2,
"name": "MAKEiT Pro-L",
"inherits": "fdmprinter",
"metadata": {
"visible": true,
"author": "NA",
"manufacturer": "NA",
"category": "Other",
"file_formats": "text/x-gcode",
"has_materials": false,
"supported_actions": [ "MachineSettingsAction", "UpgradeFirmware" ],
"machine_extruder_trains":
{
"0": "makeit_l_dual_1st",
"1": "makeit_l_dual_2nd"
}
},
"overrides": {
"machine_name": { "default_value": "MAKEiT Pro-L" },
"machine_width": {
"default_value": 305
},
"machine_height": {
"default_value": 330
},
"machine_depth": {
"default_value": 254
},
"machine_center_is_zero": {
"default_value": false
},
"machine_nozzle_size": {
"default_value": 0.4
},
"machine_nozzle_heat_up_speed": {
"default_value": 2
},
"machine_nozzle_cool_down_speed": {
"default_value": 2
},
"machine_head_with_fans_polygon":
{
"default_value": [
[ -305, 28 ],
[ -305, -28 ],
[ 305, 28 ],
[ 305, -28 ]
]
},
"gantry_height": {
"default_value": 330
},
"machine_use_extruder_offset_to_offset_coords": {
"default_value": true
},
"machine_gcode_flavor": {
"default_value": "RepRap (Marlin/Sprinter)"
},
"machine_start_gcode": {
"default_value": "G21 ;metric values\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nG92 E0 ;zero the extruded length\nG28 ;home\nG1 F200 E30 ;extrude 30 mm of feed stock\nG92 E0 ;zero the extruded length\nG1 E-5 ;retract 5 mm\nG28 SC ;Do homeing, clean nozzles and let printer to know that printing started\nG92 X-6 ;Sets Curas checker board to match printers heated bed coordinates\nG1 F{speed_travel}\nM117 Printing..."
},
"machine_end_gcode": {
"default_value": "M104 T0 S0 ;1st extruder heater off\nM104 T1 S0 ;2nd extruder heater off\nM140 S0 ;heated bed heater off (if you have it)\nG91 ;relative positioning\nG1 E-5 F9000 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\nG1 Z+5 X+20 Y+20 F9000 ;move Z up a bit\nM117 MAKEiT Pro@Done\nG28 X0 Y0 ;move X/Y to min endstops, so the head is out of the way\nM84 ;steppers off\nG90 ;absolute positioning\nM81"
},
"machine_extruder_count": {
"default_value": 2
},
"print_sequence": {
"enabled": true
},
"prime_tower_position_x": {
"default_value": 185
},
"prime_tower_position_y": {
"default_value": 160
},
"material_diameter": {
"default_value": 1.75
},
"layer_height": {
"default_value": 0.2
},
"retraction_speed": {
"default_value": 180
},
"infill_sparse_density": {
"default_value": 20
},
"retraction_amount": {
"default_value": 6
},
"retraction_min_travel": {
"default_value": 1.5
},
"speed_travel": {
"default_value": 150
},
"speed_print": {
"default_value": 60
},
"wall_thickness": {
"default_value": 1.2
},
"bottom_thickness": {
"default_value": 0.2
},
"speed_layer_0": {
"default_value": 20
},
"speed_print_layer_0": {
"default_value": 20
},
"cool_min_layer_time_fan_speed_max": {
"default_value": 5
},
"adhesion_type": {
"default_value": "skirt"
},
"machine_heated_bed": {
"default_value": true
},
"machine_heat_zone_length": {
"default_value": 20
}
}
}

View file

@ -0,0 +1,126 @@
{
"id": "makeit_pro_m",
"version": 2,
"name": "MAKEiT Pro-M",
"inherits": "fdmprinter",
"metadata": {
"visible": true,
"author": "NA",
"manufacturer": "NA",
"category": "Other",
"file_formats": "text/x-gcode",
"has_materials": false,
"supported_actions": [ "MachineSettingsAction", "UpgradeFirmware" ],
"machine_extruder_trains":
{
"0": "makeit_dual_1st",
"1": "makeit_dual_2nd"
}
},
"overrides": {
"machine_name": { "default_value": "MAKEiT Pro-M" },
"machine_width": {
"default_value": 200
},
"machine_height": {
"default_value": 200
},
"machine_depth": {
"default_value": 240
},
"machine_center_is_zero": {
"default_value": false
},
"machine_nozzle_size": {
"default_value": 0.4
},
"machine_nozzle_heat_up_speed": {
"default_value": 2
},
"machine_nozzle_cool_down_speed": {
"default_value": 2
},
"machine_head_with_fans_polygon":
{
"default_value": [
[ -200, 240 ],
[ -200, -32 ],
[ 200, 240 ],
[ 200, -32 ]
]
},
"gantry_height": {
"default_value": 200
},
"machine_use_extruder_offset_to_offset_coords": {
"default_value": true
},
"machine_gcode_flavor": {
"default_value": "RepRap (Marlin/Sprinter)"
},
"machine_start_gcode": {
"default_value": "G21 ;metric values\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nG92 E0 ;zero the extruded length\nG28 ;home\nG1 F200 E30 ;extrude 30 mm of feed stock\nG92 E0 ;zero the extruded length\nG1 E-5 ;retract 5 mm\nG28 SC ;Do homeing, clean nozzles and let printer to know that printing started\nG92 X-6 ;Sets Curas checker board to match printers heated bed coordinates\nG1 F{speed_travel}\nM117 Printing..."
},
"machine_end_gcode": {
"default_value": "M104 T0 S0 ;1st extruder heater off\nM104 T1 S0 ;2nd extruder heater off\nM140 S0 ;heated bed heater off (if you have it)\nG91 ;relative positioning\nG1 E-5 F9000 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\nG1 Z+5 X+20 Y+20 F9000 ;move Z up a bit\nM117 MAKEiT Pro@Done\nG28 X0 Y0 ;move X/Y to min endstops, so the head is out of the way\nM84 ;steppers off\nG90 ;absolute positioning\nM81"
},
"machine_extruder_count": {
"default_value": 2
},
"print_sequence": {
"enabled": false
},
"prime_tower_position_x": {
"default_value": 185
},
"prime_tower_position_y": {
"default_value": 160
},
"material_diameter": {
"default_value": 1.75
},
"layer_height": {
"default_value": 0.2
},
"retraction_speed": {
"default_value": 180
},
"infill_sparse_density": {
"default_value": 20
},
"retraction_amount": {
"default_value": 6
},
"retraction_min_travel": {
"default_value": 1.5
},
"speed_travel": {
"default_value": 150
},
"speed_print": {
"default_value": 60
},
"wall_thickness": {
"default_value": 1.2
},
"bottom_thickness": {
"default_value": 0.2
},
"speed_layer_0": {
"default_value": 20
},
"speed_print_layer_0": {
"default_value": 20
},
"cool_min_layer_time_fan_speed_max": {
"default_value": 5
},
"adhesion_type": {
"default_value": "skirt"
},
"machine_heated_bed": {
"default_value": true
}
}
}

View file

@ -58,7 +58,7 @@
"value": "100"
},
"machine_end_gcode": {
"default_value": ";End GCode\nM104 S0 ;extruder heater off\nM140 S0 ;heated bed heater off (if you have it)\nG91 ;relative positioning\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\nG1 Z+0.5 E-5 X-20 Y-20 F{speed_travel} ;move Z up a bit and retract filament even more\nG28 X0 Y0 ;move X/Y to min endstops, so the head is out of the way\nM84 ;steppers off\nG90 ;absolute positioning"
"default_value": ";End GCode\nG91 ;relative positioning\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\nG1 E-5 X-20 Y-20 ;retract filament even more\nG28 X0 Y0 ;move X/Y to min endstops, so the head is out of the way\nG0 Z{machine_height} ;move the platform all the way down\nM104 S0 ;extruder heater off\nM140 S0 ;heated bed heater off (if you have it)\nM84 ;steppers off\nG90 ;absolute positioning\nM117 Done"
},
"machine_gcode_flavor": {
"default_value": "RepRap (Marlin/Sprinter)"
@ -70,7 +70,7 @@
"default_value": "Renkforce RF100"
},
"machine_start_gcode": {
"default_value": ";Sliced at: {day} {date} {time}\nG21 ;metric values\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nM107 ;start with the fan off\nG28 X0 Y0 ;move X/Y to min endstops\nG28 Z0 ;move Z to min endstops\nG1 Z15.0 F{speed_travel} ;move the platform down 15mm\nG92 E0 ;zero the extruded length\nG1 F200 E3 ;extrude 3mm of feed stock\nG92 E0 ;zero the extruded length again\nG1 F{speed_travel}\nM117 Printing..."
"default_value": ";Start GCode\nG21 ;metric values\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nM107 ;start with the fan off\nG28 X0 Y0 ;move X/Y to min endstops\nG28 Z0 ;move Z to min endstops\nG1 Z15.0 ;move the platform down 15mm\nG92 E0 ;zero the extruded length\nG1 F200 E3 ;extrude 3mm of feed stock\nG92 E0 ;zero the extruded length again\n;Put printing message on LCD screen\nM117 Printing..."
},
"machine_width": {
"value": "100"

View file

@ -17,7 +17,7 @@
"has_machine_materials": true,
"has_variant_materials": true,
"has_variants": true,
"preferred_variant": "*aa*",
"preferred_variant": "*aa04*",
"preferred_quality": "*Normal*",
"variants_name": "Print core",
"machine_extruder_trains":

View file

@ -18,7 +18,7 @@
"has_variant_materials": true,
"has_materials": true,
"has_variants": true,
"preferred_variant": "*aa*",
"preferred_variant": "*aa04*",
"variants_name": "Print core",
"machine_extruder_trains":
{

View file

@ -0,0 +1,26 @@
{
"id": "makeit_dual_1st",
"version": 2,
"name": "1st Extruder",
"inherits": "fdmextruder",
"metadata": {
"machine": "makeit_pro_m",
"position": "0"
},
"overrides": {
"extruder_nr": {
"default_value": 0,
"maximum_value": "1"
},
"machine_nozzle_offset_x": { "default_value": 0.0 },
"machine_nozzle_offset_y": { "default_value": 0.0 },
"machine_extruder_start_pos_abs": { "default_value": true },
"machine_extruder_start_pos_x": { "value": "prime_tower_position_x" },
"machine_extruder_start_pos_y": { "value": "prime_tower_position_y" },
"machine_extruder_end_pos_abs": { "default_value": true },
"machine_extruder_end_pos_x": { "value": "prime_tower_position_x" },
"machine_extruder_end_pos_y": { "value": "prime_tower_position_y" }
}
}

View file

@ -0,0 +1,26 @@
{
"id": "makeit_dual_2nd",
"version": 2,
"name": "2nd Extruder",
"inherits": "fdmextruder",
"metadata": {
"machine": "makeit_pro_m",
"position": "1"
},
"overrides": {
"extruder_nr": {
"default_value": 1,
"maximum_value": "1"
},
"machine_nozzle_offset_x": { "default_value": 0.0 },
"machine_nozzle_offset_y": { "default_value": 0.0 },
"machine_extruder_start_pos_abs": { "default_value": true },
"machine_extruder_start_pos_x": { "value": "prime_tower_position_x" },
"machine_extruder_start_pos_y": { "value": "prime_tower_position_y" },
"machine_extruder_end_pos_abs": { "default_value": true },
"machine_extruder_end_pos_x": { "value": "prime_tower_position_x" },
"machine_extruder_end_pos_y": { "value": "prime_tower_position_y" }
}
}

View file

@ -0,0 +1,26 @@
{
"id": "makeit_l_dual_1st",
"version": 2,
"name": "1st Extruder",
"inherits": "fdmextruder",
"metadata": {
"machine": "makeit_pro_l",
"position": "0"
},
"overrides": {
"extruder_nr": {
"default_value": 0,
"maximum_value": "1"
},
"machine_nozzle_offset_x": { "default_value": 0.0 },
"machine_nozzle_offset_y": { "default_value": 0.0 },
"machine_extruder_start_pos_abs": { "default_value": true },
"machine_extruder_start_pos_x": { "value": "prime_tower_position_x" },
"machine_extruder_start_pos_y": { "value": "prime_tower_position_y" },
"machine_extruder_end_pos_abs": { "default_value": true },
"machine_extruder_end_pos_x": { "value": "prime_tower_position_x" },
"machine_extruder_end_pos_y": { "value": "prime_tower_position_y" }
}
}

View file

@ -0,0 +1,26 @@
{
"id": "makeit_l_dual_2nd",
"version": 2,
"name": "2nd Extruder",
"inherits": "fdmextruder",
"metadata": {
"machine": "makeit_pro_l",
"position": "1"
},
"overrides": {
"extruder_nr": {
"default_value": 1,
"maximum_value": "1"
},
"machine_nozzle_offset_x": { "default_value": 0.0 },
"machine_nozzle_offset_y": { "default_value": 0.0 },
"machine_extruder_start_pos_abs": { "default_value": true },
"machine_extruder_start_pos_x": { "value": "prime_tower_position_x" },
"machine_extruder_start_pos_y": { "value": "prime_tower_position_y" },
"machine_extruder_end_pos_abs": { "default_value": true },
"machine_extruder_end_pos_x": { "value": "prime_tower_position_x" },
"machine_extruder_end_pos_y": { "value": "prime_tower_position_y" }
}
}

View file

@ -10,8 +10,8 @@ import Cura 1.0 as Cura
Item
{
property alias newProject: newProjectAction;
property alias open: openAction;
property alias loadWorkspace: loadWorkspaceAction;
property alias quit: quitAction;
property alias undo: undoAction;
@ -283,15 +283,16 @@ Item
Action
{
id: openAction;
text: catalog.i18nc("@action:inmenu menubar:file","&Open File...");
text: catalog.i18nc("@action:inmenu menubar:file","&Open File(s)...");
iconName: "document-open";
shortcut: StandardKey.Open;
}
Action
{
id: loadWorkspaceAction
text: catalog.i18nc("@action:inmenu menubar:file","&Open Project...");
id: newProjectAction
text: catalog.i18nc("@action:inmenu menubar:file","&New Project...");
shortcut: StandardKey.New
}
Action

View file

@ -0,0 +1,125 @@
// Copyright (c) 2015 Ultimaker B.V.
// Cura is released under the terms of the AGPLv3 or higher.
import QtQuick 2.2
import QtQuick.Controls 1.1
import QtQuick.Controls.Styles 1.1
import QtQuick.Layouts 1.1
import QtQuick.Dialogs 1.1
import UM 1.3 as UM
import Cura 1.0 as Cura
UM.Dialog
{
// This dialog asks the user whether he/she wants to open a project file as a project or import models.
id: base
title: catalog.i18nc("@title:window", "Open project file")
width: 420
height: 140
maximumHeight: height
maximumWidth: width
minimumHeight: height
minimumWidth: width
modality: UM.Application.platform == "linux" ? Qt.NonModal : Qt.WindowModal;
property var fileUrl
function loadProjectFile(projectFile)
{
UM.WorkspaceFileHandler.readLocalFile(projectFile);
var meshName = backgroundItem.getMeshName(projectFile.toString());
backgroundItem.hasMesh(decodeURIComponent(meshName));
}
function loadModelFiles(fileUrls)
{
for (var i in fileUrls)
Printer.readLocalFile(fileUrls[i]);
var meshName = backgroundItem.getMeshName(fileUrls[0].toString());
backgroundItem.hasMesh(decodeURIComponent(meshName));
}
onVisibleChanged:
{
if (visible)
{
var rememberMyChoice = UM.Preferences.getValue("cura/choice_on_open_project") != "always_ask";
rememberChoiceCheckBox.checked = rememberMyChoice;
}
}
Column
{
anchors.fill: parent
anchors.margins: UM.Theme.getSize("default_margin").width
anchors.left: parent.left
anchors.right: parent.right
spacing: UM.Theme.getSize("default_margin").width
Label
{
text: catalog.i18nc("@text:window", "This is a Cura project file. Would you like to open it as a project\nor import the models from it?")
anchors.margins: UM.Theme.getSize("default_margin").width
wrapMode: Text.WordWrap
}
CheckBox
{
id: rememberChoiceCheckBox
text: catalog.i18nc("@text:window", "Remember my choice")
anchors.margins: UM.Theme.getSize("default_margin").width
checked: UM.Preferences.getValue("cura/choice_on_open_project") != "always_ask"
}
// Buttons
Item
{
anchors.right: parent.right
anchors.left: parent.left
height: childrenRect.height
Button
{
id: openAsProjectButton
text: catalog.i18nc("@action:button", "Open as project");
anchors.right: importModelsButton.left
anchors.rightMargin: UM.Theme.getSize("default_margin").width
isDefault: true
onClicked:
{
// update preference
if (rememberChoiceCheckBox.checked)
UM.Preferences.setValue("cura/choice_on_open_project", "open_as_project");
// load this file as project
base.hide();
loadProjectFile(base.fileUrl);
}
}
Button
{
id: importModelsButton
text: catalog.i18nc("@action:button", "Import models");
anchors.right: parent.right
onClicked:
{
// update preference
if (rememberChoiceCheckBox.checked)
UM.Preferences.setValue("cura/choice_on_open_project", "open_as_model");
// load models from this project file
base.hide();
loadModelFiles([base.fileUrl]);
}
}
}
}
}

View file

@ -66,6 +66,10 @@ UM.MainWindow
{
id: fileMenu
title: catalog.i18nc("@title:menu menubar:toplevel","&File");
MenuItem
{
action: Cura.Actions.newProject;
}
MenuItem
{
@ -74,11 +78,6 @@ UM.MainWindow
RecentFilesMenu { }
MenuItem
{
action: Cura.Actions.loadWorkspace
}
MenuSeparator { }
MenuItem
@ -86,28 +85,20 @@ UM.MainWindow
text: catalog.i18nc("@action:inmenu menubar:file", "&Save Selection to File");
enabled: UM.Selection.hasSelection;
iconName: "document-save-as";
onTriggered: UM.OutputDeviceManager.requestWriteSelectionToDevice("local_file", PrintInformation.jobName, { "filter_by_machine": false });
onTriggered: UM.OutputDeviceManager.requestWriteSelectionToDevice("local_file", PrintInformation.jobName, { "filter_by_machine": false, "preferred_mimetype": "application/vnd.ms-package.3dmanufacturing-3dmodel+xml"});
}
Menu
{
id: saveAllMenu
title: catalog.i18nc("@title:menu menubar:file","Save &All")
iconName: "document-save-all";
enabled: devicesModel.rowCount() > 0 && UM.Backend.progress > 0.99;
Instantiator
{
model: UM.OutputDevicesModel { id: devicesModel; }
MenuItem
{
text: model.description;
onTriggered: UM.OutputDeviceManager.requestWriteToDevice(model.id, PrintInformation.jobName, { "filter_by_machine": false });
}
onObjectAdded: saveAllMenu.insertItem(index, object)
onObjectRemoved: saveAllMenu.removeItem(object)
id: saveAsMenu
text: catalog.i18nc("@title:menu menubar:file", "Save &As...")
onTriggered:
{
var localDeviceId = "local_file";
UM.OutputDeviceManager.requestWriteToDevice(localDeviceId, PrintInformation.jobName, { "filter_by_machine": false, "preferred_mimetype": "application/vnd.ms-package.3dmanufacturing-3dmodel+xml"});
}
}
MenuItem
{
id: saveWorkspaceMenu
@ -534,6 +525,33 @@ UM.MainWindow
onTriggered: preferences.visible = true
}
MessageDialog
{
id: newProjectDialog
modality: Qt.ApplicationModal
title: catalog.i18nc("@title:window", "New project")
text: catalog.i18nc("@info:question", "Are you sure you want to start a new project? This will clear the build plate and any unsaved settings.")
standardButtons: StandardButton.Yes | StandardButton.No
icon: StandardIcon.Question
onYes:
{
Printer.deleteAll();
Cura.Actions.resetProfile.trigger();
}
}
Connections
{
target: Cura.Actions.newProject
onTriggered:
{
if(Printer.platformActivity || Cura.MachineManager.hasUserSettings)
{
newProjectDialog.visible = true
}
}
}
Connections
{
target: Cura.Actions.addProfile
@ -721,27 +739,60 @@ UM.MainWindow
id: openDialog;
//: File open dialog title
title: catalog.i18nc("@title:window","Open file")
title: catalog.i18nc("@title:window","Open file(s)")
modality: UM.Application.platform == "linux" ? Qt.NonModal : Qt.WindowModal;
selectMultiple: true
nameFilters: UM.MeshFileHandler.supportedReadFileTypes;
folder: CuraApplication.getDefaultPath("dialog_load_path")
onAccepted:
{
//Because several implementations of the file dialog only update the folder
//when it is explicitly set.
// Because several implementations of the file dialog only update the folder
// when it is explicitly set.
var f = folder;
folder = f;
CuraApplication.setDefaultPath("dialog_load_path", folder);
for(var i in fileUrls)
// look for valid project files
var projectFileUrlList = [];
for (var i in fileUrls)
{
Printer.readLocalFile(fileUrls[i])
if (CuraApplication.checkIsValidProjectFile(fileUrls[i]))
projectFileUrlList.push(fileUrls[i]);
}
var meshName = backgroundItem.getMeshName(fileUrls[0].toString())
backgroundItem.hasMesh(decodeURIComponent(meshName))
// we only allow opening one project file
var selectedMultipleFiles = fileUrls.length > 1;
var hasProjectFile = projectFileUrlList.length > 0;
var selectedMultipleWithProjectFile = hasProjectFile && selectedMultipleFiles;
if (selectedMultipleWithProjectFile)
{
openFilesIncludingProjectsDialog.fileUrls = fileUrls;
openFilesIncludingProjectsDialog.show();
return;
}
if (hasProjectFile)
{
var projectFile = projectFileUrlList[0];
// check preference
var choice = UM.Preferences.getValue("cura/choice_on_open_project");
if (choice == "open_as_project")
openFilesIncludingProjectsDialog.loadProjectFile(projectFile);
else if (choice == "open_as_model")
openFilesIncludingProjectsDialog.loadModelFiles([projectFile]);
else // always ask
{
// ask whether to open as project or as models
askOpenAsProjectOrModelsDialog.fileUrl = projectFile;
askOpenAsProjectOrModelsDialog.show();
}
}
else
{
openFilesIncludingProjectsDialog.loadModelFiles(fileUrls);
}
}
}
@ -751,38 +802,14 @@ UM.MainWindow
onTriggered: openDialog.open()
}
FileDialog
OpenFilesIncludingProjectsDialog
{
id: openWorkspaceDialog;
//: File open dialog title
title: catalog.i18nc("@title:window","Open workspace")
modality: UM.Application.platform == "linux" ? Qt.NonModal : Qt.WindowModal;
selectMultiple: false
nameFilters: UM.WorkspaceFileHandler.supportedReadFileTypes;
folder: CuraApplication.getDefaultPath("dialog_load_path")
onAccepted:
{
//Because several implementations of the file dialog only update the folder
//when it is explicitly set.
var f = folder;
folder = f;
CuraApplication.setDefaultPath("dialog_load_path", folder);
for(var i in fileUrls)
{
UM.WorkspaceFileHandler.readLocalFile(fileUrls[i])
}
var meshName = backgroundItem.getMeshName(fileUrls[0].toString())
backgroundItem.hasMesh(decodeURIComponent(meshName))
}
id: openFilesIncludingProjectsDialog
}
Connections
AskOpenAsProjectOrModelsDialog
{
target: Cura.Actions.loadWorkspace
onTriggered: openWorkspaceDialog.open()
id: askOpenAsProjectOrModelsDialog
}
EngineLog
@ -882,7 +909,6 @@ UM.MainWindow
{
discardOrKeepProfileChangesDialog.show()
}
}
Connections
@ -932,4 +958,3 @@ UM.MainWindow
}
}
}

View file

@ -21,9 +21,18 @@ UM.Dialog
if(visible)
{
changesModel.forceUpdate()
}
discardOrKeepProfileChangesDropDownButton.currentIndex = UM.Preferences.getValue("cura/choice_on_profile_override")
discardOrKeepProfileChangesDropDownButton.currentIndex = 0;
for (var i = 0; i < discardOrKeepProfileChangesDropDownButton.model.count; ++i)
{
var code = discardOrKeepProfileChangesDropDownButton.model.get(i).code;
if (code == UM.Preferences.getValue("cura/choice_on_profile_override"))
{
discardOrKeepProfileChangesDropDownButton.currentIndex = i;
break;
}
}
}
}
Column
@ -47,7 +56,7 @@ UM.Dialog
Label
{
text: "You have customized some profile settings.\nWould you like to keep or discard those settings?"
text: catalog.i18nc("@text:window", "You have customized some profile settings.\nWould you like to keep or discard those settings?")
anchors.margins: UM.Theme.getSize("default_margin").width
font: UM.Theme.getFont("default")
wrapMode: Text.WordWrap
@ -59,7 +68,7 @@ UM.Dialog
anchors.margins: UM.Theme.getSize("default_margin").width
anchors.left: parent.left
anchors.right: parent.right
height: base.height - 200
height: base.height - 150
id: tableView
Component
{
@ -100,11 +109,10 @@ UM.Dialog
delegate: labelDelegate
width: tableView.width * 0.4
}
TableViewColumn
{
role: "original_value"
title: "Default"
title: catalog.i18nc("@title:column", "Default")
width: tableView.width * 0.3
delegate: defaultDelegate
}
@ -134,30 +142,35 @@ UM.Dialog
ComboBox
{
id: discardOrKeepProfileChangesDropDownButton
model: [
catalog.i18nc("@option:discardOrKeep", "Always ask me this"),
catalog.i18nc("@option:discardOrKeep", "Discard and never ask again"),
catalog.i18nc("@option:discardOrKeep", "Keep and never ask again")
]
width: 300
currentIndex: UM.Preferences.getValue("cura/choice_on_profile_override")
model: ListModel
{
id: discardOrKeepProfileListModel
Component.onCompleted: {
append({ text: catalog.i18nc("@option:discardOrKeep", "Always ask me this"), code: "always_ask" })
append({ text: catalog.i18nc("@option:discardOrKeep", "Discard and never ask again"), code: "always_discard" })
append({ text: catalog.i18nc("@option:discardOrKeep", "Keep and never ask again"), code: "always_keep" })
}
}
onCurrentIndexChanged:
{
UM.Preferences.setValue("cura/choice_on_profile_override", currentIndex)
if (currentIndex == 1) {
// 1 == "Discard and never ask again", so only enable the "Discard" button
discardButton.enabled = true
keepButton.enabled = false
var code = model.get(currentIndex).code;
UM.Preferences.setValue("cura/choice_on_profile_override", code);
if (code == "always_keep") {
keepButton.enabled = true;
discardButton.enabled = false;
}
else if (currentIndex == 2) {
// 2 == "Keep and never ask again", so only enable the "Keep" button
keepButton.enabled = true
discardButton.enabled = false
else if (code == "always_discard") {
keepButton.enabled = false;
discardButton.enabled = true;
}
else {
// 0 == "Always ask me this", so show both
keepButton.enabled = true
discardButton.enabled = true
keepButton.enabled = true;
discardButton.enabled = true;
}
}
}
@ -168,7 +181,7 @@ UM.Dialog
anchors.right: parent.right
anchors.left: parent.left
anchors.margins: UM.Theme.getSize("default_margin").width
height:childrenRect.height
height: childrenRect.height
Button
{

View file

@ -4,7 +4,7 @@
import QtQuick 2.2
import QtQuick.Controls 1.1
import UM 1.2 as UM
import UM 1.3 as UM
import Cura 1.0 as Cura
Menu
@ -25,7 +25,37 @@ Menu
var path = modelData.toString()
return (index + 1) + ". " + path.slice(path.lastIndexOf("/") + 1);
}
onTriggered: {
onTriggered:
{
var toShowDialog = false;
var toOpenAsProject = false;
var toOpenAsModel = false;
if (CuraApplication.checkIsValidProjectFile(modelData)) {
// check preference
var choice = UM.Preferences.getValue("cura/choice_on_open_project");
if (choice == "open_as_project")
toOpenAsProject = true;
else if (choice == "open_as_model")
toOpenAsModel = true;
else
toShowDialog = true;
}
else {
toOpenAsModel = true;
}
if (toShowDialog) {
askOpenAsProjectOrModelsDialog.fileUrl = modelData;
askOpenAsProjectOrModelsDialog.show();
return;
}
// open file in the prefered way
if (toOpenAsProject)
UM.WorkspaceFileHandler.readLocalFile(modelData);
else if (toOpenAsModel)
Printer.readLocalFile(modelData);
var meshName = backgroundItem.getMeshName(modelData.toString())
backgroundItem.hasMesh(decodeURIComponent(meshName))
@ -34,4 +64,9 @@ Menu
onObjectAdded: menu.insertItem(index, object)
onObjectRemoved: menu.removeItem(object)
}
Cura.AskOpenAsProjectOrModelsDialog
{
id: askOpenAsProjectOrModelsDialog
}
}

View file

@ -0,0 +1,107 @@
// Copyright (c) 2015 Ultimaker B.V.
// Cura is released under the terms of the AGPLv3 or higher.
import QtQuick 2.6
import QtQuick.Controls 1.1
import QtQuick.Controls.Styles 1.1
import QtQuick.Layouts 1.1
import QtQuick.Dialogs 1.1
import UM 1.3 as UM
import Cura 1.0 as Cura
UM.Dialog
{
// This dialog asks the user whether he/she wants to open the project file we have detected or the model files.
id: base
title: catalog.i18nc("@title:window", "Open file(s)")
width: 420
height: 170
maximumHeight: height
maximumWidth: width
minimumHeight: height
minimumWidth: width
modality: UM.Application.platform == "linux" ? Qt.NonModal : Qt.WindowModal;
property var fileUrls: []
property int spacerHeight: 10
function loadProjectFile(projectFile)
{
UM.WorkspaceFileHandler.readLocalFile(projectFile);
var meshName = backgroundItem.getMeshName(projectFile.toString());
backgroundItem.hasMesh(decodeURIComponent(meshName));
}
function loadModelFiles(fileUrls)
{
for (var i in fileUrls)
Printer.readLocalFile(fileUrls[i]);
var meshName = backgroundItem.getMeshName(fileUrls[0].toString());
backgroundItem.hasMesh(decodeURIComponent(meshName));
}
Column
{
anchors.fill: parent
anchors.margins: UM.Theme.getSize("default_margin").width
anchors.left: parent.left
anchors.right: parent.right
spacing: UM.Theme.getSize("default_margin").width
Text
{
text: catalog.i18nc("@text:window", "We have found multiple project file(s) within the files you have\nselected. You can open only one project file at a time. We\nsuggest to only import models from those files. Would you like\nto proceed?")
anchors.margins: UM.Theme.getSize("default_margin").width
font: UM.Theme.getFont("default")
wrapMode: Text.WordWrap
}
Item // Spacer
{
height: base.spacerHeight
width: height
}
// Buttons
Item
{
anchors.right: parent.right
anchors.left: parent.left
height: childrenRect.height
Button
{
id: cancelButton
text: catalog.i18nc("@action:button", "Cancel");
anchors.right: importAllAsModelsButton.left
anchors.rightMargin: UM.Theme.getSize("default_margin").width
onClicked:
{
// cancel
base.hide();
}
}
Button
{
id: importAllAsModelsButton
text: catalog.i18nc("@action:button", "Import all as models");
anchors.right: parent.right
isDefault: true
onClicked:
{
// load models from all selected file
loadModelFiles(base.fileUrls);
base.hide();
}
}
}
}
}

View file

@ -25,6 +25,30 @@ UM.PreferencesPage
}
}
function setDefaultDiscardOrKeepProfile(code)
{
for (var i = 0; i < choiceOnProfileOverrideDropDownButton.model.count; i++)
{
if (choiceOnProfileOverrideDropDownButton.model.get(i).code == code)
{
choiceOnProfileOverrideDropDownButton.currentIndex = i;
break;
}
}
}
function setDefaultOpenProjectOption(code)
{
for (var i = 0; i < choiceOnOpenProjectDropDownButton.model.count; ++i)
{
if (choiceOnOpenProjectDropDownButton.model.get(i).code == code)
{
choiceOnOpenProjectDropDownButton.currentIndex = i
break;
}
}
}
function reset()
{
UM.Preferences.resetPreference("general/language")
@ -45,10 +69,16 @@ UM.PreferencesPage
showOverhangCheckbox.checked = boolCheck(UM.Preferences.getValue("view/show_overhang"))
UM.Preferences.resetPreference("view/center_on_select");
centerOnSelectCheckbox.checked = boolCheck(UM.Preferences.getValue("view/center_on_select"))
UM.Preferences.resetPreference("view/invert_zoom");
invertZoomCheckbox.checked = boolCheck(UM.Preferences.getValue("view/invert_zoom"))
UM.Preferences.resetPreference("view/top_layer_count");
topLayerCountCheckbox.checked = boolCheck(UM.Preferences.getValue("view/top_layer_count"))
UM.Preferences.resetPreference("cura/choice_on_profile_override")
choiceOnProfileOverrideDropDownButton.currentIndex = UM.Preferences.getValue("cura/choice_on_profile_override")
setDefaultDiscardOrKeepProfile(UM.Preferences.getValue("cura/choice_on_profile_override"))
UM.Preferences.resetPreference("cura/choice_on_open_project")
setDefaultOpenProjectOption(UM.Preferences.getValue("cura/choice_on_open_project"))
if (plugins.find("id", "SliceInfoPlugin") > -1) {
UM.Preferences.resetPreference("info/send_slice_info")
@ -232,6 +262,20 @@ UM.PreferencesPage
}
}
UM.TooltipArea {
width: childrenRect.width;
height: childrenRect.height;
text: catalog.i18nc("@info:tooltip","Should the default zoom behavior of cura be inverted?")
CheckBox
{
id: invertZoomCheckbox
text: catalog.i18nc("@action:button","Invert the direction of camera zoom.");
checked: boolCheck(UM.Preferences.getValue("view/invert_zoom"))
onClicked: UM.Preferences.setValue("view/invert_zoom", checked)
}
}
UM.TooltipArea {
width: childrenRect.width
height: childrenRect.height
@ -259,6 +303,25 @@ UM.PreferencesPage
}
}
UM.TooltipArea
{
width: childrenRect.width;
height: childrenRect.height;
text: catalog.i18nc("@info:tooltip","Show caution message in gcode reader.")
CheckBox
{
id: gcodeShowCautionCheckbox
checked: boolCheck(UM.Preferences.getValue("gcodereader/show_caution"))
onClicked: UM.Preferences.setValue("gcodereader/show_caution", checked)
text: catalog.i18nc("@option:check","Caution message in gcode reader");
}
}
UM.TooltipArea {
width: childrenRect.width
height: childrenRect.height
@ -341,6 +404,56 @@ UM.PreferencesPage
}
}
UM.TooltipArea {
width: childrenRect.width
height: childrenRect.height
text: catalog.i18nc("@info:tooltip", "Default behavior when opening a project file")
Column
{
spacing: 4
Label
{
text: catalog.i18nc("@window:text", "Default behavior when opening a project file: ")
}
ComboBox
{
id: choiceOnOpenProjectDropDownButton
width: 200
model: ListModel
{
id: openProjectOptionModel
Component.onCompleted: {
append({ text: catalog.i18nc("@option:openProject", "Always ask"), code: "always_ask" })
append({ text: catalog.i18nc("@option:openProject", "Always open as a project"), code: "open_as_project" })
append({ text: catalog.i18nc("@option:openProject", "Always import models"), code: "open_as_model" })
}
}
currentIndex:
{
var index = 0;
var currentChoice = UM.Preferences.getValue("cura/choice_on_open_project");
for (var i = 0; i < model.count; ++i)
{
if (model.get(i).code == currentChoice)
{
index = i;
break;
}
}
return index;
}
onActivated: UM.Preferences.setValue("cura/choice_on_open_project", model.get(index).code)
}
}
}
Item
{
//: Spacer
@ -348,12 +461,6 @@ UM.PreferencesPage
width: UM.Theme.getSize("default_margin").width
}
Label
{
font.bold: true
text: catalog.i18nc("@label", "Override Profile")
}
UM.TooltipArea
{
width: childrenRect.width;
@ -361,18 +468,48 @@ UM.PreferencesPage
text: catalog.i18nc("@info:tooltip", "When you have made changes to a profile and switched to a different one, a dialog will be shown asking whether you want to keep your modifications or not, or you can choose a default behaviour and never show that dialog again.")
Column
{
spacing: 4
Label
{
font.bold: true
text: catalog.i18nc("@label", "Override Profile")
}
ComboBox
{
id: choiceOnProfileOverrideDropDownButton
width: 200
model: [
catalog.i18nc("@option:discardOrKeep", "Always ask me this"),
catalog.i18nc("@option:discardOrKeep", "Discard and never ask again"),
catalog.i18nc("@option:discardOrKeep", "Keep and never ask again")
]
width: 300
currentIndex: UM.Preferences.getValue("cura/choice_on_profile_override")
onCurrentIndexChanged: UM.Preferences.setValue("cura/choice_on_profile_override", currentIndex)
model: ListModel
{
id: discardOrKeepProfileListModel
Component.onCompleted: {
append({ text: catalog.i18nc("@option:discardOrKeep", "Always ask me this"), code: "always_ask" })
append({ text: catalog.i18nc("@option:discardOrKeep", "Discard and never ask again"), code: "always_discard" })
append({ text: catalog.i18nc("@option:discardOrKeep", "Keep and never ask again"), code: "always_keep" })
}
}
currentIndex:
{
var index = 0;
var code = UM.Preferences.getValue("cura/choice_on_profile_override");
for (var i = 0; i < model.count; ++i)
{
if (model.get(i).code == code)
{
index = i;
break;
}
}
return index;
}
onActivated: UM.Preferences.setValue("cura/choice_on_profile_override", model.get(index).code)
}
}
}

View file

@ -155,8 +155,10 @@ TabView
decimals: 2
maximumValue: 1000
onEditingFinished: base.setMaterialPreferenceValue(properties.guid, "spool_cost", parseFloat(value))
onValueChanged: updateCostPerMeter()
onValueChanged: {
base.setMaterialPreferenceValue(properties.guid, "spool_cost", parseFloat(value))
updateCostPerMeter()
}
}
Label { width: base.firstColumnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Filament weight") }
@ -170,8 +172,10 @@ TabView
decimals: 0
maximumValue: 10000
onEditingFinished: base.setMaterialPreferenceValue(properties.guid, "spool_weight", parseFloat(value))
onValueChanged: updateCostPerMeter()
onValueChanged: {
base.setMaterialPreferenceValue(properties.guid, "spool_weight", parseFloat(value))
updateCostPerMeter()
}
}
Label { width: base.firstColumnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Filament length") }

View file

@ -128,7 +128,11 @@ UM.ManagementPage
text: catalog.i18nc("@action:button", "Activate");
iconName: "list-activate";
enabled: base.currentItem != null && base.currentItem.id != Cura.MachineManager.activeMaterialId && Cura.MachineManager.hasMaterials
onClicked: Cura.MachineManager.setActiveMaterial(base.currentItem.id)
onClicked:
{
Cura.MachineManager.setActiveMaterial(base.currentItem.id)
currentItem = base.model.getItem(base.objectList.currentIndex) // Refresh the current item.
}
},
Button
{

View file

@ -76,6 +76,17 @@ Item {
}
}
// Shortcut for "save as/print/..."
Action {
shortcut: "Ctrl+P"
onTriggered:
{
// only work when the button is enabled
if (saveToButton.enabled)
saveToButton.clicked();
}
}
Item {
id: saveRow
width: base.width
@ -215,7 +226,7 @@ Item {
text: UM.OutputDeviceManager.activeDeviceShortDescription
onClicked:
{
UM.OutputDeviceManager.requestWriteToDevice(UM.OutputDeviceManager.activeDevice, PrintInformation.jobName, { "filter_by_machine": true })
UM.OutputDeviceManager.requestWriteToDevice(UM.OutputDeviceManager.activeDevice, PrintInformation.jobName, { "filter_by_machine": true, "preferred_mimetype":Printer.preferredOutputMimetype })
}
style: ButtonStyle {

View file

@ -0,0 +1,13 @@
[general]
version = 2
name = Not Supported
definition = ultimaker3
[metadata]
type = quality
quality_type = normal
material = generic_abs_ultimaker3_AA_0.8
weight = 0
supported = false
[values]

View file

@ -0,0 +1,13 @@
[general]
version = 2
name = Not Supported
definition = ultimaker3
[metadata]
type = quality
quality_type = normal
material = generic_cpe_plus_ultimaker3_AA_0.8
weight = 0
supported = false
[values]

View file

@ -0,0 +1,13 @@
[general]
version = 2
name = Not Supported
definition = ultimaker3
[metadata]
type = quality
quality_type = normal
material = generic_cpe_ultimaker3_AA_0.8
weight = 0
supported = false
[values]

View file

@ -0,0 +1,26 @@
[general]
version = 2
name = Draft Print
definition = ultimaker3
[metadata]
type = quality
quality_type = draft
material = generic_nylon_ultimaker3_AA_0.8
weight = -2
[values]
brim_width = 8.0
cool_fan_full_at_height = =layer_height_0 + 4 * layer_height
cool_min_layer_time_fan_speed_max = 20
infill_before_walls = True
infill_pattern = triangles
machine_nozzle_cool_down_speed = 0.9
material_standby_temperature = 100
raft_airgap = =round(layer_height_0 * 0.85, 2)
raft_interface_thickness = =round(machine_nozzle_size * 0.3 / 0.4, 2)
raft_surface_thickness = =round(machine_nozzle_size * 0.2 / 0.4, 2)
switch_extruder_retraction_amount = 30
switch_extruder_retraction_speeds = 40
wall_line_width_x = =wall_line_width

View file

@ -0,0 +1,27 @@
[general]
version = 2
name = Superdraft Print
definition = ultimaker3
[metadata]
type = quality
quality_type = superdraft
material = generic_nylon_ultimaker3_AA_0.8
weight = -2
[values]
brim_width = 8.0
cool_fan_full_at_height = =layer_height_0 + 4 * layer_height
cool_min_layer_time_fan_speed_max = 20
infill_before_walls = True
infill_pattern = triangles
layer_height = 0.4
machine_nozzle_cool_down_speed = 0.9
material_standby_temperature = 100
raft_airgap = =round(layer_height_0 * 0.85, 2)
raft_interface_thickness = =round(machine_nozzle_size * 0.3 / 0.4, 2)
raft_surface_thickness = =round(machine_nozzle_size * 0.2 / 0.4, 2)
switch_extruder_retraction_amount = 30
switch_extruder_retraction_speeds = 40
wall_line_width_x = =wall_line_width

View file

@ -0,0 +1,27 @@
[general]
version = 2
name = Verydraft Print
definition = ultimaker3
[metadata]
type = quality
quality_type = verydraft
material = generic_nylon_ultimaker3_AA_0.8
weight = -2
[values]
brim_width = 8.0
cool_fan_full_at_height = =layer_height_0 + 4 * layer_height
cool_min_layer_time_fan_speed_max = 20
infill_before_walls = True
infill_pattern = triangles
layer_height = 0.3
machine_nozzle_cool_down_speed = 0.9
material_standby_temperature = 100
raft_airgap = =round(layer_height_0 * 0.85, 2)
raft_interface_thickness = =round(machine_nozzle_size * 0.3 / 0.4, 2)
raft_surface_thickness = =round(machine_nozzle_size * 0.2 / 0.4, 2)
switch_extruder_retraction_amount = 30
switch_extruder_retraction_speeds = 40
wall_line_width_x = =wall_line_width

View file

@ -0,0 +1,13 @@
[general]
version = 2
name = Not Supported
definition = ultimaker3
[metadata]
weight = 0
type = quality
quality_type = normal
material = generic_pc_ultimaker3_AA_0.8
supported = false
[values]

View file

@ -0,0 +1,34 @@
[general]
version = 2
name = Draft Print
definition = ultimaker3
[metadata]
type = quality
quality_type = draft
material = generic_pla_ultimaker3_AA_0.8
weight = 0
[values]
brim_line_count = =math.ceil(brim_width / skirt_brim_line_width)
cool_fan_speed_max = =cool_fan_speed
cool_min_speed = 2
gradual_infill_step_height = =3 * layer_height
gradual_infill_steps = 4
infill_line_width = =round(line_width * 0.535 / 0.75, 2)
infill_sparse_density = 80
line_width = =machine_nozzle_size * 0.9375
machine_nozzle_heat_up_speed = 1.6
material_final_print_temperature = =max(-273.15, material_print_temperature - 15)
material_initial_print_temperature = =max(-273.15, material_print_temperature - 10)
material_print_temperature = =default_material_print_temperature + 10
material_standby_temperature = 100
ooze_shield_angle = 60
raft_acceleration = =acceleration_print
raft_jerk = =jerk_print
raft_margin = 15
switch_extruder_prime_speed = =switch_extruder_retraction_speeds
top_bottom_thickness = =layer_height * 4
wall_line_width = =round(line_width * 0.75 / 0.75, 2)
wall_thickness = =wall_line_width_0 + wall_line_width_x

View file

@ -0,0 +1,35 @@
[general]
version = 2
name = Superdraft Print
definition = ultimaker3
[metadata]
type = quality
quality_type = superdraft
material = generic_pla_ultimaker3_AA_0.8
weight = 1
[values]
brim_line_count = =math.ceil(brim_width / skirt_brim_line_width)
cool_fan_speed_max = =cool_fan_speed
cool_min_speed = 2
gradual_infill_step_height = =3 * layer_height
gradual_infill_steps = 4
infill_line_width = =round(line_width * 0.535 / 0.75, 2)
infill_sparse_density = 80
layer_height = 0.4
line_width = =machine_nozzle_size * 0.9375
machine_nozzle_heat_up_speed = 1.6
material_final_print_temperature = =max(-273.15, material_print_temperature - 15)
material_initial_print_temperature = =max(-273.15, material_print_temperature - 10)
material_print_temperature = =default_material_print_temperature + 15
material_standby_temperature = 100
ooze_shield_angle = 60
raft_acceleration = =acceleration_print
raft_jerk = =jerk_print
raft_margin = 15
switch_extruder_prime_speed = =switch_extruder_retraction_speeds
top_bottom_thickness = =layer_height * 4
wall_line_width = =round(line_width * 0.75 / 0.75, 2)
wall_thickness = =wall_line_width_0 + wall_line_width_x

View file

@ -0,0 +1,35 @@
[general]
version = 2
name = Verydraft Print
definition = ultimaker3
[metadata]
type = quality
quality_type = verydraft
material = generic_pla_ultimaker3_AA_0.8
weight = 1
[values]
brim_line_count = =math.ceil(brim_width / skirt_brim_line_width)
cool_fan_speed_max = =cool_fan_speed
cool_min_speed = 2
gradual_infill_step_height = =3 * layer_height
gradual_infill_steps = 4
infill_line_width = =round(line_width * 0.535 / 0.75, 2)
infill_sparse_density = 80
layer_height = 0.3
line_width = =machine_nozzle_size * 0.9375
machine_nozzle_heat_up_speed = 1.6
material_final_print_temperature = =max(-273.15, material_print_temperature - 15)
material_initial_print_temperature = =max(-273.15, material_print_temperature - 10)
material_print_temperature = =default_material_print_temperature + 10
material_standby_temperature = 100
ooze_shield_angle = 60
raft_acceleration = =acceleration_print
raft_jerk = =jerk_print
raft_margin = 15
switch_extruder_prime_speed = =switch_extruder_retraction_speeds
top_bottom_thickness = =layer_height * 4
wall_line_width = =round(line_width * 0.75 / 0.75, 2)
wall_thickness = =wall_line_width_0 + wall_line_width_x

View file

@ -0,0 +1,13 @@
[general]
version = 2
name = Not Supported
definition = ultimaker3
[metadata]
weight = 0
type = quality
quality_type = normal
material = generic_pva_ultimaker3_AA_0.8
supported = false
[values]

View file

@ -0,0 +1,13 @@
[general]
version = 2
name = Not Supported
definition = ultimaker3
[metadata]
weight = 0
type = quality
quality_type = normal
material = generic_tpu_ultimaker3_AA_0.8
supported = false
[values]

View file

@ -0,0 +1,13 @@
[general]
version = 2
name = Not Supported
definition = ultimaker3
[metadata]
type = quality
quality_type = normal
material = generic_cpe_plus_ultimaker3_BB_0.4
weight = 0
supported = false
[values]

View file

@ -0,0 +1,13 @@
[general]
version = 2
name = Not Supported
definition = ultimaker3
[metadata]
type = quality
quality_type = normal
material = generic_pc_ultimaker3_BB_0.4
weight = 0
supported = false
[values]

View file

@ -0,0 +1,13 @@
[general]
version = 2
name = Not Supported
definition = ultimaker3
[metadata]
type = quality
quality_type = normal
material = generic_tpu_ultimaker3_BB_0.4
weight = 0
supported = false
[values]

View file

@ -0,0 +1,13 @@
[general]
version = 2
name = Not Supported
definition = ultimaker3
[metadata]
type = quality
quality_type = normal
material = generic_abs_ultimaker3_BB_0.8
weight = 0
supported = false
[values]

View file

@ -0,0 +1,13 @@
[general]
version = 2
name = Not Supported
definition = ultimaker3
[metadata]
type = quality
quality_type = normal
material = generic_cpe_plus_ultimaker3_BB_0.8
weight = 0
supported = false
[values]

View file

@ -0,0 +1,13 @@
[general]
version = 2
name = Not Supported
definition = ultimaker3
[metadata]
type = quality
quality_type = normal
material = generic_cpe_ultimaker3_BB_0.8
weight = 0
supported = false
[values]

View file

@ -0,0 +1,13 @@
[general]
version = 2
name = Not Supported
definition = ultimaker3
[metadata]
type = quality
quality_type = normal
material = generic_nylon_ultimaker3_BB_0.8
weight = 0
supported = false
[values]

View file

@ -0,0 +1,13 @@
[general]
version = 2
name = Not Supported
definition = ultimaker3
[metadata]
type = quality
quality_type = normal
material = generic_pc_ultimaker3_BB_0.8
weight = 0
supported = false
[values]

View file

@ -0,0 +1,13 @@
[general]
version = 2
name = Not Supported
definition = ultimaker3
[metadata]
type = quality
quality_type = normal
material = generic_pla_ultimaker3_BB_0.8
weight = 0
supported = false
[values]

View file

@ -0,0 +1,14 @@
[general]
version = 2
name = Draft Print
definition = ultimaker3
[metadata]
type = quality
quality_type = draft
weight = -2
material = generic_pva_ultimaker3_BB_0.8
[values]
material_print_temperature = =default_material_print_temperature + 5

View file

@ -0,0 +1,14 @@
[general]
version = 2
name = Superdraft Print
definition = ultimaker3
[metadata]
type = quality
quality_type = superdraft
weight = -2
material = generic_pva_ultimaker3_BB_0.8
[values]
layer_height = 0.4

View file

@ -0,0 +1,14 @@
[general]
version = 2
name = Verydraft Print
definition = ultimaker3
[metadata]
type = quality
quality_type = verydraft
weight = -2
material = generic_pva_ultimaker3_BB_0.8
[values]
layer_height = 0.3

View file

@ -0,0 +1,13 @@
[general]
version = 2
name = Not Supported
definition = ultimaker3
[metadata]
type = quality
quality_type = normal
material = generic_tpu_ultimaker3_BB_0.8
weight = 0
supported = false
[values]

View file

@ -0,0 +1,13 @@
[general]
version = 2
name = Superdraft Quality
definition = ultimaker3
[metadata]
type = quality
quality_type = superdraft
global_quality = True
weight = -2
[values]
layer_height = 0.4

View file

@ -0,0 +1,13 @@
[general]
version = 2
name = Verydraft Quality
definition = ultimaker3
[metadata]
type = quality
quality_type = verydraft
global_quality = True
weight = -2
[values]
layer_height = 0.3

View file

@ -45,19 +45,19 @@ speed_travel_layer_0 = =round(speed_travel)
speed_support_interface = =round(speed_topbottom)
retraction_combing = off
retraction_hop_enabled = true
retraction_hop_enabled = True
retraction_hop = 1
support_z_distance = 0
support_xy_distance = 0.5
support_join_distance = 10
support_interface_enable = true
support_interface_enable = True
adhesion_type = skirt
skirt_gap = 0.5
skirt_brim_minimal_length = 50
coasting_enable = true
coasting_enable = True
coasting_volume = 0.1
coasting_min_volume = 0.17
coasting_speed = 90

View file

@ -45,19 +45,19 @@ speed_travel_layer_0 = =round(speed_travel)
speed_support_interface = =round(speed_topbottom)
retraction_combing = off
retraction_hop_enabled = true
retraction_hop_enabled = True
retraction_hop = 1
support_z_distance = 0
support_xy_distance = 0.5
support_join_distance = 10
support_interface_enable = true
support_interface_enable = True
adhesion_type = skirt
skirt_gap = 0.5
skirt_brim_minimal_length = 50
coasting_enable = true
coasting_enable = True
coasting_volume = 0.1
coasting_min_volume = 0.17
coasting_speed = 90

View file

@ -46,19 +46,19 @@ speed_travel_layer_0 = =round(speed_travel)
speed_support_interface = =round(speed_topbottom)
retraction_combing = off
retraction_hop_enabled = true
retraction_hop_enabled = True
retraction_hop = 1
support_z_distance = 0
support_xy_distance = 0.5
support_join_distance = 10
support_interface_enable = true
support_interface_enable = True
adhesion_type = skirt
skirt_gap = 0.5
skirt_brim_minimal_length = 50
coasting_enable = true
coasting_enable = True
coasting_volume = 0.1
coasting_min_volume = 0.17
coasting_speed = 90

View file

@ -0,0 +1,64 @@
[general]
name = AA 0.8
version = 2
definition = ultimaker3
[metadata]
author = ultimaker
type = variant
[values]
acceleration_enabled = True
acceleration_print = 4000
brim_line_count = 7
brim_width = 7
cool_fan_full_at_height = =layer_height_0 + 2 * layer_height
cool_fan_speed = 100
cool_fan_speed_max = 100
default_material_print_temperature = 200
infill_before_walls = False
infill_overlap = 0
infill_pattern = cubic
infill_wipe_dist = 0
jerk_enabled = True
jerk_print = 25
jerk_topbottom = =math.ceil(jerk_print * 25 / 25)
jerk_wall = =math.ceil(jerk_print * 25 / 25)
jerk_wall_0 = =math.ceil(jerk_wall * 25 / 25)
layer_height = 0.2
machine_min_cool_heat_time_window = 15
machine_nozzle_cool_down_speed = 0.75
machine_nozzle_size = 0.8
material_final_print_temperature = =material_print_temperature - 10
material_initial_print_temperature = =material_print_temperature - 5
material_standby_temperature = 100
multiple_mesh_overlap = 0
ooze_shield_angle = 40
raft_acceleration = =acceleration_layer_0
raft_margin = 10
retract_at_layer_change = True
retraction_count_max = 25
retraction_extrusion_window = 1
retraction_hop = 2
retraction_hop_enabled = True
retraction_hop_only_when_collides = True
skin_overlap = 5
speed_equalize_flow_enabled = True
speed_layer_0 = 20
speed_print = 35
speed_topbottom = =math.ceil(speed_print * 25 / 35)
speed_wall_0 = =math.ceil(speed_wall * 25 / 30)
support_angle = 70
support_bottom_distance = =support_z_distance / 2
support_line_width = =line_width * 0.75
support_top_distance = =support_z_distance
support_xy_distance = =wall_line_width_0 * 1.5
support_z_distance = =layer_height * 2
switch_extruder_prime_speed = 30
switch_extruder_retraction_amount = 16.5
top_bottom_thickness = 1.4
travel_avoid_distance = 3
wall_0_inset = 0
wall_line_width_x = =round(wall_line_width * 0.625 / 0.75, 2)
wall_thickness = 2

View file

@ -0,0 +1,74 @@
[general]
name = BB 0.8
version = 2
definition = ultimaker3
[metadata]
author = ultimaker
type = variant
[values]
acceleration_enabled = True
acceleration_print = 4000
acceleration_support_interface = =math.ceil(acceleration_topbottom * 100 / 500)
brim_width = 3
cool_fan_speed = 50
cool_min_speed = 5
infill_line_width = =round(line_width * 0.8 / 0.7, 2)
infill_overlap = 0
infill_pattern = triangles
infill_wipe_dist = 0
jerk_enabled = True
jerk_print = 25
jerk_support_interface = =math.ceil(jerk_topbottom * 1 / 5)
layer_height = 0.2
machine_min_cool_heat_time_window = 15
machine_nozzle_heat_up_speed = 1.5
machine_nozzle_size = 0.8
material_print_temperature = =default_material_print_temperature + 10
material_standby_temperature = 100
multiple_mesh_overlap = 0
raft_acceleration = =acceleration_layer_0
raft_airgap = 0
raft_base_speed = 20
raft_base_thickness = 0.3
raft_interface_line_spacing = 0.5
raft_interface_line_width = 0.5
raft_interface_speed = 20
raft_interface_thickness = 0.2
raft_margin = 10
raft_speed = 25
raft_surface_layers = 1
retraction_amount = 4.5
retraction_count_max = 15
retraction_hop = 2
retraction_hop_enabled = True
retraction_hop_only_when_collides = True
retraction_min_travel = 5
retraction_prime_speed = 15
skin_overlap = 5
speed_layer_0 = 20
speed_print = 35
speed_support_interface = =math.ceil(speed_topbottom * 15 / 20)
speed_wall_0 = =math.ceil(speed_wall * 25 / 30)
support_angle = 60
support_bottom_height = =layer_height * 2
support_bottom_stair_step_height = =layer_height
support_infill_rate = 25
support_interface_enable = True
support_interface_height = =layer_height * 5
support_join_distance = 3
support_line_width = =round(line_width * 0.4 / 0.35, 2)
support_offset = 1.5
support_pattern = triangles
support_use_towers = False
support_xy_distance = =wall_line_width_0 / 2
support_xy_distance_overhang = =wall_line_width_0 / 4
support_z_distance = 0
switch_extruder_prime_speed = 15
switch_extruder_retraction_amount = 12
top_bottom_thickness = 1
travel_avoid_distance = 3
wall_0_inset = 0
wall_thickness = 1

View file

@ -0,0 +1,64 @@
[general]
name = AA 0.8
version = 2
definition = ultimaker3_extended
[metadata]
author = ultimaker
type = variant
[values]
acceleration_enabled = True
acceleration_print = 4000
brim_line_count = 7
brim_width = 7
cool_fan_full_at_height = =layer_height_0 + 2 * layer_height
cool_fan_speed = 100
cool_fan_speed_max = 100
default_material_print_temperature = 200
infill_before_walls = False
infill_overlap = 0
infill_pattern = cubic
infill_wipe_dist = 0
jerk_enabled = True
jerk_print = 25
jerk_topbottom = =math.ceil(jerk_print * 25 / 25)
jerk_wall = =math.ceil(jerk_print * 25 / 25)
jerk_wall_0 = =math.ceil(jerk_wall * 25 / 25)
layer_height = 0.2
machine_min_cool_heat_time_window = 15
machine_nozzle_cool_down_speed = 0.75
machine_nozzle_size = 0.8
material_final_print_temperature = =material_print_temperature - 10
material_initial_print_temperature = =material_print_temperature - 5
material_standby_temperature = 100
multiple_mesh_overlap = 0
ooze_shield_angle = 40
raft_acceleration = =acceleration_layer_0
raft_margin = 10
retract_at_layer_change = True
retraction_count_max = 25
retraction_extrusion_window = 1
retraction_hop = 2
retraction_hop_enabled = True
retraction_hop_only_when_collides = True
skin_overlap = 5
speed_equalize_flow_enabled = True
speed_layer_0 = 20
speed_print = 35
speed_topbottom = =math.ceil(speed_print * 25 / 35)
speed_wall_0 = =math.ceil(speed_wall * 25 / 30)
support_angle = 70
support_bottom_distance = =support_z_distance / 2
support_line_width = =line_width * 0.75
support_top_distance = =support_z_distance
support_xy_distance = =wall_line_width_0 * 1.5
support_z_distance = =layer_height * 2
switch_extruder_prime_speed = 30
switch_extruder_retraction_amount = 16.5
top_bottom_thickness = 1.4
travel_avoid_distance = 3
wall_0_inset = 0
wall_line_width_x = =round(wall_line_width * 0.625 / 0.75, 2)
wall_thickness = 2

View file

@ -0,0 +1,74 @@
[general]
name = BB 0.8
version = 2
definition = ultimaker3_extended
[metadata]
author = ultimaker
type = variant
[values]
acceleration_enabled = True
acceleration_print = 4000
acceleration_support_interface = =math.ceil(acceleration_topbottom * 100 / 500)
brim_width = 3
cool_fan_speed = 50
cool_min_speed = 5
infill_line_width = =round(line_width * 0.8 / 0.7, 2)
infill_overlap = 0
infill_pattern = triangles
infill_wipe_dist = 0
jerk_enabled = True
jerk_print = 25
jerk_support_interface = =math.ceil(jerk_topbottom * 1 / 5)
layer_height = 0.2
machine_min_cool_heat_time_window = 15
machine_nozzle_heat_up_speed = 1.5
machine_nozzle_size = 0.8
material_print_temperature = =default_material_print_temperature + 10
material_standby_temperature = 100
multiple_mesh_overlap = 0
raft_acceleration = =acceleration_layer_0
raft_airgap = 0
raft_base_speed = 20
raft_base_thickness = 0.3
raft_interface_line_spacing = 0.5
raft_interface_line_width = 0.5
raft_interface_speed = 20
raft_interface_thickness = 0.2
raft_margin = 10
raft_speed = 25
raft_surface_layers = 1
retraction_amount = 4.5
retraction_count_max = 15
retraction_hop = 2
retraction_hop_enabled = True
retraction_hop_only_when_collides = True
retraction_min_travel = 5
retraction_prime_speed = 15
skin_overlap = 5
speed_layer_0 = 20
speed_print = 35
speed_support_interface = =math.ceil(speed_topbottom * 15 / 20)
speed_wall_0 = =math.ceil(speed_wall * 25 / 30)
support_angle = 60
support_bottom_height = =layer_height * 2
support_bottom_stair_step_height = =layer_height
support_infill_rate = 25
support_interface_enable = True
support_interface_height = =layer_height * 5
support_join_distance = 3
support_line_width = =round(line_width * 0.4 / 0.35, 2)
support_offset = 1.5
support_pattern = triangles
support_use_towers = False
support_xy_distance = =wall_line_width_0 / 2
support_xy_distance_overhang = =wall_line_width_0 / 4
support_z_distance = 0
switch_extruder_prime_speed = 15
switch_extruder_retraction_amount = 12
top_bottom_thickness = 1
travel_avoid_distance = 3
wall_0_inset = 0
wall_thickness = 1

12
tests/TestMachineAction.py Normal file → Executable file
View file

@ -30,19 +30,19 @@ def test_addMachineAction():
machine_manager.addMachineAction(test_action)
# Check that the machine has no supported actions yet.
assert machine_manager.getSupportedActions(test_machine) == set()
assert machine_manager.getSupportedActions(test_machine) == list()
# Check if adding a supported action works.
machine_manager.addSupportedAction(test_machine, "test_action")
assert machine_manager.getSupportedActions(test_machine) == {test_action}
assert machine_manager.getSupportedActions(test_machine) == [test_action, ]
# Check that adding a unknown action doesn't change anything.
machine_manager.addSupportedAction(test_machine, "key_that_doesnt_exist")
assert machine_manager.getSupportedActions(test_machine) == {test_action}
assert machine_manager.getSupportedActions(test_machine) == [test_action, ]
# Check if adding multiple supported actions works.
machine_manager.addSupportedAction(test_machine, "test_action_2")
assert machine_manager.getSupportedActions(test_machine) == {test_action, test_action_2}
assert machine_manager.getSupportedActions(test_machine) == [test_action, test_action_2]
# Check that the machine has no required actions yet.
assert machine_manager.getRequiredActions(test_machine) == set()
@ -53,11 +53,11 @@ def test_addMachineAction():
## Check if adding single required action works
machine_manager.addRequiredAction(test_machine, "test_action")
assert machine_manager.getRequiredActions(test_machine) == {test_action}
assert machine_manager.getRequiredActions(test_machine) == [test_action, ]
# Check if adding multiple required actions works.
machine_manager.addRequiredAction(test_machine, "test_action_2")
assert machine_manager.getRequiredActions(test_machine) == {test_action, test_action_2}
assert machine_manager.getRequiredActions(test_machine) == [test_action, test_action_2]
# Ensure that firstStart actions are empty by default.
assert machine_manager.getFirstStartActions(test_machine) == []