mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-12-05 16:51:12 -07:00
Merge remote-tracking branch 'refs/remotes/Ultimaker/master'
This commit is contained in:
commit
145c483c02
218 changed files with 14174 additions and 6535 deletions
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
project(cura)
|
||||
project(cura NONE)
|
||||
cmake_minimum_required(VERSION 2.8.12)
|
||||
|
||||
include(GNUInstallDirs)
|
||||
|
|
@ -17,48 +17,12 @@ set(CURA_BUILDTYPE "" CACHE STRING "Build type of Cura, eg. 'PPA'")
|
|||
configure_file(${CMAKE_SOURCE_DIR}/cura.desktop.in ${CMAKE_BINARY_DIR}/cura.desktop @ONLY)
|
||||
configure_file(cura/CuraVersion.py.in CuraVersion.py @ONLY)
|
||||
|
||||
# Macro needed to list all sub-directory of a directory.
|
||||
# There is no function in cmake as far as I know.
|
||||
# Found at: http://stackoverflow.com/a/7788165
|
||||
MACRO(SUBDIRLIST result curdir)
|
||||
FILE(GLOB children RELATIVE ${curdir} ${curdir}/*)
|
||||
SET(dirlist "")
|
||||
FOREACH(child ${children})
|
||||
IF(IS_DIRECTORY ${curdir}/${child})
|
||||
STRING(REPLACE "/" "" child ${child})
|
||||
LIST(APPEND dirlist ${child})
|
||||
ENDIF()
|
||||
ENDFOREACH()
|
||||
SET(${result} ${dirlist})
|
||||
ENDMACRO()
|
||||
|
||||
if(NOT ${URANIUM_SCRIPTS_DIR} STREQUAL "")
|
||||
include(UraniumTranslationTools)
|
||||
# Extract Strings
|
||||
add_custom_target(extract-messages ${URANIUM_SCRIPTS_DIR}/extract-messages ${CMAKE_SOURCE_DIR} cura)
|
||||
|
||||
# Build Translations
|
||||
find_package(Gettext)
|
||||
if(GETTEXT_FOUND)
|
||||
# translations target will convert .po files into .mo and .qm as needed.
|
||||
# The files are checked for a _qt suffix and if it is found, converted to
|
||||
# qm, otherwise they are converted to .po.
|
||||
add_custom_target(translations ALL)
|
||||
# copy-translations can be used to copy the built translation files from the
|
||||
# build directory to the source resources directory. This is mostly a convenience
|
||||
# during development, normally you want to simply use the install target to install
|
||||
# the files along side the rest of the application.
|
||||
|
||||
SUBDIRLIST(languages ${CMAKE_SOURCE_DIR}/resources/i18n/)
|
||||
foreach(lang ${languages})
|
||||
file(GLOB po_files ${CMAKE_SOURCE_DIR}/resources/i18n/${lang}/*.po)
|
||||
foreach(po_file ${po_files})
|
||||
string(REGEX REPLACE ".*/(.*).po" "${CMAKE_BINARY_DIR}/resources/i18n/${lang}/LC_MESSAGES/\\1.mo" mo_file ${po_file})
|
||||
add_custom_command(TARGET translations POST_BUILD COMMAND mkdir ARGS -p ${CMAKE_BINARY_DIR}/resources/i18n/${lang}/LC_MESSAGES/ COMMAND ${GETTEXT_MSGFMT_EXECUTABLE} ARGS ${po_file} -o ${mo_file} -f)
|
||||
endforeach()
|
||||
endforeach()
|
||||
install(DIRECTORY ${CMAKE_BINARY_DIR}/resources
|
||||
DESTINATION ${CMAKE_INSTALL_DATADIR}/cura)
|
||||
endif()
|
||||
CREATE_TRANSLATION_TARGETS()
|
||||
endif()
|
||||
|
||||
find_package(PythonInterp 3.4.0 REQUIRED)
|
||||
|
|
|
|||
|
|
@ -42,9 +42,10 @@ Please checkout [cura-build](https://github.com/Ultimaker/cura-build)
|
|||
|
||||
Third party plugins
|
||||
-------------
|
||||
* [Print time calculator](https://github.com/nallath/PrintCostCalculator)
|
||||
* [Post processing plugin](https://github.com/nallath/PostProcessingPlugin)
|
||||
* [Barbarian Plugin](https://github.com/nallath/BarbarianPlugin) Simple scale tool for imperial to metric.
|
||||
* [Print Cost Calculator](https://github.com/nallath/PrintCostCalculator): Calculates weight and monetary cost of your print.
|
||||
* [Post Processing Plugin](https://github.com/nallath/PostProcessingPlugin): Allows for post-processing scripts to run on g-code.
|
||||
* [Barbarian Plugin](https://github.com/nallath/BarbarianPlugin): Simple scale tool for imperial to metric.
|
||||
* [X3G Writer](https://github.com/Ghostkeeper/X3GWriter): Adds support for exporting X3G files.
|
||||
|
||||
Making profiles for other printers
|
||||
----------------------------------
|
||||
|
|
|
|||
|
|
@ -67,6 +67,9 @@ class BuildVolume(SceneNode):
|
|||
self._disallowed_areas = []
|
||||
self._disallowed_area_mesh = None
|
||||
|
||||
self._prime_tower_area = None
|
||||
self._prime_tower_area_mesh = None
|
||||
|
||||
self.setCalculateBoundingBox(False)
|
||||
self._volume_aabb = None
|
||||
|
||||
|
|
@ -74,10 +77,16 @@ class BuildVolume(SceneNode):
|
|||
self._adhesion_type = None
|
||||
self._platform = Platform(self)
|
||||
|
||||
self._active_container_stack = None
|
||||
self._global_container_stack = None
|
||||
Application.getInstance().globalContainerStackChanged.connect(self._onGlobalContainerStackChanged)
|
||||
self._onGlobalContainerStackChanged()
|
||||
|
||||
self._active_extruder_stack = None
|
||||
ExtruderManager.getInstance().activeExtruderChanged.connect(self._onActiveExtruderStackChanged)
|
||||
self._onActiveExtruderStackChanged()
|
||||
|
||||
self._has_errors = False
|
||||
|
||||
def setWidth(self, width):
|
||||
if width: self._width = width
|
||||
|
||||
|
|
@ -106,6 +115,10 @@ class BuildVolume(SceneNode):
|
|||
if self._disallowed_area_mesh:
|
||||
renderer.queueNode(self, mesh = self._disallowed_area_mesh, shader = self._shader, transparent = True, backface_cull = True, sort = -9)
|
||||
|
||||
if self._prime_tower_area_mesh:
|
||||
renderer.queueNode(self, mesh = self._prime_tower_area_mesh, shader = self._shader, transparent=True,
|
||||
backface_cull=True, sort=-8)
|
||||
|
||||
return True
|
||||
|
||||
## Recalculates the build volume & disallowed areas.
|
||||
|
|
@ -180,6 +193,24 @@ class BuildVolume(SceneNode):
|
|||
else:
|
||||
self._disallowed_area_mesh = None
|
||||
|
||||
if self._prime_tower_area:
|
||||
mb = MeshBuilder()
|
||||
color = Color(1.0, 0.0, 0.0, 0.5)
|
||||
points = self._prime_tower_area.getPoints()
|
||||
first = Vector(self._clamp(points[0][0], min_w, max_w), disallowed_area_height,
|
||||
self._clamp(points[0][1], min_d, max_d))
|
||||
previous_point = Vector(self._clamp(points[0][0], min_w, max_w), disallowed_area_height,
|
||||
self._clamp(points[0][1], min_d, max_d))
|
||||
for point in points:
|
||||
new_point = Vector(self._clamp(point[0], min_w, max_w), disallowed_area_height,
|
||||
self._clamp(point[1], min_d, max_d))
|
||||
mb.addFace(first, previous_point, new_point, color=color)
|
||||
previous_point = new_point
|
||||
|
||||
self._prime_tower_area_mesh = mb.build()
|
||||
else:
|
||||
self._prime_tower_area_mesh = None
|
||||
|
||||
self._volume_aabb = AxisAlignedBox(
|
||||
minimum = Vector(min_w, min_h - 1.0, min_d),
|
||||
maximum = Vector(max_w, max_h - self._raft_thickness, max_d))
|
||||
|
|
@ -208,22 +239,22 @@ class BuildVolume(SceneNode):
|
|||
"@info:status",
|
||||
"The build volume height has been reduced due to the value of the"
|
||||
" \"Print Sequence\" setting to prevent the gantry from colliding"
|
||||
" with printed models."), lifetime=10).show()
|
||||
" with printed models.")).show()
|
||||
|
||||
def getRaftThickness(self):
|
||||
return self._raft_thickness
|
||||
|
||||
def _updateRaftThickness(self):
|
||||
old_raft_thickness = self._raft_thickness
|
||||
self._adhesion_type = self._active_container_stack.getProperty("adhesion_type", "value")
|
||||
self._adhesion_type = self._global_container_stack.getProperty("adhesion_type", "value")
|
||||
self._raft_thickness = 0.0
|
||||
if self._adhesion_type == "raft":
|
||||
self._raft_thickness = (
|
||||
self._active_container_stack.getProperty("raft_base_thickness", "value") +
|
||||
self._active_container_stack.getProperty("raft_interface_thickness", "value") +
|
||||
self._active_container_stack.getProperty("raft_surface_layers", "value") *
|
||||
self._active_container_stack.getProperty("raft_surface_thickness", "value") +
|
||||
self._active_container_stack.getProperty("raft_airgap", "value"))
|
||||
self._global_container_stack.getProperty("raft_base_thickness", "value") +
|
||||
self._global_container_stack.getProperty("raft_interface_thickness", "value") +
|
||||
self._global_container_stack.getProperty("raft_surface_layers", "value") *
|
||||
self._global_container_stack.getProperty("raft_surface_thickness", "value") +
|
||||
self._global_container_stack.getProperty("raft_airgap", "value"))
|
||||
|
||||
# Rounding errors do not matter, we check if raft_thickness has changed at all
|
||||
if old_raft_thickness != self._raft_thickness:
|
||||
|
|
@ -231,41 +262,52 @@ class BuildVolume(SceneNode):
|
|||
self.raftThicknessChanged.emit()
|
||||
|
||||
def _onGlobalContainerStackChanged(self):
|
||||
if self._active_container_stack:
|
||||
self._active_container_stack.propertyChanged.disconnect(self._onSettingPropertyChanged)
|
||||
if self._global_container_stack:
|
||||
self._global_container_stack.propertyChanged.disconnect(self._onSettingPropertyChanged)
|
||||
|
||||
self._active_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||
self._global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||
|
||||
if self._active_container_stack:
|
||||
self._active_container_stack.propertyChanged.connect(self._onSettingPropertyChanged)
|
||||
if self._global_container_stack:
|
||||
self._global_container_stack.propertyChanged.connect(self._onSettingPropertyChanged)
|
||||
|
||||
self._width = self._active_container_stack.getProperty("machine_width", "value")
|
||||
if self._active_container_stack.getProperty("print_sequence", "value") == "one_at_a_time":
|
||||
self._height = self._active_container_stack.getProperty("gantry_height", "value")
|
||||
self._buildVolumeMessage()
|
||||
self._width = self._global_container_stack.getProperty("machine_width", "value")
|
||||
machine_height = self._global_container_stack.getProperty("machine_height", "value")
|
||||
if self._global_container_stack.getProperty("print_sequence", "value") == "one_at_a_time":
|
||||
self._height = min(self._global_container_stack.getProperty("gantry_height", "value"), machine_height)
|
||||
if self._height < machine_height:
|
||||
self._buildVolumeMessage()
|
||||
else:
|
||||
self._height = self._active_container_stack.getProperty("machine_height", "value")
|
||||
self._depth = self._active_container_stack.getProperty("machine_depth", "value")
|
||||
self._height = self._global_container_stack.getProperty("machine_height", "value")
|
||||
self._depth = self._global_container_stack.getProperty("machine_depth", "value")
|
||||
|
||||
self._updateDisallowedAreas()
|
||||
self._updateRaftThickness()
|
||||
|
||||
self.rebuild()
|
||||
|
||||
def _onActiveExtruderStackChanged(self):
|
||||
if self._active_extruder_stack:
|
||||
self._active_extruder_stack.propertyChanged.disconnect(self._onSettingPropertyChanged)
|
||||
self._active_extruder_stack = ExtruderManager.getInstance().getActiveExtruderStack()
|
||||
if self._active_extruder_stack:
|
||||
self._active_extruder_stack.propertyChanged.connect(self._onSettingPropertyChanged)
|
||||
|
||||
def _onSettingPropertyChanged(self, setting_key, property_name):
|
||||
if property_name != "value":
|
||||
return
|
||||
|
||||
rebuild_me = False
|
||||
if setting_key == "print_sequence":
|
||||
machine_height = self._global_container_stack.getProperty("machine_height", "value")
|
||||
if Application.getInstance().getGlobalContainerStack().getProperty("print_sequence", "value") == "one_at_a_time":
|
||||
self._height = self._active_container_stack.getProperty("gantry_height", "value")
|
||||
self._buildVolumeMessage()
|
||||
self._height = min(self._global_container_stack.getProperty("gantry_height", "value"), machine_height)
|
||||
if self._height < machine_height:
|
||||
self._buildVolumeMessage()
|
||||
else:
|
||||
self._height = self._active_container_stack.getProperty("machine_height", "value")
|
||||
self._height = self._global_container_stack.getProperty("machine_height", "value")
|
||||
rebuild_me = True
|
||||
|
||||
if setting_key in self._skirt_settings or setting_key in self._prime_settings:
|
||||
if setting_key in self._skirt_settings or setting_key in self._prime_settings or setting_key in self._tower_settings or setting_key == "print_sequence":
|
||||
self._updateDisallowedAreas()
|
||||
rebuild_me = True
|
||||
|
||||
|
|
@ -276,20 +318,37 @@ class BuildVolume(SceneNode):
|
|||
if rebuild_me:
|
||||
self.rebuild()
|
||||
|
||||
def _updateDisallowedAreas(self):
|
||||
if not self._active_container_stack:
|
||||
return
|
||||
def hasErrors(self):
|
||||
return self._has_errors
|
||||
|
||||
def _updateDisallowedAreas(self):
|
||||
if not self._global_container_stack:
|
||||
return
|
||||
self._has_errors = False # Reset.
|
||||
disallowed_areas = copy.deepcopy(
|
||||
self._active_container_stack.getProperty("machine_disallowed_areas", "value"))
|
||||
self._global_container_stack.getProperty("machine_disallowed_areas", "value"))
|
||||
areas = []
|
||||
|
||||
machine_width = self._global_container_stack.getProperty("machine_width", "value")
|
||||
machine_depth = self._global_container_stack.getProperty("machine_depth", "value")
|
||||
self._prime_tower_area = None
|
||||
# Add prime tower location as disallowed area.
|
||||
if self._global_container_stack.getProperty("prime_tower_enable", "value") == True:
|
||||
prime_tower_size = self._global_container_stack.getProperty("prime_tower_size", "value")
|
||||
prime_tower_x = self._global_container_stack.getProperty("prime_tower_position_x", "value") - machine_width / 2
|
||||
prime_tower_y = - self._global_container_stack.getProperty("prime_tower_position_y", "value") + machine_depth / 2
|
||||
|
||||
self._prime_tower_area = Polygon([
|
||||
[prime_tower_x - prime_tower_size, prime_tower_y - prime_tower_size],
|
||||
[prime_tower_x, prime_tower_y - prime_tower_size],
|
||||
[prime_tower_x, prime_tower_y],
|
||||
[prime_tower_x - prime_tower_size, prime_tower_y],
|
||||
])
|
||||
|
||||
# Add extruder prime locations as disallowed areas.
|
||||
# Probably needs some rework after coordinate system change.
|
||||
extruder_manager = ExtruderManager.getInstance()
|
||||
extruders = extruder_manager.getMachineExtruders(self._active_container_stack.getId())
|
||||
machine_width = self._active_container_stack.getProperty("machine_width", "value")
|
||||
machine_depth = self._active_container_stack.getProperty("machine_depth", "value")
|
||||
extruders = extruder_manager.getMachineExtruders(self._global_container_stack.getId())
|
||||
for single_extruder in extruders:
|
||||
extruder_prime_pos_x = single_extruder.getProperty("extruder_prime_pos_x", "value")
|
||||
extruder_prime_pos_y = single_extruder.getProperty("extruder_prime_pos_y", "value")
|
||||
|
|
@ -305,7 +364,7 @@ class BuildVolume(SceneNode):
|
|||
[prime_x - PRIME_CLEARANCE, prime_y + PRIME_CLEARANCE],
|
||||
])
|
||||
|
||||
bed_adhesion_size = self._getBedAdhesionSize(self._active_container_stack)
|
||||
bed_adhesion_size = self._getBedAdhesionSize(self._global_container_stack)
|
||||
|
||||
if disallowed_areas:
|
||||
# Extend every area already in the disallowed_areas with the skirt size.
|
||||
|
|
@ -315,10 +374,13 @@ class BuildVolume(SceneNode):
|
|||
|
||||
areas.append(poly)
|
||||
|
||||
if self._prime_tower_area:
|
||||
self._prime_tower_area = self._prime_tower_area.getMinkowskiHull(Polygon(approximatedCircleVertices(bed_adhesion_size)))
|
||||
|
||||
# Add the skirt areas around the borders of the build plate.
|
||||
if bed_adhesion_size > 0:
|
||||
half_machine_width = self._active_container_stack.getProperty("machine_width", "value") / 2
|
||||
half_machine_depth = self._active_container_stack.getProperty("machine_depth", "value") / 2
|
||||
half_machine_width = self._global_container_stack.getProperty("machine_width", "value") / 2
|
||||
half_machine_depth = self._global_container_stack.getProperty("machine_depth", "value") / 2
|
||||
|
||||
areas.append(Polygon(numpy.array([
|
||||
[-half_machine_width, -half_machine_depth],
|
||||
|
|
@ -348,12 +410,29 @@ class BuildVolume(SceneNode):
|
|||
[half_machine_width - bed_adhesion_size, -half_machine_depth + bed_adhesion_size]
|
||||
], numpy.float32)))
|
||||
|
||||
# Check if the prime tower area intersects with any of the other areas.
|
||||
# If this is the case, keep the polygon seperate, so it can be drawn in red.
|
||||
# If not, add it back to disallowed area's, so it's rendered as normal.
|
||||
collision = False
|
||||
if self._prime_tower_area:
|
||||
for area in areas:
|
||||
if self._prime_tower_area.intersectsPolygon(area) is not None:
|
||||
collision = True
|
||||
break
|
||||
if not collision:
|
||||
areas.append(self._prime_tower_area)
|
||||
self._prime_tower_area = None
|
||||
self._has_errors = collision
|
||||
self._disallowed_areas = areas
|
||||
|
||||
## Convenience function to calculate the size of the bed adhesion in directions x, y.
|
||||
def _getBedAdhesionSize(self, container_stack):
|
||||
skirt_size = 0.0
|
||||
|
||||
# If we are printing one at a time, we need to add the bed adhesion size to the disallowed areas of the objects
|
||||
if container_stack.getProperty("print_sequence", "value") == "one_at_a_time":
|
||||
return 0.1 # Return a very small value, so we do draw disallowed area's near the edges.
|
||||
|
||||
adhesion_type = container_stack.getProperty("adhesion_type", "value")
|
||||
if adhesion_type == "skirt":
|
||||
skirt_distance = container_stack.getProperty("skirt_gap", "value")
|
||||
|
|
@ -378,3 +457,4 @@ class BuildVolume(SceneNode):
|
|||
_skirt_settings = ["adhesion_type", "skirt_gap", "skirt_line_count", "skirt_brim_line_width", "brim_width", "brim_line_count", "raft_margin", "draft_shield_enabled", "draft_shield_dist", "xy_offset"]
|
||||
_raft_settings = ["adhesion_type", "raft_base_thickness", "raft_interface_thickness", "raft_surface_layers", "raft_surface_thickness", "raft_airgap"]
|
||||
_prime_settings = ["extruder_prime_pos_x", "extruder_prime_pos_y", "extruder_prime_pos_z"]
|
||||
_tower_settings = ["prime_tower_enable", "prime_tower_size", "prime_tower_position_x", "prime_tower_position_y"]
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ class ConvexHullDecorator(SceneNodeDecorator):
|
|||
if self._global_stack and self._node:
|
||||
if self._global_stack.getProperty("print_sequence", "value") == "one_at_a_time" and not self._node.getParent().callDecoration("isGroup"):
|
||||
hull = hull.getMinkowskiHull(Polygon(numpy.array(self._global_stack.getProperty("machine_head_polygon", "value"), numpy.float32)))
|
||||
hull = self._add2DAdhesionMargin(hull)
|
||||
return hull
|
||||
|
||||
## Get the convex hull of the node with the full head size
|
||||
|
|
@ -229,17 +230,16 @@ class ConvexHullDecorator(SceneNodeDecorator):
|
|||
machine_head_coords = numpy.array(
|
||||
self._global_stack.getProperty("machine_head_with_fans_polygon", "value"),
|
||||
numpy.float32)
|
||||
head_y_size = abs(machine_head_coords).min() # safe margin to take off in all directions
|
||||
|
||||
if adhesion_type == "raft":
|
||||
extra_margin = max(0, self._global_stack.getProperty("raft_margin", "value") - head_y_size)
|
||||
extra_margin = max(0, self._global_stack.getProperty("raft_margin", "value"))
|
||||
elif adhesion_type == "brim":
|
||||
extra_margin = max(0, self._global_stack.getProperty("brim_width", "value") - head_y_size)
|
||||
extra_margin = max(0, self._global_stack.getProperty("brim_line_count", "value") * self._global_stack.getProperty("skirt_brim_line_width", "value"))
|
||||
elif adhesion_type == "skirt":
|
||||
extra_margin = max(
|
||||
0, self._global_stack.getProperty("skirt_gap", "value") +
|
||||
self._global_stack.getProperty("skirt_line_count", "value") * self._global_stack.getProperty("skirt_brim_line_width", "value") -
|
||||
head_y_size)
|
||||
self._global_stack.getProperty("skirt_line_count", "value") * self._global_stack.getProperty("skirt_brim_line_width", "value"))
|
||||
|
||||
# adjust head_and_fans with extra margin
|
||||
if extra_margin > 0:
|
||||
# In Cura 2.2+, there is a function to create this circle-like polygon.
|
||||
|
|
@ -288,4 +288,4 @@ class ConvexHullDecorator(SceneNodeDecorator):
|
|||
_affected_settings = [
|
||||
"adhesion_type", "raft_base_thickness", "raft_interface_thickness", "raft_surface_layers",
|
||||
"raft_surface_thickness", "raft_airgap", "raft_margin", "print_sequence",
|
||||
"skirt_gap", "skirt_line_count", "skirt_brim_line_width", "skirt_distance"]
|
||||
"skirt_gap", "skirt_line_count", "skirt_brim_line_width", "skirt_distance", "brim_line_count"]
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
|||
from UM.Mesh.ReadMeshJob import ReadMeshJob
|
||||
from UM.Logger import Logger
|
||||
from UM.Preferences import Preferences
|
||||
from UM.Platform import Platform
|
||||
from UM.JobQueue import JobQueue
|
||||
from UM.SaveFile import SaveFile
|
||||
from UM.Scene.Selection import Selection
|
||||
|
|
@ -24,6 +23,7 @@ from UM.Operations.AddSceneNodeOperation import AddSceneNodeOperation
|
|||
from UM.Operations.RemoveSceneNodeOperation import RemoveSceneNodeOperation
|
||||
from UM.Operations.GroupedOperation import GroupedOperation
|
||||
from UM.Operations.SetTransformOperation import SetTransformOperation
|
||||
from UM.Operations.TranslateOperation import TranslateOperation
|
||||
from cura.SetParentOperation import SetParentOperation
|
||||
|
||||
from UM.Settings.SettingDefinition import SettingDefinition, DefinitionPropertyType
|
||||
|
|
@ -37,7 +37,6 @@ from . import BuildVolume
|
|||
from . import CameraAnimation
|
||||
from . import PrintInformation
|
||||
from . import CuraActions
|
||||
from . import MultiMaterialDecorator
|
||||
from . import ZOffsetDecorator
|
||||
from . import CuraSplashScreen
|
||||
from . import CameraImageProvider
|
||||
|
|
@ -50,12 +49,12 @@ from PyQt5.QtGui import QColor, QIcon
|
|||
from PyQt5.QtWidgets import QMessageBox
|
||||
from PyQt5.QtQml import qmlRegisterUncreatableType, qmlRegisterSingletonType, qmlRegisterType
|
||||
|
||||
import platform
|
||||
import sys
|
||||
import os.path
|
||||
import numpy
|
||||
import copy
|
||||
import urllib
|
||||
|
||||
numpy.seterr(all="ignore")
|
||||
|
||||
try:
|
||||
|
|
@ -85,11 +84,12 @@ class CuraApplication(QtApplication):
|
|||
self._open_file_queue = [] # Files to open when plug-ins are loaded.
|
||||
|
||||
# Need to do this before ContainerRegistry tries to load the machines
|
||||
SettingDefinition.addSupportedProperty("settable_per_mesh", DefinitionPropertyType.Any, default = True)
|
||||
SettingDefinition.addSupportedProperty("settable_per_extruder", DefinitionPropertyType.Any, default = True)
|
||||
SettingDefinition.addSupportedProperty("settable_per_meshgroup", DefinitionPropertyType.Any, default = True)
|
||||
SettingDefinition.addSupportedProperty("settable_globally", DefinitionPropertyType.Any, default = True)
|
||||
SettingDefinition.addSupportedProperty("global_inherits_stack", DefinitionPropertyType.Function, default = "-1")
|
||||
SettingDefinition.addSupportedProperty("settable_per_mesh", DefinitionPropertyType.Any, default = True, read_only = True)
|
||||
SettingDefinition.addSupportedProperty("settable_per_extruder", DefinitionPropertyType.Any, default = True, read_only = True)
|
||||
SettingDefinition.addSupportedProperty("settable_per_meshgroup", DefinitionPropertyType.Any, default = True, read_only = True)
|
||||
SettingDefinition.addSupportedProperty("settable_globally", DefinitionPropertyType.Any, default = True, read_only = True)
|
||||
SettingDefinition.addSupportedProperty("limit_to_extruder", DefinitionPropertyType.Function, default = "-1")
|
||||
SettingDefinition.addSupportedProperty("resolve", DefinitionPropertyType.Function, default = None)
|
||||
SettingDefinition.addSettingType("extruder", None, str, Validator)
|
||||
|
||||
SettingFunction.registerOperator("extruderValues", cura.Settings.ExtruderManager.getExtruderValues)
|
||||
|
|
@ -180,8 +180,14 @@ 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.addMetaDataEntry("quality_type", "normal")
|
||||
empty_quality_container.addMetaDataEntry("type", "quality")
|
||||
ContainerRegistry.getInstance().addContainer(empty_quality_container)
|
||||
empty_quality_changes_container = copy.deepcopy(empty_container)
|
||||
empty_quality_changes_container._id = "empty_quality_changes"
|
||||
empty_quality_changes_container.addMetaDataEntry("type", "quality_changes")
|
||||
ContainerRegistry.getInstance().addContainer(empty_quality_changes_container)
|
||||
|
||||
ContainerRegistry.getInstance().load()
|
||||
|
||||
|
|
@ -248,7 +254,6 @@ class CuraApplication(QtApplication):
|
|||
blackmagic
|
||||
print_sequence
|
||||
infill_mesh
|
||||
dual
|
||||
experimental
|
||||
""".replace("\n", ";").replace(" ", ""))
|
||||
|
||||
|
|
@ -309,7 +314,7 @@ class CuraApplication(QtApplication):
|
|||
path = None
|
||||
if instance_type == "material":
|
||||
path = Resources.getStoragePath(self.ResourceTypes.MaterialInstanceContainer, file_name)
|
||||
elif instance_type == "quality":
|
||||
elif instance_type == "quality" or instance_type == "quality_changes":
|
||||
path = Resources.getStoragePath(self.ResourceTypes.QualityInstanceContainer, file_name)
|
||||
elif instance_type == "user":
|
||||
path = Resources.getStoragePath(self.ResourceTypes.UserInstanceContainer, file_name)
|
||||
|
|
@ -317,32 +322,36 @@ class CuraApplication(QtApplication):
|
|||
path = Resources.getStoragePath(self.ResourceTypes.VariantInstanceContainer, file_name)
|
||||
|
||||
if path:
|
||||
instance.setPath(path)
|
||||
with SaveFile(path, "wt", -1, "utf-8") as f:
|
||||
f.write(data)
|
||||
|
||||
for stack in ContainerRegistry.getInstance().findContainerStacks():
|
||||
if not stack.isDirty():
|
||||
continue
|
||||
self.saveStack(stack)
|
||||
|
||||
try:
|
||||
data = stack.serialize()
|
||||
except NotImplementedError:
|
||||
continue
|
||||
except Exception:
|
||||
Logger.logException("e", "An exception occurred when serializing container %s", instance.getId())
|
||||
continue
|
||||
def saveStack(self, stack):
|
||||
if not stack.isDirty():
|
||||
return
|
||||
try:
|
||||
data = stack.serialize()
|
||||
except NotImplementedError:
|
||||
return
|
||||
except Exception:
|
||||
Logger.logException("e", "An exception occurred when serializing container %s", stack.getId())
|
||||
return
|
||||
|
||||
mime_type = ContainerRegistry.getMimeTypeForContainer(type(stack))
|
||||
file_name = urllib.parse.quote_plus(stack.getId()) + "." + mime_type.preferredSuffix
|
||||
stack_type = stack.getMetaDataEntry("type", None)
|
||||
path = None
|
||||
if not stack_type or stack_type == "machine":
|
||||
path = Resources.getStoragePath(self.ResourceTypes.MachineStack, file_name)
|
||||
elif stack_type == "extruder_train":
|
||||
path = Resources.getStoragePath(self.ResourceTypes.ExtruderStack, file_name)
|
||||
if path:
|
||||
with SaveFile(path, "wt", -1, "utf-8") as f:
|
||||
f.write(data)
|
||||
mime_type = ContainerRegistry.getMimeTypeForContainer(type(stack))
|
||||
file_name = urllib.parse.quote_plus(stack.getId()) + "." + mime_type.preferredSuffix
|
||||
stack_type = stack.getMetaDataEntry("type", None)
|
||||
path = None
|
||||
if not stack_type or stack_type == "machine":
|
||||
path = Resources.getStoragePath(self.ResourceTypes.MachineStack, file_name)
|
||||
elif stack_type == "extruder_train":
|
||||
path = Resources.getStoragePath(self.ResourceTypes.ExtruderStack, file_name)
|
||||
if path:
|
||||
stack.setPath(path)
|
||||
with SaveFile(path, "wt", -1, "utf-8") as f:
|
||||
f.write(data)
|
||||
|
||||
|
||||
@pyqtSlot(str, result = QUrl)
|
||||
|
|
@ -477,6 +486,7 @@ class CuraApplication(QtApplication):
|
|||
|
||||
qmlRegisterType(cura.Settings.ContainerSettingsModel, "Cura", 1, 0, "ContainerSettingsModel")
|
||||
qmlRegisterType(cura.Settings.MaterialSettingsVisibilityHandler, "Cura", 1, 0, "MaterialSettingsVisibilityHandler")
|
||||
qmlRegisterType(cura.Settings.QualitySettingsModel, "Cura", 1, 0, "QualitySettingsModel")
|
||||
|
||||
qmlRegisterSingletonType(cura.Settings.ContainerManager, "Cura", 1, 0, "ContainerManager", cura.Settings.ContainerManager.createContainerManager)
|
||||
|
||||
|
|
@ -505,8 +515,6 @@ class CuraApplication(QtApplication):
|
|||
if self.getController().getActiveTool():
|
||||
self._previous_active_tool = self.getController().getActiveTool().getPluginId()
|
||||
self.getController().setActiveTool(None)
|
||||
else:
|
||||
self._previous_active_tool = None
|
||||
|
||||
def _onToolOperationStopped(self, event):
|
||||
if self._center_after_select:
|
||||
|
|
@ -689,15 +697,14 @@ class CuraApplication(QtApplication):
|
|||
continue # Node that doesnt have a mesh and is not a group.
|
||||
if node.getParent() and node.getParent().callDecoration("isGroup"):
|
||||
continue # Grouped nodes don't need resetting as their parent (the group) is resetted)
|
||||
|
||||
nodes.append(node)
|
||||
|
||||
if nodes:
|
||||
op = GroupedOperation()
|
||||
for node in nodes:
|
||||
# Ensure that the object is above the build platform
|
||||
node.removeDecorator(ZOffsetDecorator.ZOffsetDecorator)
|
||||
op.addOperation(SetTransformOperation(node, Vector(0,0,0)))
|
||||
|
||||
op.addOperation(SetTransformOperation(node, Vector(0, node.getWorldPosition().y - node.getBoundingBox().bottom, 0)))
|
||||
op.push()
|
||||
|
||||
## Reset all transformations on nodes with mesh data.
|
||||
|
|
@ -716,12 +723,15 @@ class CuraApplication(QtApplication):
|
|||
|
||||
if nodes:
|
||||
op = GroupedOperation()
|
||||
|
||||
for node in nodes:
|
||||
# Ensure that the object is above the build platform
|
||||
node.removeDecorator(ZOffsetDecorator.ZOffsetDecorator)
|
||||
op.addOperation(SetTransformOperation(node, Vector(0,0,0), Quaternion(), Vector(1, 1, 1)))
|
||||
|
||||
center_y = 0
|
||||
if node.callDecoration("isGroup"):
|
||||
center_y = node.getWorldPosition().y - node.getBoundingBox().bottom
|
||||
else:
|
||||
center_y = node.getMeshData().getCenterPosition().y
|
||||
op.addOperation(SetTransformOperation(node, Vector(0, center_y, 0), Quaternion(), Vector(1, 1, 1)))
|
||||
op.push()
|
||||
|
||||
## Reload all mesh data on the screen from file.
|
||||
|
|
@ -788,13 +798,18 @@ class CuraApplication(QtApplication):
|
|||
except Exception as e:
|
||||
Logger.log("d", "mergeSelected: Exception:", e)
|
||||
return
|
||||
multi_material_decorator = MultiMaterialDecorator.MultiMaterialDecorator()
|
||||
group_node.addDecorator(multi_material_decorator)
|
||||
# Reset the position of each node
|
||||
for node in group_node.getChildren():
|
||||
new_position = node.getMeshData().getCenterPosition()
|
||||
new_position = new_position.scale(node.getScale())
|
||||
node.setPosition(new_position)
|
||||
|
||||
# Compute the center of the objects when their origins are aligned.
|
||||
object_centers = [node.getMeshData().getCenterPosition().scale(node.getScale()) for node in group_node.getChildren()]
|
||||
middle_x = sum([v.x for v in object_centers]) / len(object_centers)
|
||||
middle_y = sum([v.y for v in object_centers]) / len(object_centers)
|
||||
middle_z = sum([v.z for v in object_centers]) / len(object_centers)
|
||||
offset = Vector(middle_x, middle_y, middle_z)
|
||||
|
||||
# Move each node to the same position.
|
||||
for center, node in zip(object_centers, group_node.getChildren()):
|
||||
# Align the object and also apply the offset to center it inside the group.
|
||||
node.setPosition(center - offset)
|
||||
|
||||
# Use the previously found center of the group bounding box as the new location of the group
|
||||
group_node.setPosition(group_node.getBoundingBox().center)
|
||||
|
|
@ -921,3 +936,7 @@ class CuraApplication(QtApplication):
|
|||
self._additional_components[area_id].append(component)
|
||||
|
||||
self.additionalComponentsChanged.emit(area_id)
|
||||
|
||||
@pyqtSlot(str)
|
||||
def log(self, msg):
|
||||
Logger.log("d", msg)
|
||||
|
|
|
|||
|
|
@ -14,8 +14,9 @@ class LayerPolygon:
|
|||
SupportInfillType = 7
|
||||
MoveCombingType = 8
|
||||
MoveRetractionType = 9
|
||||
SupportInterfaceType = 10
|
||||
|
||||
__jump_map = numpy.logical_or( numpy.arange(10) == NoneType, numpy.arange(10) >= MoveCombingType )
|
||||
__jump_map = numpy.logical_or(numpy.logical_or(numpy.arange(11) == NoneType, numpy.arange(11) == MoveCombingType), numpy.arange(11) == MoveRetractionType)
|
||||
|
||||
def __init__(self, mesh, extruder, line_types, data, line_widths):
|
||||
self._mesh = mesh
|
||||
|
|
@ -41,7 +42,7 @@ class LayerPolygon:
|
|||
|
||||
# When type is used as index returns true if type == LayerPolygon.InfillType or type == LayerPolygon.SkinType or type == LayerPolygon.SupportInfillType
|
||||
# Should be generated in better way, not hardcoded.
|
||||
self._isInfillOrSkinTypeMap = numpy.array([0, 0, 0, 1, 0, 0, 1, 1, 0, 0], dtype=numpy.bool)
|
||||
self._isInfillOrSkinTypeMap = numpy.array([0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1], dtype=numpy.bool)
|
||||
|
||||
self._build_cache_line_mesh_mask = None
|
||||
self._build_cache_needed_points = None
|
||||
|
|
@ -178,10 +179,11 @@ class LayerPolygon:
|
|||
SkinType: Color(1.0, 1.0, 0.0, 1.0),
|
||||
SupportType: Color(0.0, 1.0, 1.0, 1.0),
|
||||
SkirtType: Color(0.0, 1.0, 1.0, 1.0),
|
||||
InfillType: Color(1.0, 0.74, 0.0, 1.0),
|
||||
InfillType: Color(1.0, 0.75, 0.0, 1.0),
|
||||
SupportInfillType: Color(0.0, 1.0, 1.0, 1.0),
|
||||
MoveCombingType: Color(0.0, 0.0, 1.0, 1.0),
|
||||
MoveRetractionType: Color(0.5, 0.5, 1.0, 1.0),
|
||||
SupportInterfaceType: Color(0.25, 0.75, 1.0, 1.0),
|
||||
}
|
||||
|
||||
# Should be generated in better way, not hardcoded.
|
||||
|
|
@ -192,8 +194,9 @@ class LayerPolygon:
|
|||
[1.0, 1.0, 0.0, 1.0],
|
||||
[0.0, 1.0, 1.0, 1.0],
|
||||
[0.0, 1.0, 1.0, 1.0],
|
||||
[1.0, 0.74, 0.0, 1.0],
|
||||
[1.0, 0.75, 0.0, 1.0],
|
||||
[0.0, 1.0, 1.0, 1.0],
|
||||
[0.0, 0.0, 1.0, 1.0],
|
||||
[0.5, 0.5, 1.0, 1.0]
|
||||
[0.5, 0.5, 1.0, 1.0],
|
||||
[0.25, 0.75, 1.0, 1.0]
|
||||
])
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
from UM.Scene.SceneNodeDecorator import SceneNodeDecorator
|
||||
|
||||
class MultiMaterialDecorator(SceneNodeDecorator):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def isMultiMaterial(self):
|
||||
return True
|
||||
|
||||
def __deepcopy__(self, memo):
|
||||
return MultiMaterialDecorator()
|
||||
|
|
@ -15,6 +15,9 @@ from cura.ConvexHullDecorator import ConvexHullDecorator
|
|||
from . import PlatformPhysicsOperation
|
||||
from . import ZOffsetDecorator
|
||||
|
||||
import random # used for list shuffling
|
||||
|
||||
|
||||
class PlatformPhysics:
|
||||
def __init__(self, controller, volume):
|
||||
super().__init__()
|
||||
|
|
@ -29,8 +32,11 @@ class PlatformPhysics:
|
|||
self._change_timer.setInterval(100)
|
||||
self._change_timer.setSingleShot(True)
|
||||
self._change_timer.timeout.connect(self._onChangeTimerFinished)
|
||||
self._move_factor = 1.1 # By how much should we multiply overlap to calculate a new spot?
|
||||
self._max_overlap_checks = 10 # How many times should we try to find a new spot per tick?
|
||||
|
||||
Preferences.getInstance().addPreference("physics/automatic_push_free", True)
|
||||
Preferences.getInstance().addPreference("physics/automatic_drop_down", True)
|
||||
|
||||
def _onSceneChanged(self, source):
|
||||
self._change_timer.start()
|
||||
|
|
@ -41,7 +47,16 @@ class PlatformPhysics:
|
|||
|
||||
root = self._controller.getScene().getRoot()
|
||||
|
||||
for node in BreadthFirstIterator(root):
|
||||
# Keep a list of nodes that are moving. We use this so that we don't move two intersecting objects in the
|
||||
# same direction.
|
||||
transformed_nodes = []
|
||||
|
||||
group_nodes = []
|
||||
# We try to shuffle all the nodes to prevent "locked" situations, where iteration B inverts iteration A.
|
||||
# By shuffling the order of the nodes, this might happen a few times, but at some point it will resolve.
|
||||
nodes = list(BreadthFirstIterator(root))
|
||||
random.shuffle(nodes)
|
||||
for node in nodes:
|
||||
if node is root or type(node) is not SceneNode or node.getBoundingBox() is None:
|
||||
continue
|
||||
|
||||
|
|
@ -62,9 +77,12 @@ class PlatformPhysics:
|
|||
if build_volume_bounding_box.intersectsBox(bbox) != AxisAlignedBox.IntersectionResult.FullIntersection:
|
||||
node._outside_buildarea = True
|
||||
|
||||
if node.callDecoration("isGroup"):
|
||||
group_nodes.append(node) # Keep list of affected group_nodes
|
||||
|
||||
# Move it downwards if bottom is above platform
|
||||
move_vector = Vector()
|
||||
if not (node.getParent() and node.getParent().callDecoration("isGroup")): #If an object is grouped, don't move it down
|
||||
if Preferences.getInstance().getValue("physics/automatic_drop_down") and not (node.getParent() and node.getParent().callDecoration("isGroup")): #If an object is grouped, don't move it down
|
||||
z_offset = node.callDecoration("getZOffset") if node.getDecorator(ZOffsetDecorator.ZOffsetDecorator) else 0
|
||||
move_vector = move_vector.set(y=-bbox.bottom + z_offset)
|
||||
|
||||
|
|
@ -91,27 +109,42 @@ class PlatformPhysics:
|
|||
if not other_node.callDecoration("getConvexHull") or not other_node.getBoundingBox():
|
||||
continue
|
||||
|
||||
# Get the overlap distance for both convex hulls. If this returns None, there is no intersection.
|
||||
head_hull = node.callDecoration("getConvexHullHead")
|
||||
if head_hull:
|
||||
overlap = head_hull.intersectsPolygon(other_node.callDecoration("getConvexHullHead"))
|
||||
if not overlap:
|
||||
other_head_hull = other_node.callDecoration("getConvexHullHead")
|
||||
if other_head_hull:
|
||||
overlap = node.callDecoration("getConvexHullHead").intersectsPolygon(other_head_hull)
|
||||
else:
|
||||
own_convex_hull = node.callDecoration("getConvexHull")
|
||||
other_convex_hull = other_node.callDecoration("getConvexHull")
|
||||
if own_convex_hull and other_convex_hull:
|
||||
overlap = own_convex_hull.intersectsPolygon(other_convex_hull)
|
||||
else:
|
||||
# This can happen in some cases if the object is not yet done with being loaded.
|
||||
# Simply waiting for the next tick seems to resolve this correctly.
|
||||
overlap = None
|
||||
if other_node in transformed_nodes:
|
||||
continue # Other node is already moving, wait for next pass.
|
||||
|
||||
overlap = (0, 0) # Start loop with no overlap
|
||||
current_overlap_checks = 0
|
||||
# Continue to check the overlap until we no longer find one.
|
||||
while overlap and current_overlap_checks < self._max_overlap_checks:
|
||||
current_overlap_checks += 1
|
||||
head_hull = node.callDecoration("getConvexHullHead")
|
||||
if head_hull: # One at a time intersection.
|
||||
overlap = head_hull.translate(move_vector.x, move_vector.z).intersectsPolygon(other_node.callDecoration("getConvexHull"))
|
||||
if not overlap:
|
||||
other_head_hull = other_node.callDecoration("getConvexHullHead")
|
||||
if other_head_hull:
|
||||
overlap = node.callDecoration("getConvexHull").translate(move_vector.x, move_vector.z).intersectsPolygon(other_head_hull)
|
||||
if overlap:
|
||||
# Moving ensured that overlap was still there. Try anew!
|
||||
move_vector = move_vector.set(x=move_vector.x + overlap[0] * self._move_factor,
|
||||
z=move_vector.z + overlap[1] * self._move_factor)
|
||||
else:
|
||||
# Moving ensured that overlap was still there. Try anew!
|
||||
move_vector = move_vector.set(x=move_vector.x + overlap[0] * self._move_factor,
|
||||
z=move_vector.z + overlap[1] * self._move_factor)
|
||||
else:
|
||||
own_convex_hull = node.callDecoration("getConvexHull")
|
||||
other_convex_hull = other_node.callDecoration("getConvexHull")
|
||||
if own_convex_hull and other_convex_hull:
|
||||
overlap = own_convex_hull.translate(move_vector.x, move_vector.z).intersectsPolygon(other_convex_hull)
|
||||
if overlap: # Moving ensured that overlap was still there. Try anew!
|
||||
move_vector = move_vector.set(x=move_vector.x + overlap[0] * self._move_factor,
|
||||
z=move_vector.z + overlap[1] * self._move_factor)
|
||||
else:
|
||||
# This can happen in some cases if the object is not yet done with being loaded.
|
||||
# Simply waiting for the next tick seems to resolve this correctly.
|
||||
overlap = None
|
||||
|
||||
if overlap is None:
|
||||
continue
|
||||
move_vector = move_vector.set(x=overlap[0] * 1.1, z=overlap[1] * 1.1)
|
||||
convex_hull = node.callDecoration("getConvexHull")
|
||||
if convex_hull:
|
||||
if not convex_hull.isValid():
|
||||
|
|
@ -121,13 +154,19 @@ class PlatformPhysics:
|
|||
overlap = convex_hull.intersectsPolygon(area)
|
||||
if overlap is None:
|
||||
continue
|
||||
|
||||
node._outside_buildarea = True
|
||||
|
||||
if not Vector.Null.equals(move_vector, epsilon=1e-5):
|
||||
transformed_nodes.append(node)
|
||||
op = PlatformPhysicsOperation.PlatformPhysicsOperation(node, move_vector)
|
||||
op.push()
|
||||
|
||||
# Group nodes should override the _outside_buildarea property of their children.
|
||||
for group_node in group_nodes:
|
||||
for child_node in group_node.getAllChildren():
|
||||
child_node._outside_buildarea = group_node._outside_buildarea
|
||||
|
||||
|
||||
def _onToolOperationStarted(self, tool):
|
||||
self._enabled = False
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,16 @@
|
|||
from UM.i18n import i18nCatalog
|
||||
from UM.OutputDevice.OutputDevice import OutputDevice
|
||||
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject
|
||||
from PyQt5.QtWidgets import QMessageBox
|
||||
import UM.Settings.ContainerRegistry
|
||||
|
||||
from enum import IntEnum # For the connection state tracking.
|
||||
from UM.Logger import Logger
|
||||
|
||||
from UM.Application import Application
|
||||
from UM.Signal import signalemitter
|
||||
|
||||
i18n_catalog = i18nCatalog("cura")
|
||||
|
||||
## Printer output device adds extra interface options on top of output device.
|
||||
#
|
||||
# The assumption is made the printer is a FDM printer.
|
||||
|
|
@ -19,6 +25,7 @@ class PrinterOutputDevice(QObject, OutputDevice):
|
|||
def __init__(self, device_id, parent = None):
|
||||
super().__init__(device_id = device_id, parent = parent)
|
||||
|
||||
self._container_registry = UM.Settings.ContainerRegistry.getInstance()
|
||||
self._target_bed_temperature = 0
|
||||
self._bed_temperature = 0
|
||||
self._num_extruders = 1
|
||||
|
|
@ -39,6 +46,8 @@ class PrinterOutputDevice(QObject, OutputDevice):
|
|||
self._error_text = ""
|
||||
self._accepts_commands = True
|
||||
|
||||
self._printer_state = ""
|
||||
|
||||
def requestWrite(self, node, file_name = None, filter_by_machine = False):
|
||||
raise NotImplementedError("requestWrite needs to be implemented")
|
||||
|
||||
|
|
@ -86,10 +95,21 @@ class PrinterOutputDevice(QObject, OutputDevice):
|
|||
|
||||
acceptsCommandsChanged = pyqtSignal()
|
||||
|
||||
printerStateChanged = pyqtSignal()
|
||||
|
||||
@pyqtProperty(str, notify=printerStateChanged)
|
||||
def printerState(self):
|
||||
return self._printer_state
|
||||
|
||||
@pyqtProperty(str, notify = jobStateChanged)
|
||||
def jobState(self):
|
||||
return self._job_state
|
||||
|
||||
def _updatePrinterState(self, printer_state):
|
||||
if self._printer_state != printer_state:
|
||||
self._printer_state = printer_state
|
||||
self.printerStateChanged.emit()
|
||||
|
||||
def _updateJobState(self, job_state):
|
||||
if self._job_state != job_state:
|
||||
self._job_state = job_state
|
||||
|
|
@ -145,8 +165,9 @@ class PrinterOutputDevice(QObject, OutputDevice):
|
|||
@pyqtSlot(int)
|
||||
def setTargetBedTemperature(self, temperature):
|
||||
self._setTargetBedTemperature(temperature)
|
||||
self._target_bed_temperature = temperature
|
||||
self.targetBedTemperatureChanged.emit()
|
||||
if self._target_bed_temperature != temperature:
|
||||
self._target_bed_temperature = temperature
|
||||
self.targetBedTemperatureChanged.emit()
|
||||
|
||||
## Time the print has been printing.
|
||||
# Note that timeTotal - timeElapsed should give time remaining.
|
||||
|
|
@ -207,8 +228,9 @@ class PrinterOutputDevice(QObject, OutputDevice):
|
|||
# This simply sets the bed temperature, but ensures that a signal is emitted.
|
||||
# /param temperature temperature of the bed.
|
||||
def _setBedTemperature(self, temperature):
|
||||
self._bed_temperature = temperature
|
||||
self.bedTemperatureChanged.emit()
|
||||
if self._bed_temperature != temperature:
|
||||
self._bed_temperature = temperature
|
||||
self.bedTemperatureChanged.emit()
|
||||
|
||||
## Get the target bed temperature if connected printer (if any)
|
||||
@pyqtProperty(int, notify = targetBedTemperatureChanged)
|
||||
|
|
@ -223,8 +245,10 @@ class PrinterOutputDevice(QObject, OutputDevice):
|
|||
@pyqtSlot(int, int)
|
||||
def setTargetHotendTemperature(self, index, temperature):
|
||||
self._setTargetHotendTemperature(index, temperature)
|
||||
self._target_hotend_temperatures[index] = temperature
|
||||
self.targetHotendTemperaturesChanged.emit()
|
||||
|
||||
if self._target_hotend_temperatures[index] != temperature:
|
||||
self._target_hotend_temperatures[index] = temperature
|
||||
self.targetHotendTemperaturesChanged.emit()
|
||||
|
||||
## Implementation function of setTargetHotendTemperature.
|
||||
# /param index Index of the hotend to set the temperature of
|
||||
|
|
@ -246,13 +270,29 @@ class PrinterOutputDevice(QObject, OutputDevice):
|
|||
# /param index Index of the hotend
|
||||
# /param temperature temperature of the hotend (in deg C)
|
||||
def _setHotendTemperature(self, index, temperature):
|
||||
self._hotend_temperatures[index] = temperature
|
||||
self.hotendTemperaturesChanged.emit()
|
||||
if self._hotend_temperatures[index] != temperature:
|
||||
self._hotend_temperatures[index] = temperature
|
||||
self.hotendTemperaturesChanged.emit()
|
||||
|
||||
@pyqtProperty("QVariantList", notify = materialIdChanged)
|
||||
def materialIds(self):
|
||||
return self._material_ids
|
||||
|
||||
@pyqtProperty("QVariantList", notify = materialIdChanged)
|
||||
def materialNames(self):
|
||||
result = []
|
||||
for material_id in self._material_ids:
|
||||
if material_id is None:
|
||||
result.append(i18n_catalog.i18nc("@item:material", "No material loaded"))
|
||||
continue
|
||||
|
||||
containers = self._container_registry.findInstanceContainers(type = "material", GUID = material_id)
|
||||
if containers:
|
||||
result.append(containers[0].getName())
|
||||
else:
|
||||
result.append(i18n_catalog.i18nc("@item:material", "Unknown material"))
|
||||
return result
|
||||
|
||||
## Protected setter for the current material id.
|
||||
# /param index Index of the extruder
|
||||
# /param material_id id of the material
|
||||
|
|
@ -262,7 +302,6 @@ class PrinterOutputDevice(QObject, OutputDevice):
|
|||
self._material_ids[index] = material_id
|
||||
self.materialIdChanged.emit(index, material_id)
|
||||
|
||||
|
||||
@pyqtProperty("QVariantList", notify = hotendIdChanged)
|
||||
def hotendIds(self):
|
||||
return self._hotend_ids
|
||||
|
|
@ -276,6 +315,11 @@ class PrinterOutputDevice(QObject, OutputDevice):
|
|||
self._hotend_ids[index] = hotend_id
|
||||
self.hotendIdChanged.emit(index, hotend_id)
|
||||
|
||||
## Let the user decide if the hotends and/or material should be synced with the printer
|
||||
# NB: the UX needs to be implemented by the plugin
|
||||
def materialHotendChangedMessage(self, callback):
|
||||
Logger.log("w", "materialHotendChangedMessage needs to be implemented, returning 'Yes'")
|
||||
callback(QMessageBox.Yes)
|
||||
|
||||
## Attempt to establish connection
|
||||
def connect(self):
|
||||
|
|
@ -292,8 +336,9 @@ class PrinterOutputDevice(QObject, OutputDevice):
|
|||
## Set the connection state of this output device.
|
||||
# /param connection_state ConnectionState enum.
|
||||
def setConnectionState(self, connection_state):
|
||||
self._connection_state = connection_state
|
||||
self.connectionStateChanged.emit(self._id)
|
||||
if self._connection_state != connection_state:
|
||||
self._connection_state = connection_state
|
||||
self.connectionStateChanged.emit(self._id)
|
||||
|
||||
@pyqtProperty(str, notify = connectionTextChanged)
|
||||
def connectionText(self):
|
||||
|
|
@ -329,7 +374,7 @@ class PrinterOutputDevice(QObject, OutputDevice):
|
|||
return self._head_z
|
||||
|
||||
## Update the saved position of the head
|
||||
# This function should be called when a new position for the head is recieved.
|
||||
# This function should be called when a new position for the head is received.
|
||||
def _updateHeadPosition(self, x, y ,z):
|
||||
position_changed = False
|
||||
if self._head_x != x:
|
||||
|
|
@ -341,6 +386,7 @@ class PrinterOutputDevice(QObject, OutputDevice):
|
|||
if self._head_z != z:
|
||||
self._head_z = z
|
||||
position_changed = True
|
||||
|
||||
if position_changed:
|
||||
self.headPositionChanged.emit()
|
||||
|
||||
|
|
|
|||
|
|
@ -18,8 +18,8 @@ class ProfileWriter(PluginObject):
|
|||
# The profile writer may write its own file format to the specified file.
|
||||
#
|
||||
# \param path \type{string} The file to output to.
|
||||
# \param profile \type{Profile} The profile to write to the file.
|
||||
# \param profiles \type{Profile} or \type{List} The profile(s) to write to the file.
|
||||
# \return \code True \endcode if the writing was successful, or \code
|
||||
# False \endcode if it wasn't.
|
||||
def write(self, path, node):
|
||||
def write(self, path, profiles):
|
||||
raise NotImplementedError("Profile writer plugin was not correctly implemented. No write was specified.")
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ import UM.Platform
|
|||
import UM.MimeTypeDatabase
|
||||
import UM.Logger
|
||||
|
||||
import cura.Settings
|
||||
|
||||
from UM.MimeTypeDatabase import MimeTypeNotFoundError
|
||||
|
||||
from UM.i18n import i18nCatalog
|
||||
|
|
@ -28,7 +30,9 @@ class ContainerManager(QObject):
|
|||
def __init__(self, parent = None):
|
||||
super().__init__(parent)
|
||||
|
||||
self._registry = UM.Settings.ContainerRegistry.getInstance()
|
||||
self._container_registry = UM.Settings.ContainerRegistry.getInstance()
|
||||
self._machine_manager = UM.Application.getInstance().getMachineManager()
|
||||
|
||||
self._container_name_filters = {}
|
||||
|
||||
## Create a duplicate of the specified container
|
||||
|
|
@ -41,7 +45,7 @@ class ContainerManager(QObject):
|
|||
# \return The ID of the new container, or an empty string if duplication failed.
|
||||
@pyqtSlot(str, result = str)
|
||||
def duplicateContainer(self, container_id):
|
||||
containers = self._registry.findContainers(None, id = container_id)
|
||||
containers = self._container_registry.findContainers(None, id = container_id)
|
||||
if not containers:
|
||||
UM.Logger.log("w", "Could duplicate container %s because it was not found.", container_id)
|
||||
return ""
|
||||
|
|
@ -49,7 +53,7 @@ class ContainerManager(QObject):
|
|||
container = containers[0]
|
||||
|
||||
new_container = None
|
||||
new_name = self._registry.uniqueName(container.getName())
|
||||
new_name = self._container_registry.uniqueName(container.getName())
|
||||
# Only InstanceContainer has a duplicate method at the moment.
|
||||
# So fall back to serialize/deserialize when no duplicate method exists.
|
||||
if hasattr(container, "duplicate"):
|
||||
|
|
@ -60,7 +64,7 @@ class ContainerManager(QObject):
|
|||
new_container.setName(new_name)
|
||||
|
||||
if new_container:
|
||||
self._registry.addContainer(new_container)
|
||||
self._container_registry.addContainer(new_container)
|
||||
|
||||
return new_container.getId()
|
||||
|
||||
|
|
@ -73,24 +77,24 @@ class ContainerManager(QObject):
|
|||
# \return True if successful, False if not.
|
||||
@pyqtSlot(str, str, str, result = bool)
|
||||
def renameContainer(self, container_id, new_id, new_name):
|
||||
containers = self._registry.findContainers(None, id = container_id)
|
||||
containers = self._container_registry.findContainers(None, id = container_id)
|
||||
if not containers:
|
||||
UM.Logger.log("w", "Could rename container %s because it was not found.", container_id)
|
||||
return False
|
||||
|
||||
container = containers[0]
|
||||
# First, remove the container from the registry. This will clean up any files related to the container.
|
||||
self._registry.removeContainer(container)
|
||||
self._container_registry.removeContainer(container)
|
||||
|
||||
# Ensure we have a unique name for the container
|
||||
new_name = self._registry.uniqueName(new_name)
|
||||
new_name = self._container_registry.uniqueName(new_name)
|
||||
|
||||
# Then, update the name and ID of the container
|
||||
container.setName(new_name)
|
||||
container._id = new_id # TODO: Find a nicer way to set a new, unique ID
|
||||
|
||||
# Finally, re-add the container so it will be properly serialized again.
|
||||
self._registry.addContainer(container)
|
||||
self._container_registry.addContainer(container)
|
||||
|
||||
return True
|
||||
|
||||
|
|
@ -101,12 +105,12 @@ class ContainerManager(QObject):
|
|||
# \return True if the container was successfully removed, False if not.
|
||||
@pyqtSlot(str, result = bool)
|
||||
def removeContainer(self, container_id):
|
||||
containers = self._registry.findContainers(None, id = container_id)
|
||||
containers = self._container_registry.findContainers(None, id = container_id)
|
||||
if not containers:
|
||||
UM.Logger.log("w", "Could remove container %s because it was not found.", container_id)
|
||||
return False
|
||||
|
||||
self._registry.removeContainer(containers[0].getId())
|
||||
self._container_registry.removeContainer(containers[0].getId())
|
||||
|
||||
return True
|
||||
|
||||
|
|
@ -121,26 +125,25 @@ class ContainerManager(QObject):
|
|||
# \return True if successfully merged, False if not.
|
||||
@pyqtSlot(str, result = bool)
|
||||
def mergeContainers(self, merge_into_id, merge_id):
|
||||
containers = self._registry.findContainers(None, id = merge_into_id)
|
||||
containers = self._container_registry.findContainers(None, id = merge_into_id)
|
||||
if not containers:
|
||||
UM.Logger.log("w", "Could merge into container %s because it was not found.", merge_into_id)
|
||||
return False
|
||||
|
||||
merge_into = containers[0]
|
||||
|
||||
containers = self._registry.findContainers(None, id = merge_id)
|
||||
containers = self._container_registry.findContainers(None, id = merge_id)
|
||||
if not containers:
|
||||
UM.Logger.log("w", "Could not merge container %s because it was not found", merge_id)
|
||||
return False
|
||||
|
||||
merge = containers[0]
|
||||
|
||||
if type(merge) != type(merge_into):
|
||||
if not isinstance(merge, type(merge_into)):
|
||||
UM.Logger.log("w", "Cannot merge two containers of different types")
|
||||
return False
|
||||
|
||||
for key in merge.getAllKeys():
|
||||
merge_into.setProperty(key, "value", merge.getProperty(key, "value"))
|
||||
self._performMerge(merge_into, merge)
|
||||
|
||||
return True
|
||||
|
||||
|
|
@ -151,7 +154,7 @@ class ContainerManager(QObject):
|
|||
# \return True if successful, False if not.
|
||||
@pyqtSlot(str, result = bool)
|
||||
def clearContainer(self, container_id):
|
||||
containers = self._registry.findContainers(None, id = container_id)
|
||||
containers = self._container_registry.findContainers(None, id = container_id)
|
||||
if not containers:
|
||||
UM.Logger.log("w", "Could clear container %s because it was not found.", container_id)
|
||||
return False
|
||||
|
|
@ -164,6 +167,19 @@ class ContainerManager(QObject):
|
|||
|
||||
return True
|
||||
|
||||
@pyqtSlot(str, str, result=str)
|
||||
def getContainerMetaDataEntry(self, container_id, entry_name):
|
||||
containers = self._container_registry.findContainers(None, id=container_id)
|
||||
if not containers:
|
||||
UM.Logger.log("w", "Could not get metadata of container %s because it was not found.", container_id)
|
||||
return ""
|
||||
|
||||
result = containers[0].getMetaDataEntry(entry_name)
|
||||
if result is not None:
|
||||
return str(result)
|
||||
else:
|
||||
return ""
|
||||
|
||||
## Set a metadata entry of the specified container.
|
||||
#
|
||||
# This will set the specified entry of the container's metadata to the specified
|
||||
|
|
@ -178,9 +194,9 @@ class ContainerManager(QObject):
|
|||
# \return True if successful, False if not.
|
||||
@pyqtSlot(str, str, str, result = bool)
|
||||
def setContainerMetaDataEntry(self, container_id, entry_name, entry_value):
|
||||
containers = UM.Settings.ContainerRegistry.getInstance().findContainers(None, id = container_id)
|
||||
containers = self._container_registry.findContainers(None, id = container_id)
|
||||
if not containers:
|
||||
UM.Logger.log("w", "Could set metadata of container %s because it was not found.", container_id)
|
||||
UM.Logger.log("w", "Could not set metadata of container %s because it was not found.", container_id)
|
||||
return False
|
||||
|
||||
container = containers[0]
|
||||
|
|
@ -209,6 +225,24 @@ class ContainerManager(QObject):
|
|||
|
||||
return True
|
||||
|
||||
## Set the name of the specified container.
|
||||
@pyqtSlot(str, str, result = bool)
|
||||
def setContainerName(self, container_id, new_name):
|
||||
containers = self._container_registry.findContainers(None, id = container_id)
|
||||
if not containers:
|
||||
UM.Logger.log("w", "Could not set name of container %s because it was not found.", container_id)
|
||||
return False
|
||||
|
||||
container = containers[0]
|
||||
|
||||
if container.isReadOnly():
|
||||
UM.Logger.log("w", "Cannot set name of read-only container %s.", container_id)
|
||||
return False
|
||||
|
||||
container.setName(new_name)
|
||||
|
||||
return True
|
||||
|
||||
## Find instance containers matching certain criteria.
|
||||
#
|
||||
# This effectively forwards to ContainerRegistry::findInstanceContainers.
|
||||
|
|
@ -219,11 +253,24 @@ class ContainerManager(QObject):
|
|||
@pyqtSlot("QVariantMap", result = "QVariantList")
|
||||
def findInstanceContainers(self, criteria):
|
||||
result = []
|
||||
for entry in self._registry.findInstanceContainers(**criteria):
|
||||
for entry in self._container_registry.findInstanceContainers(**criteria):
|
||||
result.append(entry.getId())
|
||||
|
||||
return result
|
||||
|
||||
@pyqtSlot(str, result = bool)
|
||||
def isContainerUsed(self, container_id):
|
||||
UM.Logger.log("d", "Checking if container %s is currently used in the active stacks", container_id)
|
||||
for stack in cura.Settings.ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks():
|
||||
if container_id in [child.getId() for child in stack.getContainers()]:
|
||||
UM.Logger.log("d", "The container is in use by %s", stack.getId())
|
||||
return True
|
||||
return False
|
||||
|
||||
@pyqtSlot(str, result = str)
|
||||
def makeUniqueName(self, original_name):
|
||||
return self._container_registry.uniqueName(original_name)
|
||||
|
||||
## Get a list of string that can be used as name filters for a Qt File Dialog
|
||||
#
|
||||
# This will go through the list of available container types and generate a list of strings
|
||||
|
|
@ -276,7 +323,7 @@ class ContainerManager(QObject):
|
|||
else:
|
||||
mime_type = self._container_name_filters[file_type]["mime"]
|
||||
|
||||
containers = UM.Settings.ContainerRegistry.getInstance().findContainers(None, id = container_id)
|
||||
containers = self._container_registry.findContainers(None, id = container_id)
|
||||
if not containers:
|
||||
return { "status": "error", "message": "Container not found"}
|
||||
container = containers[0]
|
||||
|
|
@ -302,6 +349,9 @@ class ContainerManager(QObject):
|
|||
except NotImplementedError:
|
||||
return { "status": "error", "message": "Unable to serialize container"}
|
||||
|
||||
if contents is None:
|
||||
return {"status": "error", "message": "Serialization returned None. Unable to write to file"}
|
||||
|
||||
with UM.SaveFile(file_url, "w") as f:
|
||||
f.write(contents)
|
||||
|
||||
|
|
@ -334,7 +384,7 @@ class ContainerManager(QObject):
|
|||
return { "status": "error", "message": "Could not find a container to handle the specified file."}
|
||||
|
||||
container_id = urllib.parse.unquote_plus(mime_type.stripExtension(os.path.basename(file_url)))
|
||||
container_id = UM.Settings.ContainerRegistry.getInstance().uniqueName(container_id)
|
||||
container_id = self._container_registry.uniqueName(container_id)
|
||||
|
||||
container = container_type(container_id)
|
||||
|
||||
|
|
@ -346,10 +396,243 @@ class ContainerManager(QObject):
|
|||
|
||||
container.setName(container_id)
|
||||
|
||||
UM.Settings.ContainerRegistry.getInstance().addContainer(container)
|
||||
self._container_registry.addContainer(container)
|
||||
|
||||
return { "status": "success", "message": "Successfully imported container {0}".format(container.getName()) }
|
||||
|
||||
## Update the current active quality changes container with the settings from the user container.
|
||||
#
|
||||
# This will go through the active global stack and all active extruder stacks and merge the changes from the user
|
||||
# container into the quality_changes container. After that, the user container is cleared.
|
||||
#
|
||||
# \return \type{bool} True if successful, False if not.
|
||||
@pyqtSlot(result = bool)
|
||||
def updateQualityChanges(self):
|
||||
global_stack = UM.Application.getInstance().getGlobalContainerStack()
|
||||
if not global_stack:
|
||||
return False
|
||||
|
||||
self._machine_manager.blurSettings.emit()
|
||||
|
||||
for stack in cura.Settings.ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks():
|
||||
# Find the quality_changes container for this stack and merge the contents of the top container into it.
|
||||
quality_changes = stack.findContainer(type = "quality_changes")
|
||||
if not quality_changes or quality_changes.isReadOnly():
|
||||
UM.Logger.log("e", "Could not update quality of a nonexistant or read only quality profile in stack %s", stack.getId())
|
||||
continue
|
||||
|
||||
self._performMerge(quality_changes, stack.getTop())
|
||||
|
||||
self._machine_manager.activeQualityChanged.emit()
|
||||
|
||||
return True
|
||||
|
||||
## Clear the top-most (user) containers of the active stacks.
|
||||
@pyqtSlot()
|
||||
def clearUserContainers(self):
|
||||
self._machine_manager.blurSettings.emit()
|
||||
|
||||
# Go through global and extruder stacks and clear their topmost container (the user settings).
|
||||
for stack in cura.Settings.ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks():
|
||||
stack.getTop().clear()
|
||||
|
||||
## Create quality changes containers from the user containers in the active stacks.
|
||||
#
|
||||
# This will go through the global and extruder stacks and create quality_changes containers from
|
||||
# the user containers in each stack. These then replace the quality_changes containers in the
|
||||
# stack and clear the user settings.
|
||||
#
|
||||
# \return \type{bool} True if the operation was successfully, False if not.
|
||||
@pyqtSlot(str, result = bool)
|
||||
def createQualityChanges(self, base_name):
|
||||
global_stack = UM.Application.getInstance().getGlobalContainerStack()
|
||||
if not global_stack:
|
||||
return False
|
||||
|
||||
active_quality_name = self._machine_manager.activeQualityName
|
||||
if active_quality_name == "":
|
||||
UM.Logger.log("w", "No quality container found in stack %s, cannot create profile", global_stack.getId())
|
||||
return False
|
||||
|
||||
self._machine_manager.blurSettings.emit()
|
||||
if base_name is None or base_name == "":
|
||||
base_name = active_quality_name
|
||||
unique_name = self._container_registry.uniqueName(base_name)
|
||||
|
||||
# Go through the active stacks and create quality_changes containers from the user containers.
|
||||
for stack in cura.Settings.ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks():
|
||||
user_container = stack.getTop()
|
||||
quality_container = stack.findContainer(type = "quality")
|
||||
quality_changes_container = stack.findContainer(type = "quality_changes")
|
||||
if not quality_container or not quality_changes_container:
|
||||
UM.Logger.log("w", "No quality or quality changes container found in stack %s, ignoring it", stack.getId())
|
||||
continue
|
||||
|
||||
new_changes = self._createQualityChanges(quality_container, unique_name, stack.getId())
|
||||
self._performMerge(new_changes, user_container)
|
||||
|
||||
self._container_registry.addContainer(new_changes)
|
||||
stack.replaceContainer(stack.getContainerIndex(quality_changes_container), new_changes)
|
||||
|
||||
self._machine_manager.activeQualityChanged.emit()
|
||||
return True
|
||||
|
||||
## Remove all quality changes containers matching a specified name.
|
||||
#
|
||||
# This will search for quality_changes containers matching the supplied name and remove them.
|
||||
# Note that if the machine specifies that qualities should be filtered by machine and/or material
|
||||
# only the containers related to the active machine/material are removed.
|
||||
#
|
||||
# \param quality_name The name of the quality changes to remove.
|
||||
#
|
||||
# \return \type{bool} True if successful, False if not.
|
||||
@pyqtSlot(str, result = bool)
|
||||
def removeQualityChanges(self, quality_name):
|
||||
UM.Logger.log("d", "Attempting to remove the quality change containers with name %s", quality_name)
|
||||
containers_found = False
|
||||
|
||||
if not quality_name:
|
||||
return containers_found # Without a name we will never find a container to remove.
|
||||
|
||||
# If the container that is being removed is the currently active quality, set another quality as the active quality
|
||||
activate_quality = quality_name == self._machine_manager.activeQualityName
|
||||
activate_quality_type = None
|
||||
|
||||
for container in self._getFilteredContainers(name = quality_name, type = "quality_changes"):
|
||||
containers_found = True
|
||||
if activate_quality and not activate_quality_type:
|
||||
activate_quality_type = container.getMetaDataEntry("quality")
|
||||
self._container_registry.removeContainer(container.getId())
|
||||
|
||||
if not containers_found:
|
||||
UM.Logger.log("d", "Unable to remove quality containers, as we did not find any by the name of %s", quality_name)
|
||||
|
||||
elif activate_quality:
|
||||
definition_id = "fdmprinter" if not self._machine_manager.filterQualityByMachine else self._machine_manager.activeDefinitionId
|
||||
containers = self._container_registry.findInstanceContainers(type = "quality", definition = definition_id, quality_type = activate_quality_type)
|
||||
if containers:
|
||||
self._machine_manager.setActiveQuality(containers[0].getId())
|
||||
self._machine_manager.activeQualityChanged.emit()
|
||||
|
||||
return containers_found
|
||||
|
||||
## Rename a set of quality changes containers.
|
||||
#
|
||||
# This will search for quality_changes containers matching the supplied name and rename them.
|
||||
# Note that if the machine specifies that qualities should be filtered by machine and/or material
|
||||
# only the containers related to the active machine/material are renamed.
|
||||
#
|
||||
# \param quality_name The name of the quality changes containers to rename.
|
||||
# \param new_name The new name of the quality changes.
|
||||
#
|
||||
# \return True if successful, False if not.
|
||||
@pyqtSlot(str, str, result = bool)
|
||||
def renameQualityChanges(self, quality_name, new_name):
|
||||
UM.Logger.log("d", "User requested QualityChanges container rename of %s to %s", quality_name, new_name)
|
||||
if not quality_name or not new_name:
|
||||
return False
|
||||
|
||||
if quality_name == new_name:
|
||||
UM.Logger.log("w", "Unable to rename %s to %s, because they are the same.", quality_name, new_name)
|
||||
return True
|
||||
|
||||
global_stack = UM.Application.getInstance().getGlobalContainerStack()
|
||||
if not global_stack:
|
||||
return False
|
||||
|
||||
self._machine_manager.blurSettings.emit()
|
||||
|
||||
new_name = self._container_registry.uniqueName(new_name)
|
||||
|
||||
container_registry = self._container_registry
|
||||
for container in self._getFilteredContainers(name = quality_name, type = "quality_changes"):
|
||||
stack_id = container.getMetaDataEntry("extruder", global_stack.getId())
|
||||
container_registry.renameContainer(container.getId(), new_name, self._createUniqueId(stack_id, new_name))
|
||||
|
||||
self._machine_manager.activeQualityChanged.emit()
|
||||
return True
|
||||
|
||||
## Duplicate a specified set of quality or quality_changes containers.
|
||||
#
|
||||
# This will search for containers matching the specified name. If the container is a "quality" type container, a new
|
||||
# quality_changes container will be created with the specified quality as base. If the container is a "quality_changes"
|
||||
# container, it is simply duplicated and renamed.
|
||||
#
|
||||
# \param quality_name The name of the quality to duplicate.
|
||||
#
|
||||
# \return A string containing the name of the duplicated containers, or an empty string if it failed.
|
||||
@pyqtSlot(str, str, result = str)
|
||||
def duplicateQualityOrQualityChanges(self, quality_name, base_name):
|
||||
global_stack = UM.Application.getInstance().getGlobalContainerStack()
|
||||
if not global_stack or not quality_name:
|
||||
return ""
|
||||
UM.Logger.log("d", "Attempting to duplicate the quality %s", quality_name)
|
||||
containers = self._container_registry.findInstanceContainers(name = quality_name)
|
||||
if not containers:
|
||||
UM.Logger.log("d", "Unable to duplicate the quality %s, because it doesn't exist.", quality_name)
|
||||
return ""
|
||||
|
||||
if base_name is None:
|
||||
base_name = quality_name
|
||||
|
||||
new_name = self._container_registry.uniqueName(base_name)
|
||||
|
||||
container_type = containers[0].getMetaDataEntry("type")
|
||||
if container_type == "quality":
|
||||
for container in self._getFilteredContainers(name = quality_name, type = "quality"):
|
||||
for stack in cura.Settings.ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks():
|
||||
new_changes = self._createQualityChanges(container, new_name, stack.getId())
|
||||
self._container_registry.addContainer(new_changes)
|
||||
elif container_type == "quality_changes":
|
||||
for container in self._getFilteredContainers(name = quality_name, type = "quality_changes"):
|
||||
stack_id = container.getMetaDataEntry("extruder", global_stack.getId())
|
||||
new_container = container.duplicate(self._createUniqueId(stack_id, new_name), new_name)
|
||||
self._container_registry.addContainer(new_container)
|
||||
else:
|
||||
UM.Logger.log("w", "Unable to duplicate profile. It has the wrong type.")
|
||||
return ""
|
||||
|
||||
return new_name
|
||||
|
||||
@pyqtSlot(str, result = str)
|
||||
def duplicateMaterial(self, material_id):
|
||||
containers = self._container_registry.findInstanceContainers(id=material_id)
|
||||
if not containers:
|
||||
UM.Logger.log("d", "Unable to duplicate the material with id %s, because it doesn't exist.", material_id)
|
||||
return ""
|
||||
|
||||
# Ensure all settings are saved.
|
||||
UM.Application.getInstance().saveSettings()
|
||||
|
||||
# Create a new ID & container to hold the data.
|
||||
new_id = self._container_registry.uniqueName(material_id)
|
||||
container_type = type(containers[0]) # Could be either a XMLMaterialProfile or a InstanceContainer
|
||||
duplicated_container = container_type(new_id)
|
||||
|
||||
# Instead of duplicating we load the data from the basefile again.
|
||||
# This ensures that the inheritance goes well and all "cut up" subclasses of the xmlMaterial profile
|
||||
# are also correctly created.
|
||||
with open(containers[0].getPath(), encoding="utf-8") as f:
|
||||
duplicated_container.deserialize(f.read())
|
||||
duplicated_container.setDirty(True)
|
||||
self._container_registry.addContainer(duplicated_container)
|
||||
|
||||
# Factory function, used by QML
|
||||
@staticmethod
|
||||
def createContainerManager(engine, js_engine):
|
||||
return ContainerManager()
|
||||
|
||||
def _performMerge(self, merge_into, merge):
|
||||
assert isinstance(merge, type(merge_into))
|
||||
|
||||
if merge == merge_into:
|
||||
return
|
||||
|
||||
for key in merge.getAllKeys():
|
||||
merge_into.setProperty(key, "value", merge.getProperty(key, "value"))
|
||||
|
||||
merge.clear()
|
||||
|
||||
def _updateContainerNameFilters(self):
|
||||
self._container_name_filters = {}
|
||||
for plugin_id, container_type in UM.Settings.ContainerRegistry.getContainerTypes():
|
||||
|
|
@ -394,7 +677,88 @@ class ContainerManager(QObject):
|
|||
name_filter = "{0} ({1})".format(mime_type.comment, suffix_list)
|
||||
self._container_name_filters[name_filter] = entry
|
||||
|
||||
# Factory function, used by QML
|
||||
@staticmethod
|
||||
def createContainerManager(engine, js_engine):
|
||||
return ContainerManager()
|
||||
## Return a generator that iterates over a set of containers that are filtered by machine and material when needed.
|
||||
#
|
||||
# \param kwargs Initial search criteria that the containers need to match.
|
||||
#
|
||||
# \return A generator that iterates over the list of containers matching the search criteria.
|
||||
def _getFilteredContainers(self, **kwargs):
|
||||
global_stack = UM.Application.getInstance().getGlobalContainerStack()
|
||||
if not global_stack:
|
||||
return False
|
||||
|
||||
criteria = kwargs
|
||||
|
||||
filter_by_material = False
|
||||
|
||||
if global_stack.getMetaDataEntry("has_machine_quality"):
|
||||
definition = global_stack.getBottom()
|
||||
definition_id = definition.getMetaDataEntry("quality_definition", definition.getId())
|
||||
criteria["definition"] = definition_id
|
||||
|
||||
filter_by_material = global_stack.getMetaDataEntry("has_materials")
|
||||
|
||||
material_ids = []
|
||||
if filter_by_material:
|
||||
for stack in cura.Settings.ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks():
|
||||
material_ids.append(stack.findContainer(type = "material").getId())
|
||||
|
||||
containers = self._container_registry.findInstanceContainers(**criteria)
|
||||
for container in containers:
|
||||
# If the machine specifies we should filter by material, exclude containers that do not match any active material.
|
||||
if filter_by_material and container.getMetaDataEntry("material") not in material_ids:
|
||||
continue
|
||||
|
||||
yield container
|
||||
|
||||
## Creates a unique ID for a container by prefixing the name with the stack ID.
|
||||
#
|
||||
# This method creates a unique ID for a container by prefixing it with a specified stack ID.
|
||||
# This is done to ensure we have an easily identified ID for quality changes, which have the
|
||||
# same name across several stacks.
|
||||
#
|
||||
# \param stack_id The ID of the stack to prepend.
|
||||
# \param container_name The name of the container that we are creating a unique ID for.
|
||||
#
|
||||
# \return Container name prefixed with stack ID, in lower case with spaces replaced by underscores.
|
||||
def _createUniqueId(self, stack_id, container_name):
|
||||
result = stack_id + "_" + container_name
|
||||
result = result.lower()
|
||||
result.replace(" ", "_")
|
||||
return result
|
||||
|
||||
## Create a quality changes container for a specified quality container.
|
||||
#
|
||||
# \param quality_container The quality container to create a changes container for.
|
||||
# \param new_name The name of the new quality_changes container.
|
||||
# \param stack_id The ID of the container stack the new container "belongs to". It is used primarily to ensure a unique ID.
|
||||
#
|
||||
# \return A new quality_changes container with the specified container as base.
|
||||
def _createQualityChanges(self, quality_container, new_name, stack_id):
|
||||
global_stack = UM.Application.getInstance().getGlobalContainerStack()
|
||||
assert global_stack is not None
|
||||
|
||||
# Create a new quality_changes container for the quality.
|
||||
quality_changes = UM.Settings.InstanceContainer(self._createUniqueId(stack_id, new_name))
|
||||
quality_changes.setName(new_name)
|
||||
quality_changes.addMetaDataEntry("type", "quality_changes")
|
||||
quality_changes.addMetaDataEntry("quality", quality_container.getMetaDataEntry("quality_type"))
|
||||
|
||||
# If we are creating a container for an extruder, ensure we add that to the container
|
||||
if stack_id != global_stack.getId():
|
||||
quality_changes.addMetaDataEntry("extruder", stack_id)
|
||||
|
||||
# If the machine specifies qualities should be filtered, ensure we match the current criteria.
|
||||
if not global_stack.getMetaDataEntry("has_machine_quality"):
|
||||
quality_changes.setDefinition(self._container_registry.findContainers(id = "fdmprinter")[0])
|
||||
else:
|
||||
definition = global_stack.getBottom()
|
||||
definition_id = definition.getMetaDataEntry("quality_definition", definition.getId())
|
||||
definition = self._container_registry.findContainers(id=definition_id)[0]
|
||||
quality_changes.setDefinition(definition)
|
||||
|
||||
if global_stack.getMetaDataEntry("has_materials"):
|
||||
material = quality_container.getMetaDataEntry("material")
|
||||
quality_changes.addMetaDataEntry("material", material)
|
||||
|
||||
return quality_changes
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ class ContainerSettingsModel(ListModel):
|
|||
self._update()
|
||||
|
||||
def _update(self):
|
||||
self.clear()
|
||||
items = []
|
||||
|
||||
if len(self._container_ids) == 0:
|
||||
return
|
||||
|
|
@ -64,14 +64,15 @@ class ContainerSettingsModel(ListModel):
|
|||
else:
|
||||
values.append("")
|
||||
|
||||
self.appendItem({
|
||||
items.append({
|
||||
"key": key,
|
||||
"values": values,
|
||||
"label": definition.label,
|
||||
"unit": definition.unit,
|
||||
"category": category.label
|
||||
})
|
||||
self.sort(lambda k: (k["category"], k["key"]))
|
||||
items.sort(key = lambda k: (k["category"], k["key"]))
|
||||
self.setItems(items)
|
||||
|
||||
## Set the ids of the containers which have the settings this model should list.
|
||||
# Also makes sure the model updates when the containers have property changes
|
||||
|
|
|
|||
|
|
@ -58,12 +58,10 @@ class CuraContainerRegistry(ContainerRegistry):
|
|||
|
||||
## Exports an profile to a file
|
||||
#
|
||||
# \param instance_id \type{str} the ID of the profile to export.
|
||||
# \param instance_ids \type{list} the IDs of the profiles to export.
|
||||
# \param file_name \type{str} the full path and filename to export to.
|
||||
# \param file_type \type{str} the file type with the format "<description> (*.<extension>)"
|
||||
def exportProfile(self, instance_id, file_name, file_type):
|
||||
Logger.log('d', 'exportProfile instance_id: '+str(instance_id))
|
||||
|
||||
def exportProfile(self, instance_ids, file_name, file_type):
|
||||
# Parse the fileType to deduce what plugin can save the file format.
|
||||
# fileType has the format "<description> (*.<extension>)"
|
||||
split = file_type.rfind(" (*.") # Find where the description ends and the extension starts.
|
||||
|
|
@ -82,16 +80,16 @@ class CuraContainerRegistry(ContainerRegistry):
|
|||
catalog.i18nc("@label", "The file <filename>{0}</filename> already exists. Are you sure you want to overwrite it?").format(file_name))
|
||||
if result == QMessageBox.No:
|
||||
return
|
||||
|
||||
containers = ContainerRegistry.getInstance().findInstanceContainers(id=instance_id)
|
||||
if not containers:
|
||||
return
|
||||
container = containers[0]
|
||||
found_containers = []
|
||||
for instance_id in instance_ids:
|
||||
containers = ContainerRegistry.getInstance().findInstanceContainers(id=instance_id)
|
||||
if containers:
|
||||
found_containers.append(containers[0])
|
||||
|
||||
profile_writer = self._findProfileWriter(extension, description)
|
||||
|
||||
try:
|
||||
success = profile_writer.write(file_name, container)
|
||||
success = profile_writer.write(file_name, found_containers)
|
||||
except Exception as e:
|
||||
Logger.log("e", "Failed to export profile to %s: %s", file_name, str(e))
|
||||
m = Message(catalog.i18nc("@info:status", "Failed to export profile to <filename>{0}</filename>: <message>{1}</message>", file_name, str(e)), lifetime = 0)
|
||||
|
|
@ -130,6 +128,7 @@ class CuraContainerRegistry(ContainerRegistry):
|
|||
return { "status": "error", "message": catalog.i18nc("@info:status", "Failed to import profile from <filename>{0}</filename>: <message>{1}</message>", file_name, "Invalid path")}
|
||||
|
||||
plugin_registry = PluginRegistry.getInstance()
|
||||
container_registry = ContainerRegistry.getInstance()
|
||||
for plugin_id, meta_data in self._getIOPlugins("profile_reader"):
|
||||
profile_reader = plugin_registry.getPluginObject(plugin_id)
|
||||
try:
|
||||
|
|
@ -146,7 +145,11 @@ class CuraContainerRegistry(ContainerRegistry):
|
|||
return { "status": "ok", "message": catalog.i18nc("@info:status", "Successfully imported profile {0}", profile.getName()) }
|
||||
else:
|
||||
for profile in profile_or_list:
|
||||
self._configureProfile(profile, name_seed)
|
||||
profile.setDirty(True) # Ensure the profiles are correctly saved
|
||||
if profile.getId() != "":
|
||||
container_registry.addContainer(profile)
|
||||
else:
|
||||
self._configureProfile(profile, name_seed)
|
||||
|
||||
if len(profile_or_list) == 1:
|
||||
return {"status": "ok", "message": catalog.i18nc("@info:status", "Successfully imported profile {0}", profile_or_list[0].getName())}
|
||||
|
|
@ -160,7 +163,7 @@ class CuraContainerRegistry(ContainerRegistry):
|
|||
def _configureProfile(self, profile, name_seed):
|
||||
profile.setReadOnly(False)
|
||||
|
||||
new_name = self.createUniqueName("quality", "", name_seed, catalog.i18nc("@label", "Custom profile"))
|
||||
new_name = self.createUniqueName("quality_changes", "", name_seed, catalog.i18nc("@label", "Custom profile"))
|
||||
profile.setName(new_name)
|
||||
profile._id = new_name
|
||||
|
||||
|
|
@ -170,6 +173,7 @@ class CuraContainerRegistry(ContainerRegistry):
|
|||
profile.addMetaDataEntry("material", self._activeMaterialId())
|
||||
else:
|
||||
profile.setDefinition(ContainerRegistry.getInstance().findDefinitionContainers(id="fdmprinter")[0])
|
||||
|
||||
ContainerRegistry.getInstance().addContainer(profile)
|
||||
|
||||
## Gets a list of profile writer plugins
|
||||
|
|
|
|||
|
|
@ -55,6 +55,13 @@ class ExtruderManager(QObject):
|
|||
map[position] = self._extruder_trains[UM.Application.getInstance().getGlobalContainerStack().getId()][position].getId()
|
||||
return map
|
||||
|
||||
@pyqtSlot(str, result = str)
|
||||
def getQualityChangesIdByExtruderStackId(self, id):
|
||||
for position in self._extruder_trains[UM.Application.getInstance().getGlobalContainerStack().getId()]:
|
||||
extruder = self._extruder_trains[UM.Application.getInstance().getGlobalContainerStack().getId()][position]
|
||||
if extruder.getId() == id:
|
||||
return extruder.findContainer(type = "quality_changes").getId()
|
||||
|
||||
## The instance of the singleton pattern.
|
||||
#
|
||||
# It's None if the extruder manager hasn't been created yet.
|
||||
|
|
@ -131,9 +138,9 @@ class ExtruderManager(QObject):
|
|||
|
||||
# Make sure the next stack is a stack that contains only the machine definition
|
||||
if not extruder_train.getNextStack():
|
||||
shallowStack = UM.Settings.ContainerStack(machine_id + "_shallow")
|
||||
shallowStack.addContainer(machine_definition)
|
||||
extruder_train.setNextStack(shallowStack)
|
||||
shallow_stack = UM.Settings.ContainerStack(machine_id + "_shallow")
|
||||
shallow_stack.addContainer(machine_definition)
|
||||
extruder_train.setNextStack(shallow_stack)
|
||||
changed = True
|
||||
if changed:
|
||||
self.extrudersChanged.emit(machine_id)
|
||||
|
|
@ -153,7 +160,7 @@ class ExtruderManager(QObject):
|
|||
def createExtruderTrain(self, extruder_definition, machine_definition, position, machine_id):
|
||||
# Cache some things.
|
||||
container_registry = UM.Settings.ContainerRegistry.getInstance()
|
||||
machine_definition_id = machine_definition.getId()
|
||||
machine_definition_id = UM.Application.getInstance().getMachineManager().getQualityDefinitionId(machine_definition)
|
||||
|
||||
# Create a container stack for this extruder.
|
||||
extruder_stack_id = container_registry.uniqueName(extruder_definition.getId())
|
||||
|
|
@ -215,7 +222,7 @@ class ExtruderManager(QObject):
|
|||
|
||||
search_criteria = { "type": "quality" }
|
||||
if machine_definition.getMetaDataEntry("has_machine_quality"):
|
||||
search_criteria["definition"] = machine_definition.id
|
||||
search_criteria["definition"] = machine_definition_id
|
||||
if machine_definition.getMetaDataEntry("has_materials") and material:
|
||||
search_criteria["material"] = material.id
|
||||
else:
|
||||
|
|
@ -227,14 +234,17 @@ class ExtruderManager(QObject):
|
|||
|
||||
containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(**search_criteria)
|
||||
if not containers and preferred_quality:
|
||||
UM.Logger.log("w", "The preferred quality \"%s\" of machine %s doesn't exist or is not a quality profile.", preferred_quality, machine_id)
|
||||
search_criteria.pop("id", None)
|
||||
containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(**search_criteria)
|
||||
UM.Logger.log("w", "The preferred quality \"%s\" of machine %s doesn't exist or is not a quality profile.", preferred_quality, machine_id)
|
||||
search_criteria.pop("id", None)
|
||||
containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(**search_criteria)
|
||||
if containers:
|
||||
quality = containers[0]
|
||||
|
||||
container_stack.addContainer(quality)
|
||||
|
||||
empty_quality_changes = container_registry.findInstanceContainers(id = "empty_quality_changes")[0]
|
||||
container_stack.addContainer(empty_quality_changes)
|
||||
|
||||
user_profile = container_registry.findInstanceContainers(type = "user", extruder = extruder_stack_id)
|
||||
if user_profile: # There was already a user profile, loaded from settings.
|
||||
user_profile = user_profile[0]
|
||||
|
|
@ -248,9 +258,9 @@ class ExtruderManager(QObject):
|
|||
|
||||
# Make sure the next stack is a stack that contains only the machine definition
|
||||
if not container_stack.getNextStack():
|
||||
shallowStack = UM.Settings.ContainerStack(machine_id + "_shallow")
|
||||
shallowStack.addContainer(machine_definition)
|
||||
container_stack.setNextStack(shallowStack)
|
||||
shallow_stack = UM.Settings.ContainerStack(machine_id + "_shallow")
|
||||
shallow_stack.addContainer(machine_definition)
|
||||
container_stack.setNextStack(shallow_stack)
|
||||
|
||||
container_registry.addContainer(container_stack)
|
||||
|
||||
|
|
@ -274,6 +284,20 @@ class ExtruderManager(QObject):
|
|||
for name in self._extruder_trains[machine_id]:
|
||||
yield self._extruder_trains[machine_id][name]
|
||||
|
||||
## Returns a generator that will iterate over the global stack and per-extruder stacks.
|
||||
#
|
||||
# The first generated element is the global container stack. After that any extruder stacks are generated.
|
||||
def getActiveGlobalAndExtruderStacks(self):
|
||||
global_stack = UM.Application.getInstance().getGlobalContainerStack()
|
||||
if not global_stack:
|
||||
return
|
||||
|
||||
yield global_stack
|
||||
|
||||
global_id = global_stack.getId()
|
||||
for name in self._extruder_trains[global_id]:
|
||||
yield self._extruder_trains[global_id][name]
|
||||
|
||||
def __globalContainerStackChanged(self):
|
||||
self._addCurrentMachineExtruders()
|
||||
self.activeExtruderChanged.emit()
|
||||
|
|
@ -313,6 +337,17 @@ class ExtruderManager(QObject):
|
|||
|
||||
return result
|
||||
|
||||
## Get all extruder values for a certain setting.
|
||||
#
|
||||
# This is exposed to qml for display purposes
|
||||
#
|
||||
# \param key The key of the setting to retieve values for.
|
||||
#
|
||||
# \return String representing the extruder values
|
||||
@pyqtSlot(str, result="QList<int>")
|
||||
def getInstanceExtruderValues(self, key):
|
||||
return ExtruderManager.getExtruderValues(key)
|
||||
|
||||
## Get the value for a setting from a specific extruder.
|
||||
#
|
||||
# This is exposed to SettingFunction to use in value functions.
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
|
|||
self.addRoleName(self.IndexRole, "index")
|
||||
|
||||
self._add_global = False
|
||||
self._simple_names = False
|
||||
|
||||
self._active_extruder_stack = None
|
||||
|
||||
|
|
@ -70,6 +71,21 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
|
|||
def addGlobal(self):
|
||||
return self._add_global
|
||||
|
||||
## Set the simpleNames property.
|
||||
def setSimpleNames(self, simple_names):
|
||||
if simple_names != self._simple_names:
|
||||
self._simple_names = simple_names
|
||||
self.simpleNamesChanged.emit()
|
||||
self._updateExtruders()
|
||||
|
||||
## Emitted when the simpleNames property changes.
|
||||
simpleNamesChanged = pyqtSignal()
|
||||
|
||||
## Whether or not the model should show all definitions regardless of visibility.
|
||||
@pyqtProperty(bool, fset = setSimpleNames, notify = simpleNamesChanged)
|
||||
def simpleNames(self):
|
||||
return self._simple_names
|
||||
|
||||
def _onActiveExtruderChanged(self):
|
||||
manager = ExtruderManager.getInstance()
|
||||
active_extruder_stack = manager.getActiveExtruderStack()
|
||||
|
|
@ -97,9 +113,10 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
|
|||
changed = False
|
||||
|
||||
if self.rowCount() != 0:
|
||||
self.clear()
|
||||
changed = True
|
||||
|
||||
items = []
|
||||
|
||||
global_container_stack = UM.Application.getInstance().getGlobalContainerStack()
|
||||
if global_container_stack:
|
||||
if self._add_global:
|
||||
|
|
@ -111,14 +128,14 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
|
|||
"color": color,
|
||||
"index": -1
|
||||
}
|
||||
self.appendItem(item)
|
||||
items.append(item)
|
||||
changed = True
|
||||
|
||||
manager = ExtruderManager.getInstance()
|
||||
for extruder in manager.getMachineExtruders(global_container_stack.getId()):
|
||||
extruder_name = extruder.getName()
|
||||
material = extruder.findContainer({ "type": "material" })
|
||||
if material:
|
||||
if material and not self._simple_names:
|
||||
extruder_name = "%s (%s)" % (material.getName(), extruder_name)
|
||||
position = extruder.getMetaDataEntry("position", default = "0") # Get the position
|
||||
try:
|
||||
|
|
@ -133,9 +150,10 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
|
|||
"color": color,
|
||||
"index": position
|
||||
}
|
||||
self.appendItem(item)
|
||||
items.append(item)
|
||||
changed = True
|
||||
|
||||
if changed:
|
||||
self.sort(lambda item: item["index"])
|
||||
items.sort(key = lambda i: i["index"])
|
||||
self.setItems(items)
|
||||
self.modelChanged.emit()
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ from PyQt5.QtWidgets import QMessageBox
|
|||
from UM.Application import Application
|
||||
from UM.Preferences import Preferences
|
||||
from UM.Logger import Logger
|
||||
from UM.Message import Message
|
||||
from UM.Settings.SettingRelation import RelationType
|
||||
|
||||
import UM.Settings
|
||||
|
||||
|
|
@ -17,6 +19,7 @@ from UM.i18n import i18nCatalog
|
|||
catalog = i18nCatalog("cura")
|
||||
|
||||
import time
|
||||
import os
|
||||
|
||||
class MachineManager(QObject):
|
||||
def __init__(self, parent = None):
|
||||
|
|
@ -26,16 +29,17 @@ class MachineManager(QObject):
|
|||
self._global_container_stack = None
|
||||
|
||||
Application.getInstance().globalContainerStackChanged.connect(self._onGlobalContainerChanged)
|
||||
## When the global container is changed, active material probably needs to be updated.
|
||||
self.globalContainerChanged.connect(self.activeMaterialChanged)
|
||||
self.globalContainerChanged.connect(self.activeVariantChanged)
|
||||
self.globalContainerChanged.connect(self.activeQualityChanged)
|
||||
|
||||
self._active_stack_valid = None
|
||||
self._onGlobalContainerChanged()
|
||||
|
||||
ExtruderManager.getInstance().activeExtruderChanged.connect(self._onActiveExtruderStackChanged)
|
||||
self._onActiveExtruderStackChanged()
|
||||
|
||||
## When the global container is changed, active material probably needs to be updated.
|
||||
self.globalContainerChanged.connect(self.activeMaterialChanged)
|
||||
self.globalContainerChanged.connect(self.activeVariantChanged)
|
||||
self.globalContainerChanged.connect(self.activeQualityChanged)
|
||||
ExtruderManager.getInstance().activeExtruderChanged.connect(self.activeMaterialChanged)
|
||||
ExtruderManager.getInstance().activeExtruderChanged.connect(self.activeVariantChanged)
|
||||
ExtruderManager.getInstance().activeExtruderChanged.connect(self.activeQualityChanged)
|
||||
|
|
@ -47,6 +51,7 @@ class MachineManager(QObject):
|
|||
self._empty_variant_container = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = "empty_variant")[0]
|
||||
self._empty_material_container = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = "empty_material")[0]
|
||||
self._empty_quality_container = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = "empty_quality")[0]
|
||||
self._empty_quality_changes_container = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = "empty_quality_changes")[0]
|
||||
|
||||
Preferences.getInstance().addPreference("cura/active_machine", "")
|
||||
|
||||
|
|
@ -115,35 +120,10 @@ class MachineManager(QObject):
|
|||
if matching_extruder and matching_extruder.findContainer({"type": "variant"}).getName() != hotend_id:
|
||||
# Save the material that needs to be changed. Multiple changes will be handled by the callback.
|
||||
self._auto_hotends_changed[str(index)] = containers[0].getId()
|
||||
Application.getInstance().messageBox(catalog.i18nc("@window:title", "Changes on the Printer"),
|
||||
catalog.i18nc("@label",
|
||||
"Do you want to change the materials and hotends to match the material in your printer?"),
|
||||
catalog.i18nc("@label",
|
||||
"The materials and / or hotends on your printer were changed. For best results always slice for the materials . hotends that are inserted in your printer."),
|
||||
buttons=QMessageBox.Yes + QMessageBox.No,
|
||||
icon=QMessageBox.Question,
|
||||
callback=self._materialHotendChangedCallback)
|
||||
|
||||
|
||||
self._printer_output_devices[0].materialHotendChangedMessage(self._materialHotendChangedCallback)
|
||||
else:
|
||||
Logger.log("w", "No variant found for printer definition %s with id %s" % (self._global_container_stack.getBottom().getId(), hotend_id))
|
||||
|
||||
def _autoUpdateHotends(self):
|
||||
extruder_manager = ExtruderManager.getInstance()
|
||||
for position in self._auto_hotends_changed:
|
||||
hotend_id = self._auto_hotends_changed[position]
|
||||
old_index = extruder_manager.activeExtruderIndex
|
||||
|
||||
if old_index != int(position):
|
||||
extruder_manager.setActiveExtruderIndex(int(position))
|
||||
else:
|
||||
old_index = None
|
||||
Logger.log("d", "Setting hotend variant of hotend %s to %s" % (position, hotend_id))
|
||||
self.setActiveVariant(hotend_id)
|
||||
|
||||
if old_index is not None:
|
||||
extruder_manager.setActiveExtruderIndex(old_index)
|
||||
|
||||
def _onMaterialIdChanged(self, index, material_id):
|
||||
if not self._global_container_stack:
|
||||
return
|
||||
|
|
@ -164,10 +144,7 @@ class MachineManager(QObject):
|
|||
if matching_extruder and matching_extruder.findContainer({"type":"material"}).getMetaDataEntry("GUID") != material_id:
|
||||
# Save the material that needs to be changed. Multiple changes will be handled by the callback.
|
||||
self._auto_materials_changed[str(index)] = containers[0].getId()
|
||||
Application.getInstance().messageBox(catalog.i18nc("@window:title", "Changes on the Printer"), catalog.i18nc("@label", "Do you want to change the materials and hotends to match the material in your printer?"),
|
||||
catalog.i18nc("@label", "The materials and / or hotends on your printer were changed. For best results always slice for the materials and hotends that are inserted in your printer."),
|
||||
buttons = QMessageBox.Yes + QMessageBox.No, icon = QMessageBox.Question, callback = self._materialHotendChangedCallback)
|
||||
|
||||
self._printer_output_devices[0].materialHotendChangedMessage(self._materialHotendChangedCallback)
|
||||
else:
|
||||
Logger.log("w", "No material definition found for printer definition %s and GUID %s" % (definition_id, material_id))
|
||||
|
||||
|
|
@ -196,107 +173,27 @@ class MachineManager(QObject):
|
|||
if old_index is not None:
|
||||
extruder_manager.setActiveExtruderIndex(old_index)
|
||||
|
||||
def _onGlobalPropertyChanged(self, key, property_name):
|
||||
if property_name == "value":
|
||||
## We can get recursion issues. So we store a list of keys that we are still handling to prevent this.
|
||||
if key in self._global_event_keys:
|
||||
return
|
||||
self._global_event_keys.add(key)
|
||||
self.globalValueChanged.emit()
|
||||
def _autoUpdateHotends(self):
|
||||
extruder_manager = ExtruderManager.getInstance()
|
||||
for position in self._auto_hotends_changed:
|
||||
hotend_id = self._auto_hotends_changed[position]
|
||||
old_index = extruder_manager.activeExtruderIndex
|
||||
|
||||
if self._active_container_stack and self._active_container_stack != self._global_container_stack:
|
||||
# Make the global current settings mirror the stack values appropriate for this setting
|
||||
if self._active_container_stack.getProperty("extruder_nr", "value") == int(self._active_container_stack.getProperty(key, "global_inherits_stack")):
|
||||
|
||||
new_value = self._active_container_stack.getProperty(key, "value")
|
||||
self._global_container_stack.getTop().setProperty(key, "value", new_value)
|
||||
|
||||
# Global-only setting values should be set on all extruders and the global stack
|
||||
if not self._global_container_stack.getProperty(key, "settable_per_extruder"):
|
||||
extruder_stacks = list(ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId()))
|
||||
target_stack_position = int(self._active_container_stack.getProperty(key, "global_inherits_stack"))
|
||||
if target_stack_position == -1: # Prevent -1 from selecting wrong stack.
|
||||
target_stack = self._active_container_stack
|
||||
else:
|
||||
target_stack = extruder_stacks[target_stack_position]
|
||||
new_value = target_stack.getProperty(key, "value")
|
||||
target_stack_has_user_value = target_stack.getTop().getInstance(key) != None
|
||||
for extruder_stack in extruder_stacks:
|
||||
if extruder_stack != target_stack:
|
||||
if target_stack_has_user_value:
|
||||
extruder_stack.getTop().setProperty(key, "value", new_value)
|
||||
else:
|
||||
# Remove from the value from the other stacks as well, unless the
|
||||
# top value from the other stacklevels is different than the new value
|
||||
for container in extruder_stack.getContainers():
|
||||
if container.__class__ == UM.Settings.InstanceContainer and container.getInstance(key) != None:
|
||||
if container.getProperty(key, "value") != new_value:
|
||||
# It could be that the setting needs to be removed instead of updated.
|
||||
temp = extruder_stack
|
||||
containers = extruder_stack.getContainers()
|
||||
# Ensure we have the entire 'chain'
|
||||
while temp.getNextStack():
|
||||
temp = temp.getNextStack()
|
||||
containers.extend(temp.getContainers())
|
||||
instance_needs_removal = False
|
||||
|
||||
if len(containers) > 1:
|
||||
for index in range(1, len(containers)):
|
||||
deeper_container = containers[index]
|
||||
if deeper_container.getProperty(key, "value") is None:
|
||||
continue # Deeper container does not have the value, so continue.
|
||||
if deeper_container.getProperty(key, "value") == new_value:
|
||||
# Removal will result in correct value, so do that.
|
||||
# We do this to prevent the reset from showing up unneeded.
|
||||
instance_needs_removal = True
|
||||
break
|
||||
else:
|
||||
# Container has the value, but it's not the same. Stop looking.
|
||||
break
|
||||
if instance_needs_removal:
|
||||
extruder_stack.getTop().removeInstance(key)
|
||||
else:
|
||||
extruder_stack.getTop().setProperty(key, "value", new_value)
|
||||
else:
|
||||
# Check if we really need to remove something.
|
||||
if extruder_stack.getProperty(key, "value") != new_value:
|
||||
extruder_stack.getTop().removeInstance(key)
|
||||
break
|
||||
if self._global_container_stack.getProperty(key, "value") != new_value:
|
||||
self._global_container_stack.getTop().setProperty(key, "value", new_value)
|
||||
self._global_event_keys.remove(key)
|
||||
|
||||
if property_name == "global_inherits_stack":
|
||||
if self._active_container_stack and self._active_container_stack != self._global_container_stack:
|
||||
# Update the global user value when the "global_inherits_stack" function points to a different stack
|
||||
extruder_stacks = list(ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId()))
|
||||
target_stack_position = int(self._active_container_stack.getProperty(key, "global_inherits_stack"))
|
||||
if target_stack_position == -1: # Prevent -1 from selecting wrong stack.
|
||||
target_stack = self._active_container_stack
|
||||
else:
|
||||
target_stack = extruder_stacks[target_stack_position]
|
||||
|
||||
new_value = target_stack.getProperty(key, "value")
|
||||
if self._global_container_stack.getProperty(key, "value") != new_value:
|
||||
self._global_container_stack.getTop().setProperty(key, "value", new_value)
|
||||
|
||||
if property_name == "validationState":
|
||||
if self._active_stack_valid:
|
||||
changed_validation_state = self._active_container_stack.getProperty(key, property_name)
|
||||
if changed_validation_state in (UM.Settings.ValidatorState.Exception, UM.Settings.ValidatorState.MaximumError, UM.Settings.ValidatorState.MinimumError):
|
||||
self._active_stack_valid = False
|
||||
self.activeValidationChanged.emit()
|
||||
if old_index != int(position):
|
||||
extruder_manager.setActiveExtruderIndex(int(position))
|
||||
else:
|
||||
has_errors = self._checkStackForErrors(self._active_container_stack)
|
||||
if not has_errors:
|
||||
self._active_stack_valid = True
|
||||
self.activeValidationChanged.emit()
|
||||
old_index = None
|
||||
Logger.log("d", "Setting hotend variant of hotend %s to %s" % (position, hotend_id))
|
||||
self.setActiveVariant(hotend_id)
|
||||
|
||||
if old_index is not None:
|
||||
extruder_manager.setActiveExtruderIndex(old_index)
|
||||
|
||||
def _onGlobalContainerChanged(self):
|
||||
if self._global_container_stack:
|
||||
self._global_container_stack.nameChanged.disconnect(self._onMachineNameChanged)
|
||||
self._global_container_stack.containersChanged.disconnect(self._onInstanceContainersChanged)
|
||||
self._global_container_stack.propertyChanged.disconnect(self._onGlobalPropertyChanged)
|
||||
self._global_container_stack.propertyChanged.disconnect(self._onPropertyChanged)
|
||||
|
||||
material = self._global_container_stack.findContainer({"type": "material"})
|
||||
material.nameChanged.disconnect(self._onMaterialNameChanged)
|
||||
|
|
@ -313,7 +210,7 @@ class MachineManager(QObject):
|
|||
Preferences.getInstance().setValue("cura/active_machine", self._global_container_stack.getId())
|
||||
self._global_container_stack.nameChanged.connect(self._onMachineNameChanged)
|
||||
self._global_container_stack.containersChanged.connect(self._onInstanceContainersChanged)
|
||||
self._global_container_stack.propertyChanged.connect(self._onGlobalPropertyChanged)
|
||||
self._global_container_stack.propertyChanged.connect(self._onPropertyChanged)
|
||||
material = self._global_container_stack.findContainer({"type": "material"})
|
||||
material.nameChanged.connect(self._onMaterialNameChanged)
|
||||
|
||||
|
|
@ -324,11 +221,11 @@ class MachineManager(QObject):
|
|||
self.blurSettings.emit() # Ensure no-one has focus.
|
||||
if self._active_container_stack and self._active_container_stack != self._global_container_stack:
|
||||
self._active_container_stack.containersChanged.disconnect(self._onInstanceContainersChanged)
|
||||
self._active_container_stack.propertyChanged.disconnect(self._onGlobalPropertyChanged)
|
||||
self._active_container_stack.propertyChanged.disconnect(self._onPropertyChanged)
|
||||
self._active_container_stack = ExtruderManager.getInstance().getActiveExtruderStack()
|
||||
if self._active_container_stack:
|
||||
self._active_container_stack.containersChanged.connect(self._onInstanceContainersChanged)
|
||||
self._active_container_stack.propertyChanged.connect(self._onGlobalPropertyChanged)
|
||||
self._active_container_stack.propertyChanged.connect(self._onPropertyChanged)
|
||||
else:
|
||||
self._active_container_stack = self._global_container_stack
|
||||
self._active_stack_valid = not self._checkStackForErrors(self._active_container_stack)
|
||||
|
|
@ -337,17 +234,6 @@ class MachineManager(QObject):
|
|||
def _onInstanceContainersChanged(self, container):
|
||||
container_type = container.getMetaDataEntry("type")
|
||||
|
||||
if self._active_container_stack and self._active_container_stack != self._global_container_stack:
|
||||
if int(self._active_container_stack.getProperty("extruder_nr", "value")) == 0:
|
||||
global_container = self._global_container_stack.findContainer({"type": container_type})
|
||||
if global_container and global_container != container:
|
||||
container_index = self._global_container_stack.getContainerIndex(global_container)
|
||||
self._global_container_stack.replaceContainer(container_index, container)
|
||||
|
||||
for key in container.getAllKeys():
|
||||
# Make sure the values in this profile are distributed to other stacks if necessary
|
||||
self._onGlobalPropertyChanged(key, "value")
|
||||
|
||||
if container_type == "material":
|
||||
self.activeMaterialChanged.emit()
|
||||
elif container_type == "variant":
|
||||
|
|
@ -355,6 +241,38 @@ class MachineManager(QObject):
|
|||
elif container_type == "quality":
|
||||
self.activeQualityChanged.emit()
|
||||
|
||||
def _onPropertyChanged(self, key, property_name):
|
||||
if property_name == "value":
|
||||
# If a setting is not settable per extruder, but "has enabled relations" that are settable per extruder
|
||||
# we need to copy the value to global, so that the front-end displays the right settings.
|
||||
if not self._active_container_stack.getProperty(key, "settable_per_extruder"):
|
||||
relations = self._global_container_stack.getBottom()._getDefinition(key).relations
|
||||
for relation in filter(lambda r: r.role == "enabled" and r.type == RelationType.RequiredByTarget, relations):
|
||||
# Target setting is settable per extruder
|
||||
if self._active_container_stack.getProperty(relation.target.key, "settable_per_extruder"):
|
||||
new_value = self._global_container_stack.getProperty(key, "value")
|
||||
stacks = [stack for stack in ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId())]
|
||||
for extruder_stack in stacks:
|
||||
if extruder_stack.getProperty(key, "value") != new_value:
|
||||
extruder_stack.getTop().setProperty(key, "value", new_value)
|
||||
break
|
||||
|
||||
if property_name == "validationState":
|
||||
if self._active_stack_valid:
|
||||
if self._active_container_stack.getProperty(key, "settable_per_extruder"):
|
||||
changed_validation_state = self._active_container_stack.getProperty(key, property_name)
|
||||
else:
|
||||
changed_validation_state = self._global_container_stack.getProperty(key, property_name)
|
||||
if changed_validation_state in (UM.Settings.ValidatorState.Exception, UM.Settings.ValidatorState.MaximumError, UM.Settings.ValidatorState.MinimumError):
|
||||
self._active_stack_valid = False
|
||||
self.activeValidationChanged.emit()
|
||||
else:
|
||||
if not self._checkStackForErrors(self._active_container_stack) and not self._checkStackForErrors(self._global_container_stack):
|
||||
self._active_stack_valid = True
|
||||
self.activeValidationChanged.emit()
|
||||
|
||||
self.activeStackChanged.emit()
|
||||
|
||||
@pyqtSlot(str)
|
||||
def setActiveMachine(self, stack_id):
|
||||
containers = UM.Settings.ContainerRegistry.getInstance().findContainerStacks(id = stack_id)
|
||||
|
|
@ -374,7 +292,7 @@ class MachineManager(QObject):
|
|||
|
||||
variant_instance_container = self._updateVariantContainer(definition)
|
||||
material_instance_container = self._updateMaterialContainer(definition, variant_instance_container)
|
||||
quality_instance_container = self._updateQualityContainer(definition, material_instance_container)
|
||||
quality_instance_container = self._updateQualityContainer(definition, variant_instance_container, material_instance_container)
|
||||
|
||||
current_settings_instance_container = UM.Settings.InstanceContainer(name + "_current_settings")
|
||||
current_settings_instance_container.addMetaDataEntry("machine", name)
|
||||
|
|
@ -382,7 +300,6 @@ class MachineManager(QObject):
|
|||
current_settings_instance_container.setDefinition(definitions[0])
|
||||
container_registry.addContainer(current_settings_instance_container)
|
||||
|
||||
# If a definition is found, its a list. Should only have one item.
|
||||
new_global_stack.addContainer(definition)
|
||||
if variant_instance_container:
|
||||
new_global_stack.addContainer(variant_instance_container)
|
||||
|
|
@ -390,6 +307,8 @@ class MachineManager(QObject):
|
|||
new_global_stack.addContainer(material_instance_container)
|
||||
if quality_instance_container:
|
||||
new_global_stack.addContainer(quality_instance_container)
|
||||
|
||||
new_global_stack.addContainer(self._empty_quality_changes_container)
|
||||
new_global_stack.addContainer(current_settings_instance_container)
|
||||
|
||||
ExtruderManager.getInstance().addMachineExtruders(definition, new_global_stack.getId())
|
||||
|
|
@ -430,11 +349,29 @@ class MachineManager(QObject):
|
|||
## Check if the global_container has instances in the user container
|
||||
@pyqtProperty(bool, notify = activeStackChanged)
|
||||
def hasUserSettings(self):
|
||||
if not self._active_container_stack:
|
||||
if not self._global_container_stack:
|
||||
return False
|
||||
|
||||
user_settings = self._active_container_stack.getTop().findInstances(**{})
|
||||
return len(user_settings) != 0
|
||||
if self._global_container_stack.getTop().findInstances():
|
||||
return True
|
||||
|
||||
for stack in ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId()):
|
||||
if stack.getTop().findInstances():
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
## Delete a user setting from the global stack and all extruder stacks.
|
||||
# \param key \type{str} the name of the key to delete
|
||||
@pyqtSlot(str)
|
||||
def clearUserSettingAllCurrentStacks(self, key):
|
||||
if not self._global_container_stack:
|
||||
return
|
||||
|
||||
self._global_container_stack.getTop().removeInstance(key)
|
||||
|
||||
for stack in ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId()):
|
||||
stack.getTop().removeInstance(key)
|
||||
|
||||
## Check if the global profile does not contain error states
|
||||
# Note that the _active_stack_valid is cached due to performance issues
|
||||
|
|
@ -489,9 +426,47 @@ class MachineManager(QObject):
|
|||
|
||||
return ""
|
||||
|
||||
@pyqtProperty("QVariantMap", notify = activeMaterialChanged)
|
||||
def allActiveMaterialIds(self):
|
||||
if not self._global_container_stack:
|
||||
return {}
|
||||
|
||||
result = {}
|
||||
|
||||
for stack in ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks():
|
||||
material_container = stack.findContainer(type = "material")
|
||||
if not material_container:
|
||||
continue
|
||||
|
||||
result[stack.getId()] = material_container.getId()
|
||||
|
||||
return result
|
||||
|
||||
## Get the Material ID associated with the currently active material
|
||||
# \returns MaterialID (string) if found, empty string otherwise
|
||||
@pyqtProperty(str, notify=activeQualityChanged)
|
||||
def activeQualityMaterialId(self):
|
||||
if self._active_container_stack:
|
||||
quality = self._active_container_stack.findContainer({"type": "quality"})
|
||||
if quality:
|
||||
material_id = quality.getMetaDataEntry("material")
|
||||
if material_id:
|
||||
# if the currently active machine inherits its qualities from a different machine
|
||||
# definition, make sure to return a material that is relevant to that machine definition
|
||||
definition_id = self.activeDefinitionId
|
||||
quality_definition_id = self.activeQualityDefinitionId
|
||||
if definition_id != quality_definition_id:
|
||||
material_id = material_id.replace(definition_id, quality_definition_id, 1)
|
||||
|
||||
return material_id
|
||||
return ""
|
||||
|
||||
@pyqtProperty(str, notify=activeQualityChanged)
|
||||
def activeQualityName(self):
|
||||
if self._active_container_stack:
|
||||
quality = self._active_container_stack.findContainer({"type": "quality_changes"})
|
||||
if quality and quality != self._empty_quality_changes_container:
|
||||
return quality.getName()
|
||||
quality = self._active_container_stack.findContainer({"type": "quality"})
|
||||
if quality:
|
||||
return quality.getName()
|
||||
|
|
@ -499,12 +474,31 @@ class MachineManager(QObject):
|
|||
|
||||
@pyqtProperty(str, notify=activeQualityChanged)
|
||||
def activeQualityId(self):
|
||||
if self._active_container_stack:
|
||||
quality = self._active_container_stack.findContainer({"type": "quality"})
|
||||
if self._global_container_stack:
|
||||
quality = self._global_container_stack.findContainer({"type": "quality_changes"})
|
||||
if quality and quality != self._empty_quality_changes_container:
|
||||
return quality.getId()
|
||||
quality = self._global_container_stack.findContainer({"type": "quality"})
|
||||
if quality:
|
||||
return quality.getId()
|
||||
return ""
|
||||
|
||||
@pyqtProperty(str, notify = activeQualityChanged)
|
||||
def activeQualityType(self):
|
||||
if self._global_container_stack:
|
||||
quality = self._global_container_stack.findContainer(type = "quality")
|
||||
if quality:
|
||||
return quality.getMetaDataEntry("quality_type")
|
||||
return ""
|
||||
|
||||
@pyqtProperty(str, notify = activeQualityChanged)
|
||||
def activeQualityChangesId(self):
|
||||
if self._global_container_stack:
|
||||
changes = self._global_container_stack.findContainer(type = "quality_changes")
|
||||
if changes:
|
||||
return changes.getId()
|
||||
return ""
|
||||
|
||||
## Check if a container is read_only
|
||||
@pyqtSlot(str, result = bool)
|
||||
def isReadOnly(self, container_id):
|
||||
|
|
@ -526,142 +520,62 @@ class MachineManager(QObject):
|
|||
if extruder_stack != self._active_container_stack and extruder_stack.getProperty(key, "value") != new_value:
|
||||
extruder_stack.getTop().setProperty(key, "value", new_value)
|
||||
|
||||
@pyqtSlot(result = str)
|
||||
def newQualityContainerFromQualityAndUser(self):
|
||||
new_container_id = self.duplicateContainer(self.activeQualityId)
|
||||
if new_container_id == "":
|
||||
return
|
||||
self.blurSettings.emit()
|
||||
self.updateQualityContainerFromUserContainer(new_container_id)
|
||||
self.setActiveQuality(new_container_id)
|
||||
return new_container_id
|
||||
|
||||
@pyqtSlot(str, result=str)
|
||||
def duplicateContainer(self, container_id):
|
||||
if not self._active_container_stack:
|
||||
return ""
|
||||
containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = container_id)
|
||||
if containers:
|
||||
new_name = self._createUniqueName("quality", "", containers[0].getName(), catalog.i18nc("@label", "Custom profile"))
|
||||
|
||||
new_container = containers[0].duplicate(new_name, new_name)
|
||||
|
||||
UM.Settings.ContainerRegistry.getInstance().addContainer(new_container)
|
||||
|
||||
return new_name
|
||||
|
||||
return ""
|
||||
|
||||
@pyqtSlot(str, str)
|
||||
def renameQualityContainer(self, container_id, new_name):
|
||||
containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = container_id, type = "quality")
|
||||
if containers:
|
||||
new_name = self._createUniqueName("quality", containers[0].getName(), new_name,
|
||||
catalog.i18nc("@label", "Custom profile"))
|
||||
|
||||
if containers[0].getName() == new_name:
|
||||
# Nothing to do.
|
||||
return
|
||||
|
||||
# As we also want the id of the container to be changed (so that profile name is the name of the file
|
||||
# on disk. We need to create a new instance and remove it (so the old file of the container is removed)
|
||||
# If we don't do that, we might get duplicates & other weird issues.
|
||||
new_container = UM.Settings.InstanceContainer("")
|
||||
new_container.deserialize(containers[0].serialize())
|
||||
|
||||
# Actually set the name
|
||||
new_container.setName(new_name)
|
||||
new_container._id = new_name # Todo: Fix proper id change function for this.
|
||||
|
||||
# Add the "new" container.
|
||||
UM.Settings.ContainerRegistry.getInstance().addContainer(new_container)
|
||||
|
||||
# Ensure that the renamed profile is saved -before- we remove the old profile.
|
||||
Application.getInstance().saveSettings()
|
||||
|
||||
# Actually set & remove new / old quality.
|
||||
self.setActiveQuality(new_name)
|
||||
self.removeQualityContainer(containers[0].getId())
|
||||
|
||||
@pyqtSlot(str)
|
||||
def removeQualityContainer(self, container_id):
|
||||
containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = container_id)
|
||||
if not containers or not self._active_container_stack:
|
||||
return
|
||||
|
||||
# If the container that is being removed is the currently active container, set another machine as the active container
|
||||
activate_new_container = container_id == self.activeQualityId
|
||||
|
||||
UM.Settings.ContainerRegistry.getInstance().removeContainer(container_id)
|
||||
|
||||
if activate_new_container:
|
||||
definition_id = "fdmprinter" if not self.filterQualityByMachine else self.activeDefinitionId
|
||||
containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(type = "quality", definition = definition_id)
|
||||
if containers:
|
||||
self.setActiveQuality(containers[0].getId())
|
||||
self.activeQualityChanged.emit()
|
||||
|
||||
@pyqtSlot(str)
|
||||
@pyqtSlot()
|
||||
def updateQualityContainerFromUserContainer(self, quality_id = None):
|
||||
if not self._active_container_stack:
|
||||
return
|
||||
|
||||
if quality_id:
|
||||
quality = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = quality_id, type = "quality")
|
||||
if quality:
|
||||
quality = quality[0]
|
||||
else:
|
||||
quality = self._active_container_stack.findContainer({"type": "quality"})
|
||||
|
||||
if not quality:
|
||||
return
|
||||
|
||||
user_settings = self._active_container_stack.getTop()
|
||||
|
||||
for key in user_settings.getAllKeys():
|
||||
quality.setProperty(key, "value", user_settings.getProperty(key, "value"))
|
||||
self.clearUserSettings() # As all users settings are noq a quality, remove them.
|
||||
|
||||
|
||||
@pyqtSlot(str)
|
||||
def setActiveMaterial(self, material_id):
|
||||
containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = material_id)
|
||||
if not containers or not self._active_container_stack:
|
||||
return
|
||||
|
||||
old_material = self._active_container_stack.findContainer({"type":"material"})
|
||||
Logger.log("d", "Attempting to change the active material to %s", material_id)
|
||||
old_variant = self._active_container_stack.findContainer({"type": "variant"})
|
||||
old_material = self._active_container_stack.findContainer({"type": "material"})
|
||||
old_quality = self._active_container_stack.findContainer({"type": "quality"})
|
||||
if old_material:
|
||||
old_material.nameChanged.disconnect(self._onMaterialNameChanged)
|
||||
old_quality_changes = self._active_container_stack.findContainer({"type": "quality_changes"})
|
||||
if not old_material:
|
||||
Logger.log("w", "While trying to set the active material, no material was found to replace it.")
|
||||
return
|
||||
if old_quality_changes.getId() == "empty_quality_changes": #Don't want the empty one.
|
||||
old_quality_changes = None
|
||||
self.blurSettings.emit()
|
||||
old_material.nameChanged.disconnect(self._onMaterialNameChanged)
|
||||
|
||||
material_index = self._active_container_stack.getContainerIndex(old_material)
|
||||
self._active_container_stack.replaceContainer(material_index, containers[0])
|
||||
material_index = self._active_container_stack.getContainerIndex(old_material)
|
||||
self._active_container_stack.replaceContainer(material_index, containers[0])
|
||||
|
||||
containers[0].nameChanged.connect(self._onMaterialNameChanged)
|
||||
containers[0].nameChanged.connect(self._onMaterialNameChanged)
|
||||
|
||||
preferred_quality_name = None
|
||||
if old_quality:
|
||||
preferred_quality_name = old_quality.getName()
|
||||
if containers[0].getMetaDataEntry("compatible") == False:
|
||||
message = Message(catalog.i18nc("@info:status",
|
||||
"The selected material is imcompatible with the selected machine or configuration."))
|
||||
message.show()
|
||||
|
||||
self.setActiveQuality(self._updateQualityContainer(self._global_container_stack.getBottom(), containers[0], preferred_quality_name).id)
|
||||
|
||||
if old_quality:
|
||||
if old_quality_changes:
|
||||
new_quality = self._updateQualityChangesContainer(old_quality.getMetaDataEntry("quality_type"), old_quality_changes.getMetaDataEntry("name"))
|
||||
else:
|
||||
new_quality = self._updateQualityContainer(self._global_container_stack.getBottom(), old_variant, containers[0], old_quality.getName())
|
||||
else:
|
||||
Logger.log("w", "While trying to set the active material, no material was found to replace.")
|
||||
new_quality = self._updateQualityContainer(self._global_container_stack.getBottom(), old_variant, containers[0])
|
||||
|
||||
self.setActiveQuality(new_quality.getId())
|
||||
|
||||
@pyqtSlot(str)
|
||||
def setActiveVariant(self, variant_id):
|
||||
containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = variant_id)
|
||||
if not containers or not self._active_container_stack:
|
||||
return
|
||||
Logger.log("d", "Attempting to change the active variant to %s", variant_id)
|
||||
old_variant = self._active_container_stack.findContainer({"type": "variant"})
|
||||
old_material = self._active_container_stack.findContainer({"type": "material"})
|
||||
if old_variant:
|
||||
self.blurSettings.emit()
|
||||
variant_index = self._active_container_stack.getContainerIndex(old_variant)
|
||||
self._active_container_stack.replaceContainer(variant_index, containers[0])
|
||||
|
||||
preferred_material = None
|
||||
if old_material:
|
||||
preferred_material_name = old_material.getName()
|
||||
|
||||
self.setActiveMaterial(self._updateMaterialContainer(self._global_container_stack.getBottom(), containers[0], preferred_material_name).id)
|
||||
else:
|
||||
Logger.log("w", "While trying to set the active variant, no variant was found to replace.")
|
||||
|
|
@ -669,32 +583,96 @@ class MachineManager(QObject):
|
|||
@pyqtSlot(str)
|
||||
def setActiveQuality(self, quality_id):
|
||||
containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = quality_id)
|
||||
if not containers or not self._active_container_stack:
|
||||
if not containers or not self._global_container_stack:
|
||||
return
|
||||
|
||||
old_quality = self._active_container_stack.findContainer({"type": "quality"})
|
||||
if old_quality and old_quality != containers[0]:
|
||||
old_quality.nameChanged.disconnect(self._onQualityNameChanged)
|
||||
Logger.log("d", "Attempting to change the active quality to %s", quality_id)
|
||||
|
||||
quality_index = self._active_container_stack.getContainerIndex(old_quality)
|
||||
self.blurSettings.emit()
|
||||
quality_container = None
|
||||
quality_changes_container = self._empty_quality_changes_container
|
||||
|
||||
self._active_container_stack.replaceContainer(quality_index, containers[0])
|
||||
container_type = containers[0].getMetaDataEntry("type")
|
||||
|
||||
containers[0].nameChanged.connect(self._onQualityNameChanged)
|
||||
|
||||
if self.hasUserSettings and Preferences.getInstance().getValue("cura/active_mode") == 1:
|
||||
# Ask the user if the user profile should be cleared or not (discarding the current settings)
|
||||
# In Simple Mode we assume the user always wants to keep the (limited) current settings
|
||||
details = catalog.i18nc("@label", "You made changes to the following setting(s):")
|
||||
user_settings = self._active_container_stack.getTop().findInstances(**{})
|
||||
for setting in user_settings:
|
||||
details = details + "\n " + setting.definition.label
|
||||
|
||||
Application.getInstance().messageBox(catalog.i18nc("@window:title", "Switched profiles"), catalog.i18nc("@label", "Do you want to transfer your changed settings to this profile?"),
|
||||
catalog.i18nc("@label", "If you transfer your settings they will override settings in the profile."), details,
|
||||
buttons = QMessageBox.Yes + QMessageBox.No, icon = QMessageBox.Question, callback = self._keepUserSettingsDialogCallback)
|
||||
if container_type == "quality":
|
||||
quality_container = containers[0]
|
||||
elif container_type == "quality_changes":
|
||||
quality_changes_container = containers[0]
|
||||
containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(
|
||||
quality_type = quality_changes_container.getMetaDataEntry("quality"))
|
||||
if not containers:
|
||||
Logger.log("e", "Could not find quality %s for changes %s, not changing quality", quality_changes_container.getMetaDataEntry("quality"), quality_changes_container.getId())
|
||||
return
|
||||
quality_container = containers[0]
|
||||
else:
|
||||
Logger.log("w", "While trying to set the active quality, no quality was found to replace.")
|
||||
Logger.log("e", "Tried to set quality to a container that is not of the right type")
|
||||
return
|
||||
|
||||
quality_type = quality_container.getMetaDataEntry("quality_type")
|
||||
if not quality_type:
|
||||
quality_type = quality_changes_container.getName()
|
||||
|
||||
for stack in ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks():
|
||||
extruder_id = stack.getId() if stack != self._global_container_stack else None
|
||||
|
||||
criteria = { "quality_type": quality_type, "extruder": extruder_id }
|
||||
|
||||
material = stack.findContainer(type = "material")
|
||||
if material and material is not self._empty_material_container:
|
||||
criteria["material"] = material.getId()
|
||||
|
||||
if self._global_container_stack.getMetaDataEntry("has_machine_quality"):
|
||||
criteria["definition"] = self.activeQualityDefinitionId
|
||||
else:
|
||||
criteria["definition"] = "fdmprinter"
|
||||
|
||||
stack_quality = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(**criteria)
|
||||
if not stack_quality:
|
||||
criteria.pop("extruder")
|
||||
stack_quality = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(**criteria)
|
||||
if not stack_quality:
|
||||
stack_quality = quality_container
|
||||
else:
|
||||
stack_quality = stack_quality[0]
|
||||
else:
|
||||
stack_quality = stack_quality[0]
|
||||
|
||||
if quality_changes_container != self._empty_quality_changes_container:
|
||||
stack_quality_changes = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(name = quality_changes_container.getName(), extruder = extruder_id)[0]
|
||||
else:
|
||||
stack_quality_changes = self._empty_quality_changes_container
|
||||
|
||||
old_quality = stack.findContainer(type = "quality")
|
||||
if old_quality:
|
||||
old_quality.nameChanged.disconnect(self._onQualityNameChanged)
|
||||
else:
|
||||
Logger.log("w", "Could not find old quality while changing active quality.")
|
||||
|
||||
old_changes = stack.findContainer(type = "quality_changes")
|
||||
if old_changes:
|
||||
old_changes.nameChanged.disconnect(self._onQualityNameChanged)
|
||||
else:
|
||||
Logger.log("w", "Could not find old quality_changes while changing active quality.")
|
||||
|
||||
stack.replaceContainer(stack.getContainerIndex(old_quality), stack_quality)
|
||||
stack.replaceContainer(stack.getContainerIndex(old_changes), stack_quality_changes)
|
||||
|
||||
stack_quality.nameChanged.connect(self._onQualityNameChanged)
|
||||
stack_quality_changes.nameChanged.connect(self._onQualityNameChanged)
|
||||
|
||||
if self.hasUserSettings and Preferences.getInstance().getValue("cura/active_mode") == 1:
|
||||
# Ask the user if the user profile should be cleared or not (discarding the current settings)
|
||||
# In Simple Mode we assume the user always wants to keep the (limited) current settings
|
||||
details = catalog.i18nc("@label", "You made changes to the following setting(s):")
|
||||
user_settings = self._active_container_stack.getTop().findInstances(**{})
|
||||
for setting in user_settings:
|
||||
details = details + "\n " + setting.definition.label
|
||||
|
||||
Application.getInstance().messageBox(catalog.i18nc("@window:title", "Switched profiles"), catalog.i18nc("@label", "Do you want to transfer your changed settings to this profile?"),
|
||||
catalog.i18nc("@label", "If you transfer your settings they will override settings in the profile."), details,
|
||||
buttons = QMessageBox.Yes + QMessageBox.No, icon = QMessageBox.Question, callback = self._keepUserSettingsDialogCallback)
|
||||
|
||||
self.activeQualityChanged.emit()
|
||||
|
||||
def _keepUserSettingsDialogCallback(self, button):
|
||||
if button == QMessageBox.Yes:
|
||||
|
|
@ -702,7 +680,11 @@ class MachineManager(QObject):
|
|||
pass
|
||||
elif button == QMessageBox.No:
|
||||
# No, discard the settings in the user profile
|
||||
self.clearUserSettings()
|
||||
global_stack = Application.getInstance().getGlobalContainerStack()
|
||||
for extruder in ExtruderManager.getInstance().getMachineExtruders(global_stack.getId()):
|
||||
extruder.getTop().clear()
|
||||
|
||||
global_stack.getTop().clear()
|
||||
|
||||
@pyqtProperty(str, notify = activeVariantChanged)
|
||||
def activeVariantName(self):
|
||||
|
|
@ -731,6 +713,61 @@ class MachineManager(QObject):
|
|||
|
||||
return ""
|
||||
|
||||
## Get the Definition ID to use to select quality profiles for the currently active machine
|
||||
# \returns DefinitionID (string) if found, empty string otherwise
|
||||
# \sa getQualityDefinitionId
|
||||
@pyqtProperty(str, notify = globalContainerChanged)
|
||||
def activeQualityDefinitionId(self):
|
||||
if self._global_container_stack:
|
||||
return self.getQualityDefinitionId(self._global_container_stack.getBottom())
|
||||
return ""
|
||||
|
||||
## Get the Definition ID to use to select quality profiles for machines of the specified definition
|
||||
# This is normally the id of the definition itself, but machines can specify a different definition to inherit qualities from
|
||||
# \param definition (DefinitionContainer) machine definition
|
||||
# \returns DefinitionID (string) if found, empty string otherwise
|
||||
def getQualityDefinitionId(self, definition):
|
||||
definition_id = definition.getMetaDataEntry("quality_definition")
|
||||
if not definition_id:
|
||||
definition_id = definition.getId()
|
||||
return definition_id
|
||||
|
||||
## Get the Variant ID to use to select quality profiles for the currently active variant
|
||||
# \returns VariantID (string) if found, empty string otherwise
|
||||
# \sa getQualityVariantId
|
||||
@pyqtProperty(str, notify = activeVariantChanged)
|
||||
def activeQualityVariantId(self):
|
||||
if self._global_container_stack:
|
||||
variant = self._global_container_stack.findContainer({"type": "variant"})
|
||||
if variant:
|
||||
return self.getQualityVariantId(self._global_container_stack.getBottom(), variant)
|
||||
return ""
|
||||
|
||||
## Get the Variant ID to use to select quality profiles for variants of the specified definitions
|
||||
# This is normally the id of the variant itself, but machines can specify a different definition
|
||||
# to inherit qualities from, which has consequences for the variant to use as well
|
||||
# \param definition (DefinitionContainer) machine definition
|
||||
# \param variant (DefinitionContainer) variant definition
|
||||
# \returns VariantID (string) if found, empty string otherwise
|
||||
def getQualityVariantId(self, definition, variant):
|
||||
variant_id = variant.getId()
|
||||
definition_id = definition.getId()
|
||||
quality_definition_id = self.getQualityDefinitionId(definition)
|
||||
|
||||
if definition_id != quality_definition_id:
|
||||
variant_id = variant_id.replace(definition_id, quality_definition_id, 1)
|
||||
return variant_id
|
||||
|
||||
## Gets how the active definition calls variants
|
||||
# Caveat: per-definition-variant-title is currently not translated (though the fallback is)
|
||||
@pyqtProperty(str, notify = globalContainerChanged)
|
||||
def activeDefinitionVariantsName(self):
|
||||
fallback_title = catalog.i18nc("@label", "Nozzle")
|
||||
if self._global_container_stack:
|
||||
return self._global_container_stack.getBottom().getMetaDataEntry("variants_name", fallback_title)
|
||||
|
||||
return fallback_title
|
||||
|
||||
@pyqtSlot(str, str)
|
||||
def renameMachine(self, machine_id, new_name):
|
||||
containers = UM.Settings.ContainerRegistry.getInstance().findContainerStacks(id = machine_id)
|
||||
|
|
@ -825,10 +862,10 @@ class MachineManager(QObject):
|
|||
search_criteria = { "type": "material" }
|
||||
|
||||
if definition.getMetaDataEntry("has_machine_materials"):
|
||||
search_criteria["definition"] = definition.id
|
||||
search_criteria["definition"] = self.getQualityDefinitionId(definition)
|
||||
|
||||
if definition.getMetaDataEntry("has_variants") and variant_container:
|
||||
search_criteria["variant"] = variant_container.id
|
||||
search_criteria["variant"] = self.getQualityVariantId(definition, variant_container)
|
||||
else:
|
||||
search_criteria["definition"] = "fdmprinter"
|
||||
|
||||
|
|
@ -843,50 +880,120 @@ class MachineManager(QObject):
|
|||
if containers:
|
||||
return containers[0]
|
||||
|
||||
if "name" in search_criteria or "id" in search_criteria:
|
||||
containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(**search_criteria)
|
||||
if "variant" in search_criteria or "id" in search_criteria:
|
||||
# If a material by this name can not be found, try a wider set of search criteria
|
||||
search_criteria.pop("name", None)
|
||||
search_criteria.pop("variant", None)
|
||||
search_criteria.pop("id", None)
|
||||
|
||||
containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(**search_criteria)
|
||||
if containers:
|
||||
return containers[0]
|
||||
|
||||
Logger.log("w", "Unable to find a material container with provided criteria, returning an empty one instead.")
|
||||
return self._empty_material_container
|
||||
|
||||
def _updateQualityContainer(self, definition, material_container = None, preferred_quality_name = None):
|
||||
def _updateQualityContainer(self, definition, variant_container, material_container = None, preferred_quality_name = None):
|
||||
container_registry = UM.Settings.ContainerRegistry.getInstance()
|
||||
search_criteria = { "type": "quality" }
|
||||
|
||||
if definition.getMetaDataEntry("has_machine_quality"):
|
||||
search_criteria["definition"] = definition.id
|
||||
search_criteria["definition"] = self.getQualityDefinitionId(definition)
|
||||
|
||||
if definition.getMetaDataEntry("has_materials") and material_container:
|
||||
search_criteria["material"] = material_container.id
|
||||
else:
|
||||
search_criteria["definition"] = "fdmprinter"
|
||||
|
||||
if preferred_quality_name:
|
||||
if preferred_quality_name and preferred_quality_name != "empty":
|
||||
search_criteria["name"] = preferred_quality_name
|
||||
else:
|
||||
preferred_quality = definition.getMetaDataEntry("preferred_quality")
|
||||
if preferred_quality:
|
||||
search_criteria["id"] = preferred_quality
|
||||
|
||||
containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(**search_criteria)
|
||||
containers = container_registry.findInstanceContainers(**search_criteria)
|
||||
if containers:
|
||||
return containers[0]
|
||||
|
||||
if "material" in search_criteria:
|
||||
# First check if we can solve our material not found problem by checking if we can find quality containers
|
||||
# that are assigned to the parents of this material profile.
|
||||
try:
|
||||
inherited_files = material_container.getInheritedFiles()
|
||||
except AttributeError: # Material_container does not support inheritance.
|
||||
inherited_files = []
|
||||
|
||||
if inherited_files:
|
||||
for inherited_file in inherited_files:
|
||||
# Extract the ID from the path we used to load the file.
|
||||
search_criteria["material"] = os.path.basename(inherited_file).split(".")[0]
|
||||
containers = container_registry.findInstanceContainers(**search_criteria)
|
||||
if containers:
|
||||
return containers[0]
|
||||
# We still weren't able to find a quality for this specific material.
|
||||
# Try to find qualities for a generic version of the material.
|
||||
material_search_criteria = { "type": "material", "material": material_container.getMetaDataEntry("material"), "color_name": "Generic"}
|
||||
if definition.getMetaDataEntry("has_machine_quality"):
|
||||
if material_container:
|
||||
material_search_criteria["definition"] = material_container.getDefinition().id
|
||||
|
||||
if definition.getMetaDataEntry("has_variants"):
|
||||
material_search_criteria["variant"] = material_container.getMetaDataEntry("variant")
|
||||
else:
|
||||
material_search_criteria["definition"] = self.getQualityDefinitionId(definition)
|
||||
|
||||
if definition.getMetaDataEntry("has_variants") and variant_container:
|
||||
material_search_criteria["variant"] = self.getQualityVariantId(definition, variant_container)
|
||||
else:
|
||||
material_search_criteria["definition"] = "fdmprinter"
|
||||
material_containers = container_registry.findInstanceContainers(**material_search_criteria)
|
||||
if material_containers:
|
||||
search_criteria["material"] = material_containers[0].getId()
|
||||
|
||||
containers = container_registry.findInstanceContainers(**search_criteria)
|
||||
if containers:
|
||||
return containers[0]
|
||||
|
||||
if "name" in search_criteria or "id" in search_criteria:
|
||||
# If a quality by this name can not be found, try a wider set of search criteria
|
||||
search_criteria.pop("name", None)
|
||||
search_criteria.pop("id", None)
|
||||
|
||||
containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(**search_criteria)
|
||||
containers = container_registry.findInstanceContainers(**search_criteria)
|
||||
if containers:
|
||||
return containers[0]
|
||||
|
||||
# Notify user that we were unable to find a matching quality
|
||||
message = Message(catalog.i18nc("@info:status", "Unable to find a quality profile for this combination. Default settings will be used instead."))
|
||||
message.show()
|
||||
return self._empty_quality_container
|
||||
|
||||
## Finds a quality-changes container to use if any other container
|
||||
# changes.
|
||||
#
|
||||
# \param quality_type The quality type to find a quality-changes for.
|
||||
# \param preferred_quality_changes_name The name of the quality-changes to
|
||||
# pick, if any such quality-changes profile is available.
|
||||
def _updateQualityChangesContainer(self, quality_type, preferred_quality_changes_name = None):
|
||||
container_registry = UM.Settings.ContainerRegistry.getInstance() # Cache.
|
||||
search_criteria = { "type": "quality_changes" }
|
||||
|
||||
search_criteria["quality"] = quality_type
|
||||
if preferred_quality_changes_name:
|
||||
search_criteria["name"] = preferred_quality_changes_name
|
||||
|
||||
# Try to search with the name in the criteria first, since we prefer to have the correct name.
|
||||
containers = container_registry.findInstanceContainers(**search_criteria)
|
||||
if containers: # Found one!
|
||||
return containers[0]
|
||||
|
||||
if "name" in search_criteria:
|
||||
del search_criteria["name"] # Not found, then drop the name requirement (if we had one) and search again.
|
||||
containers = container_registry.findInstanceContainers(**search_criteria)
|
||||
if containers:
|
||||
return containers[0]
|
||||
|
||||
return self._empty_quality_changes_container # Didn't find anything with the required quality_type.
|
||||
|
||||
def _onMachineNameChanged(self):
|
||||
self.globalContainerChanged.emit()
|
||||
|
||||
|
|
|
|||
194
cura/Settings/QualitySettingsModel.py
Normal file
194
cura/Settings/QualitySettingsModel.py
Normal file
|
|
@ -0,0 +1,194 @@
|
|||
# Copyright (c) 2016 Ultimaker B.V.
|
||||
# Cura is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
import collections
|
||||
|
||||
from PyQt5.QtCore import pyqtProperty, pyqtSignal, Qt
|
||||
|
||||
import UM.Application
|
||||
import UM.Logger
|
||||
import UM.Qt
|
||||
import UM.Settings
|
||||
|
||||
|
||||
class QualitySettingsModel(UM.Qt.ListModel.ListModel):
|
||||
KeyRole = Qt.UserRole + 1
|
||||
LabelRole = Qt.UserRole + 2
|
||||
UnitRole = Qt.UserRole + 3
|
||||
ProfileValueRole = Qt.UserRole + 4
|
||||
UserValueRole = Qt.UserRole + 5
|
||||
CategoryRole = Qt.UserRole + 6
|
||||
|
||||
def __init__(self, parent = None):
|
||||
super().__init__(parent = parent)
|
||||
|
||||
self._container_registry = UM.Settings.ContainerRegistry.getInstance()
|
||||
|
||||
self._extruder_id = None
|
||||
self._quality = None
|
||||
self._material = None
|
||||
|
||||
self.addRoleName(self.KeyRole, "key")
|
||||
self.addRoleName(self.LabelRole, "label")
|
||||
self.addRoleName(self.UnitRole, "unit")
|
||||
self.addRoleName(self.ProfileValueRole, "profile_value")
|
||||
self.addRoleName(self.UserValueRole, "user_value")
|
||||
self.addRoleName(self.CategoryRole, "category")
|
||||
|
||||
def setExtruderId(self, extruder_id):
|
||||
if extruder_id != self._extruder_id:
|
||||
self._extruder_id = extruder_id
|
||||
self._update()
|
||||
self.extruderIdChanged.emit()
|
||||
|
||||
extruderIdChanged = pyqtSignal()
|
||||
@pyqtProperty(str, fset = setExtruderId, notify = extruderIdChanged)
|
||||
def extruderId(self):
|
||||
return self._extruder_id
|
||||
|
||||
def setQuality(self, quality):
|
||||
if quality != self._quality:
|
||||
self._quality = quality
|
||||
self._update()
|
||||
self.qualityChanged.emit()
|
||||
|
||||
qualityChanged = pyqtSignal()
|
||||
@pyqtProperty(str, fset = setQuality, notify = qualityChanged)
|
||||
def quality(self):
|
||||
return self._quality
|
||||
|
||||
def setMaterial(self, material):
|
||||
if material != self._material:
|
||||
self._material = material
|
||||
self._update()
|
||||
self.materialChanged.emit()
|
||||
|
||||
materialChanged = pyqtSignal()
|
||||
@pyqtProperty(str, fset = setMaterial, notify = materialChanged)
|
||||
def material(self):
|
||||
return self._material
|
||||
|
||||
def _update(self):
|
||||
if not self._quality:
|
||||
return
|
||||
|
||||
items = []
|
||||
|
||||
settings = collections.OrderedDict()
|
||||
definition_container = UM.Application.getInstance().getGlobalContainerStack().getBottom()
|
||||
|
||||
containers = self._container_registry.findInstanceContainers(id = self._quality)
|
||||
if not containers:
|
||||
UM.Logger.log("w", "Could not find a quality container with id %s", self._quality)
|
||||
return
|
||||
|
||||
quality_container = None
|
||||
quality_changes_container = None
|
||||
|
||||
if containers[0].getMetaDataEntry("type") == "quality":
|
||||
quality_container = containers[0]
|
||||
else:
|
||||
quality_changes_container = containers[0]
|
||||
|
||||
criteria = {
|
||||
"type": "quality",
|
||||
"quality_type": quality_changes_container.getMetaDataEntry("quality"),
|
||||
"definition": quality_changes_container.getDefinition().getId()
|
||||
}
|
||||
|
||||
if self._material:
|
||||
criteria["material"] = self._material
|
||||
|
||||
quality_container = self._container_registry.findInstanceContainers(**criteria)
|
||||
if not quality_container:
|
||||
UM.Logger.log("w", "Could not find a quality container matching quality changes %s", quality_changes_container.getId())
|
||||
return
|
||||
quality_container = quality_container[0]
|
||||
|
||||
quality_type = quality_container.getMetaDataEntry("quality_type")
|
||||
definition_id = quality_container.getDefinition().getId()
|
||||
|
||||
criteria = {"type": "quality", "quality_type": quality_type, "definition": definition_id}
|
||||
|
||||
if self._material:
|
||||
criteria["material"] = self._material
|
||||
|
||||
criteria["extruder"] = self._extruder_id
|
||||
|
||||
containers = self._container_registry.findInstanceContainers(**criteria)
|
||||
if not containers:
|
||||
# Try again, this time without extruder
|
||||
new_criteria = criteria.copy()
|
||||
new_criteria.pop("extruder")
|
||||
containers = self._container_registry.findInstanceContainers(**new_criteria)
|
||||
|
||||
if not containers:
|
||||
# Try again, this time without material
|
||||
criteria.pop("material")
|
||||
containers = self._container_registry.findInstanceContainers(**criteria)
|
||||
|
||||
if not containers:
|
||||
# Try again, this time without material or extruder
|
||||
criteria.pop("extruder") # "material" has already been popped
|
||||
containers = self._container_registry.findInstanceContainers(**criteria)
|
||||
|
||||
if not containers:
|
||||
UM.Logger.log("Could not find any quality containers matching the search criteria %s" % str(criteria))
|
||||
return
|
||||
|
||||
if quality_changes_container:
|
||||
criteria = {"type": "quality_changes", "quality": quality_type, "definition": definition_id, "name": quality_changes_container.getName()}
|
||||
if self._extruder_id != "":
|
||||
criteria["extruder"] = self._extruder_id
|
||||
else:
|
||||
criteria["extruder"] = None
|
||||
|
||||
changes = self._container_registry.findInstanceContainers(**criteria)
|
||||
if changes:
|
||||
containers.extend(changes)
|
||||
|
||||
global_container_stack = UM.Application.getInstance().getGlobalContainerStack()
|
||||
is_multi_extrusion = global_container_stack.getProperty("machine_extruder_count", "value") > 1
|
||||
|
||||
current_category = ""
|
||||
for definition in definition_container.findDefinitions():
|
||||
if definition.type == "category":
|
||||
current_category = definition.label
|
||||
continue
|
||||
|
||||
profile_value = None
|
||||
for container in containers:
|
||||
new_value = container.getProperty(definition.key, "value")
|
||||
if new_value is not None:
|
||||
profile_value = new_value
|
||||
|
||||
user_value = None
|
||||
if not self._extruder_id:
|
||||
user_value = global_container_stack.getTop().getProperty(definition.key, "value")
|
||||
else:
|
||||
extruder_stack = self._container_registry.findContainerStacks(id = self._extruder_id)
|
||||
if extruder_stack:
|
||||
user_value = extruder_stack[0].getTop().getProperty(definition.key, "value")
|
||||
|
||||
if profile_value is None and user_value is None:
|
||||
continue
|
||||
|
||||
if is_multi_extrusion:
|
||||
settable_per_extruder = global_container_stack.getProperty(definition.key, "settable_per_extruder")
|
||||
# If a setting is not settable per extruder (global) and we're looking at an extruder tab, don't show this value.
|
||||
if self._extruder_id != "" and not settable_per_extruder:
|
||||
continue
|
||||
|
||||
# If a setting is settable per extruder (not global) and we're looking at global tab, don't show this value.
|
||||
if self._extruder_id == "" and settable_per_extruder:
|
||||
continue
|
||||
items.append({
|
||||
"key": definition.key,
|
||||
"label": definition.label,
|
||||
"unit": definition.unit,
|
||||
"profile_value": "" if profile_value is None else str(profile_value), # it is for display only
|
||||
"user_value": "" if user_value is None else str(user_value),
|
||||
"category": current_category
|
||||
})
|
||||
|
||||
self.setItems(items)
|
||||
|
|
@ -30,7 +30,7 @@ class SettingOverrideDecorator(SceneNodeDecorator):
|
|||
self._stack.addContainer(self._instance)
|
||||
|
||||
if cura.Settings.ExtruderManager.getInstance().extruderCount > 1:
|
||||
self._extruder_stack = cura.Settings.ExtruderManager.getInstance().activeExtruderStackId
|
||||
self._extruder_stack = cura.Settings.ExtruderManager.getInstance().getExtruderStack(0).getId()
|
||||
else:
|
||||
self._extruder_stack = None
|
||||
|
||||
|
|
|
|||
|
|
@ -10,3 +10,4 @@ from .ExtrudersModel import ExtrudersModel
|
|||
from .MachineManager import MachineManager
|
||||
from .MaterialSettingsVisibilityHandler import MaterialSettingsVisibilityHandler
|
||||
from .SettingOverrideDecorator import SettingOverrideDecorator
|
||||
from .QualitySettingsModel import QualitySettingsModel
|
||||
|
|
|
|||
|
|
@ -1,3 +1,98 @@
|
|||
[2.3.0]
|
||||
|
||||
*Speed improvements
|
||||
The first thing you will notice is the speed. STL loading is now 10 to 20 times faster, layerview is significantly faster and slicing speed is slightly improved.
|
||||
|
||||
*Multi Extrusion Support
|
||||
Machines with multiple extruders are now supported. If you’ve got the Ultimaker Original with the dual extrusion upgrade kit, we’ve got you covered.
|
||||
|
||||
*Custom Machine Support
|
||||
The new custom machine plug-in allows you to change machine settings with ease. That means it’s now much easier to use Cura with custom machines.
|
||||
|
||||
*Improved Position Tool
|
||||
Place objects precisely where you want them by manually entering the values for the position.
|
||||
|
||||
*Improved Grouping
|
||||
It's now possible to transform objects that are already grouped.
|
||||
Select an individual item in a group or merged object and edit as usual. Just Ctrl + Click and edit away.
|
||||
|
||||
*Enhanced Profile Management
|
||||
Profile management is improved. You can now easily see and track changes made to your profiles.
|
||||
|
||||
*Improved Setting Visibility
|
||||
Make multiple settings visible at once. The Visibility Overview setting indicates why a setting is not shown in the sidebar even if it is selected.
|
||||
|
||||
*Improved time estimation
|
||||
Time estimations are more accurate. Based on our test time estimations should be within 5% accuracy.
|
||||
|
||||
*Optional G-code Machine Prefix
|
||||
Disable the g-code prefix in Preferences. No more UM2_ on your printer display!
|
||||
|
||||
*Print Weight Estimates
|
||||
Cura now estimates print weight as well as length.
|
||||
|
||||
*Automatic Import Configuration
|
||||
Configurations from older installations of Cura 2.1 are automatically imported into the newest installation.
|
||||
|
||||
*Slicing features
|
||||
*Infill Improvements
|
||||
We've introduced two new infill types: Tetrahedral and Cubic. They change along with the Z-axis for more uniform strength in all directions. Also, now you can change the density of the infill based on the distance from the top layers. You print get faster, use less material, and maintain the same object strength.
|
||||
|
||||
*Set Acceleration and Jerk by Feature
|
||||
You can now set Jerk and Acceleration by feature, (infill, walls, top/bottom, etc) for more precision.
|
||||
|
||||
*Outer Wall Offset
|
||||
Apply an offset to where the outer wall is printed for better surface quality when the outer wall line width is smaller than the nozzle size.
|
||||
|
||||
*Enhanced Combing
|
||||
The “No Skin” option allows you to comb over infill only to avoid scars on top surfaces.
|
||||
|
||||
*Z Hop
|
||||
Can’t avoid previously printed parts by horizontal moves? The Z Hop Only Over Printed Parts gives you the ability to Z Hop to avoid collisions for better surface quality.
|
||||
|
||||
*Skin and Wall Overlap
|
||||
The Skin Overlap setting allows you to overlap the skin lines with the walls for better adhesion.
|
||||
|
||||
*Control Initial Layer Travel Speed
|
||||
Set the travel speed of the initial layer(s) to reduce risk of extruder pulling the print from the bed.
|
||||
|
||||
*Support Bottoms
|
||||
This new feature duplicates the Support Roofs feature in the places where the support rests on the model.
|
||||
|
||||
*Bug fixes & minor changes
|
||||
Deleting grouped objects works as intended again.
|
||||
Duplicating groups works as intended again.
|
||||
Bridging works as intended again.
|
||||
Rafts are no longer printed outside of build area.
|
||||
Messages are now displayed 30 seconds instead of 10, making it less likely that certain messages are missed.
|
||||
Drag and drop on the first run on windows works again.
|
||||
You are now notified if you try to save to a locked SD card.
|
||||
Combing is applied in more cases and results in better paths.
|
||||
Infill thickness now supports Grid infill also for even multiples of the layer height.
|
||||
Unretraction speeds are correct again.
|
||||
Spiralize mode doesn’t spiralize the bottom layer any more.
|
||||
Spiralize now spiralizes each part in a layer.
|
||||
Support is no longer removed by unprintable thin parts of the model.
|
||||
Support is now generated on each layer it’s supposed to.
|
||||
Support doesn't go outside overhang areas any more.
|
||||
Line distance is now the actual line distance.
|
||||
Enabling raft doesn’t influence at which height the model is sliced any more.
|
||||
Brim is now always printed just once.
|
||||
Overlap Compensation works as intended again.
|
||||
A raft now produces a reasonable amount of retractions.
|
||||
No more string plume on top of one-at-a-time printed objects.
|
||||
Engine log can be viewed even when slicing is finished.
|
||||
Support roofs now only occur just below overhang.
|
||||
Brim is now also generated under the support.
|
||||
Compensate overlapping wall parts now also works for inner walls.
|
||||
Bed Level and Checkup procedures for UMO+ can now be done without re-adding machine.
|
||||
Undo and Redo now work correctly with multiple operations.
|
||||
The last used folder is now remembered (instead of defaulting to home folder)
|
||||
You can now adjust the speed at which the bed is lowered each layer.
|
||||
Support roofs now only generate directly below the mesh.
|
||||
Settings shared between skirt and brim now also activate when brim is selected.
|
||||
Made it possible to add multiple Per Model Settings at once.
|
||||
|
||||
[2.1.3]
|
||||
|
||||
*Material Profiles
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ message Slice
|
|||
repeated ObjectList object_lists = 1; // The meshgroups to be printed one after another
|
||||
SettingList global_settings = 2; // The global settings used for the whole print job
|
||||
repeated Extruder extruders = 3; // The settings sent to each extruder object
|
||||
repeated SettingExtruder global_inherits_stack = 4; //From which stack the setting would inherit if not defined in a stack.
|
||||
repeated SettingExtruder limit_to_extruder = 4; //From which stack the setting would inherit if not defined in a stack.
|
||||
}
|
||||
|
||||
message Extruder
|
||||
|
|
@ -56,6 +56,7 @@ message Polygon {
|
|||
SupportInfillType = 7;
|
||||
MoveCombingType = 8;
|
||||
MoveRetractionType = 9;
|
||||
SupportInterfaceType = 10;
|
||||
}
|
||||
Type type = 1; // Type of move
|
||||
bytes points = 2; // The points of the polygon, or two points if only a line segment (Currently only line segments are used)
|
||||
|
|
|
|||
|
|
@ -127,7 +127,6 @@ class CuraEngineBackend(Backend):
|
|||
def close(self):
|
||||
# Terminate CuraEngine if it is still running at this point
|
||||
self._terminate()
|
||||
super().close()
|
||||
|
||||
## Get the command that is used to call the engine.
|
||||
# This is useful for debugging and used to actually start the engine.
|
||||
|
|
@ -197,7 +196,6 @@ class CuraEngineBackend(Backend):
|
|||
Logger.log("d", "Attempting to kill the engine process")
|
||||
|
||||
if Application.getInstance().getCommandLineOption("external-backend", False):
|
||||
self._createSocket()
|
||||
return
|
||||
|
||||
if self._process is not None:
|
||||
|
|
@ -206,8 +204,12 @@ class CuraEngineBackend(Backend):
|
|||
self._process.terminate()
|
||||
Logger.log("d", "Engine process is killed. Received return code %s", self._process.wait())
|
||||
self._process = None
|
||||
|
||||
except Exception as e: # terminating a process that is already terminating causes an exception, silently ignore this.
|
||||
Logger.log("d", "Exception occurred while trying to kill the engine %s", str(e))
|
||||
else:
|
||||
# Process is none, but something did went wrong here. Try and re-create the socket
|
||||
self._createSocket()
|
||||
|
||||
## Event handler to call when the job to initiate the slicing process is
|
||||
# completed.
|
||||
|
|
@ -225,9 +227,19 @@ class CuraEngineBackend(Backend):
|
|||
if job.isCancelled() or job.getError() or job.getResult() == StartSliceJob.StartJobResult.Error:
|
||||
return
|
||||
|
||||
if job.getResult() == StartSliceJob.StartJobResult.MaterialIncompatible:
|
||||
if Application.getInstance().getPlatformActivity:
|
||||
self._error_message = Message(catalog.i18nc("@info:status",
|
||||
"The selected material is imcompatible with the selected machine or configuration."))
|
||||
self._error_message.show()
|
||||
self.backendStateChange.emit(BackendState.Error)
|
||||
else:
|
||||
self.backendStateChange.emit(BackendState.NotStarted)
|
||||
return
|
||||
|
||||
if job.getResult() == StartSliceJob.StartJobResult.SettingError:
|
||||
if Application.getInstance().getPlatformActivity:
|
||||
self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice. Please check your setting values for errors."))
|
||||
self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice with the current settings. Please check your settings for errors."))
|
||||
self._error_message.show()
|
||||
self.backendStateChange.emit(BackendState.Error)
|
||||
else:
|
||||
|
|
@ -236,15 +248,18 @@ class CuraEngineBackend(Backend):
|
|||
|
||||
if job.getResult() == StartSliceJob.StartJobResult.NothingToSlice:
|
||||
if Application.getInstance().getPlatformActivity:
|
||||
self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice. No suitable models found."))
|
||||
self._error_message = Message(catalog.i18nc("@info:status", "Nothing to slice because none of the models fit the build volume. Please scale or rotate models to fit."))
|
||||
self._error_message.show()
|
||||
self.backendStateChange.emit(BackendState.Error)
|
||||
else:
|
||||
self.backendStateChange.emit(BackendState.NotStarted)
|
||||
return
|
||||
|
||||
# Preparation completed, send it to the backend.
|
||||
self._socket.sendMessage(job.getSliceMessage())
|
||||
|
||||
# Notify the user that it's now up to the backend to do it's job
|
||||
self.backendStateChange.emit(BackendState.Processing)
|
||||
|
||||
Logger.log("d", "Sending slice message took %s seconds", time() - self._slice_start_time )
|
||||
|
||||
## Listener for when the scene has changed.
|
||||
|
|
@ -374,8 +389,9 @@ class CuraEngineBackend(Backend):
|
|||
#
|
||||
# \param tool The tool that the user is using.
|
||||
def _onToolOperationStarted(self, tool):
|
||||
self._terminate() # Do not continue slicing once a tool has started
|
||||
self._enabled = False # Do not reslice when a tool is doing it's 'thing'
|
||||
self._terminate() # Do not continue slicing once a tool has started
|
||||
|
||||
|
||||
## Called when the user stops using some tool.
|
||||
#
|
||||
|
|
@ -404,9 +420,10 @@ class CuraEngineBackend(Backend):
|
|||
#
|
||||
# We should reset our state and start listening for new connections.
|
||||
def _onBackendQuit(self):
|
||||
if not self._restart and self._process:
|
||||
Logger.log("d", "Backend quit with return code %s. Resetting process and socket.", self._process.wait())
|
||||
self._process = None
|
||||
if not self._restart:
|
||||
if self._process:
|
||||
Logger.log("d", "Backend quit with return code %s. Resetting process and socket.", self._process.wait())
|
||||
self._process = None
|
||||
self._createSocket()
|
||||
|
||||
## Called when the global container stack changes
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ class StartJobResult(IntEnum):
|
|||
Error = 2
|
||||
SettingError = 3
|
||||
NothingToSlice = 4
|
||||
MaterialIncompatible = 5
|
||||
|
||||
|
||||
## Formatter class that handles token expansion in start/end gcod
|
||||
|
|
@ -78,6 +79,17 @@ class StartSliceJob(Job):
|
|||
self.setResult(StartJobResult.SettingError)
|
||||
return
|
||||
|
||||
if Application.getInstance().getBuildVolume().hasErrors():
|
||||
self.setResult(StartJobResult.SettingError)
|
||||
return
|
||||
|
||||
for extruder_stack in cura.Settings.ExtruderManager.getInstance().getMachineExtruders(stack.getId()):
|
||||
material = extruder_stack.findContainer({"type": "material"})
|
||||
if material:
|
||||
if material.getMetaDataEntry("compatible") == False:
|
||||
self.setResult(StartJobResult.MaterialIncompatible)
|
||||
return
|
||||
|
||||
# Don't slice if there is a per object setting with an error value.
|
||||
for node in DepthFirstIterator(self._scene.getRoot()):
|
||||
if type(node) is not SceneNode or not node.isSelectable():
|
||||
|
|
@ -203,7 +215,20 @@ class StartSliceJob(Job):
|
|||
keys = stack.getAllKeys()
|
||||
settings = {}
|
||||
for key in keys:
|
||||
settings[key] = stack.getProperty(key, "value")
|
||||
# Use resolvement value if available, or take the value
|
||||
resolved_value = stack.getProperty(key, "resolve")
|
||||
if resolved_value is not None:
|
||||
# There is a resolvement value. Check if we need to use it.
|
||||
user_container = stack.findContainer({"type": "user"})
|
||||
quality_changes_container = stack.findContainer({"type": "quality_changes"})
|
||||
if user_container.hasProperty(key,"value") or quality_changes_container.hasProperty(key,"value"):
|
||||
# Normal case
|
||||
settings[key] = stack.getProperty(key, "value")
|
||||
else:
|
||||
settings[key] = resolved_value
|
||||
else:
|
||||
# Normal case
|
||||
settings[key] = stack.getProperty(key, "value")
|
||||
|
||||
start_gcode = settings["machine_start_gcode"]
|
||||
settings["material_bed_temp_prepend"] = "{material_bed_temperature}" not in start_gcode #Pre-compute material material_bed_temp_prepend and material_print_temp_prepend
|
||||
|
|
@ -220,16 +245,16 @@ class StartSliceJob(Job):
|
|||
## Sends for some settings which extruder they should fallback to if not
|
||||
# set.
|
||||
#
|
||||
# This is only set for settings that have the global_inherits_stack
|
||||
# This is only set for settings that have the limit_to_extruder
|
||||
# property.
|
||||
#
|
||||
# \param stack The global stack with all settings, from which to read the
|
||||
# global_inherits_stack property.
|
||||
# limit_to_extruder property.
|
||||
def _buildGlobalInheritsStackMessage(self, stack):
|
||||
for key in stack.getAllKeys():
|
||||
extruder = int(round(float(stack.getProperty(key, "global_inherits_stack"))))
|
||||
extruder = int(round(float(stack.getProperty(key, "limit_to_extruder"))))
|
||||
if extruder >= 0: #Set to a specific extruder.
|
||||
setting_extruder = self._slice_message.addRepeatedMessage("global_inherits_stack")
|
||||
setting_extruder = self._slice_message.addRepeatedMessage("limit_to_extruder")
|
||||
setting_extruder.name = key
|
||||
setting_extruder.extruder = extruder
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
# Copyright (c) 2015 Ultimaker B.V.
|
||||
# Copyright (c) 2016 Ultimaker B.V.
|
||||
# Cura is released under the terms of the AGPLv3 or higher.
|
||||
import configparser
|
||||
|
||||
import os.path
|
||||
|
||||
from UM import PluginRegistry
|
||||
from UM.Logger import Logger
|
||||
from UM.Settings.InstanceContainer import InstanceContainer #The new profile to make.
|
||||
from UM.Settings.InstanceContainer import InstanceContainer # The new profile to make.
|
||||
from cura.ProfileReader import ProfileReader
|
||||
|
||||
import zipfile
|
||||
|
||||
## A plugin that reads profile data from Cura profile files.
|
||||
#
|
||||
# It reads a profile from a .curaprofile file, and returns it as a profile
|
||||
|
|
@ -24,19 +26,77 @@ class CuraProfileReader(ProfileReader):
|
|||
# not be read or didn't contain a valid profile, \code None \endcode is
|
||||
# returned.
|
||||
def read(self, file_name):
|
||||
# Create an empty profile.
|
||||
profile = InstanceContainer(os.path.basename(os.path.splitext(file_name)[0]))
|
||||
profile.addMetaDataEntry("type", "quality")
|
||||
try:
|
||||
with open(file_name) as f: # Open file for reading.
|
||||
serialized = f.read()
|
||||
except IOError as e:
|
||||
Logger.log("e", "Unable to open file %s for reading: %s", file_name, str(e))
|
||||
return None
|
||||
with zipfile.ZipFile(file_name, "r") as archive:
|
||||
results = []
|
||||
for profile_id in archive.namelist():
|
||||
with archive.open(profile_id) as f:
|
||||
serialized = f.read()
|
||||
profile = self._loadProfile(serialized.decode("utf-8"), profile_id)
|
||||
if profile is not None:
|
||||
results.append(profile)
|
||||
return results
|
||||
|
||||
except zipfile.BadZipFile:
|
||||
# It must be an older profile from Cura 2.1.
|
||||
with open(file_name, encoding="utf-8") as fhandle:
|
||||
serialized = fhandle.read()
|
||||
return [self._loadProfile(serialized, profile_id) for serialized, profile_id in self._upgradeProfile(serialized, file_name)]
|
||||
|
||||
## Convert a profile from an old Cura to this Cura if needed.
|
||||
#
|
||||
# \param serialized \type{str} The profile data to convert in the serialized on-disk format.
|
||||
# \param profile_id \type{str} The name of the profile.
|
||||
# \return \type{List[Tuple[str,str]]} List of serialized profile strings and matching profile names.
|
||||
def _upgradeProfile(self, serialized, profile_id):
|
||||
parser = configparser.ConfigParser(interpolation=None)
|
||||
parser.read_string(serialized)
|
||||
|
||||
if not "general" in parser:
|
||||
Logger.log("w", "Missing required section 'general'.")
|
||||
return []
|
||||
if not "version" in parser["general"]:
|
||||
Logger.log("w", "Missing required 'version' property")
|
||||
return []
|
||||
|
||||
version = int(parser["general"]["version"])
|
||||
if InstanceContainer.Version != version:
|
||||
name = parser["general"]["name"]
|
||||
return self._upgradeProfileVersion(serialized, name, version)
|
||||
else:
|
||||
return [(serialized, profile_id)]
|
||||
|
||||
## Load a profile from a serialized string.
|
||||
#
|
||||
# \param serialized \type{str} The profile data to read.
|
||||
# \param profile_id \type{str} The name of the profile.
|
||||
# \return \type{InstanceContainer|None}
|
||||
def _loadProfile(self, serialized, profile_id):
|
||||
# Create an empty profile.
|
||||
profile = InstanceContainer(profile_id)
|
||||
profile.addMetaDataEntry("type", "quality_changes")
|
||||
try:
|
||||
profile.deserialize(serialized)
|
||||
except Exception as e: # Parsing error. This is not a (valid) Cura profile then.
|
||||
Logger.log("e", "Error while trying to parse profile: %s", str(e))
|
||||
return None
|
||||
return profile
|
||||
|
||||
## Upgrade a serialized profile to the current profile format.
|
||||
#
|
||||
# \param serialized \type{str} The profile data to convert.
|
||||
# \param profile_id \type{str} The name of the profile.
|
||||
# \param source_version \type{int} The profile version of 'serialized'.
|
||||
# \return \type{List[Tuple[str,str]]} List of serialized profile strings and matching profile names.
|
||||
def _upgradeProfileVersion(self, serialized, profile_id, source_version):
|
||||
converter_plugins = PluginRegistry.getInstance().getAllMetaData(filter={"version_upgrade": {} }, active_only=True)
|
||||
|
||||
source_format = ("profile", source_version)
|
||||
profile_convert_funcs = [plugin["version_upgrade"][source_format][2] for plugin in converter_plugins
|
||||
if source_format in plugin["version_upgrade"] and plugin["version_upgrade"][source_format][1] == InstanceContainer.Version]
|
||||
|
||||
if not profile_convert_funcs:
|
||||
return []
|
||||
|
||||
filenames, outputs = profile_convert_funcs[0](serialized, profile_id)
|
||||
return list(zip(outputs, filenames))
|
||||
|
|
|
|||
|
|
@ -5,22 +5,31 @@
|
|||
from UM.Logger import Logger
|
||||
from UM.SaveFile import SaveFile
|
||||
from cura.ProfileWriter import ProfileWriter
|
||||
|
||||
import zipfile
|
||||
|
||||
## Writes profiles to Cura's own profile format with config files.
|
||||
class CuraProfileWriter(ProfileWriter):
|
||||
## Writes a profile to the specified file path.
|
||||
#
|
||||
# \param path \type{string} The file to output to.
|
||||
# \param profile \type{Profile} The profile to write to that file.
|
||||
# \param profiles \type{Profile} \type{List} The profile(s) to write to that file.
|
||||
# \return \code True \endcode if the writing was successful, or \code
|
||||
# False \endcode if it wasn't.
|
||||
def write(self, path, profile):
|
||||
serialized = profile.serialize()
|
||||
def write(self, path, profiles):
|
||||
if type(profiles) != list:
|
||||
profiles = [profiles]
|
||||
|
||||
stream = open(path, "wb") # Open file for writing in binary.
|
||||
archive = zipfile.ZipFile(stream, "w", compression=zipfile.ZIP_DEFLATED)
|
||||
try:
|
||||
with SaveFile(path, "wt", -1, "utf-8") as f: # Open the specified file.
|
||||
f.write(serialized)
|
||||
# Open the specified file.
|
||||
for profile in profiles:
|
||||
serialized = profile.serialize()
|
||||
profile_file = zipfile.ZipInfo(profile.getId())
|
||||
archive.writestr(profile_file, serialized)
|
||||
except Exception as e:
|
||||
Logger.log("e", "Failed to write profile to %s: %s", path, str(e))
|
||||
return False
|
||||
finally:
|
||||
archive.close()
|
||||
return True
|
||||
|
|
|
|||
|
|
@ -66,7 +66,10 @@ class GCodeWriter(MeshWriter):
|
|||
## Create a new container with container 2 as base and container 1 written over it.
|
||||
def _createFlattenedContainerInstance(self, instance_container1, instance_container2):
|
||||
flat_container = InstanceContainer(instance_container2.getName())
|
||||
flat_container.setDefinition(instance_container2.getDefinition())
|
||||
if instance_container1.getDefinition():
|
||||
flat_container.setDefinition(instance_container1.getDefinition())
|
||||
else:
|
||||
flat_container.setDefinition(instance_container2.getDefinition())
|
||||
flat_container.setMetaData(instance_container2.getMetaData())
|
||||
|
||||
for key in instance_container2.getAllKeys():
|
||||
|
|
@ -89,17 +92,17 @@ class GCodeWriter(MeshWriter):
|
|||
prefix = ";SETTING_" + str(GCodeWriter.version) + " " # The prefix to put before each line.
|
||||
prefix_length = len(prefix)
|
||||
|
||||
container_with_profile = stack.findContainer({"type": "quality"})
|
||||
container_with_profile = stack.findContainer({"type": "quality_changes"})
|
||||
if not container_with_profile:
|
||||
Logger.log("e", "No valid quality profile found, not writing settings to GCode!")
|
||||
return ""
|
||||
|
||||
flat_global_container = self._createFlattenedContainerInstance(stack.getTop(),container_with_profile)
|
||||
flat_global_container = self._createFlattenedContainerInstance(stack.getTop(), container_with_profile)
|
||||
serialized = flat_global_container.serialize()
|
||||
data = {"global_quality": serialized}
|
||||
|
||||
for extruder in ExtruderManager.getInstance().getMachineExtruders(stack.getId()):
|
||||
extruder_quality = extruder.findContainer({"type": "quality"})
|
||||
extruder_quality = extruder.findContainer({"type": "quality_changes"})
|
||||
if not extruder_quality:
|
||||
Logger.log("w", "No extruder quality profile found, not writing quality for extruder %s to file!", extruder.getId())
|
||||
continue
|
||||
|
|
|
|||
|
|
@ -12,9 +12,11 @@ from UM.Mesh.MeshBuilder import MeshBuilder
|
|||
from UM.Job import Job
|
||||
from UM.Preferences import Preferences
|
||||
from UM.Logger import Logger
|
||||
|
||||
from UM.Scene.SceneNode import SceneNode
|
||||
from UM.View.RenderBatch import RenderBatch
|
||||
from UM.View.GL.OpenGL import OpenGL
|
||||
from UM.Message import Message
|
||||
from UM.Application import Application
|
||||
|
||||
from cura.ConvexHullNode import ConvexHullNode
|
||||
|
||||
|
|
@ -33,7 +35,7 @@ class LayerView(View):
|
|||
def __init__(self):
|
||||
super().__init__()
|
||||
self._shader = None
|
||||
self._selection_shader = None
|
||||
self._ghost_shader = None
|
||||
self._num_layers = 0
|
||||
self._layer_percentage = 0 # what percentage of layers need to be shown (Slider gives value between 0 - 100)
|
||||
self._proxy = LayerViewProxy.LayerViewProxy()
|
||||
|
|
@ -45,6 +47,7 @@ class LayerView(View):
|
|||
self._top_layers_job = None
|
||||
self._activity = False
|
||||
self._old_max_layers = 0
|
||||
self._global_container_stack = None
|
||||
|
||||
Preferences.getInstance().addPreference("view/top_layer_count", 5)
|
||||
Preferences.getInstance().addPreference("view/only_show_top_layers", False)
|
||||
|
|
@ -54,6 +57,8 @@ class LayerView(View):
|
|||
self._only_show_top_layers = bool(Preferences.getInstance().getValue("view/only_show_top_layers"))
|
||||
self._busy = False
|
||||
|
||||
self._wireprint_warning_message = Message(catalog.i18nc("@info:status", "Cura does not accurately display layers when Wire Printing is enabled"))
|
||||
|
||||
def getActivity(self):
|
||||
return self._activity
|
||||
|
||||
|
|
@ -84,9 +89,9 @@ class LayerView(View):
|
|||
scene = self.getController().getScene()
|
||||
renderer = self.getRenderer()
|
||||
|
||||
if not self._selection_shader:
|
||||
self._selection_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "color.shader"))
|
||||
self._selection_shader.setUniformValue("u_color", Color(32, 32, 32, 128))
|
||||
if not self._ghost_shader:
|
||||
self._ghost_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "color.shader"))
|
||||
self._ghost_shader.setUniformValue("u_color", Color(0, 0, 0, 64))
|
||||
|
||||
for node in DepthFirstIterator(scene.getRoot()):
|
||||
# We do not want to render ConvexHullNode as it conflicts with the bottom layers.
|
||||
|
|
@ -96,8 +101,13 @@ class LayerView(View):
|
|||
|
||||
if not node.render(renderer):
|
||||
if node.getMeshData() and node.isVisible():
|
||||
if Selection.isSelected(node):
|
||||
renderer.queueNode(node, transparent = True, shader = self._selection_shader)
|
||||
renderer.queueNode(node,
|
||||
shader = self._ghost_shader,
|
||||
type = RenderBatch.RenderType.Transparent )
|
||||
|
||||
for node in DepthFirstIterator(scene.getRoot()):
|
||||
if type(node) is SceneNode:
|
||||
if node.getMeshData() and node.isVisible():
|
||||
layer_data = node.callDecoration("getLayerData")
|
||||
if not layer_data:
|
||||
continue
|
||||
|
|
@ -184,6 +194,33 @@ class LayerView(View):
|
|||
self.setLayer(self._current_layer_num - 1)
|
||||
return True
|
||||
|
||||
if event.type == Event.ViewActivateEvent:
|
||||
Application.getInstance().globalContainerStackChanged.connect(self._onGlobalStackChanged)
|
||||
self._onGlobalStackChanged()
|
||||
|
||||
elif event.type == Event.ViewDeactivateEvent:
|
||||
self._wireprint_warning_message.hide()
|
||||
Application.getInstance().globalContainerStackChanged.disconnect(self._onGlobalStackChanged)
|
||||
if self._global_container_stack:
|
||||
self._global_container_stack.propertyChanged.disconnect(self._onPropertyChanged)
|
||||
|
||||
def _onGlobalStackChanged(self):
|
||||
if self._global_container_stack:
|
||||
self._global_container_stack.propertyChanged.disconnect(self._onPropertyChanged)
|
||||
self._global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||
if self._global_container_stack:
|
||||
self._global_container_stack.propertyChanged.connect(self._onPropertyChanged)
|
||||
self._onPropertyChanged("wireframe_enabled", "value")
|
||||
else:
|
||||
self._wireprint_warning_message.hide()
|
||||
|
||||
def _onPropertyChanged(self, key, property_name):
|
||||
if key == "wireframe_enabled" and property_name == "value":
|
||||
if self._global_container_stack.getProperty("wireframe_enabled", "value"):
|
||||
self._wireprint_warning_message.show()
|
||||
else:
|
||||
self._wireprint_warning_message.hide()
|
||||
|
||||
def _startUpdateTopLayers(self):
|
||||
if self._top_layers_job:
|
||||
self._top_layers_job.finished.disconnect(self._updateCurrentLayerMesh)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"source_version": "15.04",
|
||||
"target_version": 1,
|
||||
"target_version": 2,
|
||||
|
||||
"translation": {
|
||||
"machine_nozzle_size": "nozzle_size",
|
||||
|
|
@ -21,7 +21,7 @@
|
|||
"retraction_amount": "retraction_amount",
|
||||
"retraction_speed": "retraction_speed",
|
||||
"retraction_min_travel": "retraction_min_travel",
|
||||
"retraction_hop": "retraction_hop",
|
||||
"retraction_hop_enabled": "retraction_hop != 0",
|
||||
"speed_print": "print_speed",
|
||||
"speed_infill": "infill_speed if (float(infill_speed) != 0) else print_speed",
|
||||
"speed_wall_0": "inset0_speed if (float(inset0_speed) != 0) else print_speed",
|
||||
|
|
@ -29,7 +29,7 @@
|
|||
"speed_topbottom": "solidarea_speed if (float(solidarea_speed) != 0) else print_speed",
|
||||
"speed_travel": "travel_speed if (float(travel_speed) != 0) else travel_speed",
|
||||
"speed_layer_0": "bottom_layer_speed",
|
||||
"retraction_combing": "True if (retraction_combing == \"All\" or retraction_combing == \"No Skin\") else False",
|
||||
"retraction_combing": "\"all\" if retraction_combing == \"All\" else \"noskin\" if retraction_combing == \"No Skin\" else \"off\"",
|
||||
"cool_fan_enabled": "fan_enabled",
|
||||
"cool_fan_speed_min": "fan_speed",
|
||||
"cool_fan_speed_max": "fan_speed_max",
|
||||
|
|
@ -66,11 +66,12 @@
|
|||
"meshfix_union_all_remove_holes": "fix_horrible_union_all_type_b",
|
||||
"meshfix_extensive_stitching": "fix_horrible_extensive_stitching",
|
||||
"meshfix_keep_open_polygons": "fix_horrible_use_open_bits",
|
||||
"magic_mesh_surface_mode": "simple_mode",
|
||||
"magic_mesh_surface_mode": "\"surface\" if simple_mode else \"normal\"",
|
||||
"magic_spiralize": "spiralize",
|
||||
"prime_tower_enable": "wipe_tower",
|
||||
"prime_tower_size": "math.sqrt(float(wipe_tower_volume) / float(layer_height))",
|
||||
"ooze_shield_enabled": "ooze_shield"
|
||||
"ooze_shield_enabled": "ooze_shield",
|
||||
"skin_overlap": "fill_overlap"
|
||||
},
|
||||
|
||||
"defaults": {
|
||||
|
|
|
|||
|
|
@ -111,7 +111,8 @@ class LegacyProfileReader(ProfileReader):
|
|||
if "translation" not in dict_of_doom:
|
||||
Logger.log("e", "Dictionary of Doom has no translation. Is it the correct JSON file?")
|
||||
return None
|
||||
current_printer = Application.getInstance().getGlobalContainerStack().findContainer({ }, DefinitionContainer)
|
||||
current_printer_definition = Application.getInstance().getGlobalContainerStack().getBottom()
|
||||
profile.setDefinition(current_printer_definition)
|
||||
for new_setting in dict_of_doom["translation"]: #Evaluate all new settings that would get a value from the translations.
|
||||
old_setting_expression = dict_of_doom["translation"][new_setting]
|
||||
compiled = compile(old_setting_expression, new_setting, "eval")
|
||||
|
|
@ -121,10 +122,13 @@ class LegacyProfileReader(ProfileReader):
|
|||
except Exception: #Probably some setting name that was missing or something else that went wrong in the ini file.
|
||||
Logger.log("w", "Setting " + new_setting + " could not be set because the evaluation failed. Something is probably missing from the imported legacy profile.")
|
||||
continue
|
||||
if new_value != value_using_defaults and current_printer.findDefinitions(key = new_setting).default_value != new_value: #Not equal to the default in the new Cura OR the default in the legacy Cura.
|
||||
profile.setSettingValue(new_setting, new_value) #Store the setting in the profile!
|
||||
definitions = current_printer_definition.findDefinitions(key = new_setting)
|
||||
if definitions:
|
||||
if new_value != value_using_defaults and definitions[0].default_value != new_value: # Not equal to the default in the new Cura OR the default in the legacy Cura.
|
||||
profile.setProperty(new_setting, "value", new_value) # Store the setting in the profile!
|
||||
|
||||
if len(profile.getChangedSettings()) == 0:
|
||||
if len(profile.getAllKeys()) == 0:
|
||||
Logger.log("i", "A legacy profile was imported but everything evaluates to the defaults, creating an empty profile.")
|
||||
profile.setDirty(True)
|
||||
profile.addMetaDataEntry("type", "quality")
|
||||
return profile
|
||||
93
plugins/MachineSettingsAction/MachineSettingsAction.py
Normal file
93
plugins/MachineSettingsAction/MachineSettingsAction.py
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
# Copyright (c) 2016 Ultimaker B.V.
|
||||
# Cura is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot
|
||||
|
||||
from cura.MachineAction import MachineAction
|
||||
import cura.Settings.CuraContainerRegistry
|
||||
|
||||
import UM.Application
|
||||
import UM.Settings.InstanceContainer
|
||||
import UM.Settings.DefinitionContainer
|
||||
import UM.Logger
|
||||
|
||||
import UM.i18n
|
||||
catalog = UM.i18n.i18nCatalog("cura")
|
||||
|
||||
class MachineSettingsAction(MachineAction):
|
||||
def __init__(self, parent = None):
|
||||
super().__init__("MachineSettingsAction", catalog.i18nc("@action", "Machine Settings"))
|
||||
self._qml_url = "MachineSettingsAction.qml"
|
||||
|
||||
cura.Settings.CuraContainerRegistry.getInstance().containerAdded.connect(self._onContainerAdded)
|
||||
|
||||
def _reset(self):
|
||||
global_container_stack = UM.Application.getInstance().getGlobalContainerStack()
|
||||
if global_container_stack:
|
||||
variant = global_container_stack.findContainer({"type": "variant"})
|
||||
if variant and variant.getId() == "empty_variant":
|
||||
variant_index = global_container_stack.getContainerIndex(variant)
|
||||
self._createVariant(global_container_stack, variant_index)
|
||||
|
||||
def _createVariant(self, global_container_stack, variant_index):
|
||||
# Create and switch to a variant to store the settings in
|
||||
new_variant = UM.Settings.InstanceContainer(global_container_stack.getName() + "_variant")
|
||||
new_variant.addMetaDataEntry("type", "variant")
|
||||
new_variant.setDefinition(global_container_stack.getBottom())
|
||||
UM.Settings.ContainerRegistry.getInstance().addContainer(new_variant)
|
||||
global_container_stack.replaceContainer(variant_index, new_variant)
|
||||
|
||||
def _onContainerAdded(self, container):
|
||||
# Add this action as a supported action to all machine definitions
|
||||
if isinstance(container, UM.Settings.DefinitionContainer) and container.getMetaDataEntry("type") == "machine":
|
||||
if container.getProperty("machine_extruder_count", "value") > 1:
|
||||
# Multiextruder printers are not currently supported
|
||||
UM.Logger.log("d", "Not attaching MachineSettingsAction to %s; Multi-extrusion printers are not supported", container.getId())
|
||||
return
|
||||
if container.getMetaDataEntry("has_variants", False):
|
||||
# Machines that use variants are not currently supported
|
||||
UM.Logger.log("d", "Not attaching MachineSettingsAction to %s; Machines that use variants are not supported", container.getId())
|
||||
return
|
||||
|
||||
UM.Application.getInstance().getMachineActionManager().addSupportedAction(container.getId(), self.getKey())
|
||||
|
||||
@pyqtSlot()
|
||||
def forceUpdate(self):
|
||||
# Force rebuilding the build volume by reloading the global container stack.
|
||||
# This is a bit of a hack, but it seems quick enough.
|
||||
UM.Application.getInstance().globalContainerStackChanged.emit()
|
||||
|
||||
@pyqtSlot()
|
||||
def updateHasMaterialsMetadata(self):
|
||||
# Updates the has_materials metadata flag after switching gcode flavor
|
||||
global_container_stack = UM.Application.getInstance().getGlobalContainerStack()
|
||||
if global_container_stack:
|
||||
definition = global_container_stack.getBottom()
|
||||
if definition.getProperty("machine_gcode_flavor", "value") == "UltiGCode" and not definition.getMetaDataEntry("has_materials", False):
|
||||
has_materials = global_container_stack.getProperty("machine_gcode_flavor", "value") != "UltiGCode"
|
||||
|
||||
material_container = global_container_stack.findContainer({"type": "material"})
|
||||
material_index = global_container_stack.getContainerIndex(material_container)
|
||||
|
||||
if has_materials:
|
||||
if "has_materials" in global_container_stack.getMetaData():
|
||||
global_container_stack.setMetaDataEntry("has_materials", True)
|
||||
else:
|
||||
global_container_stack.addMetaDataEntry("has_materials", True)
|
||||
|
||||
# Set the material container to a sane default
|
||||
if material_container.getId() == "empty_material":
|
||||
search_criteria = { "type": "material", "definition": "fdmprinter", "id": "*pla*" }
|
||||
containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(**search_criteria)
|
||||
if containers:
|
||||
global_container_stack.replaceContainer(material_index, containers[0])
|
||||
else:
|
||||
# The metadata entry is stored in an ini, and ini files are parsed as strings only.
|
||||
# Because any non-empty string evaluates to a boolean True, we have to remove the entry to make it False.
|
||||
if "has_materials" in global_container_stack.getMetaData():
|
||||
global_container_stack.removeMetaDataEntry("has_materials")
|
||||
|
||||
empty_material = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = "empty_material")[0]
|
||||
global_container_stack.replaceContainer(material_index, empty_material)
|
||||
|
||||
UM.Application.getInstance().globalContainerStackChanged.emit()
|
||||
476
plugins/MachineSettingsAction/MachineSettingsAction.qml
Normal file
476
plugins/MachineSettingsAction/MachineSettingsAction.qml
Normal file
|
|
@ -0,0 +1,476 @@
|
|||
// Copyright (c) 2016 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.Layouts 1.1
|
||||
import QtQuick.Window 2.1
|
||||
|
||||
import UM 1.2 as UM
|
||||
import Cura 1.0 as Cura
|
||||
|
||||
|
||||
Cura.MachineAction
|
||||
{
|
||||
anchors.fill: parent;
|
||||
Item
|
||||
{
|
||||
id: bedLevelMachineAction
|
||||
anchors.fill: parent;
|
||||
|
||||
UM.I18nCatalog { id: catalog; name: "cura"; }
|
||||
|
||||
Label
|
||||
{
|
||||
id: pageTitle
|
||||
width: parent.width
|
||||
text: catalog.i18nc("@title", "Machine Settings")
|
||||
wrapMode: Text.WordWrap
|
||||
font.pointSize: 18;
|
||||
}
|
||||
Label
|
||||
{
|
||||
id: pageDescription
|
||||
anchors.top: pageTitle.bottom
|
||||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
||||
width: parent.width
|
||||
wrapMode: Text.WordWrap
|
||||
text: catalog.i18nc("@label", "Please enter the correct settings for your printer below:")
|
||||
}
|
||||
|
||||
Column
|
||||
{
|
||||
height: parent.height - y
|
||||
width: parent.width - UM.Theme.getSize("default_margin").width
|
||||
spacing: UM.Theme.getSize("default_margin").height
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.top: pageDescription.bottom
|
||||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
||||
|
||||
Row
|
||||
{
|
||||
width: parent.width
|
||||
spacing: UM.Theme.getSize("default_margin").height
|
||||
|
||||
Column
|
||||
{
|
||||
width: parent.width / 2
|
||||
spacing: UM.Theme.getSize("default_margin").height
|
||||
|
||||
Label
|
||||
{
|
||||
text: catalog.i18nc("@label", "Printer Settings")
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
Grid
|
||||
{
|
||||
columns: 3
|
||||
columnSpacing: UM.Theme.getSize("default_margin").width
|
||||
|
||||
Label
|
||||
{
|
||||
text: catalog.i18nc("@label", "X (Width)")
|
||||
}
|
||||
TextField
|
||||
{
|
||||
id: buildAreaWidthField
|
||||
text: machineWidthProvider.properties.value
|
||||
validator: RegExpValidator { regExp: /[0-9]{0,6}/ }
|
||||
onEditingFinished: { machineWidthProvider.setPropertyValue("value", text); manager.forceUpdate() }
|
||||
}
|
||||
Label
|
||||
{
|
||||
text: catalog.i18nc("@label", "mm")
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
text: catalog.i18nc("@label", "Y (Depth)")
|
||||
}
|
||||
TextField
|
||||
{
|
||||
id: buildAreaDepthField
|
||||
text: machineDepthProvider.properties.value
|
||||
validator: RegExpValidator { regExp: /[0-9]{0,6}/ }
|
||||
onEditingFinished: { machineDepthProvider.setPropertyValue("value", text); manager.forceUpdate() }
|
||||
}
|
||||
Label
|
||||
{
|
||||
text: catalog.i18nc("@label", "mm")
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
text: catalog.i18nc("@label", "Z (Height)")
|
||||
}
|
||||
TextField
|
||||
{
|
||||
id: buildAreaHeightField
|
||||
text: machineHeightProvider.properties.value
|
||||
validator: RegExpValidator { regExp: /[0-9]{0,6}/ }
|
||||
onEditingFinished: { machineHeightProvider.setPropertyValue("value", text); manager.forceUpdate() }
|
||||
}
|
||||
Label
|
||||
{
|
||||
text: catalog.i18nc("@label", "mm")
|
||||
}
|
||||
}
|
||||
|
||||
Column
|
||||
{
|
||||
CheckBox
|
||||
{
|
||||
id: heatedBedCheckBox
|
||||
text: catalog.i18nc("@option:check", "Heated Bed")
|
||||
checked: String(machineHeatedBedProvider.properties.value).toLowerCase() != 'false'
|
||||
onClicked: machineHeatedBedProvider.setPropertyValue("value", checked)
|
||||
}
|
||||
CheckBox
|
||||
{
|
||||
id: centerIsZeroCheckBox
|
||||
text: catalog.i18nc("@option:check", "Machine Center is Zero")
|
||||
checked: String(machineCenterIsZeroProvider.properties.value).toLowerCase() != 'false'
|
||||
onClicked: machineCenterIsZeroProvider.setPropertyValue("value", checked)
|
||||
}
|
||||
}
|
||||
|
||||
Row
|
||||
{
|
||||
spacing: UM.Theme.getSize("default_margin").width
|
||||
|
||||
Label
|
||||
{
|
||||
text: catalog.i18nc("@label", "GCode Flavor")
|
||||
}
|
||||
|
||||
ComboBox
|
||||
{
|
||||
model: ["RepRap (Marlin/Sprinter)", "UltiGCode"]
|
||||
currentIndex: machineGCodeFlavorProvider.properties.value != model[1] ? 0 : 1
|
||||
onActivated:
|
||||
{
|
||||
machineGCodeFlavorProvider.setPropertyValue("value", model[index]);
|
||||
manager.updateHasMaterialsMetadata();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column
|
||||
{
|
||||
width: parent.width / 2
|
||||
spacing: UM.Theme.getSize("default_margin").height
|
||||
|
||||
Label
|
||||
{
|
||||
text: catalog.i18nc("@label", "Printhead Settings")
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
Grid
|
||||
{
|
||||
columns: 3
|
||||
columnSpacing: UM.Theme.getSize("default_margin").width
|
||||
|
||||
Label
|
||||
{
|
||||
text: catalog.i18nc("@label", "X min")
|
||||
}
|
||||
TextField
|
||||
{
|
||||
id: printheadXMinField
|
||||
text: getHeadPolygonCoord("x", "min")
|
||||
validator: RegExpValidator { regExp: /[0-9]{0,6}/ }
|
||||
onEditingFinished: setHeadPolygon()
|
||||
}
|
||||
Label
|
||||
{
|
||||
text: catalog.i18nc("@label", "mm")
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
text: catalog.i18nc("@label", "Y min")
|
||||
}
|
||||
TextField
|
||||
{
|
||||
id: printheadYMinField
|
||||
text: getHeadPolygonCoord("y", "min")
|
||||
validator: RegExpValidator { regExp: /[0-9]{0,6}/ }
|
||||
onEditingFinished: setHeadPolygon()
|
||||
}
|
||||
Label
|
||||
{
|
||||
text: catalog.i18nc("@label", "mm")
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
text: catalog.i18nc("@label", "X max")
|
||||
}
|
||||
TextField
|
||||
{
|
||||
id: printheadXMaxField
|
||||
text: getHeadPolygonCoord("x", "max")
|
||||
validator: RegExpValidator { regExp: /[0-9]{0,6}/ }
|
||||
onEditingFinished: setHeadPolygon()
|
||||
}
|
||||
Label
|
||||
{
|
||||
text: catalog.i18nc("@label", "mm")
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
text: catalog.i18nc("@label", "Y max")
|
||||
}
|
||||
TextField
|
||||
{
|
||||
id: printheadYMaxField
|
||||
text: getHeadPolygonCoord("y", "max")
|
||||
validator: RegExpValidator { regExp: /[0-9]{0,6}/ }
|
||||
onEditingFinished: setHeadPolygon()
|
||||
}
|
||||
Label
|
||||
{
|
||||
text: catalog.i18nc("@label", "mm")
|
||||
}
|
||||
|
||||
Item { width: UM.Theme.getSize("default_margin").width; height: UM.Theme.getSize("default_margin").height }
|
||||
Item { width: UM.Theme.getSize("default_margin").width; height: UM.Theme.getSize("default_margin").height }
|
||||
Item { width: UM.Theme.getSize("default_margin").width; height: UM.Theme.getSize("default_margin").height }
|
||||
|
||||
Label
|
||||
{
|
||||
text: catalog.i18nc("@label", "Gantry height")
|
||||
}
|
||||
TextField
|
||||
{
|
||||
id: gantryHeightField
|
||||
text: gantryHeightProvider.properties.value
|
||||
validator: RegExpValidator { regExp: /[0-9\.]{0,6}/ }
|
||||
onEditingFinished: { gantryHeightProvider.setPropertyValue("value", text) }
|
||||
}
|
||||
Label
|
||||
{
|
||||
text: catalog.i18nc("@label", "mm")
|
||||
}
|
||||
|
||||
Item { width: UM.Theme.getSize("default_margin").width; height: UM.Theme.getSize("default_margin").height }
|
||||
Item { width: UM.Theme.getSize("default_margin").width; height: UM.Theme.getSize("default_margin").height }
|
||||
Item { width: UM.Theme.getSize("default_margin").width; height: UM.Theme.getSize("default_margin").height }
|
||||
|
||||
Label
|
||||
{
|
||||
text: catalog.i18nc("@label", "Nozzle size")
|
||||
}
|
||||
TextField
|
||||
{
|
||||
id: nozzleSizeField
|
||||
text: machineNozzleSizeProvider.properties.value
|
||||
validator: RegExpValidator { regExp: /[0-9\.]{0,6}/ }
|
||||
onEditingFinished: { machineNozzleSizeProvider.setPropertyValue("value", text) }
|
||||
}
|
||||
Label
|
||||
{
|
||||
text: catalog.i18nc("@label", "mm")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row
|
||||
{
|
||||
spacing: UM.Theme.getSize("default_margin").width
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
height: parent.height - y
|
||||
Column
|
||||
{
|
||||
height: parent.height
|
||||
width: parent.width / 2
|
||||
Label
|
||||
{
|
||||
text: catalog.i18nc("@label", "Start Gcode")
|
||||
}
|
||||
TextArea
|
||||
{
|
||||
id: machineStartGcodeField
|
||||
width: parent.width
|
||||
height: parent.height - y
|
||||
text: machineStartGcodeProvider.properties.value
|
||||
onActiveFocusChanged:
|
||||
{
|
||||
if(!activeFocus)
|
||||
{
|
||||
machineStartGcodeProvider.setPropertyValue("value", machineStartGcodeField.text)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Column {
|
||||
height: parent.height
|
||||
width: parent.width / 2
|
||||
Label
|
||||
{
|
||||
text: catalog.i18nc("@label", "End Gcode")
|
||||
}
|
||||
TextArea
|
||||
{
|
||||
id: machineEndGcodeField
|
||||
width: parent.width
|
||||
height: parent.height - y
|
||||
text: machineEndGcodeProvider.properties.value
|
||||
onActiveFocusChanged:
|
||||
{
|
||||
if(!activeFocus)
|
||||
{
|
||||
machineEndGcodeProvider.setPropertyValue("value", machineEndGcodeField.text)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getHeadPolygonCoord(axis, minMax)
|
||||
{
|
||||
var polygon = JSON.parse(machineHeadPolygonProvider.properties.value);
|
||||
var item = (axis == "x") ? 0 : 1
|
||||
var result = polygon[0][item];
|
||||
for(var i = 1; i < polygon.length; i++) {
|
||||
if (minMax == "min") {
|
||||
result = Math.min(result, polygon[i][item]);
|
||||
} else {
|
||||
result = Math.max(result, polygon[i][item]);
|
||||
}
|
||||
}
|
||||
return Math.abs(result);
|
||||
}
|
||||
|
||||
function setHeadPolygon()
|
||||
{
|
||||
var polygon = [];
|
||||
polygon.push([-parseFloat(printheadXMinField.text), parseFloat(printheadYMaxField.text)]);
|
||||
polygon.push([-parseFloat(printheadXMinField.text),-parseFloat(printheadYMinField.text)]);
|
||||
polygon.push([ parseFloat(printheadXMaxField.text), parseFloat(printheadYMaxField.text)]);
|
||||
polygon.push([ parseFloat(printheadXMaxField.text),-parseFloat(printheadYMinField.text)]);
|
||||
machineHeadPolygonProvider.setPropertyValue("value", JSON.stringify(polygon));
|
||||
manager.forceUpdate();
|
||||
}
|
||||
|
||||
UM.SettingPropertyProvider
|
||||
{
|
||||
id: machineWidthProvider
|
||||
|
||||
containerStackId: Cura.MachineManager.activeMachineId
|
||||
key: "machine_width"
|
||||
watchedProperties: [ "value" ]
|
||||
storeIndex: 4
|
||||
}
|
||||
|
||||
UM.SettingPropertyProvider
|
||||
{
|
||||
id: machineDepthProvider
|
||||
|
||||
containerStackId: Cura.MachineManager.activeMachineId
|
||||
key: "machine_depth"
|
||||
watchedProperties: [ "value" ]
|
||||
storeIndex: 4
|
||||
}
|
||||
|
||||
UM.SettingPropertyProvider
|
||||
{
|
||||
id: machineHeightProvider
|
||||
|
||||
containerStackId: Cura.MachineManager.activeMachineId
|
||||
key: "machine_height"
|
||||
watchedProperties: [ "value" ]
|
||||
storeIndex: 4
|
||||
}
|
||||
|
||||
UM.SettingPropertyProvider
|
||||
{
|
||||
id: machineHeatedBedProvider
|
||||
|
||||
containerStackId: Cura.MachineManager.activeMachineId
|
||||
key: "machine_heated_bed"
|
||||
watchedProperties: [ "value" ]
|
||||
storeIndex: 4
|
||||
}
|
||||
|
||||
UM.SettingPropertyProvider
|
||||
{
|
||||
id: machineCenterIsZeroProvider
|
||||
|
||||
containerStackId: Cura.MachineManager.activeMachineId
|
||||
key: "machine_center_is_zero"
|
||||
watchedProperties: [ "value" ]
|
||||
storeIndex: 4
|
||||
}
|
||||
|
||||
UM.SettingPropertyProvider
|
||||
{
|
||||
id: machineGCodeFlavorProvider
|
||||
|
||||
containerStackId: Cura.MachineManager.activeMachineId
|
||||
key: "machine_gcode_flavor"
|
||||
watchedProperties: [ "value" ]
|
||||
storeIndex: 4
|
||||
}
|
||||
|
||||
UM.SettingPropertyProvider
|
||||
{
|
||||
id: machineNozzleSizeProvider
|
||||
|
||||
containerStackId: Cura.MachineManager.activeMachineId
|
||||
key: "machine_nozzle_size"
|
||||
watchedProperties: [ "value" ]
|
||||
storeIndex: 4
|
||||
}
|
||||
|
||||
UM.SettingPropertyProvider
|
||||
{
|
||||
id: gantryHeightProvider
|
||||
|
||||
containerStackId: Cura.MachineManager.activeMachineId
|
||||
key: "gantry_height"
|
||||
watchedProperties: [ "value" ]
|
||||
storeIndex: 4
|
||||
}
|
||||
|
||||
UM.SettingPropertyProvider
|
||||
{
|
||||
id: machineHeadPolygonProvider
|
||||
|
||||
containerStackId: Cura.MachineManager.activeMachineId
|
||||
key: "machine_head_with_fans_polygon"
|
||||
watchedProperties: [ "value" ]
|
||||
storeIndex: 4
|
||||
}
|
||||
|
||||
|
||||
UM.SettingPropertyProvider
|
||||
{
|
||||
id: machineStartGcodeProvider
|
||||
|
||||
containerStackId: Cura.MachineManager.activeMachineId
|
||||
key: "machine_start_gcode"
|
||||
watchedProperties: [ "value" ]
|
||||
storeIndex: 4
|
||||
}
|
||||
|
||||
UM.SettingPropertyProvider
|
||||
{
|
||||
id: machineEndGcodeProvider
|
||||
|
||||
containerStackId: Cura.MachineManager.activeMachineId
|
||||
key: "machine_end_gcode"
|
||||
watchedProperties: [ "value" ]
|
||||
storeIndex: 4
|
||||
}
|
||||
|
||||
}
|
||||
21
plugins/MachineSettingsAction/__init__.py
Normal file
21
plugins/MachineSettingsAction/__init__.py
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
# Copyright (c) 2016 Ultimaker B.V.
|
||||
# Cura is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
from . import MachineSettingsAction
|
||||
|
||||
from UM.i18n import i18nCatalog
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
def getMetaData():
|
||||
return {
|
||||
"plugin": {
|
||||
"name": catalog.i18nc("@label", "Machine Settings action"),
|
||||
"author": "fieldOfView",
|
||||
"version": "1.0",
|
||||
"description": catalog.i18nc("@info:whatsthis", "Provides a way to change machine settings (such as build volume, nozzle size, etc)"),
|
||||
"api": 3
|
||||
}
|
||||
}
|
||||
|
||||
def register(app):
|
||||
return { "machine_action": MachineSettingsAction.MachineSettingsAction() }
|
||||
|
|
@ -60,16 +60,16 @@ class PerObjectSettingVisibilityHandler(UM.Settings.Models.SettingVisibilityHand
|
|||
if definition:
|
||||
new_instance = SettingInstance(definition, settings)
|
||||
stack_nr = -1
|
||||
if definition.global_inherits_stack and self._stack.getProperty("machine_extruder_count", "value") > 1:
|
||||
if definition.limit_to_extruder and self._stack.getProperty("machine_extruder_count", "value") > 1:
|
||||
#Obtain the value from the correct container stack. Only once, upon adding the setting.
|
||||
stack_nr = str(int(round(float(self._stack.getProperty(item, "global_inherits_stack"))))) #Stack to get the setting from. Round it and remove the fractional part.
|
||||
stack_nr = str(int(round(float(self._stack.getProperty(item, "limit_to_extruder"))))) #Stack to get the setting from. Round it and remove the fractional part.
|
||||
if stack_nr not in ExtruderManager.getInstance().extruderIds and self._stack.getProperty("extruder_nr", "value"): #Property not defined, but we have an extruder number.
|
||||
stack_nr = str(int(round(float(self._stack.getProperty("extruder_nr", "value")))))
|
||||
if stack_nr in ExtruderManager.getInstance().extruderIds: #We have either a global_inherits_stack or an extruder_nr.
|
||||
if stack_nr in ExtruderManager.getInstance().extruderIds: #We have either a limit_to_extruder or an extruder_nr.
|
||||
stack = UM.Settings.ContainerRegistry.getInstance().findContainerStacks(id = ExtruderManager.getInstance().extruderIds[stack_nr])[0]
|
||||
else:
|
||||
stack = UM.Application.getInstance().getGlobalContainerStack()
|
||||
new_instance.setProperty("value", stack.getProperty(item, "value"))
|
||||
new_instance.setProperty("value", stack.getRawProperty(item, "value"))
|
||||
new_instance.resetState() # Ensure that the state is not seen as a user state.
|
||||
settings.addInstance(new_instance)
|
||||
visibility_changed = True
|
||||
|
|
|
|||
|
|
@ -44,13 +44,11 @@ Item {
|
|||
|
||||
model: Cura.ExtrudersModel
|
||||
{
|
||||
id: extruders_model
|
||||
onRowsInserted: extruderSelector.visible = extruders_model.rowCount() > 1
|
||||
onModelReset: extruderSelector.visible = extruders_model.rowCount() > 1
|
||||
onModelChanged: extruderSelector.color = extruders_model.getItem(extruderSelector.currentIndex).color
|
||||
id: extrudersModel
|
||||
onModelChanged: extruderSelector.color = extrudersModel.getItem(extruderSelector.currentIndex).color
|
||||
}
|
||||
property string color: extruders_model.getItem(extruderSelector.currentIndex).color
|
||||
visible: extruders_model.rowCount() > 1
|
||||
property string color: extrudersModel.getItem(extruderSelector.currentIndex).color
|
||||
visible: machineExtruderCount.properties.value > 1
|
||||
textRole: "name"
|
||||
width: UM.Theme.getSize("setting_control").width
|
||||
height: UM.Theme.getSize("section").height
|
||||
|
|
@ -130,19 +128,19 @@ Item {
|
|||
|
||||
onActivated:
|
||||
{
|
||||
UM.ActiveTool.setProperty("SelectedActiveExtruder", extruders_model.getItem(index).id);
|
||||
extruderSelector.color = extruders_model.getItem(index).color;
|
||||
UM.ActiveTool.setProperty("SelectedActiveExtruder", extrudersModel.getItem(index).id);
|
||||
extruderSelector.color = extrudersModel.getItem(index).color;
|
||||
}
|
||||
onModelChanged: updateCurrentIndex();
|
||||
|
||||
function updateCurrentIndex()
|
||||
{
|
||||
for(var i = 0; i < extruders_model.rowCount(); ++i)
|
||||
for(var i = 0; i < extrudersModel.rowCount(); ++i)
|
||||
{
|
||||
if(extruders_model.getItem(i).id == UM.ActiveTool.properties.getValue("SelectedActiveExtruder"))
|
||||
if(extrudersModel.getItem(i).id == UM.ActiveTool.properties.getValue("SelectedActiveExtruder"))
|
||||
{
|
||||
extruderSelector.currentIndex = i;
|
||||
extruderSelector.color = extruders_model.getItem(i).color;
|
||||
extruderSelector.color = extrudersModel.getItem(i).color;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -154,107 +152,114 @@ Item {
|
|||
Column
|
||||
{
|
||||
spacing: UM.Theme.getSize("default_lining").height
|
||||
|
||||
Repeater
|
||||
// This is to ensure that the panel is first increasing in size up to 200 and then shows a scrollbar.
|
||||
// It kinda looks ugly otherwise (big panel, no content on it)
|
||||
height: contents.count * UM.Theme.getSize("section").height < 200 ? contents.count * UM.Theme.getSize("section").height : 200
|
||||
ScrollView
|
||||
{
|
||||
id: contents
|
||||
height: childrenRect.height;
|
||||
|
||||
model: UM.SettingDefinitionsModel
|
||||
height: parent.height
|
||||
width: UM.Theme.getSize("setting").width + UM.Theme.getSize("setting").height
|
||||
style: UM.Theme.styles.scrollview
|
||||
ListView
|
||||
{
|
||||
id: addedSettingsModel;
|
||||
containerId: Cura.MachineManager.activeDefinitionId
|
||||
expanded: [ "*" ]
|
||||
id: contents
|
||||
|
||||
visibilityHandler: Cura.PerObjectSettingVisibilityHandler
|
||||
model: UM.SettingDefinitionsModel
|
||||
{
|
||||
selectedObjectId: UM.ActiveTool.properties.getValue("SelectedObjectId")
|
||||
}
|
||||
}
|
||||
id: addedSettingsModel;
|
||||
containerId: Cura.MachineManager.activeDefinitionId
|
||||
expanded: [ "*" ]
|
||||
|
||||
delegate: Row
|
||||
{
|
||||
Loader
|
||||
{
|
||||
id: settingLoader
|
||||
width: UM.Theme.getSize("setting").width
|
||||
height: UM.Theme.getSize("section").height
|
||||
|
||||
property var definition: model
|
||||
property var settingDefinitionsModel: addedSettingsModel
|
||||
property var propertyProvider: provider
|
||||
|
||||
//Qt5.4.2 and earlier has a bug where this causes a crash: https://bugreports.qt.io/browse/QTBUG-35989
|
||||
//In addition, while it works for 5.5 and higher, the ordering of the actual combo box drop down changes,
|
||||
//causing nasty issues when selecting different options. So disable asynchronous loading of enum type completely.
|
||||
asynchronous: model.type != "enum" && model.type != "extruder"
|
||||
|
||||
onLoaded: {
|
||||
settingLoader.item.showRevertButton = false
|
||||
settingLoader.item.showInheritButton = false
|
||||
settingLoader.item.showLinkedSettingIcon = false
|
||||
settingLoader.item.doDepthIndentation = false
|
||||
settingLoader.item.doQualityUserSettingEmphasis = false
|
||||
}
|
||||
|
||||
sourceComponent:
|
||||
visibilityHandler: Cura.PerObjectSettingVisibilityHandler
|
||||
{
|
||||
switch(model.type)
|
||||
{
|
||||
case "int":
|
||||
return settingTextField
|
||||
case "float":
|
||||
return settingTextField
|
||||
case "enum":
|
||||
return settingComboBox
|
||||
case "extruder":
|
||||
return settingExtruder
|
||||
case "bool":
|
||||
return settingCheckBox
|
||||
case "str":
|
||||
return settingTextField
|
||||
case "category":
|
||||
return settingCategory
|
||||
default:
|
||||
return settingUnknown
|
||||
selectedObjectId: UM.ActiveTool.properties.getValue("SelectedObjectId")
|
||||
}
|
||||
}
|
||||
|
||||
delegate: Row
|
||||
{
|
||||
Loader
|
||||
{
|
||||
id: settingLoader
|
||||
width: UM.Theme.getSize("setting").width
|
||||
height: UM.Theme.getSize("section").height
|
||||
|
||||
property var definition: model
|
||||
property var settingDefinitionsModel: addedSettingsModel
|
||||
property var propertyProvider: provider
|
||||
|
||||
//Qt5.4.2 and earlier has a bug where this causes a crash: https://bugreports.qt.io/browse/QTBUG-35989
|
||||
//In addition, while it works for 5.5 and higher, the ordering of the actual combo box drop down changes,
|
||||
//causing nasty issues when selecting different options. So disable asynchronous loading of enum type completely.
|
||||
asynchronous: model.type != "enum" && model.type != "extruder"
|
||||
|
||||
onLoaded: {
|
||||
settingLoader.item.showRevertButton = false
|
||||
settingLoader.item.showInheritButton = false
|
||||
settingLoader.item.showLinkedSettingIcon = false
|
||||
settingLoader.item.doDepthIndentation = false
|
||||
settingLoader.item.doQualityUserSettingEmphasis = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Button
|
||||
{
|
||||
width: UM.Theme.getSize("setting").height / 2;
|
||||
height: UM.Theme.getSize("setting").height;
|
||||
|
||||
onClicked: addedSettingsModel.setVisible(model.key, false);
|
||||
|
||||
style: ButtonStyle
|
||||
{
|
||||
background: Item
|
||||
sourceComponent:
|
||||
{
|
||||
UM.RecolorImage
|
||||
switch(model.type)
|
||||
{
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: parent.width
|
||||
height: parent.height / 2
|
||||
sourceSize.width: width
|
||||
sourceSize.height: width
|
||||
color: control.hovered ? UM.Theme.getColor("setting_control_button_hover") : UM.Theme.getColor("setting_control_button")
|
||||
source: UM.Theme.getIcon("minus")
|
||||
case "int":
|
||||
return settingTextField
|
||||
case "float":
|
||||
return settingTextField
|
||||
case "enum":
|
||||
return settingComboBox
|
||||
case "extruder":
|
||||
return settingExtruder
|
||||
case "bool":
|
||||
return settingCheckBox
|
||||
case "str":
|
||||
return settingTextField
|
||||
case "category":
|
||||
return settingCategory
|
||||
default:
|
||||
return settingUnknown
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UM.SettingPropertyProvider
|
||||
{
|
||||
id: provider
|
||||
Button
|
||||
{
|
||||
width: UM.Theme.getSize("setting").height / 2;
|
||||
height: UM.Theme.getSize("setting").height;
|
||||
|
||||
containerStackId: UM.ActiveTool.properties.getValue("ContainerID")
|
||||
key: model.key
|
||||
watchedProperties: [ "value", "enabled", "validationState" ]
|
||||
storeIndex: 0
|
||||
removeUnusedValue: false
|
||||
onClicked: addedSettingsModel.setVisible(model.key, false);
|
||||
|
||||
style: ButtonStyle
|
||||
{
|
||||
background: Item
|
||||
{
|
||||
UM.RecolorImage
|
||||
{
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: parent.width
|
||||
height: parent.height / 2
|
||||
sourceSize.width: width
|
||||
sourceSize.height: width
|
||||
color: control.hovered ? UM.Theme.getColor("setting_control_button_hover") : UM.Theme.getColor("setting_control_button")
|
||||
source: UM.Theme.getIcon("minus")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UM.SettingPropertyProvider
|
||||
{
|
||||
id: provider
|
||||
|
||||
containerStackId: UM.ActiveTool.properties.getValue("ContainerID")
|
||||
key: model.key
|
||||
watchedProperties: [ "value", "enabled", "validationState" ]
|
||||
storeIndex: 0
|
||||
removeUnusedValue: false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -423,6 +428,16 @@ Item {
|
|||
]
|
||||
}
|
||||
|
||||
UM.SettingPropertyProvider
|
||||
{
|
||||
id: machineExtruderCount
|
||||
|
||||
containerStackId: Cura.MachineManager.activeMachineId
|
||||
key: "machine_extruder_count"
|
||||
watchedProperties: [ "value" ]
|
||||
storeIndex: 0
|
||||
}
|
||||
|
||||
SystemPalette { id: palette; }
|
||||
|
||||
Component
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
|||
from UM.Application import Application
|
||||
from UM.Preferences import Preferences
|
||||
from cura.Settings.SettingOverrideDecorator import SettingOverrideDecorator
|
||||
from cura.Settings.ExtruderManager import ExtruderManager
|
||||
|
||||
|
||||
## This tool allows the user to add & change settings per node in the scene.
|
||||
|
|
@ -71,11 +72,20 @@ class PerObjectSettingsTool(Tool):
|
|||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||
if global_container_stack:
|
||||
self._multi_extrusion = global_container_stack.getProperty("machine_extruder_count", "value") > 1
|
||||
|
||||
# Ensure that all extruder data is reset
|
||||
if not self._multi_extrusion:
|
||||
# Ensure that all extruder data is reset
|
||||
root_node = Application.getInstance().getController().getScene().getRoot()
|
||||
for node in DepthFirstIterator(root_node):
|
||||
node.callDecoration("setActiveExtruder", global_container_stack.getId())
|
||||
default_stack_id = global_container_stack.getId()
|
||||
else:
|
||||
default_stack = ExtruderManager.getInstance().getExtruderStack(0)
|
||||
if default_stack:
|
||||
default_stack_id = default_stack.getId()
|
||||
else: default_stack_id = global_container_stack.getId()
|
||||
|
||||
root_node = Application.getInstance().getController().getScene().getRoot()
|
||||
for node in DepthFirstIterator(root_node):
|
||||
node.callDecoration("setActiveExtruder", default_stack_id)
|
||||
|
||||
self._updateEnabled()
|
||||
|
||||
def _updateEnabled(self):
|
||||
|
|
|
|||
|
|
@ -112,5 +112,5 @@ class RemovableDriveOutputDevice(OutputDevice):
|
|||
|
||||
def _onActionTriggered(self, message, action):
|
||||
if action == "eject":
|
||||
Application.getInstance().getOutputDeviceManager().getOutputDevicePlugin("RemovableDriveOutputDevice").ejectDevice(self)
|
||||
|
||||
if Application.getInstance().getOutputDeviceManager().getOutputDevicePlugin("RemovableDriveOutputDevice").ejectDevice(self):
|
||||
message.hide()
|
||||
|
|
@ -49,8 +49,9 @@ class RemovableDrivePlugin(OutputDevicePlugin):
|
|||
message = Message(catalog.i18nc("@info:status", "Ejected {0}. You can now safely remove the drive.").format(device.getName()))
|
||||
message.show()
|
||||
else:
|
||||
message = Message(catalog.i18nc("@info:status", "Failed to eject {0}. Maybe it is still in use?").format(device.getName()))
|
||||
message = Message(catalog.i18nc("@info:status", "Failed to eject {0}. Another program may be using the drive.").format(device.getName()))
|
||||
message.show()
|
||||
return result
|
||||
|
||||
def performEjectDevice(self, device):
|
||||
raise NotImplementedError()
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
# Copyright (c) 2015 Ultimaker B.V.
|
||||
# Cura is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
from cura.CuraApplication import CuraApplication
|
||||
|
||||
from UM.Extension import Extension
|
||||
from UM.Application import Application
|
||||
from UM.Preferences import Preferences
|
||||
|
|
@ -18,6 +20,7 @@ import math
|
|||
import urllib.request
|
||||
import urllib.parse
|
||||
import ssl
|
||||
import hashlib
|
||||
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
|
|
@ -43,9 +46,11 @@ class SliceInfoJob(Job):
|
|||
if Platform.isOSX():
|
||||
kwoptions["context"] = ssl._create_unverified_context()
|
||||
|
||||
Logger.log("d", "Sending anonymous slice info to [%s]...", self.url)
|
||||
|
||||
try:
|
||||
f = urllib.request.urlopen(self.url, **kwoptions)
|
||||
Logger.log("i", "Sent anonymous slice info to %s", self.url)
|
||||
Logger.log("i", "Sent anonymous slice info.")
|
||||
f.close()
|
||||
except urllib.error.HTTPError as http_exception:
|
||||
Logger.log("e", "An HTTP error occurred while trying to send slice information: %s" % http_exception)
|
||||
|
|
@ -56,7 +61,7 @@ class SliceInfoJob(Job):
|
|||
# The data is only sent when the user in question gave permission to do so. All data is anonymous and
|
||||
# no model files are being sent (Just a SHA256 hash of the model).
|
||||
class SliceInfo(Extension):
|
||||
info_url = "https://stats.youmagine.com/curastats/slice"
|
||||
info_url = "http://stats.youmagine.com/curastats/slice"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
|
@ -65,7 +70,7 @@ class SliceInfo(Extension):
|
|||
Preferences.getInstance().addPreference("info/asked_send_slice_info", False)
|
||||
|
||||
if not Preferences.getInstance().getValue("info/asked_send_slice_info"):
|
||||
self.send_slice_info_message = Message(catalog.i18nc("@info", "Cura automatically sends slice info. You can disable this in preferences"), lifetime = 0, dismissable = False)
|
||||
self.send_slice_info_message = Message(catalog.i18nc("@info", "Cura collects anonymised slicing statistics. You can disable this in preferences"), lifetime = 0, dismissable = False)
|
||||
self.send_slice_info_message.addAction("Dismiss", catalog.i18nc("@action:button", "Dismiss"), None, "")
|
||||
self.send_slice_info_message.actionTriggered.connect(self.messageActionTriggered)
|
||||
self.send_slice_info_message.show()
|
||||
|
|
@ -80,6 +85,16 @@ class SliceInfo(Extension):
|
|||
Logger.log("d", "'info/send_slice_info' is turned off.")
|
||||
return # Do nothing, user does not want to send data
|
||||
|
||||
# Listing all files placed on the buildplate
|
||||
modelhashes = []
|
||||
for node in DepthFirstIterator(CuraApplication.getInstance().getController().getScene().getRoot()):
|
||||
if type(node) is not SceneNode or not node.getMeshData():
|
||||
continue
|
||||
modelhashes.append(node.getMeshData().getHash())
|
||||
|
||||
# Creating md5sums and formatting them as discussed on JIRA
|
||||
modelhash_formatted = ",".join(modelhashes)
|
||||
|
||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||
|
||||
# Get total material used (in mm^3)
|
||||
|
|
@ -89,27 +104,6 @@ class SliceInfo(Extension):
|
|||
# TODO: Send material per extruder instead of mashing it on a pile
|
||||
material_used = math.pi * material_radius * material_radius * sum(print_information.materialLengths) #Volume of all materials used
|
||||
|
||||
# Get model information (bounding boxes, hashes and transformation matrix)
|
||||
models_info = []
|
||||
for node in DepthFirstIterator(Application.getInstance().getController().getScene().getRoot()):
|
||||
if type(node) is SceneNode and node.getMeshData() and node.getMeshData().getVertices() is not None:
|
||||
if not getattr(node, "_outside_buildarea", False):
|
||||
model_info = {}
|
||||
model_info["hash"] = node.getMeshData().getHash()
|
||||
model_info["bounding_box"] = {}
|
||||
model_info["bounding_box"]["minimum"] = {}
|
||||
model_info["bounding_box"]["minimum"]["x"] = node.getBoundingBox().minimum.x
|
||||
model_info["bounding_box"]["minimum"]["y"] = node.getBoundingBox().minimum.y
|
||||
model_info["bounding_box"]["minimum"]["z"] = node.getBoundingBox().minimum.z
|
||||
|
||||
model_info["bounding_box"]["maximum"] = {}
|
||||
model_info["bounding_box"]["maximum"]["x"] = node.getBoundingBox().maximum.x
|
||||
model_info["bounding_box"]["maximum"]["y"] = node.getBoundingBox().maximum.y
|
||||
model_info["bounding_box"]["maximum"]["z"] = node.getBoundingBox().maximum.z
|
||||
model_info["transformation"] = str(node.getWorldTransformation().getData())
|
||||
|
||||
models_info.append(model_info)
|
||||
|
||||
# Bundle the collected data
|
||||
submitted_data = {
|
||||
"processor": platform.processor(),
|
||||
|
|
@ -117,7 +111,7 @@ class SliceInfo(Extension):
|
|||
"platform": platform.platform(),
|
||||
"settings": global_container_stack.serialize(), # global_container with references on used containers
|
||||
"version": Application.getInstance().getVersion(),
|
||||
"modelhash": "None",
|
||||
"modelhash": modelhash_formatted,
|
||||
"printtime": print_information.currentPrintTime.getDisplayString(DurationFormat.Format.ISO8601),
|
||||
"filament": material_used,
|
||||
"language": Preferences.getInstance().getValue("general/language"),
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ from UM.Settings.Validator import ValidatorState
|
|||
from UM.View.GL.OpenGL import OpenGL
|
||||
|
||||
import cura.Settings
|
||||
from cura.Settings.ExtruderManager import ExtruderManager
|
||||
|
||||
import math
|
||||
|
||||
|
|
@ -45,16 +46,27 @@ class SolidView(View):
|
|||
|
||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||
if global_container_stack:
|
||||
multi_extrusion = global_container_stack.getProperty("machine_extruder_count", "value") > 1
|
||||
|
||||
if multi_extrusion:
|
||||
support_extruder_nr = global_container_stack.getProperty("support_extruder_nr", "value")
|
||||
support_angle_stack = ExtruderManager.getInstance().getExtruderStack(support_extruder_nr)
|
||||
if not support_angle_stack:
|
||||
support_angle_stack = global_container_stack
|
||||
else:
|
||||
support_angle_stack = global_container_stack
|
||||
|
||||
if Preferences.getInstance().getValue("view/show_overhang"):
|
||||
angle = global_container_stack.getProperty("support_angle", "value")
|
||||
if angle is not None and global_container_stack.getProperty("support_angle", "validationState") == ValidatorState.Valid:
|
||||
angle = support_angle_stack.getProperty("support_angle", "value")
|
||||
# Make sure the overhang angle is valid before passing it to the shader
|
||||
# Note: if the overhang angle is set to its default value, it does not need to get validated (validationState = None)
|
||||
if angle is not None and global_container_stack.getProperty("support_angle", "validationState") in [None, ValidatorState.Valid]:
|
||||
self._enabled_shader.setUniformValue("u_overhangAngle", math.cos(math.radians(90 - angle)))
|
||||
else:
|
||||
self._enabled_shader.setUniformValue("u_overhangAngle", math.cos(math.radians(0))) #Overhang angle of 0 causes no area at all to be marked as overhang.
|
||||
else:
|
||||
self._enabled_shader.setUniformValue("u_overhangAngle", math.cos(math.radians(0)))
|
||||
|
||||
multi_extrusion = global_container_stack.getProperty("machine_extruder_count", "value") > 1
|
||||
|
||||
for node in DepthFirstIterator(scene.getRoot()):
|
||||
if not node.render(renderer):
|
||||
|
|
|
|||
|
|
@ -32,20 +32,44 @@ UM.Dialog
|
|||
}
|
||||
|
||||
text: {
|
||||
if (manager.firmwareUpdateCompleteStatus)
|
||||
if (manager.errorCode == 0)
|
||||
{
|
||||
//: Firmware update status label
|
||||
return catalog.i18nc("@label","Firmware update completed.")
|
||||
}
|
||||
else if (manager.progress == 0)
|
||||
{
|
||||
//: Firmware update status label
|
||||
return catalog.i18nc("@label","Starting firmware update, this may take a while.")
|
||||
if (manager.firmwareUpdateCompleteStatus)
|
||||
{
|
||||
//: Firmware update status label
|
||||
return catalog.i18nc("@label","Firmware update completed.")
|
||||
}
|
||||
else if (manager.progress == 0)
|
||||
{
|
||||
//: Firmware update status label
|
||||
return catalog.i18nc("@label","Starting firmware update, this may take a while.")
|
||||
}
|
||||
else
|
||||
{
|
||||
//: Firmware update status label
|
||||
return catalog.i18nc("@label","Updating firmware.")
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//: Firmware update status label
|
||||
return catalog.i18nc("@label","Updating firmware.")
|
||||
switch (manager.errorCode)
|
||||
{
|
||||
case 1:
|
||||
//: Firmware update status label
|
||||
return catalog.i18nc("@label","Firmware update failed due to an unknown error.")
|
||||
case 2:
|
||||
//: Firmware update status label
|
||||
return catalog.i18nc("@label","Firmware update failed due to an communication error.")
|
||||
case 3:
|
||||
//: Firmware update status label
|
||||
return catalog.i18nc("@label","Firmware update failed due to an input/output error.")
|
||||
case 4:
|
||||
//: Firmware update status label
|
||||
return catalog.i18nc("@label","Firmware update failed due to missing firmware.")
|
||||
default:
|
||||
//: Firmware update status label
|
||||
return catalog.i18nc("@label", "Unknown error code: %1").arg(manager.errorCode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ from UM.Logger import Logger
|
|||
from cura.PrinterOutputDevice import PrinterOutputDevice, ConnectionState
|
||||
from UM.Message import Message
|
||||
|
||||
from PyQt5.QtCore import QUrl, pyqtSlot, pyqtSignal
|
||||
from PyQt5.QtCore import QUrl, pyqtSlot, pyqtSignal, pyqtProperty
|
||||
|
||||
from UM.i18n import i18nCatalog
|
||||
catalog = i18nCatalog("cura")
|
||||
|
|
@ -90,6 +90,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
|||
self._firmware_update_finished = False
|
||||
|
||||
self._error_message = None
|
||||
self._error_code = 0
|
||||
|
||||
onError = pyqtSignal()
|
||||
|
||||
|
|
@ -139,7 +140,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
|||
# \param gcode_list List with gcode (strings).
|
||||
def printGCode(self, gcode_list):
|
||||
if self._progress or self._connection_state != ConnectionState.connected:
|
||||
self._error_message = Message(catalog.i18nc("@info:status", "Printer is busy or not connected. Unable to start a new job."))
|
||||
self._error_message = Message(catalog.i18nc("@info:status", "Unable to start a new job because the printer is busy or not connected."))
|
||||
self._error_message.show()
|
||||
Logger.log("d", "Printer is busy or not connected, aborting print")
|
||||
self.writeError.emit(self)
|
||||
|
|
@ -173,6 +174,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
|||
|
||||
## Private function (threaded) that actually uploads the firmware.
|
||||
def _updateFirmware(self):
|
||||
self._error_code = 0
|
||||
self.setProgress(0, 100)
|
||||
self._firmware_update_finished = False
|
||||
|
||||
|
|
@ -182,7 +184,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
|||
|
||||
if len(hex_file) == 0:
|
||||
Logger.log("e", "Unable to read provided hex file. Could not update firmware")
|
||||
self._updateFirmware_completed()
|
||||
self._updateFirmwareFailedMissingFirmware()
|
||||
return
|
||||
|
||||
programmer = stk500v2.Stk500v2()
|
||||
|
|
@ -198,7 +200,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
|||
|
||||
if not programmer.isConnected():
|
||||
Logger.log("e", "Unable to connect with serial. Could not update firmware")
|
||||
self._updateFirmware_completed()
|
||||
self._updateFirmwareFailedCommunicationError()
|
||||
return
|
||||
|
||||
self._updating_firmware = True
|
||||
|
|
@ -206,20 +208,54 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
|||
try:
|
||||
programmer.programChip(hex_file)
|
||||
self._updating_firmware = False
|
||||
except serial.SerialException as e:
|
||||
Logger.log("e", "SerialException while trying to update firmware: <%s>" %(repr(e)))
|
||||
self._updateFirmwareFailedIOError()
|
||||
return
|
||||
except Exception as e:
|
||||
Logger.log("e", "Exception while trying to update firmware %s" %e)
|
||||
self._updateFirmware_completed()
|
||||
Logger.log("e", "Exception while trying to update firmware: <%s>" %(repr(e)))
|
||||
self._updateFirmwareFailedUnknown()
|
||||
return
|
||||
programmer.close()
|
||||
|
||||
self._updateFirmware_completed()
|
||||
self._updateFirmwareCompletedSucessfully()
|
||||
return
|
||||
|
||||
## Private function which makes sure that firmware update process has completed/ended
|
||||
def _updateFirmware_completed(self):
|
||||
## Private function which makes sure that firmware update process has failed by missing firmware
|
||||
def _updateFirmwareFailedMissingFirmware(self):
|
||||
return self._updateFirmwareFailedCommon(4)
|
||||
|
||||
## Private function which makes sure that firmware update process has failed by an IO error
|
||||
def _updateFirmwareFailedIOError(self):
|
||||
return self._updateFirmwareFailedCommon(3)
|
||||
|
||||
## Private function which makes sure that firmware update process has failed by a communication problem
|
||||
def _updateFirmwareFailedCommunicationError(self):
|
||||
return self._updateFirmwareFailedCommon(2)
|
||||
|
||||
## Private function which makes sure that firmware update process has failed by an unknown error
|
||||
def _updateFirmwareFailedUnknown(self):
|
||||
return self._updateFirmwareFailedCommon(1)
|
||||
|
||||
## Private common function which makes sure that firmware update process has completed/ended with a set progress state
|
||||
def _updateFirmwareFailedCommon(self, code):
|
||||
if not code:
|
||||
raise Exception("Error code not set!")
|
||||
|
||||
self._error_code = code
|
||||
|
||||
self._firmware_update_finished = True
|
||||
self.resetFirmwareUpdate(update_has_finished = True)
|
||||
self.progressChanged.emit()
|
||||
self.firmwareUpdateComplete.emit()
|
||||
|
||||
return
|
||||
|
||||
## Private function which makes sure that firmware update process has successfully completed
|
||||
def _updateFirmwareCompletedSucessfully(self):
|
||||
self.setProgress(100, 100)
|
||||
self._firmware_update_finished = True
|
||||
self.resetFirmwareUpdate(update_has_finished=True)
|
||||
self.resetFirmwareUpdate(update_has_finished = True)
|
||||
self.firmwareUpdateComplete.emit()
|
||||
|
||||
return
|
||||
|
|
|
|||
|
|
@ -57,6 +57,13 @@ class USBPrinterOutputDeviceManager(QObject, OutputDevicePlugin, Extension):
|
|||
progress += device.progress
|
||||
return progress / len(self._usb_output_devices)
|
||||
|
||||
@pyqtProperty(int, notify = progressChanged)
|
||||
def errorCode(self):
|
||||
for printer_name, device in self._usb_output_devices.items(): # TODO: @UnusedVariable "printer_name"
|
||||
if device._error_code:
|
||||
return device._error_code
|
||||
return 0
|
||||
|
||||
## Return True if all printers finished firmware update
|
||||
@pyqtProperty(float, notify = firmwareUpdateChange)
|
||||
def firmwareUpdateCompleteStatus(self):
|
||||
|
|
@ -96,10 +103,12 @@ class USBPrinterOutputDeviceManager(QObject, OutputDevicePlugin, Extension):
|
|||
|
||||
self._firmware_view.show()
|
||||
|
||||
@pyqtSlot()
|
||||
def updateAllFirmware(self):
|
||||
@pyqtSlot(str)
|
||||
def updateAllFirmware(self, file_name):
|
||||
if file_name.startswith("file://"):
|
||||
file_name = QUrl(file_name).toLocalFile() # File dialogs prepend the path with file://, which we don't need / want
|
||||
if not self._usb_output_devices:
|
||||
Message(i18n_catalog.i18nc("@info","Cannot update firmware, there were no connected printers found.")).show()
|
||||
Message(i18n_catalog.i18nc("@info", "Unable to update firmware because there are no printers connected.")).show()
|
||||
return
|
||||
|
||||
for printer_connection in self._usb_output_devices:
|
||||
|
|
@ -107,26 +116,26 @@ class USBPrinterOutputDeviceManager(QObject, OutputDevicePlugin, Extension):
|
|||
self.spawnFirmwareInterface("")
|
||||
for printer_connection in self._usb_output_devices:
|
||||
try:
|
||||
self._usb_output_devices[printer_connection].updateFirmware(Resources.getPath(CuraApplication.ResourceTypes.Firmware, self._getDefaultFirmwareName()))
|
||||
self._usb_output_devices[printer_connection].updateFirmware(file_name)
|
||||
except FileNotFoundError:
|
||||
# Should only happen in dev environments where the resources/firmware folder is absent.
|
||||
self._usb_output_devices[printer_connection].setProgress(100, 100)
|
||||
Logger.log("w", "No firmware found for printer %s called '%s'" %(printer_connection, self._getDefaultFirmwareName()))
|
||||
Logger.log("w", "No firmware found for printer %s called '%s'", printer_connection, file_name)
|
||||
Message(i18n_catalog.i18nc("@info",
|
||||
"Could not find firmware required for the printer at %s.") % printer_connection).show()
|
||||
self._firmware_view.close()
|
||||
|
||||
continue
|
||||
|
||||
@pyqtSlot(str, result = bool)
|
||||
def updateFirmwareBySerial(self, serial_port):
|
||||
@pyqtSlot(str, str, result = bool)
|
||||
def updateFirmwareBySerial(self, serial_port, file_name):
|
||||
if serial_port in self._usb_output_devices:
|
||||
self.spawnFirmwareInterface(self._usb_output_devices[serial_port].getSerialPort())
|
||||
try:
|
||||
self._usb_output_devices[serial_port].updateFirmware(Resources.getPath(CuraApplication.ResourceTypes.Firmware, self._getDefaultFirmwareName()))
|
||||
self._usb_output_devices[serial_port].updateFirmware(file_name)
|
||||
except FileNotFoundError:
|
||||
self._firmware_view.close()
|
||||
Logger.log("e", "Could not find firmware required for this machine called '%s'" %(self._getDefaultFirmwareName()))
|
||||
Logger.log("e", "Could not find firmware required for this machine called '%s'", file_name)
|
||||
return False
|
||||
return True
|
||||
return False
|
||||
|
|
@ -140,7 +149,8 @@ class USBPrinterOutputDeviceManager(QObject, OutputDevicePlugin, Extension):
|
|||
|
||||
return USBPrinterOutputDeviceManager._instance
|
||||
|
||||
def _getDefaultFirmwareName(self):
|
||||
@pyqtSlot(result = str)
|
||||
def getDefaultFirmwareName(self):
|
||||
# Check if there is a valid global container stack
|
||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||
if not global_container_stack:
|
||||
|
|
@ -186,13 +196,13 @@ class USBPrinterOutputDeviceManager(QObject, OutputDevicePlugin, Extension):
|
|||
Logger.log("d", "Choosing basic firmware for machine %s.", machine_id)
|
||||
hex_file = machine_without_extras[machine_id] # Return "basic" firmware
|
||||
else:
|
||||
Logger.log("e", "There is no firmware for machine %s.", machine_id)
|
||||
Logger.log("w", "There is no firmware for machine %s.", machine_id)
|
||||
|
||||
if hex_file:
|
||||
return hex_file.format(baudrate=baudrate)
|
||||
return Resources.getPath(CuraApplication.ResourceTypes.Firmware, hex_file.format(baudrate=baudrate))
|
||||
else:
|
||||
Logger.log("e", "Could not find any firmware for machine %s.", machine_id)
|
||||
raise FileNotFoundError()
|
||||
Logger.log("w", "Could not find any firmware for machine %s.", machine_id)
|
||||
return ""
|
||||
|
||||
## Helper to identify serial ports (and scan for them)
|
||||
def _addRemovePorts(self, serial_ports):
|
||||
|
|
|
|||
|
|
@ -64,9 +64,7 @@ Cura.MachineAction
|
|||
{
|
||||
startBedLevelingButton.visible = false;
|
||||
bedlevelingButton.visible = true;
|
||||
checkupMachineAction.heatupHotendStarted = false;
|
||||
checkupMachineAction.heatupBedStarted = false;
|
||||
manager.startCheck();
|
||||
manager.startBedLeveling();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,31 +20,26 @@ class UMOUpgradeSelection(MachineAction):
|
|||
@pyqtProperty(bool, notify = heatedBedChanged)
|
||||
def hasHeatedBed(self):
|
||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||
return global_container_stack.getProperty("machine_heated_bed", "value")
|
||||
if global_container_stack:
|
||||
return global_container_stack.getProperty("machine_heated_bed", "value")
|
||||
|
||||
@pyqtSlot()
|
||||
def addHeatedBed(self):
|
||||
@pyqtSlot(bool)
|
||||
def setHeatedBed(self, heated_bed = True):
|
||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||
if global_container_stack:
|
||||
variant = global_container_stack.findContainer({"type": "variant"})
|
||||
if variant:
|
||||
if variant.getId() == "empty_variant":
|
||||
variant_index = global_container_stack.getContainerIndex(variant)
|
||||
stack_name = global_container_stack.getName()
|
||||
new_variant = UM.Settings.InstanceContainer(stack_name + "_variant")
|
||||
new_variant.addMetaDataEntry("type", "variant")
|
||||
new_variant.setDefinition(global_container_stack.getBottom())
|
||||
UM.Settings.ContainerRegistry.getInstance().addContainer(new_variant)
|
||||
global_container_stack.replaceContainer(variant_index, new_variant)
|
||||
variant = new_variant
|
||||
variant.setProperty("machine_heated_bed", "value", True)
|
||||
self.heatedBedChanged.emit()
|
||||
|
||||
@pyqtSlot()
|
||||
def removeHeatedBed(self):
|
||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||
if global_container_stack:
|
||||
variant = global_container_stack.findContainer({"type": "variant"})
|
||||
if variant:
|
||||
variant.setProperty("machine_heated_bed", "value", False)
|
||||
variant = self._createVariant(global_container_stack, variant_index)
|
||||
variant.setProperty("machine_heated_bed", "value", heated_bed)
|
||||
self.heatedBedChanged.emit()
|
||||
|
||||
def _createVariant(self, global_container_stack, variant_index):
|
||||
# Create and switch to a variant to store the settings in
|
||||
new_variant = UM.Settings.InstanceContainer(global_container_stack.getName() + "_variant")
|
||||
new_variant.addMetaDataEntry("type", "variant")
|
||||
new_variant.setDefinition(global_container_stack.getBottom())
|
||||
UM.Settings.ContainerRegistry.getInstance().addContainer(new_variant)
|
||||
global_container_stack.replaceContainer(variant_index, new_variant)
|
||||
return new_variant
|
||||
|
|
@ -44,7 +44,7 @@ Cura.MachineAction
|
|||
|
||||
text: catalog.i18nc("@label", "Heated Build Plate (official kit or self-built)")
|
||||
checked: manager.hasHeatedBed
|
||||
onClicked: checked ? manager.addHeatedBed() : manager.removeHeatedBed()
|
||||
onClicked: manager.setHeatedBed(checked)
|
||||
}
|
||||
|
||||
UM.I18nCatalog { id: catalog; name: "cura"; }
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
from cura.MachineAction import MachineAction
|
||||
from UM.i18n import i18nCatalog
|
||||
import cura.Settings.CuraContainerRegistry
|
||||
import UM.Settings.DefinitionContainer
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
|
||||
|
|
@ -7,3 +9,9 @@ class UpgradeFirmwareMachineAction(MachineAction):
|
|||
def __init__(self):
|
||||
super().__init__("UpgradeFirmware", catalog.i18nc("@action", "Upgrade Firmware"))
|
||||
self._qml_url = "UpgradeFirmwareMachineAction.qml"
|
||||
cura.Settings.CuraContainerRegistry.getInstance().containerAdded.connect(self._onContainerAdded)
|
||||
|
||||
def _onContainerAdded(self, container):
|
||||
# Add this action as a supported action to all machine definitions
|
||||
if isinstance(container, UM.Settings.DefinitionContainer) and container.getMetaDataEntry("type") == "machine" and container.getMetaDataEntry("supports_usb_connection"):
|
||||
UM.Application.getInstance().getMachineActionManager().addSupportedAction(container.getId(), self.getKey())
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import QtQuick 2.2
|
|||
import QtQuick.Controls 1.1
|
||||
import QtQuick.Layouts 1.1
|
||||
import QtQuick.Window 2.1
|
||||
import QtQuick.Dialogs 1.2 // For filedialog
|
||||
|
||||
import UM 1.2 as UM
|
||||
import Cura 1.0 as Cura
|
||||
|
|
@ -44,34 +45,45 @@ Cura.MachineAction
|
|||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
||||
width: parent.width
|
||||
wrapMode: Text.WordWrap
|
||||
text: catalog.i18nc("@label", "The firmware shipping with new Ultimakers works, but upgrades have been made to make better prints, and make calibration easier.");
|
||||
text: catalog.i18nc("@label", "The firmware shipping with new printers works, but new versions tend to have more features and improvements.");
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
id: upgradeText2
|
||||
anchors.top: upgradeText1.bottom
|
||||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
||||
width: parent.width
|
||||
wrapMode: Text.WordWrap
|
||||
text: catalog.i18nc("@label", "Cura requires these new features and thus your firmware will most likely need to be upgraded. You can do so now.");
|
||||
}
|
||||
Row
|
||||
{
|
||||
anchors.top: upgradeText2.bottom
|
||||
anchors.top: upgradeText1.bottom
|
||||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
width: childrenRect.width
|
||||
spacing: UM.Theme.getSize("default_margin").width
|
||||
property var firmwareName: Cura.USBPrinterManager.getDefaultFirmwareName()
|
||||
Button
|
||||
{
|
||||
id: upgradeButton
|
||||
text: catalog.i18nc("@action:button","Upgrade to Marlin Firmware");
|
||||
id: autoUpgradeButton
|
||||
text: catalog.i18nc("@action:button", "Automatically upgrade Firmware");
|
||||
enabled: parent.firmwareName != ""
|
||||
onClicked:
|
||||
{
|
||||
Cura.USBPrinterManager.updateAllFirmware()
|
||||
Cura.USBPrinterManager.updateAllFirmware(parent.firmwareName)
|
||||
}
|
||||
}
|
||||
Button
|
||||
{
|
||||
id: manualUpgradeButton
|
||||
text: catalog.i18nc("@action:button", "Upload custom Firmware");
|
||||
onClicked:
|
||||
{
|
||||
customFirmwareDialog.open()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FileDialog
|
||||
{
|
||||
id: customFirmwareDialog
|
||||
title: catalog.i18nc("@title:window", "Select custom firmware")
|
||||
nameFilters: "Firmware image files (*.hex)"
|
||||
selectExisting: true
|
||||
onAccepted: Cura.USBPrinterManager.updateAllFirmware(fileUrl)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -69,26 +69,33 @@ class MachineInstance:
|
|||
config.add_section("general")
|
||||
config.set("general", "name", self._name)
|
||||
config.set("general", "id", self._name)
|
||||
config.set("general", "type", self._type_name)
|
||||
config.set("general", "version", "2") # Hard-code version 2, since if this number changes the programmer MUST change this entire function.
|
||||
|
||||
import VersionUpgrade21to22 # Import here to prevent circular dependencies.
|
||||
has_machine_qualities = self._type_name in VersionUpgrade21to22.VersionUpgrade21to22.VersionUpgrade21to22.machinesWithMachineQuality()
|
||||
type_name = VersionUpgrade21to22.VersionUpgrade21to22.VersionUpgrade21to22.translatePrinter(self._type_name)
|
||||
active_material = VersionUpgrade21to22.VersionUpgrade21to22.VersionUpgrade21to22.translateProfile(self._active_material_name)
|
||||
active_material = VersionUpgrade21to22.VersionUpgrade21to22.VersionUpgrade21to22.translateMaterial(self._active_material_name)
|
||||
variant = VersionUpgrade21to22.VersionUpgrade21to22.VersionUpgrade21to22.translateVariant(self._variant_name, type_name)
|
||||
variant_materials = VersionUpgrade21to22.VersionUpgrade21to22.VersionUpgrade21to22.translateVariantForMaterials(self._variant_name, type_name)
|
||||
active_profile = VersionUpgrade21to22.VersionUpgrade21to22.VersionUpgrade21to22.translateProfile(self._active_profile_name)
|
||||
if has_machine_qualities: #This machine now has machine-quality profiles.
|
||||
active_profile += "_" + active_material + "_" + variant
|
||||
active_material += "_" + variant_materials #That means that the profile was split into multiple.
|
||||
current_settings = "empty" #The profile didn't know the definition ID when it was upgraded, so it will have been invalid. Sorry, your current settings are lost now.
|
||||
|
||||
#Convert to quality profile if we have one of the built-in profiles, otherwise convert to a quality-changes profile.
|
||||
if self._active_profile_name in VersionUpgrade21to22.VersionUpgrade21to22.VersionUpgrade21to22.builtInProfiles():
|
||||
active_quality = VersionUpgrade21to22.VersionUpgrade21to22.VersionUpgrade21to22.translateProfile(self._active_profile_name)
|
||||
active_quality_changes = "empty_quality_changes"
|
||||
else:
|
||||
current_settings = self._name + "_current_settings"
|
||||
active_quality = VersionUpgrade21to22.VersionUpgrade21to22.VersionUpgrade21to22.getQualityFallback(type_name, variant, active_material)
|
||||
if has_machine_qualities: #Then the profile will have split into multiple.
|
||||
active_quality_changes = self._active_profile_name + "_" + active_material + "_" + variant
|
||||
else:
|
||||
active_quality_changes = self._active_profile_name
|
||||
|
||||
if has_machine_qualities: #This machine now has machine-quality profiles.
|
||||
active_material += "_" + variant_materials #That means that the profile was split into multiple.
|
||||
|
||||
containers = [
|
||||
current_settings,
|
||||
active_profile,
|
||||
"", #The current profile doesn't know the definition ID when it was upgraded, only the instance ID, so it will be invalid. Sorry, your current settings are lost now.
|
||||
active_quality_changes,
|
||||
active_quality,
|
||||
active_material,
|
||||
variant,
|
||||
type_name
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ class Profile:
|
|||
import VersionUpgrade21to22 # Import here to prevent circular dependencies.
|
||||
|
||||
if self._name == "Current settings":
|
||||
self._filename += "_current_settings" #This resolves a duplicate ID arising from how Cura 2.1 stores its current settings.
|
||||
return None, None #Can't upgrade these, because the new current profile needs to specify the definition ID and the old file only had the machine instance, not the definition.
|
||||
|
||||
config = configparser.ConfigParser(interpolation = None)
|
||||
|
||||
|
|
@ -94,12 +94,10 @@ class Profile:
|
|||
config.set("general", "definition", "fdmprinter") #In this case, the machine definition is unknown, and it might now have machine-specific profiles, in which case this will fail.
|
||||
|
||||
config.add_section("metadata")
|
||||
if self._type:
|
||||
config.set("metadata", "type", self._type)
|
||||
else:
|
||||
config.set("metadata", "type", "quality")
|
||||
config.set("metadata", "quality", "normal") #This feature doesn't exist in 2.1 yet, so we don't know the actual quality type. For now, always base it on normal.
|
||||
config.set("metadata", "type", "quality_changes")
|
||||
if self._weight:
|
||||
config.set("metadata", "weight", self._weight)
|
||||
config.set("metadata", "weight", str(self._weight))
|
||||
if self._machine_variant_name:
|
||||
if self._machine_type_id:
|
||||
config.set("metadata", "variant", VersionUpgrade21to22.VersionUpgrade21to22.VersionUpgrade21to22.translateVariant(self._machine_variant_name, self._machine_type_id))
|
||||
|
|
|
|||
|
|
@ -23,29 +23,105 @@ from . import Profile # To upgrade profiles.
|
|||
# may have changed in later versions than 2.2.
|
||||
_machines_with_machine_quality = {
|
||||
"ultimaker2plus": {
|
||||
"materials": { "generic_abs", "generic_cpe", "generic_pla", "generic_pva" },
|
||||
"materials": { "generic_abs", "generic_cpe", "generic_pla", "generic_pva", "generic_cpe_plus", "generic_nylon", "generic_pc", "generic_tpu" },
|
||||
"variants": { "0.25 mm", "0.4 mm", "0.6 mm", "0.8 mm" }
|
||||
},
|
||||
"ultimaker2_extended_plus": {
|
||||
"materials": { "generic_abs", "generic_cpe", "generic_pla", "generic_pva" },
|
||||
"materials": { "generic_abs", "generic_cpe", "generic_pla", "generic_pva", "generic_cpe_plus", "generic_nylon", "generic_pc", "generic_tpu" },
|
||||
"variants": { "0.25 mm", "0.4 mm", "0.6 mm", "0.8 mm" }
|
||||
}
|
||||
}
|
||||
|
||||
## How to translate material names from the old version to the new.
|
||||
_material_translations = {
|
||||
"PLA": "generic_pla",
|
||||
"ABS": "generic_abs",
|
||||
"CPE": "generic_cpe",
|
||||
"CPE+": "generic_cpe_plus",
|
||||
"Nylon": "generic_nylon",
|
||||
"PC": "generic_pc",
|
||||
"TPU": "generic_tpu",
|
||||
}
|
||||
|
||||
## How to translate material names for in the profile names.
|
||||
_material_translations_profiles = {
|
||||
"PLA": "pla",
|
||||
"ABS": "abs",
|
||||
"CPE": "cpe",
|
||||
"CPE+": "cpep",
|
||||
"Nylon": "nylon",
|
||||
"PC": "pc",
|
||||
"TPU": "tpu",
|
||||
}
|
||||
|
||||
## How to translate printer names from the old version to the new.
|
||||
_printer_translations = {
|
||||
"ultimaker2plus": "ultimaker2_plus"
|
||||
}
|
||||
|
||||
_printer_translations_profiles = {
|
||||
"ultimaker2plus": "um2p", #Does NOT get included in PLA profiles!
|
||||
"ultimaker2_extended_plus": "um2ep" #Has no profiles for CPE+, Nylon, PC and TPU!
|
||||
}
|
||||
|
||||
## How to translate profile names from the old version to the new.
|
||||
#
|
||||
# This must have an entry for every built-in profile, since it also services
|
||||
# as a set for which profiles were built-in.
|
||||
_profile_translations = {
|
||||
"PLA": "generic_pla",
|
||||
"ABS": "generic_abs",
|
||||
"CPE": "generic_cpe",
|
||||
"Low Quality": "low",
|
||||
"Normal Quality": "normal",
|
||||
"High Quality": "high",
|
||||
"Ulti Quality": "high" #This one doesn't have an equivalent. Map it to high.
|
||||
"Ulti Quality": "high", #This one doesn't have an equivalent. Map it to high.
|
||||
"abs_0.25_normal": "um2p_abs_0.25_normal",
|
||||
"abs_0.4_fast": "um2p_abs_0.4_fast",
|
||||
"abs_0.4_high": "um2p_abs_0.4_high",
|
||||
"abs_0.4_normal": "um2p_abs_0.4_normal",
|
||||
"abs_0.6_normal": "um2p_abs_0.6_normal",
|
||||
"abs_0.8_normal": "um2p_abs_0.8_normal",
|
||||
"cpe_0.25_normal": "um2p_cpe_0.25_normal",
|
||||
"cpe_0.4_fast": "um2p_cpe_0.4_fast",
|
||||
"cpe_0.4_high": "um2p_cpe_0.4_high",
|
||||
"cpe_0.4_normal": "um2p_cpe_0.4_normal",
|
||||
"cpe_0.6_normal": "um2p_cpe_0.6_normal",
|
||||
"cpe_0.8_normal": "um2p_cpe_0.8_normal",
|
||||
"cpep_0.4_draft": "um2p_cpep_0.4_draft",
|
||||
"cpep_0.4_normal": "um2p_cpep_0.4_normal",
|
||||
"cpep_0.6_draft": "um2p_cpep_0.6_draft",
|
||||
"cpep_0.6_normal": "um2p_cpep_0.6_normal",
|
||||
"cpep_0.8_draft": "um2p_cpep_0.8_draft",
|
||||
"cpep_0.8_normal": "um2p_cpep_0.8_normal",
|
||||
"nylon_0.25_high": "um2p_nylon_0.25_high",
|
||||
"nylon_0.25_normal": "um2p_nylon_0.25_normal",
|
||||
"nylon_0.4_fast": "um2p_nylon_0.4_fast",
|
||||
"nylon_0.4_normal": "um2p_nylon_0.4_normal",
|
||||
"nylon_0.6_fast": "um2p_nylon_0.6_fast",
|
||||
"nylon_0.6_normal": "um2p_nylon_0.6_normal",
|
||||
"nylon_0.8_draft": "um2p_nylon_0.8_draft",
|
||||
"nylon_0.8_normal": "um2p_nylon_0.8_normal",
|
||||
"pc_0.25_high": "um2p_pc_0.25_high",
|
||||
"pc_0.25_normal": "um2p_pc_0.25_normal",
|
||||
"pc_0.4_fast": "um2p_pc_0.4_fast",
|
||||
"pc_0.4_normal": "um2p_pc_0.4_normal",
|
||||
"pc_0.6_fast": "um2p_pc_0.6_fast",
|
||||
"pc_0.6_normal": "um2p_pc_0.6_normal",
|
||||
"pc_0.8_draft": "um2p_pc_0.8_draft",
|
||||
"pc_0.8_normal": "um2p_pc_0.8_normal",
|
||||
"pla_0.25_normal": "pla_0.25_normal", #Note that the PLA profiles don't get the um2p_ prefix, though they are for UM2+.
|
||||
"pla_0.4_fast": "pla_0.4_fast",
|
||||
"pla_0.4_high": "pla_0.4_high",
|
||||
"pla_0.4_normal": "pla_0.4_normal",
|
||||
"pla_0.6_normal": "pla_0.6_normal",
|
||||
"pla_0.8_normal": "pla_0.8_normal",
|
||||
"tpu_0.25_high": "um2p_tpu_0.25_high",
|
||||
"tpu_0.4_normal": "um2p_tpu_0.4_normal",
|
||||
"tpu_0.6_fast": "um2p_tpu_0.6_fast"
|
||||
}
|
||||
|
||||
## Settings that are no longer in the new version.
|
||||
_removed_settings = {
|
||||
"fill_perimeter_gaps",
|
||||
"support_area_smoothing"
|
||||
}
|
||||
|
||||
## How to translate setting names from the old version to the new.
|
||||
|
|
@ -67,6 +143,54 @@ _setting_name_translations = {
|
|||
"support_roof_pattern": "support_interface_pattern"
|
||||
}
|
||||
|
||||
## Custom profiles become quality_changes. This dictates which quality to base
|
||||
# the quality_changes profile on.
|
||||
#
|
||||
# Which quality profile to base the quality_changes on depends on the machine,
|
||||
# material and nozzle.
|
||||
#
|
||||
# If a current configuration is missing, fall back to "normal".
|
||||
_quality_fallbacks = {
|
||||
"ultimaker2_plus": {
|
||||
"ultimaker2_plus_0.25": {
|
||||
"generic_abs": "um2p_abs_0.25_normal",
|
||||
"generic_cpe": "um2p_cpe_0.25_normal",
|
||||
#No CPE+.
|
||||
"generic_nylon": "um2p_nylon_0.25_normal",
|
||||
"generic_pc": "um2p_pc_0.25_normal",
|
||||
"generic_pla": "pla_0.25_normal",
|
||||
"generic_tpu": "um2p_tpu_0.25_high"
|
||||
},
|
||||
"ultimaker2_plus_0.4": {
|
||||
"generic_abs": "um2p_abs_0.4_normal",
|
||||
"generic_cpe": "um2p_cpe_0.4_normal",
|
||||
"generic_cpep": "um2p_cpep_0.4_normal",
|
||||
"generic_nylon": "um2p_nylon_0.4_normal",
|
||||
"generic_pc": "um2p_pc_0.4_normal",
|
||||
"generic_pla": "pla_0.4_normal",
|
||||
"generic_tpu": "um2p_tpu_0.4_normal"
|
||||
},
|
||||
"ultimaker2_plus_0.6": {
|
||||
"generic_abs": "um2p_abs_0.6_normal",
|
||||
"generic_cpe": "um2p_cpe_0.6_normal",
|
||||
"generic_cpep": "um2p_cpep_0.6_normal",
|
||||
"generic_nylon": "um2p_nylon_0.6_normal",
|
||||
"generic_pc": "um2p_pc_0.6_normal",
|
||||
"generic_pla": "pla_0.6_normal",
|
||||
"generic_tpu": "um2p_tpu_0.6_fast",
|
||||
},
|
||||
"ultimaker2_plus_0.8": {
|
||||
"generic_abs": "um2p_abs_0.8_normal",
|
||||
"generic_cpe": "um2p_cpe_0.8_normal",
|
||||
"generic_cpep": "um2p_cpep_0.8_normal",
|
||||
"generic_nylon": "um2p_nylon_0.8_normal",
|
||||
"generic_pc": "um2p_pc_0.8_normal",
|
||||
"generic_pla": "pla_0.8_normal",
|
||||
#No TPU.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
## How to translate variants of specific machines from the old version to the
|
||||
# new.
|
||||
_variant_translations = {
|
||||
|
|
@ -84,6 +208,14 @@ _variant_translations = {
|
|||
}
|
||||
}
|
||||
|
||||
## How to translate variant names for in the profile names.
|
||||
_variant_translations_profiles = {
|
||||
"0.25 mm": "0.25",
|
||||
"0.4 mm": "0.4",
|
||||
"0.6 mm": "0.6",
|
||||
"0.8 mm": "0.8"
|
||||
}
|
||||
|
||||
## Cura 2.2's material profiles use a different naming scheme for variants.
|
||||
#
|
||||
# Getting pretty stressed out by this sort of thing...
|
||||
|
|
@ -118,6 +250,33 @@ class VersionUpgrade21to22(VersionUpgrade):
|
|||
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.
|
||||
|
||||
## Gets the fallback quality to use for a specific machine-variant-material
|
||||
# combination.
|
||||
#
|
||||
# For custom profiles we fall back onto this quality profile, since we
|
||||
# don't know which quality profile it was based on.
|
||||
#
|
||||
# \param machine The machine ID of the user's configuration in 2.2.
|
||||
# \param variant The variant ID of the user's configuration in 2.2.
|
||||
# \param material The material ID of the user's configuration in 2.2.
|
||||
@staticmethod
|
||||
def getQualityFallback(machine, variant, material):
|
||||
if machine not in _quality_fallbacks:
|
||||
return "normal"
|
||||
if variant not in _quality_fallbacks[machine]:
|
||||
return "normal"
|
||||
if material not in _quality_fallbacks[machine][variant]:
|
||||
return "normal"
|
||||
return _quality_fallbacks[machine][variant][material]
|
||||
|
||||
## Gets the set of built-in profile names in Cura 2.1.
|
||||
#
|
||||
# This is required to test if profiles should be converted to a quality
|
||||
# profile or a quality-changes profile.
|
||||
@staticmethod
|
||||
def builtInProfiles():
|
||||
return _profile_translations.keys()
|
||||
|
||||
## Gets a set of the machines which now have per-material quality profiles.
|
||||
#
|
||||
# \return A set of machine identifiers.
|
||||
|
|
@ -142,7 +301,7 @@ class VersionUpgrade21to22(VersionUpgrade):
|
|||
## Converts preferences from format version 2 to version 3.
|
||||
#
|
||||
# \param serialised The serialised preferences file in version 2.
|
||||
# \param filename THe supposed file name of the preferences file, without
|
||||
# \param filename The supposed file name of the preferences file, without
|
||||
# extension.
|
||||
# \return A tuple containing the new filename and the serialised
|
||||
# preferences in version 3, or None if the input was not of the correct
|
||||
|
|
@ -166,6 +325,28 @@ class VersionUpgrade21to22(VersionUpgrade):
|
|||
return filename, None
|
||||
return profile.export()
|
||||
|
||||
## Translates a material name for the change from Cura 2.1 to 2.2.
|
||||
#
|
||||
# \param material A material name in Cura 2.1.
|
||||
# \return The name of the corresponding material in Cura 2.2.
|
||||
@staticmethod
|
||||
def translateMaterial(material):
|
||||
if material in _material_translations:
|
||||
return _material_translations[material]
|
||||
return material
|
||||
|
||||
## Translates a material name for the change from Cura 2.1 to 2.2 in
|
||||
# quality profile names.
|
||||
#
|
||||
# \param material A material name in Cura 2.1.
|
||||
# \return The name of the corresponding material in the quality profiles
|
||||
# in Cura 2.2.
|
||||
@staticmethod
|
||||
def translateMaterialForProfiles(material):
|
||||
if material in _material_translations_profiles:
|
||||
return _material_translations_profiles[material]
|
||||
return material
|
||||
|
||||
## Translates a printer name that might have changed since the last
|
||||
# version.
|
||||
#
|
||||
|
|
@ -177,6 +358,17 @@ class VersionUpgrade21to22(VersionUpgrade):
|
|||
return _printer_translations[printer]
|
||||
return printer #Doesn't need to be translated.
|
||||
|
||||
## Translates a printer name for the change from Cura 2.1 to 2.2 in quality
|
||||
# profile names.
|
||||
#
|
||||
# \param printer A printer name in 2.1.
|
||||
# \return The name of the corresponding printer in Cura 2.2.
|
||||
@staticmethod
|
||||
def translatePrinterForProfile(printer):
|
||||
if printer in _printer_translations_profiles:
|
||||
return _printer_translations_profiles[printer]
|
||||
return printer
|
||||
|
||||
## Translates a built-in profile name that might have changed since the
|
||||
# last version.
|
||||
#
|
||||
|
|
@ -199,13 +391,15 @@ class VersionUpgrade21to22(VersionUpgrade):
|
|||
@staticmethod
|
||||
def translateSettings(settings):
|
||||
for key, value in settings.items():
|
||||
if key == "fill_perimeter_gaps": #Setting is removed.
|
||||
if key in _removed_settings:
|
||||
del settings[key]
|
||||
elif key == "retraction_combing": #Combing was made into an enum instead of a boolean.
|
||||
settings[key] = "off" if (value == "False") else "all"
|
||||
elif key in _setting_name_translations:
|
||||
del settings[key]
|
||||
settings[_setting_name_translations[key]] = value
|
||||
if "infill_overlap" in settings: # New setting, added in 2.3
|
||||
settings["skin_overlap"] = settings["infill_overlap"]
|
||||
return settings
|
||||
|
||||
## Translates a setting name for the change from Cura 2.1 to 2.2.
|
||||
|
|
@ -243,3 +437,15 @@ class VersionUpgrade21to22(VersionUpgrade):
|
|||
if machine in _variant_translations_materials and variant in _variant_translations_materials[machine]:
|
||||
return _variant_translations_materials[machine][variant]
|
||||
return variant
|
||||
|
||||
## Translates a variant name for the change from Cura 2.1 to 2.2 in quality
|
||||
# profiles.
|
||||
#
|
||||
# \param variant The name of the variant in Cura 2.1.
|
||||
# \return The name of the corresponding variant for in quality profiles in
|
||||
# Cura 2.2.
|
||||
@staticmethod
|
||||
def translateVariantForProfiles(variant):
|
||||
if variant in _variant_translations_profiles:
|
||||
return _variant_translations_profiles[variant]
|
||||
return variant
|
||||
|
|
@ -7,7 +7,10 @@ import io
|
|||
import xml.etree.ElementTree as ET
|
||||
import uuid
|
||||
|
||||
from UM.Resources import Resources
|
||||
from UM.Logger import Logger
|
||||
from UM.Util import parseBool
|
||||
from cura.CuraApplication import CuraApplication
|
||||
|
||||
import UM.Dictionary
|
||||
|
||||
|
|
@ -17,66 +20,70 @@ import UM.Settings
|
|||
class XmlMaterialProfile(UM.Settings.InstanceContainer):
|
||||
def __init__(self, container_id, *args, **kwargs):
|
||||
super().__init__(container_id, *args, **kwargs)
|
||||
self._inherited_files = []
|
||||
|
||||
## Overridden from InstanceContainer
|
||||
def duplicate(self, new_id, new_name = None):
|
||||
base_file = self.getMetaDataEntry("base_file", None)
|
||||
new_uuid = str(uuid.uuid4())
|
||||
|
||||
if base_file:
|
||||
containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = base_file)
|
||||
if containers:
|
||||
new_basefile = containers[0].duplicate(self.getMetaDataEntry("brand") + "_" + new_id, new_name)
|
||||
new_basefile.setMetaDataEntry("GUID", new_uuid)
|
||||
base_file = new_basefile.id
|
||||
UM.Settings.ContainerRegistry.getInstance().addContainer(new_basefile)
|
||||
|
||||
new_id = self.getMetaDataEntry("brand") + "_" + new_id + "_" + self.getDefinition().getId()
|
||||
variant = self.getMetaDataEntry("variant")
|
||||
if variant:
|
||||
variant_containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = variant)
|
||||
if variant_containers:
|
||||
new_id += "_" + variant_containers[0].getName().replace(" ", "_")
|
||||
|
||||
result = super().duplicate(new_id, new_name)
|
||||
result.setMetaDataEntry("GUID", new_uuid)
|
||||
if result.getMetaDataEntry("base_file", None):
|
||||
result.setMetaDataEntry("base_file", base_file)
|
||||
return result
|
||||
def getInheritedFiles(self):
|
||||
return self._inherited_files
|
||||
|
||||
## Overridden from InstanceContainer
|
||||
def setReadOnly(self, read_only):
|
||||
super().setReadOnly(read_only)
|
||||
|
||||
for container in UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(GUID = self.getMetaDataEntry("GUID")):
|
||||
container._read_only = read_only
|
||||
basefile = self.getMetaDataEntry("base_file", self._id) # if basefile is self.id, this is a basefile.
|
||||
for container in UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(base_file = basefile):
|
||||
container._read_only = read_only # prevent loop instead of calling setReadOnly
|
||||
|
||||
## Overridden from InstanceContainer
|
||||
# set the meta data for all machine / variant combinations
|
||||
def setMetaDataEntry(self, key, value):
|
||||
if self.isReadOnly():
|
||||
return
|
||||
if self.getMetaDataEntry(key, None) == value:
|
||||
# Prevent loop caused by for loop.
|
||||
return
|
||||
|
||||
super().setMetaDataEntry(key, value)
|
||||
|
||||
if key == "material":
|
||||
self.setName(value)
|
||||
basefile = self.getMetaDataEntry("base_file", self._id) #if basefile is self.id, this is a basefile.
|
||||
# Update all containers that share GUID and basefile
|
||||
for container in UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(base_file = basefile):
|
||||
container.setMetaDataEntry(key, value)
|
||||
|
||||
for container in UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(GUID = self.getMetaDataEntry("GUID")):
|
||||
container.setMetaData(copy.deepcopy(self._metadata))
|
||||
if key == "material":
|
||||
container.setName(value)
|
||||
|
||||
## Overridden from InstanceContainer
|
||||
def setProperty(self, key, property_name, property_value, container = None):
|
||||
## Overridden from InstanceContainer, similar to setMetaDataEntry.
|
||||
# without this function the setName would only set the name of the specific nozzle / material / machine combination container
|
||||
# The function is a bit tricky. It will not set the name of all containers if it has the correct name itself.
|
||||
def setName(self, new_name):
|
||||
if self.isReadOnly():
|
||||
return
|
||||
|
||||
super().setProperty(key, property_name, property_value)
|
||||
# Not only is this faster, it also prevents a major loop that causes a stack overflow.
|
||||
if self.getName() == new_name:
|
||||
return
|
||||
|
||||
for container in UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(GUID = self.getMetaDataEntry("GUID")):
|
||||
container._dirty = True
|
||||
super().setName(new_name)
|
||||
|
||||
basefile = self.getMetaDataEntry("base_file", self._id) # if basefile is self.id, this is a basefile.
|
||||
# Update the basefile as well, this is actually what we're trying to do
|
||||
# Update all containers that share GUID and basefile
|
||||
containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(base_file = basefile)
|
||||
for container in containers:
|
||||
container.setName(new_name)
|
||||
|
||||
## Overridden from InstanceContainer
|
||||
# def setProperty(self, key, property_name, property_value, container = None):
|
||||
# if self.isReadOnly():
|
||||
# return
|
||||
#
|
||||
# super().setProperty(key, property_name, property_value)
|
||||
#
|
||||
# basefile = self.getMetaDataEntry("base_file", self._id) #if basefile is self.id, this is a basefile.
|
||||
# for container in UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(base_file = basefile):
|
||||
# if not container.isReadOnly():
|
||||
# container.setDirty(True)
|
||||
|
||||
## Overridden from InstanceContainer
|
||||
# base file: global settings + supported machines
|
||||
# machine / variant combination: only changes for itself.
|
||||
def serialize(self):
|
||||
registry = UM.Settings.ContainerRegistry.getInstance()
|
||||
|
||||
|
|
@ -85,7 +92,7 @@ class XmlMaterialProfile(UM.Settings.InstanceContainer):
|
|||
# Since we create an instance of XmlMaterialProfile for each machine and nozzle in the profile,
|
||||
# we should only serialize the "base" material definition, since that can then take care of
|
||||
# serializing the machine/nozzle specific profiles.
|
||||
raise NotImplementedError("Cannot serialize non-root XML materials")
|
||||
raise NotImplementedError("Ignoring serializing non-root XML materials, the data is contained in the base material")
|
||||
|
||||
builder = ET.TreeBuilder()
|
||||
|
||||
|
|
@ -118,11 +125,19 @@ class XmlMaterialProfile(UM.Settings.InstanceContainer):
|
|||
builder.data(metadata.pop("color_name", ""))
|
||||
builder.end("color")
|
||||
|
||||
builder.start("label")
|
||||
builder.data(self._name)
|
||||
builder.end("label")
|
||||
|
||||
builder.end("name")
|
||||
## End Name Block
|
||||
|
||||
for key, value in metadata.items():
|
||||
builder.start(key)
|
||||
# Normally value is a string.
|
||||
# Nones get handled well.
|
||||
if isinstance(value, bool):
|
||||
value = str(value) # parseBool in deserialize expects 'True'.
|
||||
builder.data(value)
|
||||
builder.end(key)
|
||||
|
||||
|
|
@ -215,19 +230,130 @@ class XmlMaterialProfile(UM.Settings.InstanceContainer):
|
|||
_indent(root)
|
||||
stream = io.StringIO()
|
||||
tree = ET.ElementTree(root)
|
||||
tree.write(stream, "unicode", True)
|
||||
tree.write(stream, encoding="unicode", xml_declaration=True)
|
||||
|
||||
return stream.getvalue()
|
||||
|
||||
# Recursively resolve loading inherited files
|
||||
def _resolveInheritance(self, file_name):
|
||||
xml = self._loadFile(file_name)
|
||||
|
||||
inherits = xml.find("./um:inherits", self.__namespaces)
|
||||
if inherits is not None:
|
||||
inherited = self._resolveInheritance(inherits.text)
|
||||
xml = self._mergeXML(inherited, xml)
|
||||
|
||||
return xml
|
||||
|
||||
def _loadFile(self, file_name):
|
||||
path = Resources.getPath(CuraApplication.getInstance().ResourceTypes.MaterialInstanceContainer, file_name + ".xml.fdm_material")
|
||||
|
||||
with open(path, encoding="utf-8") as f:
|
||||
contents = f.read()
|
||||
|
||||
self._inherited_files.append(path)
|
||||
return ET.fromstring(contents)
|
||||
|
||||
# The XML material profile can have specific settings for machines.
|
||||
# Some machines share profiles, so they are only created once.
|
||||
# This function duplicates those elements so that each machine tag only has one identifier.
|
||||
def _expandMachinesXML(self, element):
|
||||
settings_element = element.find("./um:settings", self.__namespaces)
|
||||
machines = settings_element.iterfind("./um:machine", self.__namespaces)
|
||||
machines_to_add = []
|
||||
machines_to_remove = []
|
||||
for machine in machines:
|
||||
identifiers = list(machine.iterfind("./um:machine_identifier", self.__namespaces))
|
||||
has_multiple_identifiers = len(identifiers) > 1
|
||||
if has_multiple_identifiers:
|
||||
# Multiple identifiers found. We need to create a new machine element and copy all it's settings there.
|
||||
for identifier in identifiers:
|
||||
new_machine = copy.deepcopy(machine)
|
||||
# Create list of identifiers that need to be removed from the copied element.
|
||||
other_identifiers = [self._createKey(other_identifier) for other_identifier in identifiers if other_identifier is not identifier]
|
||||
# As we can only remove by exact object reference, we need to look through the identifiers of copied machine.
|
||||
new_machine_identifiers = list(new_machine.iterfind("./um:machine_identifier", self.__namespaces))
|
||||
for new_machine_identifier in new_machine_identifiers:
|
||||
key = self._createKey(new_machine_identifier)
|
||||
# Key was in identifiers to remove, so this element needs to be purged
|
||||
if key in other_identifiers:
|
||||
new_machine.remove(new_machine_identifier)
|
||||
machines_to_add.append(new_machine)
|
||||
machines_to_remove.append(machine)
|
||||
else:
|
||||
pass # Machine only has one identifier. Nothing to do.
|
||||
# Remove & add all required machines.
|
||||
for machine_to_remove in machines_to_remove:
|
||||
settings_element.remove(machine_to_remove)
|
||||
for machine_to_add in machines_to_add:
|
||||
settings_element.append(machine_to_add)
|
||||
return element
|
||||
|
||||
def _mergeXML(self, first, second):
|
||||
result = copy.deepcopy(first)
|
||||
self._combineElement(self._expandMachinesXML(result), self._expandMachinesXML(second))
|
||||
return result
|
||||
|
||||
def _createKey(self, element):
|
||||
key = element.tag.split("}")[-1]
|
||||
if "key" in element.attrib:
|
||||
key += " key:" + element.attrib["key"]
|
||||
if "manufacturer" in element.attrib:
|
||||
key += " manufacturer:" + element.attrib["manufacturer"]
|
||||
if "product" in element.attrib:
|
||||
key += " product:" + element.attrib["product"]
|
||||
if key == "machine":
|
||||
for item in element:
|
||||
if "machine_identifier" in item.tag:
|
||||
key += " " + item.attrib["product"]
|
||||
return key
|
||||
|
||||
# Recursively merges XML elements. Updates either the text or children if another element is found in first.
|
||||
# If it does not exist, copies it from second.
|
||||
def _combineElement(self, first, second):
|
||||
# Create a mapping from tag name to element.
|
||||
|
||||
mapping = {}
|
||||
for element in first:
|
||||
key = self._createKey(element)
|
||||
mapping[key] = element
|
||||
for element in second:
|
||||
key = self._createKey(element)
|
||||
if len(element): # Check if element has children.
|
||||
try:
|
||||
if "setting" in element.tag and not "settings" in element.tag:
|
||||
# Setting can have points in it. In that case, delete all values and override them.
|
||||
for child in list(mapping[key]):
|
||||
mapping[key].remove(child)
|
||||
for child in element:
|
||||
mapping[key].append(child)
|
||||
else:
|
||||
self._combineElement(mapping[key], element) # Multiple elements, handle those.
|
||||
except KeyError:
|
||||
mapping[key] = element
|
||||
first.append(element)
|
||||
else:
|
||||
try:
|
||||
mapping[key].text = element.text
|
||||
except KeyError: # Not in the mapping, so simply add it
|
||||
mapping[key] = element
|
||||
first.append(element)
|
||||
|
||||
## Overridden from InstanceContainer
|
||||
def deserialize(self, serialized):
|
||||
data = ET.fromstring(serialized)
|
||||
|
||||
self.addMetaDataEntry("type", "material")
|
||||
self.addMetaDataEntry("base_file", self.id)
|
||||
|
||||
# TODO: Add material verfication
|
||||
self.addMetaDataEntry("status", "unknown")
|
||||
|
||||
inherits = data.find("./um:inherits", self.__namespaces)
|
||||
if inherits is not None:
|
||||
inherited = self._resolveInheritance(inherits.text)
|
||||
data = self._mergeXML(inherited, data)
|
||||
|
||||
metadata = data.iterfind("./um:metadata/*", self.__namespaces)
|
||||
for entry in metadata:
|
||||
tag_name = _tag_without_namespace(entry)
|
||||
|
|
@ -236,8 +362,12 @@ class XmlMaterialProfile(UM.Settings.InstanceContainer):
|
|||
brand = entry.find("./um:brand", self.__namespaces)
|
||||
material = entry.find("./um:material", self.__namespaces)
|
||||
color = entry.find("./um:color", self.__namespaces)
|
||||
label = entry.find("./um:label", self.__namespaces)
|
||||
|
||||
self.setName(material.text)
|
||||
if label is not None:
|
||||
self.setName(label.text)
|
||||
else:
|
||||
self.setName(self._profile_name(material.text, color.text))
|
||||
|
||||
self.addMetaDataEntry("brand", brand.text)
|
||||
self.addMetaDataEntry("material", material.text)
|
||||
|
|
@ -266,6 +396,7 @@ class XmlMaterialProfile(UM.Settings.InstanceContainer):
|
|||
|
||||
self.setDefinition(UM.Settings.ContainerRegistry.getInstance().findDefinitionContainers(id = "fdmprinter")[0])
|
||||
|
||||
global_compatibility = True
|
||||
global_setting_values = {}
|
||||
settings = data.iterfind("./um:settings/um:setting", self.__namespaces)
|
||||
for entry in settings:
|
||||
|
|
@ -273,6 +404,9 @@ class XmlMaterialProfile(UM.Settings.InstanceContainer):
|
|||
if key in self.__material_property_setting_map:
|
||||
self.setProperty(self.__material_property_setting_map[key], "value", entry.text, self._definition)
|
||||
global_setting_values[self.__material_property_setting_map[key]] = entry.text
|
||||
elif key in self.__unmapped_settings:
|
||||
if key == "hardware compatible":
|
||||
global_compatibility = parseBool(entry.text)
|
||||
else:
|
||||
Logger.log("d", "Unsupported material setting %s", key)
|
||||
|
||||
|
|
@ -280,12 +414,16 @@ class XmlMaterialProfile(UM.Settings.InstanceContainer):
|
|||
|
||||
machines = data.iterfind("./um:settings/um:machine", self.__namespaces)
|
||||
for machine in machines:
|
||||
machine_compatibility = global_compatibility
|
||||
machine_setting_values = {}
|
||||
settings = machine.iterfind("./um:setting", self.__namespaces)
|
||||
for entry in settings:
|
||||
key = entry.get("key")
|
||||
if key in self.__material_property_setting_map:
|
||||
machine_setting_values[self.__material_property_setting_map[key]] = entry.text
|
||||
elif key in self.__unmapped_settings:
|
||||
if key == "hardware compatible":
|
||||
machine_compatibility = parseBool(entry.text)
|
||||
else:
|
||||
Logger.log("d", "Unsupported material setting %s", key)
|
||||
|
||||
|
|
@ -293,8 +431,8 @@ class XmlMaterialProfile(UM.Settings.InstanceContainer):
|
|||
for identifier in identifiers:
|
||||
machine_id = self.__product_id_map.get(identifier.get("product"), None)
|
||||
if machine_id is None:
|
||||
Logger.log("w", "Cannot create material for unknown machine %s", machine_id)
|
||||
continue
|
||||
# Lets try again with some naive heuristics.
|
||||
machine_id = identifier.get("product").replace(" ", "").lower()
|
||||
|
||||
definitions = UM.Settings.ContainerRegistry.getInstance().findDefinitionContainers(id = machine_id)
|
||||
if not definitions:
|
||||
|
|
@ -303,21 +441,22 @@ class XmlMaterialProfile(UM.Settings.InstanceContainer):
|
|||
|
||||
definition = definitions[0]
|
||||
|
||||
new_material = XmlMaterialProfile(self.id + "_" + machine_id)
|
||||
new_material.setName(self.getName())
|
||||
new_material.setMetaData(copy.deepcopy(self.getMetaData()))
|
||||
new_material.setDefinition(definition)
|
||||
new_material.addMetaDataEntry("base_file", self.id)
|
||||
if machine_compatibility:
|
||||
new_material = XmlMaterialProfile(self.id + "_" + machine_id)
|
||||
new_material.setName(self.getName())
|
||||
new_material.setMetaData(copy.deepcopy(self.getMetaData()))
|
||||
new_material.setDefinition(definition)
|
||||
|
||||
for key, value in global_setting_values.items():
|
||||
new_material.setProperty(key, "value", value, definition)
|
||||
for key, value in global_setting_values.items():
|
||||
new_material.setProperty(key, "value", value, definition)
|
||||
|
||||
for key, value in machine_setting_values.items():
|
||||
new_material.setProperty(key, "value", value, definition)
|
||||
for key, value in machine_setting_values.items():
|
||||
new_material.setProperty(key, "value", value, definition)
|
||||
|
||||
new_material._dirty = False
|
||||
new_material._dirty = False
|
||||
|
||||
UM.Settings.ContainerRegistry.getInstance().addContainer(new_material)
|
||||
|
||||
UM.Settings.ContainerRegistry.getInstance().addContainer(new_material)
|
||||
|
||||
hotends = machine.iterfind("./um:hotend", self.__namespaces)
|
||||
for hotend in hotends:
|
||||
|
|
@ -334,13 +473,25 @@ class XmlMaterialProfile(UM.Settings.InstanceContainer):
|
|||
Logger.log("d", "No variants found with ID or name %s for machine %s", hotend_id, definition.id)
|
||||
continue
|
||||
|
||||
hotend_compatibility = machine_compatibility
|
||||
hotend_setting_values = {}
|
||||
settings = hotend.iterfind("./um:setting", self.__namespaces)
|
||||
for entry in settings:
|
||||
key = entry.get("key")
|
||||
if key in self.__material_property_setting_map:
|
||||
hotend_setting_values[self.__material_property_setting_map[key]] = entry.text
|
||||
elif key in self.__unmapped_settings:
|
||||
if key == "hardware compatible":
|
||||
hotend_compatibility = parseBool(entry.text)
|
||||
else:
|
||||
Logger.log("d", "Unsupported material setting %s", key)
|
||||
|
||||
new_hotend_material = XmlMaterialProfile(self.id + "_" + machine_id + "_" + hotend_id.replace(" ", "_"))
|
||||
new_hotend_material.setName(self.getName())
|
||||
new_hotend_material.setMetaData(copy.deepcopy(self.getMetaData()))
|
||||
new_hotend_material.setDefinition(definition)
|
||||
new_hotend_material.addMetaDataEntry("base_file", self.id)
|
||||
|
||||
new_hotend_material.addMetaDataEntry("variant", variant_containers[0].id)
|
||||
new_hotend_material.addMetaDataEntry("compatible", hotend_compatibility)
|
||||
|
||||
for key, value in global_setting_values.items():
|
||||
new_hotend_material.setProperty(key, "value", value, definition)
|
||||
|
|
@ -348,17 +499,20 @@ class XmlMaterialProfile(UM.Settings.InstanceContainer):
|
|||
for key, value in machine_setting_values.items():
|
||||
new_hotend_material.setProperty(key, "value", value, definition)
|
||||
|
||||
settings = hotend.iterfind("./um:setting", self.__namespaces)
|
||||
for entry in settings:
|
||||
key = entry.get("key")
|
||||
if key in self.__material_property_setting_map:
|
||||
new_hotend_material.setProperty(self.__material_property_setting_map[key], "value", entry.text, definition)
|
||||
else:
|
||||
Logger.log("d", "Unsupported material setting %s", key)
|
||||
for key, value in hotend_setting_values.items():
|
||||
new_hotend_material.setProperty(key, "value", value, definition)
|
||||
|
||||
new_hotend_material._dirty = False
|
||||
UM.Settings.ContainerRegistry.getInstance().addContainer(new_hotend_material)
|
||||
|
||||
if not global_compatibility:
|
||||
# Change the type of this container so it is not shown as an option in menus.
|
||||
# This uses InstanceContainer.setMetaDataEntry because otherwise all containers that
|
||||
# share this basefile are also updated.
|
||||
dirty = self.isDirty()
|
||||
super().setMetaDataEntry("type", "incompatible_material")
|
||||
super().setDirty(dirty) # reset dirty flag after setMetaDataEntry
|
||||
|
||||
def _addSettingElement(self, builder, instance):
|
||||
try:
|
||||
key = UM.Dictionary.findKey(self.__material_property_setting_map, instance.definition.key)
|
||||
|
|
@ -369,24 +523,34 @@ class XmlMaterialProfile(UM.Settings.InstanceContainer):
|
|||
builder.data(str(instance.value))
|
||||
builder.end("setting")
|
||||
|
||||
def _profile_name(self, material_name, color_name):
|
||||
if color_name != "Generic":
|
||||
return "%s %s" % (color_name, material_name)
|
||||
else:
|
||||
return material_name
|
||||
|
||||
# Map XML file setting names to internal names
|
||||
__material_property_setting_map = {
|
||||
"print temperature": "material_print_temperature",
|
||||
"heated bed temperature": "material_bed_temperature",
|
||||
"standby temperature": "material_standby_temperature",
|
||||
"processing temperature graph": "material_flow_temp_graph",
|
||||
"print cooling": "cool_fan_speed",
|
||||
"retraction amount": "retraction_amount",
|
||||
"retraction speed": "retraction_speed",
|
||||
"retraction speed": "retraction_speed"
|
||||
}
|
||||
__unmapped_settings = [
|
||||
"hardware compatible"
|
||||
]
|
||||
|
||||
# Map XML file product names to internal ids
|
||||
# TODO: Move this to definition's metadata
|
||||
__product_id_map = {
|
||||
"Ultimaker2": "ultimaker2",
|
||||
"Ultimaker2+": "ultimaker2_plus",
|
||||
"Ultimaker2go": "ultimaker2_go",
|
||||
"Ultimaker2extended": "ultimaker2_extended",
|
||||
"Ultimaker2extended+": "ultimaker2_extended_plus",
|
||||
"Ultimaker 2": "ultimaker2",
|
||||
"Ultimaker 2+": "ultimaker2_plus",
|
||||
"Ultimaker 2 Go": "ultimaker2_go",
|
||||
"Ultimaker 2 Extended": "ultimaker2_extended",
|
||||
"Ultimaker 2 Extended+": "ultimaker2_extended_plus",
|
||||
"Ultimaker Original": "ultimaker_original",
|
||||
"Ultimaker Original+": "ultimaker_original_plus"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
},
|
||||
|
||||
"overrides": {
|
||||
"machine_name": { "default_value": "BQ Prusa i3 Hephestos" },
|
||||
"machine_start_gcode": {
|
||||
"default_value": "; -- START GCODE --\nG21 ;set units to millimetres\nG90 ;set to absolute positioning\nM106 S0 ;set fan speed to zero (turned off)\nG28 X0 Y0 ;move to the X/Y origin (Home)\nG28 Z0 ;move to the Z origin (Home)\nG1 Z15.0 F1200 ;move Z to position 15.0 mm\nG92 E0 ;zero the extruded length\nG1 E20 F200 ;extrude 20mm of feed stock\nG92 E0 ;zero the extruded length again\nG1 F7200 ;set feedrate to 120 mm/s\n; -- end of START GCODE --"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
},
|
||||
|
||||
"overrides": {
|
||||
"machine_name": { "default_value": "BQ Hephestos 2" },
|
||||
"machine_start_gcode": { "default_value": "; -- START GCODE --\nM104 S{material_print_temperature} ; Heat up extruder while leveling\nM800 ; Custom GCODE to fire start print procedure\nM109 S{material_print_temperature} ; Makes sure the temperature is correct before printing\n; -- end of START GCODE --" },
|
||||
"machine_end_gcode": { "default_value": "; -- END GCODE --\nM801 ; Custom GCODE to fire end print procedure\n; -- end of END GCODE --" },
|
||||
"machine_width": { "default_value": 210 },
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
},
|
||||
|
||||
"overrides": {
|
||||
"machine_name": { "default_value": "BQ Prusa i3 Hephestos XL" },
|
||||
"machine_start_gcode": {
|
||||
"default_value": "; -- START GCODE --\nG21 ;set units to millimetres\nG90 ;set to absolute positioning\nM106 S0 ;set fan speed to zero (turned off)\nG28 X0 Y0 ;move to the X/Y origin (Home)\nG28 Z0 ;move to the Z origin (Home)\nG1 Z15.0 F1200 ;move Z to position 15.0 mm\nG92 E0 ;zero the extruded length\nG1 E20 F200 ;extrude 20mm of feed stock\nG92 E0 ;zero the extruded length again\nG1 F7200 ;set feedrate to 120 mm/s\n; -- end of START GCODE --"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
},
|
||||
|
||||
"overrides": {
|
||||
"machine_name": { "default_value": "BQ Witbox" },
|
||||
"machine_start_gcode": {
|
||||
"default_value": "; -- START GCODE --\nG21 ;set units to millimetres\nG90 ;set to absolute positioning\nM106 S0 ;set fan speed to zero (turned off)\nG28 X0 Y0 ;move to the X/Y origin (Home)\nG28 Z0 ;move to the Z origin (Home)\nG1 Z15.0 F1200 ;move Z to position 15.0 mm\nG92 E0 ;zero the extruded length\nG1 E20 F200 ;extrude 20mm of feed stock\nG92 E0 ;zero the extruded length again\nG1 F7200 ;set feedrate to 120 mm/s\n; -- end of START GCODE --"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
},
|
||||
|
||||
"overrides": {
|
||||
"machine_name": { "default_value": "BQ Witbox 2" },
|
||||
"machine_start_gcode": {
|
||||
"default_value": "; -- START GCODE --\nM800 ; Custom GCODE to fire start print procedure\n; -- end of START GCODE --"
|
||||
},
|
||||
|
|
|
|||
15
resources/definitions/custom.def.json
Normal file
15
resources/definitions/custom.def.json
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"id": "custom",
|
||||
"version": 2,
|
||||
"name": "Custom FDM printer",
|
||||
"inherits": "fdmprinter",
|
||||
"metadata": {
|
||||
"visible": true,
|
||||
"author": "Ultimaker",
|
||||
"manufacturer": "Custom",
|
||||
"category": "Custom",
|
||||
"file_formats": "text/x-gcode",
|
||||
"has_materials": true,
|
||||
"first_start_actions": ["MachineSettingsAction"]
|
||||
}
|
||||
}
|
||||
|
|
@ -177,8 +177,7 @@
|
|||
"minimum_value_warning": "machine_nozzle_offset_x",
|
||||
"maximum_value": "machine_width",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true,
|
||||
"enabled": false
|
||||
"settable_per_extruder": true
|
||||
},
|
||||
"extruder_prime_pos_y":
|
||||
{
|
||||
|
|
@ -190,8 +189,7 @@
|
|||
"minimum_value_warning": "machine_nozzle_offset_y",
|
||||
"maximum_value_warning": "machine_depth",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true,
|
||||
"enabled": false
|
||||
"settable_per_extruder": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,12 +10,14 @@
|
|||
"manufacturer": "Ultimaker",
|
||||
"file_formats": "text/x-gcode;application/x-stl-ascii;application/x-stl-binary;application/x-wavefront-obj;application/x3g",
|
||||
"visible": false,
|
||||
"preferred_material": "pla",
|
||||
"preferred_quality": "normal",
|
||||
"has_materials": true,
|
||||
"preferred_material": "*generic_pla*",
|
||||
"preferred_quality": "*normal*",
|
||||
"machine_extruder_trains":
|
||||
{
|
||||
"0": "fdmextruder"
|
||||
}
|
||||
},
|
||||
"supports_usb_connection": true
|
||||
},
|
||||
"settings":
|
||||
{
|
||||
|
|
@ -27,6 +29,16 @@
|
|||
"icon": "category_machine",
|
||||
"children":
|
||||
{
|
||||
"machine_name":
|
||||
{
|
||||
"label": "Machine Type",
|
||||
"description": "The name of your 3D printer model.",
|
||||
"default_value": "Unknown",
|
||||
"type": "str",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": false,
|
||||
"settable_per_meshgroup": false
|
||||
},
|
||||
"machine_show_variants":
|
||||
{
|
||||
"label": "Show machine variants",
|
||||
|
|
@ -402,7 +414,7 @@
|
|||
"description": "The maximum speed of the filament.",
|
||||
"unit": "mm/s",
|
||||
"type": "float",
|
||||
"default_value": 25,
|
||||
"default_value": 299792458000,
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": false,
|
||||
"settable_per_meshgroup": false
|
||||
|
|
@ -642,6 +654,7 @@
|
|||
"type": "float",
|
||||
"enabled": "support_enable",
|
||||
"value": "line_width",
|
||||
"limit_to_extruder": "support_infill_extruder_nr",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
},
|
||||
|
|
@ -654,7 +667,8 @@
|
|||
"minimum_value": "0.0001",
|
||||
"maximum_value_warning": "machine_nozzle_size * 2",
|
||||
"type": "float",
|
||||
"enabled": "extruderValue(support_extruder_nr, 'support_interface_enable')",
|
||||
"enabled": "extruderValue(support_interface_extruder_nr, 'support_interface_enable')",
|
||||
"limit_to_extruder": "support_interface_extruder_nr",
|
||||
"value": "line_width",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
|
|
@ -1099,6 +1113,7 @@
|
|||
"description": "The temperature used for the heated build plate. Set at 0 to pre-heat the printer manually.",
|
||||
"unit": "°C",
|
||||
"type": "float",
|
||||
"resolve": "sum(extruderValues('material_bed_temperature')) / len(extruderValues('material_bed_temperature'))",
|
||||
"default_value": 60,
|
||||
"minimum_value": "-273.15",
|
||||
"maximum_value_warning": "260",
|
||||
|
|
@ -1158,7 +1173,7 @@
|
|||
"default_value": 25,
|
||||
"minimum_value": "0",
|
||||
"maximum_value": "machine_max_feedrate_e",
|
||||
"maximum_value_warning": "100",
|
||||
"maximum_value_warning": "25",
|
||||
"enabled": "retraction_enable",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true,
|
||||
|
|
@ -1171,7 +1186,7 @@
|
|||
"default_value": 25,
|
||||
"minimum_value": "0",
|
||||
"maximum_value": "machine_max_feedrate_e",
|
||||
"maximum_value_warning": "100",
|
||||
"maximum_value_warning": "25",
|
||||
"enabled": "retraction_enable",
|
||||
"value": "retraction_speed",
|
||||
"settable_per_mesh": false,
|
||||
|
|
@ -1185,7 +1200,7 @@
|
|||
"default_value": 25,
|
||||
"minimum_value": "0",
|
||||
"maximum_value": "machine_max_feedrate_e",
|
||||
"maximum_value_warning": "100",
|
||||
"maximum_value_warning": "25",
|
||||
"enabled": "retraction_enable",
|
||||
"value": "retraction_speed",
|
||||
"settable_per_mesh": false,
|
||||
|
|
@ -1455,6 +1470,7 @@
|
|||
"value": "speed_print",
|
||||
"enabled": "support_enable",
|
||||
"settable_per_mesh": false,
|
||||
"limit_to_extruder": "support_extruder_nr",
|
||||
"settable_per_extruder": true,
|
||||
"children":
|
||||
{
|
||||
|
|
@ -1470,6 +1486,7 @@
|
|||
"maximum_value_warning": "150",
|
||||
"value": "speed_support",
|
||||
"enabled": "support_enable",
|
||||
"limit_to_extruder": "support_infill_extruder_nr",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
},
|
||||
|
|
@ -1483,7 +1500,8 @@
|
|||
"minimum_value": "0.1",
|
||||
"maximum_value": "math.sqrt(machine_max_feedrate_x ** 2 + machine_max_feedrate_y ** 2)",
|
||||
"maximum_value_warning": "150",
|
||||
"enabled": "extruderValue(support_extruder_nr, 'support_interface_enable') and support_enable",
|
||||
"enabled": "extruderValue(support_interface_extruder_nr, 'support_interface_enable') and support_enable",
|
||||
"limit_to_extruder": "support_interface_extruder_nr",
|
||||
"value": "speed_support / 1.5",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
|
|
@ -1574,7 +1592,8 @@
|
|||
"value": "speed_layer_0",
|
||||
"enabled": "adhesion_type == \"skirt\" or adhesion_type == \"brim\"",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
"settable_per_extruder": true,
|
||||
"limit_to_extruder": "adhesion_extruder_nr"
|
||||
},
|
||||
"max_feedrate_z_override":
|
||||
{
|
||||
|
|
@ -1724,6 +1743,7 @@
|
|||
"value": "acceleration_print",
|
||||
"enabled": "acceleration_enabled and support_enable",
|
||||
"settable_per_mesh": false,
|
||||
"limit_to_extruder": "support_extruder_nr",
|
||||
"settable_per_extruder": true,
|
||||
"children": {
|
||||
"acceleration_support_infill": {
|
||||
|
|
@ -1737,6 +1757,7 @@
|
|||
"minimum_value_warning": "100",
|
||||
"maximum_value_warning": "10000",
|
||||
"enabled": "acceleration_enabled and support_enable",
|
||||
"limit_to_extruder": "support_infill_extruder_nr",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
},
|
||||
|
|
@ -1750,7 +1771,8 @@
|
|||
"minimum_value": "0.1",
|
||||
"minimum_value_warning": "100",
|
||||
"maximum_value_warning": "10000",
|
||||
"enabled": "acceleration_enabled and extruderValue(support_extruder_nr, 'support_interface_enable') and support_enable",
|
||||
"enabled": "acceleration_enabled and extruderValue(support_interface_extruder_nr, 'support_interface_enable') and support_enable",
|
||||
"limit_to_extruder": "support_interface_extruder_nr",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
}
|
||||
|
|
@ -1839,11 +1861,10 @@
|
|||
"minimum_value_warning": "100",
|
||||
"maximum_value_warning": "10000",
|
||||
"enabled": "acceleration_enabled",
|
||||
"settable_per_mesh": false
|
||||
"settable_per_mesh": false,
|
||||
"limit_to_extruder": "adhesion_extruder_nr"
|
||||
},
|
||||
|
||||
|
||||
|
||||
"jerk_enabled": {
|
||||
"label": "Enable Jerk Control",
|
||||
"description": "Enables adjusting the jerk of print head when the velocity in the X or Y axis changes. Increasing the jerk can reduce printing time at the cost of print quality.",
|
||||
|
|
@ -1944,6 +1965,7 @@
|
|||
"enabled": "jerk_enabled and support_enable",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true,
|
||||
"limit_to_extruder": "support_extruder_nr",
|
||||
"children": {
|
||||
"jerk_support_infill": {
|
||||
"label": "Support Infill Jerk",
|
||||
|
|
@ -1956,6 +1978,7 @@
|
|||
"minimum_value_warning": "5",
|
||||
"maximum_value_warning": "50",
|
||||
"enabled": "jerk_enabled and support_enable",
|
||||
"limit_to_extruder": "support_infill_extruder_nr",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
},
|
||||
|
|
@ -1969,7 +1992,8 @@
|
|||
"minimum_value": "0.1",
|
||||
"minimum_value_warning": "5",
|
||||
"maximum_value_warning": "50",
|
||||
"enabled": "jerk_enabled and extruderValue(support_extruder_nr, 'support_interface_enable') and support_enable",
|
||||
"enabled": "jerk_enabled and extruderValue(support_interface_extruder_nr, 'support_interface_enable') and support_enable",
|
||||
"limit_to_extruder": "support_interface_extruder_nr",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
}
|
||||
|
|
@ -2058,7 +2082,8 @@
|
|||
"maximum_value_warning": "50",
|
||||
"value": "jerk_layer_0",
|
||||
"enabled": "jerk_enabled",
|
||||
"settable_per_mesh": false
|
||||
"settable_per_mesh": false,
|
||||
"limit_to_extruder": "adhesion_extruder_nr"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -2288,7 +2313,7 @@
|
|||
"minimum_value": "0",
|
||||
"maximum_value": "90",
|
||||
"default_value": 50,
|
||||
"global_inherits_stack": "support_extruder_nr",
|
||||
"limit_to_extruder": "support_interface_extruder_nr if support_interface_enable else support_infill_extruder_nr",
|
||||
"enabled": "support_enable",
|
||||
"settable_per_mesh": true
|
||||
},
|
||||
|
|
@ -2307,7 +2332,7 @@
|
|||
},
|
||||
"default_value": "zigzag",
|
||||
"enabled": "support_enable",
|
||||
"global_inherits_stack": "support_extruder_nr",
|
||||
"limit_to_extruder": "support_infill_extruder_nr",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
},
|
||||
|
|
@ -2318,6 +2343,7 @@
|
|||
"type": "bool",
|
||||
"default_value": true,
|
||||
"enabled": "support_enable and (support_pattern == 'zigzag')",
|
||||
"limit_to_extruder": "support_infill_extruder_nr",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
},
|
||||
|
|
@ -2331,6 +2357,7 @@
|
|||
"maximum_value_warning": "100",
|
||||
"default_value": 15,
|
||||
"enabled": "support_enable",
|
||||
"limit_to_extruder": "support_infill_extruder_nr",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true,
|
||||
"children": {
|
||||
|
|
@ -2344,6 +2371,7 @@
|
|||
"default_value": 2.66,
|
||||
"enabled": "support_enable",
|
||||
"value": "(support_line_width * 100) / support_infill_rate * (2 if support_pattern == \"grid\" else (3 if support_pattern == \"triangles\" else 1))",
|
||||
"limit_to_extruder": "support_infill_extruder_nr",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
}
|
||||
|
|
@ -2358,7 +2386,7 @@
|
|||
"minimum_value": "0",
|
||||
"maximum_value_warning": "10",
|
||||
"default_value": 0.1,
|
||||
"global_inherits_stack": "support_extruder_nr",
|
||||
"limit_to_extruder": "support_interface_extruder_nr if support_interface_enable else support_infill_extruder_nr",
|
||||
"enabled": "support_enable",
|
||||
"settable_per_mesh": true,
|
||||
"children":
|
||||
|
|
@ -2374,7 +2402,7 @@
|
|||
"type": "float",
|
||||
"enabled": "support_enable",
|
||||
"value": "extruderValue(support_extruder_nr, 'support_z_distance')",
|
||||
"global_inherits_stack": "support_extruder_nr",
|
||||
"limit_to_extruder": "support_interface_extruder_nr if support_interface_enable else support_infill_extruder_nr",
|
||||
"settable_per_mesh": true
|
||||
},
|
||||
"support_bottom_distance":
|
||||
|
|
@ -2386,7 +2414,7 @@
|
|||
"maximum_value_warning": "10",
|
||||
"default_value": 0.1,
|
||||
"value": "extruderValue(support_extruder_nr, 'support_z_distance') if support_type == 'everywhere' else 0",
|
||||
"global_inherits_stack": "support_extruder_nr",
|
||||
"limit_to_extruder": "support_interface_extruder_nr if support_interface_enable else support_infill_extruder_nr",
|
||||
"type": "float",
|
||||
"enabled": "support_enable and support_type == 'everywhere'",
|
||||
"settable_per_mesh": true
|
||||
|
|
@ -2402,7 +2430,7 @@
|
|||
"minimum_value": "0",
|
||||
"maximum_value_warning": "10",
|
||||
"default_value": 0.7,
|
||||
"global_inherits_stack": "support_extruder_nr",
|
||||
"limit_to_extruder": "support_infill_extruder_nr",
|
||||
"enabled": "support_enable",
|
||||
"settable_per_mesh": true
|
||||
},
|
||||
|
|
@ -2415,7 +2443,7 @@
|
|||
"z_overrides_xy": "Z overrides X/Y"
|
||||
},
|
||||
"default_value": "z_overrides_xy",
|
||||
"global_inherits_stack": "support_extruder_nr",
|
||||
"limit_to_extruder": "support_infill_extruder_nr",
|
||||
"enabled": "support_enable",
|
||||
"settable_per_mesh": true
|
||||
},
|
||||
|
|
@ -2428,8 +2456,8 @@
|
|||
"maximum_value_warning": "10",
|
||||
"default_value": 0.2,
|
||||
"value": "machine_nozzle_size / 2",
|
||||
"global_inherits_stack": "support_extruder_nr",
|
||||
"enabled": "support_enable and extruderValue(support_extruder_nr, 'support_xy_overrides_z') == 'z_overrides_xy'",
|
||||
"limit_to_extruder": "support_infill_extruder_nr",
|
||||
"enabled": "support_enable and extruderValue(support_infill_extruder_nr, 'support_xy_overrides_z') == 'z_overrides_xy'",
|
||||
"settable_per_mesh": true
|
||||
},
|
||||
"support_bottom_stair_step_height":
|
||||
|
|
@ -2439,7 +2467,7 @@
|
|||
"unit": "mm",
|
||||
"type": "float",
|
||||
"default_value": 0.3,
|
||||
"global_inherits_stack": "support_extruder_nr",
|
||||
"limit_to_extruder": "support_interface_extruder_nr if support_interface_enable else support_infill_extruder_nr",
|
||||
"minimum_value": "0",
|
||||
"maximum_value_warning": "1.0",
|
||||
"enabled": "support_enable",
|
||||
|
|
@ -2452,7 +2480,7 @@
|
|||
"unit": "mm",
|
||||
"type": "float",
|
||||
"default_value": 2.0,
|
||||
"global_inherits_stack": "support_extruder_nr",
|
||||
"limit_to_extruder": "support_infill_extruder_nr",
|
||||
"minimum_value_warning": "0",
|
||||
"maximum_value_warning": "10",
|
||||
"enabled": "support_enable",
|
||||
|
|
@ -2465,32 +2493,19 @@
|
|||
"unit": "mm",
|
||||
"type": "float",
|
||||
"default_value": 0.2,
|
||||
"global_inherits_stack": "support_extruder_nr",
|
||||
"limit_to_extruder": "support_infill_extruder_nr",
|
||||
"minimum_value_warning": "-0.5",
|
||||
"maximum_value_warning": "5.0",
|
||||
"enabled": "support_enable",
|
||||
"settable_per_mesh": true
|
||||
},
|
||||
"support_area_smoothing":
|
||||
{
|
||||
"label": "Support Area Smoothing",
|
||||
"description": "Maximum distance in the X/Y directions of a line segment which is to be smoothed out. Ragged lines are introduced by the join distance and support bridge, which cause the machine to resonate. Smoothing the support areas won't cause them to break with the constraints, except it might change the overhang.",
|
||||
"unit": "mm",
|
||||
"type": "float",
|
||||
"default_value": 0.6,
|
||||
"global_inherits_stack": "support_extruder_nr",
|
||||
"minimum_value": "0",
|
||||
"maximum_value_warning": "1.0",
|
||||
"enabled": "support_enable",
|
||||
"settable_per_mesh": true
|
||||
},
|
||||
"support_interface_enable":
|
||||
{
|
||||
"label": "Enable Support Interface",
|
||||
"description": "Generate a dense interface between the model and the support. This will create a skin at the top of the support on which the model is printed and at the bottom of the support, where it rests on the model.",
|
||||
"type": "bool",
|
||||
"default_value": false,
|
||||
"global_inherits_stack": "support_extruder_nr",
|
||||
"limit_to_extruder": "support_interface_extruder_nr",
|
||||
"enabled": "support_enable",
|
||||
"settable_per_mesh": true
|
||||
},
|
||||
|
|
@ -2502,9 +2517,9 @@
|
|||
"type": "float",
|
||||
"default_value": 1,
|
||||
"minimum_value": "0",
|
||||
"global_inherits_stack": "support_extruder_nr",
|
||||
"limit_to_extruder": "support_interface_extruder_nr",
|
||||
"maximum_value_warning": "10",
|
||||
"enabled": "extruderValue(support_extruder_nr, 'support_interface_enable') and support_enable",
|
||||
"enabled": "extruderValue(support_interface_extruder_nr, 'support_interface_enable') and support_enable",
|
||||
"settable_per_mesh": true,
|
||||
"children":
|
||||
{
|
||||
|
|
@ -2515,11 +2530,11 @@
|
|||
"unit": "mm",
|
||||
"type": "float",
|
||||
"default_value": 1,
|
||||
"value": "extruderValue(support_extruder_nr, 'support_interface_height')",
|
||||
"value": "extruderValue(support_interface_extruder_nr, 'support_interface_height')",
|
||||
"minimum_value": "0",
|
||||
"global_inherits_stack": "support_extruder_nr",
|
||||
"limit_to_extruder": "support_interface_extruder_nr",
|
||||
"maximum_value_warning": "10",
|
||||
"enabled": "extruderValue(support_extruder_nr, 'support_interface_enable') and support_enable",
|
||||
"enabled": "extruderValue(support_interface_extruder_nr, 'support_interface_enable') and support_enable",
|
||||
"settable_per_mesh": true
|
||||
},
|
||||
"support_bottom_height":
|
||||
|
|
@ -2529,16 +2544,29 @@
|
|||
"unit": "mm",
|
||||
"type": "float",
|
||||
"default_value": 1,
|
||||
"value": "extruderValue(support_extruder_nr, 'support_interface_height')",
|
||||
"value": "extruderValue(support_interface_extruder_nr, 'support_interface_height')",
|
||||
"minimum_value": "0",
|
||||
"minimum_value_warning": "extruderValue(support_extruder_nr, 'support_bottom_stair_step_height')",
|
||||
"global_inherits_stack": "support_extruder_nr",
|
||||
"minimum_value_warning": "extruderValue(support_interface_extruder_nr, 'support_bottom_stair_step_height')",
|
||||
"limit_to_extruder": "support_interface_extruder_nr",
|
||||
"maximum_value_warning": "10",
|
||||
"enabled": "extruderValue(support_extruder_nr, 'support_interface_enable') and support_enable",
|
||||
"enabled": "extruderValue(support_interface_extruder_nr, 'support_interface_enable') and support_enable",
|
||||
"settable_per_mesh": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"support_interface_skip_height":
|
||||
{
|
||||
"label": "Support Interface Resolution",
|
||||
"description": "When checking where there's model above the support, take steps of the given height. Lower values will slice slower, while higher values may cause normal support to be printed in some places where there should have been support interface.",
|
||||
"unit": "mm",
|
||||
"type": "float",
|
||||
"default_value": 0.3,
|
||||
"minimum_value": "0",
|
||||
"maximum_value_warning": "support_interface_height",
|
||||
"limit_to_extruder": "support_interface_extruder_nr",
|
||||
"enabled": "extruderValue(support_interface_extruder_nr, 'support_interface_enable') and support_enable",
|
||||
"settable_per_mesh": true
|
||||
},
|
||||
"support_interface_density":
|
||||
{
|
||||
"label": "Support Interface Density",
|
||||
|
|
@ -2548,8 +2576,8 @@
|
|||
"default_value": 100,
|
||||
"minimum_value": "0",
|
||||
"maximum_value_warning": "100",
|
||||
"global_inherits_stack": "support_extruder_nr",
|
||||
"enabled": "extruderValue(support_extruder_nr, 'support_interface_enable') and support_enable",
|
||||
"limit_to_extruder": "support_interface_extruder_nr",
|
||||
"enabled": "extruderValue(support_interface_extruder_nr, 'support_interface_enable') and support_enable",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true,
|
||||
"children":
|
||||
|
|
@ -2563,8 +2591,8 @@
|
|||
"default_value": 0.4,
|
||||
"minimum_value": "0",
|
||||
"value": "0 if support_interface_density == 0 else (support_interface_line_width * 100) / support_interface_density * (2 if support_interface_pattern == \"grid\" else (3 if support_interface_pattern == \"triangles\" else 1))",
|
||||
"global_inherits_stack": "support_extruder_nr",
|
||||
"enabled": "extruderValue(support_extruder_nr, 'support_interface_enable') and support_enable",
|
||||
"limit_to_extruder": "support_interface_extruder_nr",
|
||||
"enabled": "extruderValue(support_interface_extruder_nr, 'support_interface_enable') and support_enable",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
}
|
||||
|
|
@ -2584,8 +2612,8 @@
|
|||
"zigzag": "Zig Zag"
|
||||
},
|
||||
"default_value": "concentric",
|
||||
"global_inherits_stack": "support_extruder_nr",
|
||||
"enabled": "extruderValue(support_extruder_nr, 'support_interface_enable') and support_enable",
|
||||
"limit_to_extruder": "support_interface_extruder_nr",
|
||||
"enabled": "extruderValue(support_interface_extruder_nr, 'support_interface_enable') and support_enable",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
},
|
||||
|
|
@ -2595,7 +2623,7 @@
|
|||
"description": "Use specialized towers to support tiny overhang areas. These towers have a larger diameter than the region they support. Near the overhang the towers' diameter decreases, forming a roof.",
|
||||
"type": "bool",
|
||||
"default_value": true,
|
||||
"global_inherits_stack": "support_extruder_nr",
|
||||
"limit_to_extruder": "support_infill_extruder_nr",
|
||||
"enabled": "support_enable",
|
||||
"settable_per_mesh": true
|
||||
},
|
||||
|
|
@ -2606,10 +2634,10 @@
|
|||
"unit": "mm",
|
||||
"type": "float",
|
||||
"default_value": 3.0,
|
||||
"global_inherits_stack": "support_extruder_nr",
|
||||
"limit_to_extruder": "support_infill_extruder_nr",
|
||||
"minimum_value": "0",
|
||||
"maximum_value_warning": "10",
|
||||
"enabled": "support_enable and extruderValue(support_extruder_nr, 'support_use_towers')",
|
||||
"enabled": "support_enable and extruderValue(support_infill_extruder_nr, 'support_use_towers')",
|
||||
"settable_per_mesh": true
|
||||
},
|
||||
"support_minimal_diameter":
|
||||
|
|
@ -2619,11 +2647,11 @@
|
|||
"unit": "mm",
|
||||
"type": "float",
|
||||
"default_value": 3.0,
|
||||
"global_inherits_stack": "support_extruder_nr",
|
||||
"limit_to_extruder": "support_infill_extruder_nr",
|
||||
"minimum_value": "0",
|
||||
"maximum_value_warning": "10",
|
||||
"maximum_value": "extruderValue(support_extruder_nr, 'support_tower_diameter')",
|
||||
"enabled": "support_enable and extruderValue(support_extruder_nr, 'support_use_towers')",
|
||||
"maximum_value": "extruderValue(support_infill_extruder_nr, 'support_tower_diameter')",
|
||||
"enabled": "support_enable and extruderValue(support_infill_extruder_nr, 'support_use_towers')",
|
||||
"settable_per_mesh": true
|
||||
},
|
||||
"support_tower_roof_angle":
|
||||
|
|
@ -2635,8 +2663,8 @@
|
|||
"minimum_value": "0",
|
||||
"maximum_value": "90",
|
||||
"default_value": 65,
|
||||
"global_inherits_stack": "support_extruder_nr",
|
||||
"enabled": "support_enable and extruderValue(support_extruder_nr, 'support_use_towers')",
|
||||
"limit_to_extruder": "support_infill_extruder_nr",
|
||||
"enabled": "support_enable and extruderValue(support_infill_extruder_nr, 'support_use_towers')",
|
||||
"settable_per_mesh": true
|
||||
}
|
||||
}
|
||||
|
|
@ -2687,6 +2715,7 @@
|
|||
"raft": "Raft"
|
||||
},
|
||||
"default_value": "brim",
|
||||
"resolve": "'raft' if 'raft' in extruderValues('adhesion_type') else ('brim' if 'brim' in extruderValues('adhesion_type') else 'skirt')",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": false
|
||||
},
|
||||
|
|
@ -2700,7 +2729,8 @@
|
|||
"maximum_value_warning": "10",
|
||||
"enabled": "adhesion_type == \"skirt\"",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
"settable_per_extruder": true,
|
||||
"limit_to_extruder": "adhesion_extruder_nr"
|
||||
},
|
||||
"skirt_gap":
|
||||
{
|
||||
|
|
@ -2713,7 +2743,8 @@
|
|||
"maximum_value_warning": "100",
|
||||
"enabled": "adhesion_type == \"skirt\"",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
"settable_per_extruder": true,
|
||||
"limit_to_extruder": "adhesion_extruder_nr"
|
||||
},
|
||||
"skirt_brim_minimal_length":
|
||||
{
|
||||
|
|
@ -2741,6 +2772,7 @@
|
|||
"enabled": "adhesion_type == \"brim\"",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true,
|
||||
"limit_to_extruder": "adhesion_extruder_nr",
|
||||
"children":
|
||||
{
|
||||
"brim_line_count":
|
||||
|
|
@ -2754,7 +2786,8 @@
|
|||
"value": "math.ceil(brim_width / skirt_brim_line_width)",
|
||||
"enabled": "adhesion_type == \"brim\"",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
"settable_per_extruder": true,
|
||||
"limit_to_extruder": "adhesion_extruder_nr"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -2766,7 +2799,8 @@
|
|||
"default_value": true,
|
||||
"enabled": "adhesion_type == \"brim\"",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
"settable_per_extruder": true,
|
||||
"limit_to_extruder": "adhesion_extruder_nr"
|
||||
},
|
||||
"raft_margin":
|
||||
{
|
||||
|
|
@ -2777,7 +2811,8 @@
|
|||
"default_value": 15,
|
||||
"minimum_value_warning": "0",
|
||||
"maximum_value_warning": "10",
|
||||
"enabled": "adhesion_type == \"raft\""
|
||||
"enabled": "adhesion_type == \"raft\"",
|
||||
"limit_to_extruder": "adhesion_extruder_nr"
|
||||
},
|
||||
"raft_airgap":
|
||||
{
|
||||
|
|
@ -2790,7 +2825,8 @@
|
|||
"maximum_value_warning": "1.0",
|
||||
"enabled": "adhesion_type == \"raft\"",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
"settable_per_extruder": true,
|
||||
"limit_to_extruder": "adhesion_extruder_nr"
|
||||
},
|
||||
"layer_0_z_overlap": {
|
||||
"label": "Initial Layer Z Overlap",
|
||||
|
|
@ -2803,7 +2839,8 @@
|
|||
"maximum_value_warning": "layer_height",
|
||||
"enabled": "adhesion_type == \"raft\"",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
"settable_per_extruder": true,
|
||||
"limit_to_extruder": "adhesion_extruder_nr"
|
||||
},
|
||||
"raft_surface_layers":
|
||||
{
|
||||
|
|
@ -2815,7 +2852,8 @@
|
|||
"maximum_value_warning": "20",
|
||||
"enabled": "adhesion_type == \"raft\"",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
"settable_per_extruder": true,
|
||||
"limit_to_extruder": "adhesion_extruder_nr"
|
||||
},
|
||||
"raft_surface_thickness":
|
||||
{
|
||||
|
|
@ -2829,7 +2867,8 @@
|
|||
"maximum_value_warning": "2.0",
|
||||
"enabled": "adhesion_type == \"raft\"",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
"settable_per_extruder": true,
|
||||
"limit_to_extruder": "adhesion_extruder_nr"
|
||||
},
|
||||
"raft_surface_line_width":
|
||||
{
|
||||
|
|
@ -2843,7 +2882,8 @@
|
|||
"maximum_value_warning": "machine_nozzle_size * 2",
|
||||
"enabled": "adhesion_type == \"raft\"",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
"settable_per_extruder": true,
|
||||
"limit_to_extruder": "adhesion_extruder_nr"
|
||||
},
|
||||
"raft_surface_line_spacing":
|
||||
{
|
||||
|
|
@ -2857,7 +2897,8 @@
|
|||
"enabled": "adhesion_type == \"raft\"",
|
||||
"value": "raft_surface_line_width",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
"settable_per_extruder": true,
|
||||
"limit_to_extruder": "adhesion_extruder_nr"
|
||||
},
|
||||
"raft_interface_thickness":
|
||||
{
|
||||
|
|
@ -2871,7 +2912,8 @@
|
|||
"maximum_value_warning": "5.0",
|
||||
"enabled": "adhesion_type == \"raft\"",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
"settable_per_extruder": true,
|
||||
"limit_to_extruder": "adhesion_extruder_nr"
|
||||
},
|
||||
"raft_interface_line_width":
|
||||
{
|
||||
|
|
@ -2885,7 +2927,8 @@
|
|||
"maximum_value_warning": "machine_nozzle_size * 2",
|
||||
"enabled": "adhesion_type == \"raft\"",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
"settable_per_extruder": true,
|
||||
"limit_to_extruder": "adhesion_extruder_nr"
|
||||
},
|
||||
"raft_interface_line_spacing":
|
||||
{
|
||||
|
|
@ -2899,7 +2942,8 @@
|
|||
"maximum_value_warning": "15.0",
|
||||
"enabled": "adhesion_type == \"raft\"",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
"settable_per_extruder": true,
|
||||
"limit_to_extruder": "adhesion_extruder_nr"
|
||||
},
|
||||
"raft_base_thickness":
|
||||
{
|
||||
|
|
@ -2913,7 +2957,8 @@
|
|||
"maximum_value_warning": "5.0",
|
||||
"enabled": "adhesion_type == \"raft\"",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
"settable_per_extruder": true,
|
||||
"limit_to_extruder": "adhesion_extruder_nr"
|
||||
},
|
||||
"raft_base_line_width":
|
||||
{
|
||||
|
|
@ -2927,7 +2972,8 @@
|
|||
"maximum_value_warning": "machine_nozzle_size * 3",
|
||||
"enabled": "adhesion_type == \"raft\"",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
"settable_per_extruder": true,
|
||||
"limit_to_extruder": "adhesion_extruder_nr"
|
||||
},
|
||||
"raft_base_line_spacing":
|
||||
{
|
||||
|
|
@ -2941,7 +2987,8 @@
|
|||
"maximum_value_warning": "100",
|
||||
"enabled": "adhesion_type == \"raft\"",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
"settable_per_extruder": true,
|
||||
"limit_to_extruder": "adhesion_extruder_nr"
|
||||
},
|
||||
"raft_speed":
|
||||
{
|
||||
|
|
@ -2957,6 +3004,7 @@
|
|||
"value": "speed_print / 60 * 30",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true,
|
||||
"limit_to_extruder": "adhesion_extruder_nr",
|
||||
"children":
|
||||
{
|
||||
"raft_surface_speed":
|
||||
|
|
@ -2972,7 +3020,8 @@
|
|||
"enabled": "adhesion_type == \"raft\"",
|
||||
"value": "raft_speed",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
"settable_per_extruder": true,
|
||||
"limit_to_extruder": "adhesion_extruder_nr"
|
||||
},
|
||||
"raft_interface_speed":
|
||||
{
|
||||
|
|
@ -2987,7 +3036,8 @@
|
|||
"maximum_value_warning": "150",
|
||||
"enabled": "adhesion_type == \"raft\"",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
"settable_per_extruder": true,
|
||||
"limit_to_extruder": "adhesion_extruder_nr"
|
||||
},
|
||||
"raft_base_speed":
|
||||
{
|
||||
|
|
@ -3002,13 +3052,11 @@
|
|||
"enabled": "adhesion_type == \"raft\"",
|
||||
"value": "0.75 * raft_speed",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
"settable_per_extruder": true,
|
||||
"limit_to_extruder": "adhesion_extruder_nr"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
|
||||
"raft_acceleration": {
|
||||
"label": "Raft Print Acceleration",
|
||||
"description": "The acceleration with which the raft is printed.",
|
||||
|
|
@ -3021,6 +3069,7 @@
|
|||
"value": "acceleration_print",
|
||||
"enabled": "adhesion_type == \"raft\" and acceleration_enabled",
|
||||
"settable_per_mesh": false,
|
||||
"limit_to_extruder": "adhesion_extruder_nr",
|
||||
"children": {
|
||||
"raft_surface_acceleration": {
|
||||
"label": "Raft Top Print Acceleration",
|
||||
|
|
@ -3033,7 +3082,8 @@
|
|||
"minimum_value_warning": "100",
|
||||
"maximum_value_warning": "10000",
|
||||
"enabled": "adhesion_type == \"raft\" and acceleration_enabled",
|
||||
"settable_per_mesh": false
|
||||
"settable_per_mesh": false,
|
||||
"limit_to_extruder": "adhesion_extruder_nr"
|
||||
},
|
||||
"raft_interface_acceleration": {
|
||||
"label": "Raft Middle Print Acceleration",
|
||||
|
|
@ -3046,7 +3096,8 @@
|
|||
"minimum_value_warning": "100",
|
||||
"maximum_value_warning": "10000",
|
||||
"enabled": "adhesion_type == \"raft\" and acceleration_enabled",
|
||||
"settable_per_mesh": false
|
||||
"settable_per_mesh": false,
|
||||
"limit_to_extruder": "adhesion_extruder_nr"
|
||||
},
|
||||
"raft_base_acceleration": {
|
||||
"label": "Raft Base Print Acceleration",
|
||||
|
|
@ -3059,13 +3110,11 @@
|
|||
"minimum_value_warning": "100",
|
||||
"maximum_value_warning": "10000",
|
||||
"enabled": "adhesion_type == \"raft\" and acceleration_enabled",
|
||||
"settable_per_mesh": false
|
||||
"settable_per_mesh": false,
|
||||
"limit_to_extruder": "adhesion_extruder_nr"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
|
||||
"raft_jerk": {
|
||||
"label": "Raft Print Jerk",
|
||||
"description": "The jerk with which the raft is printed.",
|
||||
|
|
@ -3078,6 +3127,7 @@
|
|||
"value": "jerk_print",
|
||||
"enabled": "adhesion_type == \"raft\" and jerk_enabled",
|
||||
"settable_per_mesh": false,
|
||||
"limit_to_extruder": "adhesion_extruder_nr",
|
||||
"children": {
|
||||
"raft_surface_jerk": {
|
||||
"label": "Raft Top Print Jerk",
|
||||
|
|
@ -3090,7 +3140,8 @@
|
|||
"minimum_value_warning": "5",
|
||||
"maximum_value_warning": "100",
|
||||
"enabled": "adhesion_type == \"raft\" and jerk_enabled",
|
||||
"settable_per_mesh": false
|
||||
"settable_per_mesh": false,
|
||||
"limit_to_extruder": "adhesion_extruder_nr"
|
||||
},
|
||||
"raft_interface_jerk": {
|
||||
"label": "Raft Middle Print Jerk",
|
||||
|
|
@ -3103,7 +3154,8 @@
|
|||
"minimum_value_warning": "5",
|
||||
"maximum_value_warning": "50",
|
||||
"enabled": "adhesion_type == \"raft\" and jerk_enabled",
|
||||
"settable_per_mesh": false
|
||||
"settable_per_mesh": false,
|
||||
"limit_to_extruder": "adhesion_extruder_nr"
|
||||
},
|
||||
"raft_base_jerk": {
|
||||
"label": "Raft Base Print Jerk",
|
||||
|
|
@ -3116,12 +3168,11 @@
|
|||
"minimum_value_warning": "5",
|
||||
"maximum_value_warning": "50",
|
||||
"enabled": "adhesion_type == \"raft\" and jerk_enabled",
|
||||
"settable_per_mesh": false
|
||||
"settable_per_mesh": false,
|
||||
"limit_to_extruder": "adhesion_extruder_nr"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
"raft_fan_speed": {
|
||||
"label": "Raft Fan Speed",
|
||||
"description": "The fan speed for the raft.",
|
||||
|
|
@ -3133,6 +3184,7 @@
|
|||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true,
|
||||
"enabled": "adhesion_type == \"raft\"",
|
||||
"limit_to_extruder": "adhesion_extruder_nr",
|
||||
"children":
|
||||
{
|
||||
"raft_surface_fan_speed":
|
||||
|
|
@ -3147,7 +3199,8 @@
|
|||
"value": "raft_fan_speed",
|
||||
"enabled": "adhesion_type == \"raft\"",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
"settable_per_extruder": true,
|
||||
"limit_to_extruder": "adhesion_extruder_nr"
|
||||
},
|
||||
"raft_interface_fan_speed":
|
||||
{
|
||||
|
|
@ -3161,7 +3214,8 @@
|
|||
"value": "raft_fan_speed",
|
||||
"enabled": "adhesion_type == \"raft\"",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
"settable_per_extruder": true,
|
||||
"limit_to_extruder": "adhesion_extruder_nr"
|
||||
},
|
||||
"raft_base_fan_speed":
|
||||
{
|
||||
|
|
@ -3175,7 +3229,8 @@
|
|||
"value": "raft_fan_speed",
|
||||
"enabled": "adhesion_type == \"raft\"",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
"settable_per_extruder": true,
|
||||
"limit_to_extruder": "adhesion_extruder_nr"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3187,6 +3242,7 @@
|
|||
"type": "category",
|
||||
"icon": "category_dual",
|
||||
"description": "Settings used for printing with multiple extruders.",
|
||||
"enabled": "machine_extruder_count > 1",
|
||||
"children":
|
||||
{
|
||||
"adhesion_extruder_nr":
|
||||
|
|
@ -3238,7 +3294,7 @@
|
|||
"type": "extruder",
|
||||
"default_value": "0",
|
||||
"value": "support_extruder_nr",
|
||||
"enabled": "support_enable and machine_extruder_count > 1 and extruderValue(support_extruder_nr, 'support_interface_enable')",
|
||||
"enabled": "support_enable and machine_extruder_count > 1",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": false
|
||||
}
|
||||
|
|
@ -3249,7 +3305,9 @@
|
|||
"label": "Enable Prime Tower",
|
||||
"description": "Print a tower next to the print which serves to prime the material after each nozzle switch.",
|
||||
"type": "bool",
|
||||
"enabled": "machine_extruder_count > 1",
|
||||
"default_value": false,
|
||||
"resolve": "max(extruderValues('prime_tower_enable'))",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": false
|
||||
},
|
||||
|
|
@ -3263,6 +3321,7 @@
|
|||
"default_value": 15,
|
||||
"value": "15 if prime_tower_enable else 0",
|
||||
"minimum_value": "0",
|
||||
"maximum_value": "min(0.5 * machine_width, 0.5 * machine_depth)",
|
||||
"maximum_value_warning": "20",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": false
|
||||
|
|
@ -3277,6 +3336,8 @@
|
|||
"default_value": 200,
|
||||
"minimum_value_warning": "-1000",
|
||||
"maximum_value_warning": "1000",
|
||||
"maximum_value": "machine_width - 0.5 * prime_tower_size",
|
||||
"minimum_value": "0.5 * prime_tower_size",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": false
|
||||
},
|
||||
|
|
@ -3290,6 +3351,8 @@
|
|||
"default_value": 200,
|
||||
"minimum_value_warning": "-1000",
|
||||
"maximum_value_warning": "1000",
|
||||
"maximum_value": "machine_depth - 0.5 * prime_tower_size",
|
||||
"minimum_value": "0.5 * prime_tower_size",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": false
|
||||
},
|
||||
|
|
@ -3481,7 +3544,7 @@
|
|||
},
|
||||
"experimental":
|
||||
{
|
||||
"label": "Experimental Modes",
|
||||
"label": "Experimental",
|
||||
"type": "category",
|
||||
"icon": "category_experimental",
|
||||
"description": "experimental!",
|
||||
|
|
@ -3643,6 +3706,7 @@
|
|||
"maximum_value": "90",
|
||||
"default_value": 30,
|
||||
"enabled": "support_conical_enabled and support_enable",
|
||||
"limit_to_extruder": "support_infill_extruder_nr",
|
||||
"settable_per_mesh": true
|
||||
},
|
||||
"support_conical_min_width":
|
||||
|
|
@ -3656,6 +3720,7 @@
|
|||
"maximum_value_warning": "100.0",
|
||||
"type": "float",
|
||||
"enabled": "support_conical_enabled and support_enable",
|
||||
"limit_to_extruder": "support_infill_extruder_nr",
|
||||
"settable_per_mesh": true
|
||||
},
|
||||
"magic_fuzzy_skin_enabled":
|
||||
|
|
@ -3726,6 +3791,7 @@
|
|||
"type": "float",
|
||||
"unit": "mm",
|
||||
"default_value": 3,
|
||||
"value": "machine_nozzle_head_distance",
|
||||
"minimum_value": "0.0001",
|
||||
"maximum_value_warning": "20",
|
||||
"enabled": "wireframe_enabled",
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
},
|
||||
|
||||
"overrides": {
|
||||
"machine_name": { "default_value": "German RepRap Neo" },
|
||||
"machine_width": {
|
||||
"default_value": 150
|
||||
},
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
},
|
||||
|
||||
"overrides": {
|
||||
"machine_name": { "default_value": "Innovo INVENTOR" },
|
||||
"machine_width": {
|
||||
"default_value": 340
|
||||
},
|
||||
|
|
@ -27,7 +28,7 @@
|
|||
"default_value": true
|
||||
},
|
||||
"machine_center_is_zero": {
|
||||
"default_value": false
|
||||
"default_value": true
|
||||
},
|
||||
"machine_nozzle_size": {
|
||||
"default_value": 0.4
|
||||
|
|
@ -56,7 +57,7 @@
|
|||
"default_value": "G28 ; Home extruder\nM107 ; Turn off fan\nG90 ; Absolute positioning\nM82 ; Extruder in absolute mode\n{IF_BED}M190 S{BED}\n{IF_EXT0}M104 T0 S{TEMP0}\n{IF_EXT0}M109 T0 S{TEMP0}\n{IF_EXT1}M104 T1 S{TEMP1}\n{IF_EXT1}M109 T1 S{TEMP1}\nG32 S3 ; auto level\nG92 E0 ; Reset extruder position"
|
||||
},
|
||||
"machine_end_gcode": {
|
||||
"default_value": "M104 S0\nG91 ; relative positioning\nG1 E-2 F5000; retract 2mm\nG28 Z; move bed down\nG90 ; absolute positioning\nM84 ; disable motors"
|
||||
"default_value": "M104 S0 ; turn off extruders\nM140 S0 ; heated bed heater off\nG91 ; relative positioning\nG1 E-2 F5000; retract 2mm\nG28 Z; move bed down\nG90 ; absolute positioning\nM84 ; disable motors"
|
||||
},
|
||||
"layer_height": {
|
||||
"default_value": 0.15
|
||||
|
|
@ -65,10 +66,10 @@
|
|||
"default_value": 0.8
|
||||
},
|
||||
"top_bottom_thickness": {
|
||||
"default_value": 0.3
|
||||
"default_value": 1.2
|
||||
},
|
||||
"material_print_temperature": {
|
||||
"default_value": 215
|
||||
"default_value": 205
|
||||
},
|
||||
"material_bed_temperature": {
|
||||
"default_value": 60
|
||||
|
|
@ -77,23 +78,30 @@
|
|||
"default_value": 1.75
|
||||
},
|
||||
"speed_print": {
|
||||
"default_value": 60
|
||||
"default_value": 50
|
||||
},
|
||||
"speed_wall_0": {
|
||||
"default_value": 25
|
||||
},
|
||||
"speed_wall_x": {
|
||||
"default_value": 40
|
||||
},
|
||||
"speed_infill": {
|
||||
"default_value": 100
|
||||
"default_value": 80
|
||||
},
|
||||
"speed_topbottom": {
|
||||
"default_value": 30
|
||||
},
|
||||
"speed_support_interface":
|
||||
{
|
||||
"default_value": 20
|
||||
},
|
||||
"speed_travel": {
|
||||
"default_value": 150
|
||||
},
|
||||
"speed_layer_0": {
|
||||
"default_value": 30.0,
|
||||
"minimum_value": 0.1
|
||||
},
|
||||
"infill_overlap": {
|
||||
"default_value": 10.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -12,6 +12,7 @@
|
|||
},
|
||||
|
||||
"overrides": {
|
||||
"machine_name": { "default_value": "Malyan M180" },
|
||||
"machine_width": {
|
||||
"default_value": 230
|
||||
},
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
},
|
||||
|
||||
"overrides": {
|
||||
"machine_name": { "default_value": "3DMaker Starter" },
|
||||
"machine_width": {
|
||||
"default_value": 210
|
||||
},
|
||||
|
|
@ -145,7 +146,7 @@
|
|||
"adhesion_type": {
|
||||
"default_value": "Raft"
|
||||
},
|
||||
"skirt_minimal_length": {
|
||||
"skirt_brim_minimal_length": {
|
||||
"default_value": 100
|
||||
},
|
||||
"raft_base_line_spacing": {
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
"platform": "mankati_fullscale_xt_plus_platform.stl"
|
||||
},
|
||||
"overrides": {
|
||||
"machine_name": { "default_value": "Mankati Fullscale XT Plus" },
|
||||
"machine_width": { "default_value": 260 },
|
||||
"machine_depth": { "default_value": 260 },
|
||||
"machine_height": { "default_value": 300 },
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
],
|
||||
|
||||
"overrides": {
|
||||
"machine_name": { "default_value": "Mendel90" },
|
||||
"machine_start_gcode": {
|
||||
"default_value": "G21 ;metric values\nG90 ;absolute positioning\nG92 E0 ;zero the extruded length\nM107 ;start with the fan off\nG1 X90 Y200 F6000 ;go to the middle of the front\nG1 Z0.05 ;close to the bed\nG1 Z0.3 ;lift Z\n"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -8,16 +8,18 @@
|
|||
"author": "Calvindog717",
|
||||
"manufacturer": "PrintrBot",
|
||||
"category": "Other",
|
||||
"platform": "printrbot_simple_metal_platform.stl",
|
||||
"file_formats": "text/x-gcode"
|
||||
},
|
||||
|
||||
"overrides": {
|
||||
"machine_name": { "default_value": "Printrbot Simple" },
|
||||
"machine_heated_bed": { "default_value": false },
|
||||
"machine_width": { "default_value": 150 },
|
||||
"machine_height": { "default_value": 150 },
|
||||
"machine_depth": { "default_value": 140 },
|
||||
"machine_center_is_zero": { "default_value": false },
|
||||
"machine_nozzle_size": { "default_value": 0.3 },
|
||||
"machine_nozzle_size": { "default_value": 0.4 },
|
||||
"material_diameter": { "default_value": 1.75 },
|
||||
"machine_nozzle_heat_up_speed": { "default_value": 2 },
|
||||
"machine_nozzle_cool_down_speed": { "default_value": 2 },
|
||||
|
|
@ -33,10 +35,10 @@
|
|||
"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\nM107 ;start with the fan off\nG28 X0 Y0 ;move X/Y to min endstops\nG28 Z0 ;move Z to min endstops\nG29 ; auto bed-levelling\nG1 Z15.0 F9000 ;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 F9000\n;Put printing message on LCD screen\nM117 Printing..."
|
||||
"default_value": "G21 ;metric values\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nM107 ;start with the fan off\nG28 X0 Y0 ;home X/Y\nG28 Z0 ;home Z\nG92 E0 ;zero the extruded length\nG29 ;initiate auto bed leveling sequence\nG92 X132.4 Y20 ;correct bed origin (G29 changes it)"
|
||||
},
|
||||
"machine_end_gcode": {
|
||||
"default_value": "M104 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 F9000 ;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": "M104 S0 ;extruder heater off\nM140 S0 ;heated bed heater off (if you have it)\nM106 S0 ;fan off\nG91 ;relative positioning\nG1 E-1 F300 ;retract the filament a bit\nG1 Z+1 E-5 F9000 ;move Z up a bit and retract even more\nG28 X0 Y0 ;home X/Y, so the head is out of the way\nM84 ;steppers off\nG90 ;absolute positioning"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
},
|
||||
|
||||
"overrides": {
|
||||
"machine_name": { "default_value": "Prusa i3" },
|
||||
"machine_heated_bed": {
|
||||
"default_value": true
|
||||
},
|
||||
|
|
|
|||
53
resources/definitions/prusa_i3_mk2.def.json
Normal file
53
resources/definitions/prusa_i3_mk2.def.json
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
{
|
||||
"id": "prusa_i3_mk2",
|
||||
"version": 2,
|
||||
"name": "Prusa i3 Mk2",
|
||||
"inherits": "fdmprinter",
|
||||
"metadata": {
|
||||
"visible": true,
|
||||
"author": "Apsu",
|
||||
"manufacturer": "Prusa Research",
|
||||
"category": "Other",
|
||||
"file_formats": "text/x-gcode",
|
||||
"icon": "icon_ultimaker2",
|
||||
"platform": "prusai3_platform.stl",
|
||||
"has_materials": true
|
||||
},
|
||||
|
||||
"overrides": {
|
||||
"machine_name": { "default_value": "Prusa i3 Mk2" },
|
||||
"machine_heated_bed": { "default_value": true },
|
||||
"machine_width": { "default_value": 250 },
|
||||
"machine_height": { "default_value": 200 },
|
||||
"machine_depth": { "default_value": 210 },
|
||||
"machine_center_is_zero": { "default_value": false },
|
||||
"material_diameter": { "default_value": 1.75 },
|
||||
"material_bed_temperature": { "default_value": 55 },
|
||||
"machine_nozzle_size": { "default_value": 0.4 },
|
||||
"layer_height": { "default_value": 0.1 },
|
||||
"layer_height_0": { "default_value": 0.15 },
|
||||
"retraction_amount": { "default_value": 0.8 },
|
||||
"retraction_speed": { "default_value": 35 },
|
||||
"retraction_retract_speed": { "default_value": 35 },
|
||||
"retraction_prime_speed": { "default_value": 35 },
|
||||
"adhesion_type": { "default_value": "skirt" },
|
||||
"machine_nozzle_heat_up_speed": { "default_value": 2 },
|
||||
"machine_nozzle_cool_down_speed": { "default_value": 2 },
|
||||
"machine_head_with_fans_polygon": { "default_value": [[-31,31],[34,31],[34,-40],[-31,-40]] },
|
||||
"gantry_height": { "default_value": 28 },
|
||||
"machine_max_feedrate_z": { "default_value": 12 },
|
||||
"machine_max_feedrate_e": { "default_value": 120 },
|
||||
"machine_max_acceleration_z": { "default_value": 500 },
|
||||
"machine_acceleration": { "default_value": 1000 },
|
||||
"machine_max_jerk_xy": { "default_value": 10 },
|
||||
"machine_max_jerk_z": { "default_value": 0.2 },
|
||||
"machine_max_jerk_e": { "default_value": 2.5 },
|
||||
"machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" },
|
||||
"machine_start_gcode": {
|
||||
"default_value": "G21 ; set units to millimeters\nG90 ; use absolute positioning\nM82 ; absolute extrusion mode\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nM104 S{material_print_temperature} ; set extruder temp\nM140 S{material_bed_temperature} ; set bed temp\nM190 S{material_bed_temperature} ; wait for bed temp\nM109 S{material_print_temperature} ; wait for extruder temp\nG92 E0.0 ; reset extruder distance position\nG1 Y-3.0 F1000.0 ; go outside print area\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E21.5 F1000.0 ; intro line\nG92 E0.0 ; reset extruder distance position"
|
||||
},
|
||||
"machine_end_gcode": {
|
||||
"default_value": "M104 S0 ; turn off extruder\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y210; home X axis and push Y forward\nM84 ; disable motors"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -14,6 +14,7 @@
|
|||
},
|
||||
|
||||
"overrides": {
|
||||
"machine_name": { "default_value": "Prusa i3 xl" },
|
||||
"machine_heated_bed": {
|
||||
"default_value": true
|
||||
},
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
},
|
||||
|
||||
"overrides": {
|
||||
"machine_name": { "default_value": "RigidBot" },
|
||||
"machine_width": {
|
||||
"default_value": 254
|
||||
},
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
},
|
||||
|
||||
"overrides": {
|
||||
"machine_name": { "default_value": "RigidBotBig" },
|
||||
"machine_width": {
|
||||
"default_value": 400
|
||||
},
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@
|
|||
"visible": false
|
||||
},
|
||||
"overrides": {
|
||||
"machine_max_feedrate_e": {
|
||||
"default_value": 45
|
||||
},
|
||||
"material_print_temperature": {
|
||||
"minimum_value": "0"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -14,9 +14,11 @@
|
|||
"platform": "ultimaker2_platform.obj",
|
||||
"platform_texture": "Ultimaker2backplate.png",
|
||||
"platform_offset": [9, 0, 0],
|
||||
"has_materials": false,
|
||||
"supported_actions":["UpgradeFirmware"]
|
||||
},
|
||||
"overrides": {
|
||||
"machine_name": { "default_value": "Ultimaker 2" },
|
||||
"machine_start_gcode" : {
|
||||
"default_value": "",
|
||||
"value": "\"\" if machine_gcode_flavor == \"UltiGCode\" else \"G21 ;metric values\\nG90 ;absolute positioning\\nM82 ;set extruder to absolute mode\\nM107 ;start with the fan off\\nG1 X10 Y0 F4000;move X/Y to min endstops\\nG28 Z0 ;move Z to bottom endstops\\nG1 Z15.0 F9000 ;move the platform to 15mm\\nG92 E0 ;zero the extruded length\\nG1 F200 E10 ;extrude 10 mm of feed stock\\nG92 E0 ;zero the extruded length again\\nG1 F9000\\n;Put printing message on LCD screen\\nM117 Printing...\""
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
},
|
||||
|
||||
"overrides": {
|
||||
"machine_name": { "default_value": "Ultimaker 2 Extended" },
|
||||
"machine_height": {
|
||||
"default_value": 305
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
"author": "Ultimaker",
|
||||
"manufacturer": "Ultimaker",
|
||||
"category": "Ultimaker",
|
||||
"quality_definition": "ultimaker2_plus",
|
||||
"weight": 2,
|
||||
"file_formats": "text/x-gcode",
|
||||
"platform": "ultimaker2_platform.obj",
|
||||
|
|
@ -15,17 +16,9 @@
|
|||
},
|
||||
|
||||
"overrides": {
|
||||
"machine_name": { "default_value": "Ultimaker 2 Extended+" },
|
||||
"machine_height": {
|
||||
"default_value": 305
|
||||
},
|
||||
"machine_show_variants": {
|
||||
"default_value": true
|
||||
},
|
||||
"machine_nozzle_head_distance": {
|
||||
"default_value": 5
|
||||
},
|
||||
"machine_nozzle_expansion_angle": {
|
||||
"default_value": 45
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
},
|
||||
|
||||
"overrides": {
|
||||
"machine_name": { "default_value": "Ultimaker 2 Go" },
|
||||
"machine_width": {
|
||||
"default_value": 120
|
||||
},
|
||||
|
|
|
|||
|
|
@ -11,9 +11,7 @@
|
|||
"file_formats": "text/x-gcode",
|
||||
"platform": "ultimaker2_platform.obj",
|
||||
"platform_texture": "Ultimaker2Plusbackplate.png",
|
||||
"preferred_variant": "ultimaker2_plus_0.4",
|
||||
"preferred_material": "*pla*",
|
||||
"preferred_quality": "*normal*",
|
||||
"preferred_variant": "*0.4*",
|
||||
"has_variants": true,
|
||||
"has_materials": true,
|
||||
"has_machine_materials": true,
|
||||
|
|
@ -22,6 +20,7 @@
|
|||
},
|
||||
|
||||
"overrides": {
|
||||
"machine_name": { "default_value": "Ultimaker 2+" },
|
||||
"speed_infill": {
|
||||
"value": "speed_print"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -13,13 +13,12 @@
|
|||
"icon": "icon_ultimaker.png",
|
||||
"platform": "ultimaker_platform.stl",
|
||||
"has_materials": true,
|
||||
"preferred_material": "*pla*",
|
||||
"preferred_quality": "*normal*",
|
||||
"first_start_actions": ["UMOUpgradeSelection", "UMOCheckup", "BedLevel"],
|
||||
"supported_actions": ["UMOUpgradeSelection", "UMOCheckup", "BedLevel", "UpgradeFirmware"]
|
||||
},
|
||||
|
||||
"overrides": {
|
||||
"machine_name": { "default_value": "Ultimaker Original" },
|
||||
"machine_width": {
|
||||
"default_value": 205
|
||||
},
|
||||
|
|
|
|||
|
|
@ -13,18 +13,17 @@
|
|||
"icon": "icon_ultimaker.png",
|
||||
"platform": "ultimaker_platform.stl",
|
||||
"has_materials": true,
|
||||
"preferred_material": "*pla*",
|
||||
"preferred_quality": "*normal*",
|
||||
"machine_extruder_trains":
|
||||
{
|
||||
"0": "ultimaker_original_dual_left",
|
||||
"1": "ultimaker_original_dual_right"
|
||||
"0": "ultimaker_original_dual_1st",
|
||||
"1": "ultimaker_original_dual_2nd"
|
||||
},
|
||||
"first_start_actions": ["UMOUpgradeSelection", "UMOCheckup", "BedLevel"],
|
||||
"supported_actions": ["UMOUpgradeSelection", "UMOCheckup", "BedLevel", "UpgradeFirmware"]
|
||||
},
|
||||
|
||||
"overrides": {
|
||||
"machine_name": { "default_value": "Ultimaker Original" },
|
||||
"machine_width": {
|
||||
"default_value": 205
|
||||
},
|
||||
|
|
@ -65,12 +64,22 @@
|
|||
"default_value": "RepRap (Marlin/Sprinter)"
|
||||
},
|
||||
"machine_start_gcode": {
|
||||
"default_value": "G21 ;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 F9000 ;move the platform down 15mm\nG92 E0 ;zero the extruded length\nG1 F200 E6 ;extrude 6 mm of feed stock\nG92 E0 ;zero the extruded length again\nG1 F9000\n;Put printing message on LCD screen\nM117 Printing..."
|
||||
"default_value": "G21 ;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 F9000 ;move the platform down 15mm\nT1 ;Switch to the 2nd extruder\nG92 E0 ;zero the extruded length\nG1 F200 E6 ;extrude 6 mm of feed stock\nG92 E0 ;zero the extruded length again\nG1 F200 E-{switch_extruder_retraction_amount}\nT0 ;Switch to the 1st extruder\nG92 E0 ;zero the extruded length\nG1 F200 E6 ;extrude 6 mm of feed stock\nG92 E0 ;zero the extruded length again\nG1 F9000\n;Put printing message on LCD screen\nM117 Printing..."
|
||||
},
|
||||
"machine_end_gcode": {
|
||||
"default_value": "M104 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 F9000 ;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": "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-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 F9000 ;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"
|
||||
},
|
||||
"machine_extruder_count": { "default_value": 2 },
|
||||
"print_sequence": {"enabled": false}
|
||||
"machine_extruder_count": {
|
||||
"default_value": 2
|
||||
},
|
||||
"print_sequence": {
|
||||
"enabled": false
|
||||
},
|
||||
"prime_tower_position_x": {
|
||||
"default_value": 185
|
||||
},
|
||||
"prime_tower_position_y": {
|
||||
"default_value": 160
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
},
|
||||
|
||||
"overrides": {
|
||||
"machine_name": { "default_value": "Ultimaker Original+" },
|
||||
"machine_heated_bed": {
|
||||
"default_value": true
|
||||
},
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
},
|
||||
|
||||
"overrides": {
|
||||
"machine_name": { "default_value": "Uniqbot" },
|
||||
"machine_heated_bed": {
|
||||
"default_value": false
|
||||
},
|
||||
|
|
|
|||
26
resources/extruders/ultimaker_original_dual_1st.def.json
Normal file
26
resources/extruders/ultimaker_original_dual_1st.def.json
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"id": "ultimaker_original_dual_1st",
|
||||
"version": 2,
|
||||
"name": "1st Extruder",
|
||||
"inherits": "fdmextruder",
|
||||
"metadata": {
|
||||
"machine": "ultimaker_original_dual",
|
||||
"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" }
|
||||
}
|
||||
}
|
||||
26
resources/extruders/ultimaker_original_dual_2nd.def.json
Normal file
26
resources/extruders/ultimaker_original_dual_2nd.def.json
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"id": "ultimaker_original_dual_2nd",
|
||||
"version": 2,
|
||||
"name": "2nd Extruder",
|
||||
"inherits": "fdmextruder",
|
||||
"metadata": {
|
||||
"machine": "ultimaker_original_dual",
|
||||
"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": 21.6 },
|
||||
|
||||
"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" }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
{
|
||||
"id": "ultimaker_original_dual_left",
|
||||
"version": 2,
|
||||
"name": "Left Extruder",
|
||||
"inherits": "fdmextruder",
|
||||
"metadata": {
|
||||
"machine": "ultimaker_original_dual",
|
||||
"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 }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
{
|
||||
"id": "ultimaker_original_dual_right",
|
||||
"version": 2,
|
||||
"name": "Right Extruder",
|
||||
"inherits": "fdmextruder",
|
||||
"metadata": {
|
||||
"machine": "ultimaker_original_dual",
|
||||
"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": 21.6 }
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Generic PLA profile. Serves as an example file, data in this file is not correct.
|
||||
Generic ABS profile. Serves as an example file, data in this file is not correct.
|
||||
-->
|
||||
<fdmmaterial xmlns="http://www.ultimaker.com/material">
|
||||
<metadata>
|
||||
|
|
@ -10,20 +10,21 @@ Generic PLA profile. Serves as an example file, data in this file is not correct
|
|||
<color>Generic</color>
|
||||
</name>
|
||||
<GUID>60636bb4-518f-42e7-8237-fe77b194ebe0</GUID>
|
||||
<version>0</version>
|
||||
<version>1</version>
|
||||
<color_code>#8cb219</color_code>
|
||||
</metadata>
|
||||
<properties>
|
||||
<density>1.07</density>
|
||||
<density>1.10</density>
|
||||
<diameter>2.85</diameter>
|
||||
</properties>
|
||||
<settings>
|
||||
<setting key="print temperature">250</setting>
|
||||
<setting key="print temperature">240</setting>
|
||||
<setting key="heated bed temperature">80</setting>
|
||||
<setting key="standby temperature">200</setting>
|
||||
|
||||
<machine>
|
||||
<machine_identifier manufacturer="Ultimaker" product="Ultimaker2+"/>
|
||||
<machine_identifier manufacturer="Ultimaker" product="Ultimaker2extended+"/>
|
||||
<machine_identifier manufacturer="Ultimaker" product="Ultimaker 2+"/>
|
||||
<machine_identifier manufacturer="Ultimaker" product="Ultimaker 2 Extended+"/>
|
||||
|
||||
<hotend id="0.25 mm" />
|
||||
<hotend id="0.4 mm" />
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Generic PLA profile. Serves as an example file, data in this file is not correct.
|
||||
Generic CPE profile. Serves as an example file, data in this file is not correct.
|
||||
-->
|
||||
<fdmmaterial xmlns="http://www.ultimaker.com/material">
|
||||
<metadata>
|
||||
|
|
@ -10,20 +10,21 @@ Generic PLA profile. Serves as an example file, data in this file is not correct
|
|||
<color>Generic</color>
|
||||
</name>
|
||||
<GUID>12f41353-1a33-415e-8b4f-a775a6c70cc6</GUID>
|
||||
<version>0</version>
|
||||
<version>1</version>
|
||||
<color_code>#159499</color_code>
|
||||
</metadata>
|
||||
<properties>
|
||||
<density>0.94</density>
|
||||
<density>1.27</density>
|
||||
<diameter>2.85</diameter>
|
||||
</properties>
|
||||
<settings>
|
||||
<setting key="print temperature">250</setting>
|
||||
<setting key="heated bed temperature">70</setting>
|
||||
<setting key="standby temperature">175</setting>
|
||||
|
||||
<machine>
|
||||
<machine_identifier manufacturer="Ultimaker" product="Ultimaker2+"/>
|
||||
<machine_identifier manufacturer="Ultimaker" product="Ultimaker2extended+"/>
|
||||
<machine_identifier manufacturer="Ultimaker" product="Ultimaker 2+"/>
|
||||
<machine_identifier manufacturer="Ultimaker" product="Ultimaker 2 Extended+"/>
|
||||
|
||||
<hotend id="0.25 mm" />
|
||||
<hotend id="0.4 mm" />
|
||||
|
|
|
|||
39
resources/materials/generic_cpe_plus.xml.fdm_material
Normal file
39
resources/materials/generic_cpe_plus.xml.fdm_material
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Generic CPE+ profile. Serves as an example file, data in this file is not correct.
|
||||
-->
|
||||
<fdmmaterial xmlns="http://www.ultimaker.com/material">
|
||||
<metadata>
|
||||
<name>
|
||||
<brand>Generic</brand>
|
||||
<material>CPE+</material>
|
||||
<color>Generic</color>
|
||||
</name>
|
||||
<GUID>e2409626-b5a0-4025-b73e-b58070219259</GUID>
|
||||
<version>1</version>
|
||||
<color_code>#3633F2</color_code>
|
||||
</metadata>
|
||||
<properties>
|
||||
<density>1.18</density>
|
||||
<diameter>2.85</diameter>
|
||||
</properties>
|
||||
<settings>
|
||||
<setting key="hardware compatible">no</setting><!-- This material is not supported on most printers due to high temperatures -->
|
||||
<setting key="print temperature">260</setting>
|
||||
<setting key="heated bed temperature">110</setting>
|
||||
<setting key="standby temperature">175</setting>
|
||||
|
||||
<machine>
|
||||
<machine_identifier manufacturer="Ultimaker" product="Ultimaker 2+"/>
|
||||
<machine_identifier manufacturer="Ultimaker" product="Ultimaker 2 Extended+"/>
|
||||
<setting key="hardware compatible">yes</setting>
|
||||
|
||||
<hotend id="0.25 mm">
|
||||
<setting key="hardware compatible">no</setting>
|
||||
</hotend>
|
||||
<hotend id="0.4 mm" />
|
||||
<hotend id="0.6 mm" />
|
||||
<hotend id="0.8 mm" />
|
||||
</machine>
|
||||
</settings>
|
||||
</fdmmaterial>
|
||||
36
resources/materials/generic_nylon.xml.fdm_material
Normal file
36
resources/materials/generic_nylon.xml.fdm_material
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Generic Nylon profile. Serves as an example file, data in this file is not correct.
|
||||
-->
|
||||
<fdmmaterial xmlns="http://www.ultimaker.com/material">
|
||||
<metadata>
|
||||
<name>
|
||||
<brand>Generic</brand>
|
||||
<material>Nylon</material>
|
||||
<color>Generic</color>
|
||||
</name>
|
||||
<GUID>35ebb8df-66d2-41cd-9662-b7d96b9c2cbd</GUID>
|
||||
<version>1</version>
|
||||
<color_code>#3DF266</color_code>
|
||||
</metadata>
|
||||
<properties>
|
||||
<density>1.14</density>
|
||||
<diameter>2.85</diameter>
|
||||
</properties>
|
||||
<settings>
|
||||
<setting key="print temperature">250</setting>
|
||||
<setting key="heated bed temperature">60</setting>
|
||||
<setting key="standby temperature">175</setting>
|
||||
|
||||
<machine>
|
||||
<machine_identifier manufacturer="Ultimaker" product="Ultimaker 2+"/>
|
||||
<machine_identifier manufacturer="Ultimaker" product="Ultimaker 2 Extended+"/>
|
||||
<setting key="hardware compatible">yes</setting>
|
||||
|
||||
<hotend id="0.25 mm" />
|
||||
<hotend id="0.4 mm" />
|
||||
<hotend id="0.6 mm" />
|
||||
<hotend id="0.8 mm" />
|
||||
</machine>
|
||||
</settings>
|
||||
</fdmmaterial>
|
||||
37
resources/materials/generic_pc.xml.fdm_material
Normal file
37
resources/materials/generic_pc.xml.fdm_material
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Generic PC profile. Serves as an example file, data in this file is not correct.
|
||||
-->
|
||||
<fdmmaterial xmlns="http://www.ultimaker.com/material">
|
||||
<metadata>
|
||||
<name>
|
||||
<brand>Generic</brand>
|
||||
<material>PC</material>
|
||||
<color>Generic</color>
|
||||
</name>
|
||||
<GUID>98c05714-bf4e-4455-ba27-57d74fe331e4</GUID>
|
||||
<version>1</version>
|
||||
<color_code>#F29030</color_code>
|
||||
</metadata>
|
||||
<properties>
|
||||
<density>1.19</density>
|
||||
<diameter>2.85</diameter>
|
||||
</properties>
|
||||
<settings>
|
||||
<setting key="hardware compatible">no</setting><!-- This material is not supported on most printers due to high temperatures -->
|
||||
<setting key="print temperature">260</setting>
|
||||
<setting key="heated bed temperature">110</setting>
|
||||
<setting key="standby temperature">175</setting>
|
||||
|
||||
<machine>
|
||||
<machine_identifier manufacturer="Ultimaker" product="Ultimaker 2+"/>
|
||||
<machine_identifier manufacturer="Ultimaker" product="Ultimaker 2 Extended+"/>
|
||||
<setting key="hardware compatible">yes</setting>
|
||||
|
||||
<hotend id="0.25 mm" />
|
||||
<hotend id="0.4 mm" />
|
||||
<hotend id="0.6 mm" />
|
||||
<hotend id="0.8 mm" />
|
||||
</machine>
|
||||
</settings>
|
||||
</fdmmaterial>
|
||||
|
|
@ -10,25 +10,42 @@ Generic PLA profile. Serves as an example file, data in this file is not correct
|
|||
<color>Generic</color>
|
||||
</name>
|
||||
<GUID>506c9f0d-e3aa-4bd4-b2d2-23e2425b1aa9</GUID>
|
||||
<version>0</version>
|
||||
<version>1</version>
|
||||
<color_code>#ffc924</color_code>
|
||||
</metadata>
|
||||
<properties>
|
||||
<density>1.3</density>
|
||||
<density>1.24</density>
|
||||
<diameter>2.85</diameter>
|
||||
</properties>
|
||||
<settings>
|
||||
<setting key="print temperature">210</setting>
|
||||
<setting key="print temperature">200</setting>
|
||||
<setting key="heated bed temperature">60</setting>
|
||||
<setting key="standby temperature">175</setting>
|
||||
|
||||
<machine>
|
||||
<machine_identifier manufacturer="Ultimaker" product="Ultimaker2+"/>
|
||||
<machine_identifier manufacturer="Ultimaker" product="Ultimaker2extended+"/>
|
||||
<machine_identifier manufacturer="Ultimaker" product="Ultimaker 2+"/>
|
||||
<machine_identifier manufacturer="Ultimaker" product="Ultimaker 2 Extended+"/>
|
||||
|
||||
<hotend id="0.25 mm" />
|
||||
<hotend id="0.4 mm" />
|
||||
<hotend id="0.6 mm" />
|
||||
<hotend id="0.8 mm" />
|
||||
</machine>
|
||||
|
||||
<machine>
|
||||
<machine_identifier manufacturer="Ultimaker" product="Ultimaker 2"/>
|
||||
<machine_identifier manufacturer="Ultimaker" product="Ultimaker 2 Go"/>
|
||||
<machine_identifier manufacturer="Ultimaker" product="Ultimaker 2 Extended"/>
|
||||
<setting key="standby temperature">150</setting>
|
||||
<setting key="processing temperature graph">
|
||||
<point flow="2" temperature="180"/>
|
||||
<point flow="10" temperature="230"/>
|
||||
</setting>
|
||||
</machine>
|
||||
|
||||
<machine>
|
||||
<machine_identifier manufacturer="Ultimaker" product="Ultimaker Original"/>
|
||||
<setting key="standby temperature">150</setting>
|
||||
</machine>
|
||||
</settings>
|
||||
</fdmmaterial>
|
||||
|
|
|
|||
39
resources/materials/generic_tpu.xml.fdm_material
Normal file
39
resources/materials/generic_tpu.xml.fdm_material
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Generic TPU 95A profile. Serves as an example file, data in this file is not correct.
|
||||
-->
|
||||
<fdmmaterial xmlns="http://www.ultimaker.com/material">
|
||||
<metadata>
|
||||
<name>
|
||||
<brand>Generic</brand>
|
||||
<material>TPU 95A</material>
|
||||
<color>Generic</color>
|
||||
</name>
|
||||
<GUID>1d52b2be-a3a2-41de-a8b1-3bcdb5618695</GUID>
|
||||
<version>1</version>
|
||||
<color_code>#B22744</color_code>
|
||||
</metadata>
|
||||
<properties>
|
||||
<density>1.22</density>
|
||||
<diameter>2.85</diameter>
|
||||
</properties>
|
||||
<settings>
|
||||
<setting key="hardware compatible">no</setting><!-- This material is not supported on most printers due to high temperatures -->
|
||||
<setting key="print temperature">260</setting>
|
||||
<setting key="heated bed temperature">110</setting>
|
||||
<setting key="standby temperature">175</setting>
|
||||
|
||||
<machine>
|
||||
<machine_identifier manufacturer="Ultimaker" product="Ultimaker 2+"/>
|
||||
<machine_identifier manufacturer="Ultimaker" product="Ultimaker 2 Extended+"/>
|
||||
<setting key="hardware compatible">yes</setting>
|
||||
|
||||
<hotend id="0.25 mm" />
|
||||
<hotend id="0.4 mm" />
|
||||
<hotend id="0.6 mm" />
|
||||
<hotend id="0.8 mm">
|
||||
<setting key="hardware compatible">no</setting>
|
||||
</hotend>
|
||||
</machine>
|
||||
</settings>
|
||||
</fdmmaterial>
|
||||
35
resources/materials/ultimaker_abs_black.xml.fdm_material
Normal file
35
resources/materials/ultimaker_abs_black.xml.fdm_material
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Automatically generated ABS profile. Data in this file may not be not correct.
|
||||
-->
|
||||
<fdmmaterial xmlns="http://www.ultimaker.com/material">
|
||||
<metadata>
|
||||
<name>
|
||||
<brand>Ultimaker</brand>
|
||||
<material>ABS</material>
|
||||
<color>Black</color>
|
||||
</name>
|
||||
<GUID>2f9d2279-9b0e-4765-bf9b-d1e1e13f3c49</GUID>
|
||||
<version>0</version>
|
||||
<color_code>#2a292a</color_code>
|
||||
</metadata>
|
||||
<properties>
|
||||
<density>1.10</density>
|
||||
<diameter>2.85</diameter>
|
||||
</properties>
|
||||
<settings>
|
||||
<setting key="print temperature">240</setting>
|
||||
<setting key="heated bed temperature">80</setting>
|
||||
<setting key="standby temperature">200</setting>
|
||||
|
||||
<machine>
|
||||
<machine_identifier manufacturer="Ultimaker" product="Ultimaker 2+"/>
|
||||
<machine_identifier manufacturer="Ultimaker" product="Ultimaker 2 Extended+"/>
|
||||
|
||||
<hotend id="0.25 mm" />
|
||||
<hotend id="0.4 mm" />
|
||||
<hotend id="0.6 mm" />
|
||||
<hotend id="0.8 mm" />
|
||||
</machine>
|
||||
</settings>
|
||||
</fdmmaterial>
|
||||
35
resources/materials/ultimaker_abs_blue.xml.fdm_material
Normal file
35
resources/materials/ultimaker_abs_blue.xml.fdm_material
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Automatically generated ABS profile. Data in this file may not be not correct.
|
||||
-->
|
||||
<fdmmaterial xmlns="http://www.ultimaker.com/material">
|
||||
<metadata>
|
||||
<name>
|
||||
<brand>Ultimaker</brand>
|
||||
<material>ABS</material>
|
||||
<color>Blue</color>
|
||||
</name>
|
||||
<GUID>7c9575a6-c8d6-40ec-b3dd-18d7956bfaae</GUID>
|
||||
<version>0</version>
|
||||
<color_code>#00387b</color_code>
|
||||
</metadata>
|
||||
<properties>
|
||||
<density>1.10</density>
|
||||
<diameter>2.85</diameter>
|
||||
</properties>
|
||||
<settings>
|
||||
<setting key="print temperature">240</setting>
|
||||
<setting key="heated bed temperature">80</setting>
|
||||
<setting key="standby temperature">200</setting>
|
||||
|
||||
<machine>
|
||||
<machine_identifier manufacturer="Ultimaker" product="Ultimaker 2+"/>
|
||||
<machine_identifier manufacturer="Ultimaker" product="Ultimaker 2 Extended+"/>
|
||||
|
||||
<hotend id="0.25 mm" />
|
||||
<hotend id="0.4 mm" />
|
||||
<hotend id="0.6 mm" />
|
||||
<hotend id="0.8 mm" />
|
||||
</machine>
|
||||
</settings>
|
||||
</fdmmaterial>
|
||||
35
resources/materials/ultimaker_abs_green.xml.fdm_material
Normal file
35
resources/materials/ultimaker_abs_green.xml.fdm_material
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Automatically generated ABS profile. Data in this file may not be not correct.
|
||||
-->
|
||||
<fdmmaterial xmlns="http://www.ultimaker.com/material">
|
||||
<metadata>
|
||||
<name>
|
||||
<brand>Ultimaker</brand>
|
||||
<material>ABS</material>
|
||||
<color>Green</color>
|
||||
</name>
|
||||
<GUID>3400c0d1-a4e3-47de-a444-7b704f287171</GUID>
|
||||
<version>0</version>
|
||||
<color_code>#61993b</color_code>
|
||||
</metadata>
|
||||
<properties>
|
||||
<density>1.10</density>
|
||||
<diameter>2.85</diameter>
|
||||
</properties>
|
||||
<settings>
|
||||
<setting key="print temperature">240</setting>
|
||||
<setting key="heated bed temperature">80</setting>
|
||||
<setting key="standby temperature">200</setting>
|
||||
|
||||
<machine>
|
||||
<machine_identifier manufacturer="Ultimaker" product="Ultimaker 2+"/>
|
||||
<machine_identifier manufacturer="Ultimaker" product="Ultimaker 2 Extended+"/>
|
||||
|
||||
<hotend id="0.25 mm" />
|
||||
<hotend id="0.4 mm" />
|
||||
<hotend id="0.6 mm" />
|
||||
<hotend id="0.8 mm" />
|
||||
</machine>
|
||||
</settings>
|
||||
</fdmmaterial>
|
||||
35
resources/materials/ultimaker_abs_grey.xml.fdm_material
Normal file
35
resources/materials/ultimaker_abs_grey.xml.fdm_material
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Automatically generated ABS profile. Data in this file may not be not correct.
|
||||
-->
|
||||
<fdmmaterial xmlns="http://www.ultimaker.com/material">
|
||||
<metadata>
|
||||
<name>
|
||||
<brand>Ultimaker</brand>
|
||||
<material>ABS</material>
|
||||
<color>Grey</color>
|
||||
</name>
|
||||
<GUID>8b75b775-d3f2-4d0f-8fb2-2a3dd53cf673</GUID>
|
||||
<version>0</version>
|
||||
<color_code>#52595d</color_code>
|
||||
</metadata>
|
||||
<properties>
|
||||
<density>1.10</density>
|
||||
<diameter>2.85</diameter>
|
||||
</properties>
|
||||
<settings>
|
||||
<setting key="print temperature">240</setting>
|
||||
<setting key="heated bed temperature">80</setting>
|
||||
<setting key="standby temperature">200</setting>
|
||||
|
||||
<machine>
|
||||
<machine_identifier manufacturer="Ultimaker" product="Ultimaker 2+"/>
|
||||
<machine_identifier manufacturer="Ultimaker" product="Ultimaker 2 Extended+"/>
|
||||
|
||||
<hotend id="0.25 mm" />
|
||||
<hotend id="0.4 mm" />
|
||||
<hotend id="0.6 mm" />
|
||||
<hotend id="0.8 mm" />
|
||||
</machine>
|
||||
</settings>
|
||||
</fdmmaterial>
|
||||
35
resources/materials/ultimaker_abs_orange.xml.fdm_material
Normal file
35
resources/materials/ultimaker_abs_orange.xml.fdm_material
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Automatically generated ABS profile. Data in this file may not be not correct.
|
||||
-->
|
||||
<fdmmaterial xmlns="http://www.ultimaker.com/material">
|
||||
<metadata>
|
||||
<name>
|
||||
<brand>Ultimaker</brand>
|
||||
<material>ABS</material>
|
||||
<color>Orange</color>
|
||||
</name>
|
||||
<GUID>0b4ca6ef-eac8-4b23-b3ca-5f21af00e54f</GUID>
|
||||
<version>0</version>
|
||||
<color_code>#ed6b21</color_code>
|
||||
</metadata>
|
||||
<properties>
|
||||
<density>1.10</density>
|
||||
<diameter>2.85</diameter>
|
||||
</properties>
|
||||
<settings>
|
||||
<setting key="print temperature">240</setting>
|
||||
<setting key="heated bed temperature">80</setting>
|
||||
<setting key="standby temperature">200</setting>
|
||||
|
||||
<machine>
|
||||
<machine_identifier manufacturer="Ultimaker" product="Ultimaker 2+"/>
|
||||
<machine_identifier manufacturer="Ultimaker" product="Ultimaker 2 Extended+"/>
|
||||
|
||||
<hotend id="0.25 mm" />
|
||||
<hotend id="0.4 mm" />
|
||||
<hotend id="0.6 mm" />
|
||||
<hotend id="0.8 mm" />
|
||||
</machine>
|
||||
</settings>
|
||||
</fdmmaterial>
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue