Merge branch 'master' into feature_support_top_bottom_speed

This commit is contained in:
Ghostkeeper 2017-04-10 10:52:31 +02:00
commit cb7c25f92f
No known key found for this signature in database
GPG key ID: C5F96EE2BC0F7E75
180 changed files with 67911 additions and 67729 deletions

View file

@ -22,6 +22,9 @@ 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)
if(NOT ${URANIUM_DIR} STREQUAL "")
set(CMAKE_MODULE_PATH "${URANIUM_DIR}/cmake")
endif()
if(NOT ${URANIUM_SCRIPTS_DIR} STREQUAL "")
list(APPEND CMAKE_MODULE_PATH ${URANIUM_DIR}/cmake)
include(UraniumTranslationTools)
@ -64,5 +67,3 @@ else()
install(FILES ${CMAKE_BINARY_DIR}/CuraVersion.py
DESTINATION lib/python${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR}/site-packages/cura)
endif()
include(CPackConfig.cmake)

View file

@ -1,16 +0,0 @@
set(CPACK_PACKAGE_VENDOR "Ultimaker B.V.")
set(CPACK_PACKAGE_CONTACT "Arjen Hiemstra <a.hiemstra@ultimaker.com>")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Cura application to drive the CuraEngine")
set(CPACK_PACKAGE_VERSION_MAJOR 15)
set(CPACK_PACKAGE_VERSION_MINOR 05)
set(CPACK_PACKAGE_VERSION_PATCH 90)
set(CPACK_PACKAGE_VERSION_REVISION 1)
set(CPACK_GENERATOR "DEB")
set(DEB_DEPENDS
"uranium (>= 15.05.93)"
)
string(REPLACE ";" ", " DEB_DEPENDS "${DEB_DEPENDS}")
set(CPACK_DEBIAN_PACKAGE_DEPENDS ${DEB_DEPENDS})
include(CPack)

188
cura/Arrange.py Executable file
View file

@ -0,0 +1,188 @@
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Logger import Logger
from UM.Math.Vector import Vector
from cura.ShapeArray import ShapeArray
from cura import ZOffsetDecorator
from collections import namedtuple
import numpy
import copy
## Return object for bestSpot
LocationSuggestion = namedtuple("LocationSuggestion", ["x", "y", "penalty_points", "priority"])
## The Arrange classed is used together with ShapeArray. Use it to find
# good locations for objects that you try to put on a build place.
# Different priority schemes can be defined so it alters the behavior while using
# the same logic.
class Arrange:
build_volume = None
def __init__(self, x, y, offset_x, offset_y, scale= 1.0):
self.shape = (y, x)
self._priority = numpy.zeros((x, y), dtype=numpy.int32)
self._priority_unique_values = []
self._occupied = numpy.zeros((x, y), dtype=numpy.int32)
self._scale = scale # convert input coordinates to arrange coordinates
self._offset_x = offset_x
self._offset_y = offset_y
self._last_priority = 0
## Helper to create an Arranger instance
#
# Either fill in scene_root and create will find all sliceable nodes by itself,
# or use fixed_nodes to provide the nodes yourself.
# \param scene_root Root for finding all scene nodes
# \param fixed_nodes Scene nodes to be placed
@classmethod
def create(cls, scene_root = None, fixed_nodes = None, scale = 0.5):
arranger = Arrange(220, 220, 110, 110, scale = scale)
arranger.centerFirst()
if fixed_nodes is None:
fixed_nodes = []
for node_ in DepthFirstIterator(scene_root):
# Only count sliceable objects
if node_.callDecoration("isSliceable"):
fixed_nodes.append(node_)
# Place all objects fixed nodes
for fixed_node in fixed_nodes:
vertices = fixed_node.callDecoration("getConvexHull")
points = copy.deepcopy(vertices._points)
shape_arr = ShapeArray.fromPolygon(points, scale = scale)
arranger.place(0, 0, shape_arr)
# If a build volume was set, add the disallowed areas
if Arrange.build_volume:
disallowed_areas = Arrange.build_volume.getDisallowedAreas()
for area in disallowed_areas:
points = copy.deepcopy(area._points)
shape_arr = ShapeArray.fromPolygon(points, scale = scale)
arranger.place(0, 0, shape_arr)
return arranger
## Find placement for a node (using offset shape) and place it (using hull shape)
# return the nodes that should be placed
# \param node
# \param offset_shape_arr ShapeArray with offset, used to find location
# \param hull_shape_arr ShapeArray without offset, for placing the shape
def findNodePlacement(self, node, offset_shape_arr, hull_shape_arr, step = 1):
new_node = copy.deepcopy(node)
best_spot = self.bestSpot(
offset_shape_arr, start_prio = self._last_priority, step = step)
x, y = best_spot.x, best_spot.y
# Save the last priority.
self._last_priority = best_spot.priority
# Ensure that the object is above the build platform
new_node.removeDecorator(ZOffsetDecorator.ZOffsetDecorator)
if new_node.getBoundingBox():
center_y = new_node.getWorldPosition().y - new_node.getBoundingBox().bottom
else:
center_y = 0
if x is not None: # We could find a place
new_node.setPosition(Vector(x, center_y, y))
found_spot = True
self.place(x, y, hull_shape_arr) # place the object in arranger
else:
Logger.log("d", "Could not find spot!"),
found_spot = False
new_node.setPosition(Vector(200, center_y, 100))
return new_node, found_spot
## Fill priority, center is best. Lower value is better
# This is a strategy for the arranger.
def centerFirst(self):
# Square distance: creates a more round shape
self._priority = numpy.fromfunction(
lambda i, j: (self._offset_x - i) ** 2 + (self._offset_y - j) ** 2, self.shape, dtype=numpy.int32)
self._priority_unique_values = numpy.unique(self._priority)
self._priority_unique_values.sort()
## Fill priority, back is best. Lower value is better
# This is a strategy for the arranger.
def backFirst(self):
self._priority = numpy.fromfunction(
lambda i, j: 10 * j + abs(self._offset_x - i), self.shape, dtype=numpy.int32)
self._priority_unique_values = numpy.unique(self._priority)
self._priority_unique_values.sort()
## Return the amount of "penalty points" for polygon, which is the sum of priority
# 999999 if occupied
# \param x x-coordinate to check shape
# \param y y-coordinate
# \param shape_arr the ShapeArray object to place
def checkShape(self, x, y, shape_arr):
x = int(self._scale * x)
y = int(self._scale * y)
offset_x = x + self._offset_x + shape_arr.offset_x
offset_y = y + self._offset_y + shape_arr.offset_y
occupied_slice = self._occupied[
offset_y:offset_y + shape_arr.arr.shape[0],
offset_x:offset_x + shape_arr.arr.shape[1]]
try:
if numpy.any(occupied_slice[numpy.where(shape_arr.arr == 1)]):
return 999999
except IndexError: # out of bounds if you try to place an object outside
return 999999
prio_slice = self._priority[
offset_y:offset_y + shape_arr.arr.shape[0],
offset_x:offset_x + shape_arr.arr.shape[1]]
return numpy.sum(prio_slice[numpy.where(shape_arr.arr == 1)])
## Find "best" spot for ShapeArray
# Return namedtuple with properties x, y, penalty_points, priority
# \param shape_arr ShapeArray
# \param start_prio Start with this priority value (and skip the ones before)
# \param step Slicing value, higher = more skips = faster but less accurate
def bestSpot(self, shape_arr, start_prio = 0, step = 1):
start_idx_list = numpy.where(self._priority_unique_values == start_prio)
if start_idx_list:
start_idx = start_idx_list[0][0]
else:
start_idx = 0
for priority in self._priority_unique_values[start_idx::step]:
tryout_idx = numpy.where(self._priority == priority)
for idx in range(len(tryout_idx[0])):
x = tryout_idx[0][idx]
y = tryout_idx[1][idx]
projected_x = x - self._offset_x
projected_y = y - self._offset_y
# array to "world" coordinates
penalty_points = self.checkShape(projected_x, projected_y, shape_arr)
if penalty_points != 999999:
return LocationSuggestion(x = projected_x, y = projected_y, penalty_points = penalty_points, priority = priority)
return LocationSuggestion(x = None, y = None, penalty_points = None, priority = priority) # No suitable location found :-(
## Place the object.
# Marks the locations in self._occupied and self._priority
# \param x x-coordinate
# \param y y-coordinate
# \param shape_arr ShapeArray object
def place(self, x, y, shape_arr):
x = int(self._scale * x)
y = int(self._scale * y)
offset_x = x + self._offset_x + shape_arr.offset_x
offset_y = y + self._offset_y + shape_arr.offset_y
shape_y, shape_x = self._occupied.shape
min_x = min(max(offset_x, 0), shape_x - 1)
min_y = min(max(offset_y, 0), shape_y - 1)
max_x = min(max(offset_x + shape_arr.arr.shape[1], 0), shape_x - 1)
max_y = min(max(offset_y + shape_arr.arr.shape[0], 0), shape_y - 1)
occupied_slice = self._occupied[min_y:max_y, min_x:max_x]
# we use a slice of shape because it can be out of bounds
occupied_slice[numpy.where(shape_arr.arr[
min_y - offset_y:max_y - offset_y, min_x - offset_x:max_x - offset_x] == 1)] = 1
# Set priority to low (= high number), so it won't get picked at trying out.
prio_slice = self._priority[min_y:max_y, min_x:max_x]
prio_slice[numpy.where(shape_arr.arr[
min_y - offset_y:max_y - offset_y, min_x - offset_x:max_x - offset_x] == 1)] = 999

86
cura/ArrangeObjectsJob.py Executable file
View file

@ -0,0 +1,86 @@
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the AGPLv3 or higher.
from UM.Job import Job
from UM.Scene.SceneNode import SceneNode
from UM.Math.Vector import Vector
from UM.Operations.SetTransformOperation import SetTransformOperation
from UM.Operations.TranslateOperation import TranslateOperation
from UM.Operations.GroupedOperation import GroupedOperation
from UM.Logger import Logger
from UM.Message import Message
from UM.i18n import i18nCatalog
i18n_catalog = i18nCatalog("cura")
from cura.ZOffsetDecorator import ZOffsetDecorator
from cura.Arrange import Arrange
from cura.ShapeArray import ShapeArray
from typing import List
class ArrangeObjectsJob(Job):
def __init__(self, nodes: List[SceneNode], fixed_nodes: List[SceneNode], min_offset = 8):
super().__init__()
self._nodes = nodes
self._fixed_nodes = fixed_nodes
self._min_offset = min_offset
def run(self):
status_message = Message(i18n_catalog.i18nc("@info:status", "Finding new location for objects"), lifetime = 0, dismissable=False, progress = 0)
status_message.show()
arranger = Arrange.create(fixed_nodes = self._fixed_nodes)
# Collect nodes to be placed
nodes_arr = [] # fill with (size, node, offset_shape_arr, hull_shape_arr)
for node in self._nodes:
offset_shape_arr, hull_shape_arr = ShapeArray.fromNode(node, min_offset = self._min_offset)
nodes_arr.append((offset_shape_arr.arr.shape[0] * offset_shape_arr.arr.shape[1], node, offset_shape_arr, hull_shape_arr))
# Sort the nodes with the biggest area first.
nodes_arr.sort(key=lambda item: item[0])
nodes_arr.reverse()
# Place nodes one at a time
start_priority = 0
last_priority = start_priority
last_size = None
grouped_operation = GroupedOperation()
found_solution_for_all = True
for idx, (size, node, offset_shape_arr, hull_shape_arr) in enumerate(nodes_arr):
# For performance reasons, we assume that when a location does not fit,
# it will also not fit for the next object (while what can be untrue).
# We also skip possibilities by slicing through the possibilities (step = 10)
if last_size == size: # This optimization works if many of the objects have the same size
start_priority = last_priority
else:
start_priority = 0
best_spot = arranger.bestSpot(offset_shape_arr, start_prio=start_priority, step=10)
x, y = best_spot.x, best_spot.y
node.removeDecorator(ZOffsetDecorator)
if node.getBoundingBox():
center_y = node.getWorldPosition().y - node.getBoundingBox().bottom
else:
center_y = 0
if x is not None: # We could find a place
last_size = size
last_priority = best_spot.priority
arranger.place(x, y, hull_shape_arr) # take place before the next one
grouped_operation.addOperation(TranslateOperation(node, Vector(x, center_y, y), set_position = True))
else:
Logger.log("d", "Arrange all: could not find spot!")
found_solution_for_all = False
grouped_operation.addOperation(TranslateOperation(node, Vector(200, center_y, - idx * 20), set_position = True))
status_message.setProgress((idx + 1) / len(nodes_arr) * 100)
Job.yieldThread()
grouped_operation.push()
status_message.hide()
if not found_solution_for_all:
no_full_solution_message = Message(i18n_catalog.i18nc("@info:status", "Unable to find a location within the build volume for all objects"))
no_full_solution_message.show()

View file

@ -23,7 +23,6 @@ from UM.View.GL.OpenGL import OpenGL
catalog = i18nCatalog("cura")
import numpy
import copy
import math
# Setting for clearance around the prime
@ -110,10 +109,11 @@ class BuildVolume(SceneNode):
def _onChangeTimerFinished(self):
root = Application.getInstance().getController().getScene().getRoot()
new_scene_objects = set(node for node in BreadthFirstIterator(root) if node.getMeshData() and type(node) is SceneNode)
new_scene_objects = set(node for node in BreadthFirstIterator(root) if node.callDecoration("isSliceable"))
if new_scene_objects != self._scene_objects:
for node in new_scene_objects - self._scene_objects: #Nodes that were added to the scene.
node.decoratorsChanged.connect(self._onNodeDecoratorChanged)
self._updateNodeListeners(node)
node.decoratorsChanged.connect(self._updateNodeListeners) # Make sure that decoration changes afterwards also receive the same treatment
for node in self._scene_objects - new_scene_objects: #Nodes that were removed from the scene.
per_mesh_stack = node.callDecoration("getStack")
if per_mesh_stack:
@ -121,7 +121,7 @@ class BuildVolume(SceneNode):
active_extruder_changed = node.callDecoration("getActiveExtruderChangedSignal")
if active_extruder_changed is not None:
node.callDecoration("getActiveExtruderChangedSignal").disconnect(self._updateDisallowedAreasAndRebuild)
node.decoratorsChanged.disconnect(self._onNodeDecoratorChanged)
node.decoratorsChanged.disconnect(self._updateNodeListeners)
self._scene_objects = new_scene_objects
self._onSettingPropertyChanged("print_sequence", "value") # Create fake event, so right settings are triggered.
@ -129,7 +129,7 @@ class BuildVolume(SceneNode):
## Updates the listeners that listen for changes in per-mesh stacks.
#
# \param node The node for which the decorators changed.
def _onNodeDecoratorChanged(self, node):
def _updateNodeListeners(self, node):
per_mesh_stack = node.callDecoration("getStack")
if per_mesh_stack:
per_mesh_stack.propertyChanged.connect(self._onSettingPropertyChanged)
@ -179,6 +179,54 @@ class BuildVolume(SceneNode):
return True
## For every sliceable node, update node._outside_buildarea
#
def updateNodeBoundaryCheck(self):
root = Application.getInstance().getController().getScene().getRoot()
nodes = list(BreadthFirstIterator(root))
group_nodes = []
build_volume_bounding_box = self.getBoundingBox()
if build_volume_bounding_box:
# It's over 9000!
build_volume_bounding_box = build_volume_bounding_box.set(bottom=-9001)
else:
# No bounding box. This is triggered when running Cura from command line with a model for the first time
# In that situation there is a model, but no machine (and therefore no build volume.
return
for node in nodes:
# Need to check group nodes later
if node.callDecoration("isGroup"):
group_nodes.append(node) # Keep list of affected group_nodes
if node.callDecoration("isSliceable") or node.callDecoration("isGroup"):
node._outside_buildarea = False
bbox = node.getBoundingBox()
# Mark the node as outside the build volume if the bounding box test fails.
if build_volume_bounding_box.intersectsBox(bbox) != AxisAlignedBox.IntersectionResult.FullIntersection:
node._outside_buildarea = True
continue
convex_hull = node.callDecoration("getConvexHull")
if convex_hull:
if not convex_hull.isValid():
return
# Check for collisions between disallowed areas and the object
for area in self.getDisallowedAreas():
overlap = convex_hull.intersectsPolygon(area)
if overlap is None:
continue
node._outside_buildarea = True
continue
# 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
## Recalculates the build volume & disallowed areas.
def rebuild(self):
if not self._width or not self._height or not self._depth:
@ -362,6 +410,8 @@ class BuildVolume(SceneNode):
Application.getInstance().getController().getScene()._maximum_bounds = scale_to_max_bounds
self.updateNodeBoundaryCheck()
def getBoundingBox(self):
return self._volume_aabb
@ -629,11 +679,12 @@ class BuildVolume(SceneNode):
if not self._global_container_stack.getProperty("machine_center_is_zero", "value"):
prime_x = prime_x - machine_width / 2 #Offset by half machine_width and _depth to put the origin in the front-left.
prime_y = prime_x + machine_depth / 2
prime_y = prime_y + machine_depth / 2
prime_polygon = Polygon.approximatedCircle(PRIME_CLEARANCE)
prime_polygon = prime_polygon.translate(prime_x, prime_y)
prime_polygon = prime_polygon.getMinkowskiHull(Polygon.approximatedCircle(border_size))
prime_polygon = prime_polygon.translate(prime_x, prime_y)
result[extruder.getId()] = [prime_polygon]
return result

View file

@ -258,12 +258,16 @@ class ConvexHullDecorator(SceneNodeDecorator):
# influences the collision area.
def _offsetHull(self, convex_hull):
horizontal_expansion = self._getSettingProperty("xy_offset", "value")
if horizontal_expansion != 0:
mold_width = 0
if self._getSettingProperty("mold_enabled", "value"):
mold_width = self._getSettingProperty("mold_width", "value")
hull_offset = horizontal_expansion + mold_width
if hull_offset != 0:
expansion_polygon = Polygon(numpy.array([
[-horizontal_expansion, -horizontal_expansion],
[-horizontal_expansion, horizontal_expansion],
[horizontal_expansion, horizontal_expansion],
[horizontal_expansion, -horizontal_expansion]
[-hull_offset, -hull_offset],
[-hull_offset, hull_offset],
[hull_offset, hull_offset],
[hull_offset, -hull_offset]
], numpy.float32))
return convex_hull.getMinkowskiHull(expansion_polygon)
else:
@ -331,4 +335,4 @@ class ConvexHullDecorator(SceneNodeDecorator):
## Settings that change the convex hull.
#
# If these settings change, the convex hull should be recalculated.
_influencing_settings = {"xy_offset"}
_influencing_settings = {"xy_offset", "mold_enabled", "mold_width"}

View file

@ -9,7 +9,10 @@ from UM.Mesh.MeshBuilder import MeshBuilder # To create a mesh to display the c
from UM.View.GL.OpenGL import OpenGL
class ConvexHullNode(SceneNode):
shader = None # To prevent the shader from being re-built over and over again, only load it once.
## Convex hull node is a special type of scene node that is used to display an area, to indicate the
# location an object uses on the buildplate. This area (or area's in case of one at a time printing) is
# then displayed as a transparent shadow. If the adhesion type is set to raft, the area is extruded
@ -19,8 +22,6 @@ class ConvexHullNode(SceneNode):
self.setCalculateBoundingBox(False)
self._shader = None
self._original_parent = parent
# Color of the drawn convex hull
@ -59,16 +60,16 @@ class ConvexHullNode(SceneNode):
return self._node
def render(self, renderer):
if not self._shader:
self._shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "transparent_object.shader"))
self._shader.setUniformValue("u_diffuseColor", self._color)
self._shader.setUniformValue("u_opacity", 0.6)
if not ConvexHullNode.shader:
ConvexHullNode.shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "transparent_object.shader"))
ConvexHullNode.shader.setUniformValue("u_diffuseColor", self._color)
ConvexHullNode.shader.setUniformValue("u_opacity", 0.6)
if self.getParent():
if self.getMeshData():
renderer.queueNode(self, transparent = True, shader = self._shader, backface_cull = True, sort = -8)
renderer.queueNode(self, transparent = True, shader = ConvexHullNode.shader, backface_cull = True, sort = -8)
if self._convex_hull_head_mesh:
renderer.queueNode(self, shader = self._shader, transparent = True, mesh = self._convex_hull_head_mesh, backface_cull = True, sort = -8)
renderer.queueNode(self, shader = ConvexHullNode.shader, transparent = True, mesh = self._convex_hull_head_mesh, backface_cull = True, sort = -8)
return True

View file

@ -16,7 +16,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.JobQueue import JobQueue
from UM.SaveFile import SaveFile
from UM.Scene.Selection import Selection
from UM.Scene.GroupDecorator import GroupDecorator
@ -25,15 +24,23 @@ from UM.Settings.InstanceContainer import InstanceContainer
from UM.Settings.Validator import Validator
from UM.Message import Message
from UM.i18n import i18nCatalog
from UM.Workspace.WorkspaceReader import WorkspaceReader
from UM.Platform import Platform
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 cura.Arrange import Arrange
from cura.ShapeArray import ShapeArray
from cura.ConvexHullDecorator import ConvexHullDecorator
from cura.SetParentOperation import SetParentOperation
from cura.SliceableObjectDecorator import SliceableObjectDecorator
from cura.BlockSlicingDecorator import BlockSlicingDecorator
from cura.ArrangeObjectsJob import ArrangeObjectsJob
from cura.MultiplyObjectsJob import MultiplyObjectsJob
from UM.Settings.SettingDefinition import SettingDefinition, DefinitionPropertyType
from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.Settings.SettingFunction import SettingFunction
@ -87,6 +94,7 @@ if not MYPY:
CuraVersion = "master" # [CodeStyle: Reflecting imported value]
CuraBuildType = ""
class CuraApplication(QtApplication):
class ResourceTypes:
QmlFiles = Resources.UserType + 1
@ -101,7 +109,6 @@ class CuraApplication(QtApplication):
Q_ENUMS(ResourceTypes)
def __init__(self):
Resources.addSearchPath(os.path.join(QtApplication.getInstallPrefix(), "share", "cura", "resources"))
if not hasattr(sys, "frozen"):
Resources.addSearchPath(os.path.join(os.path.abspath(os.path.dirname(__file__)), "..", "resources"))
@ -181,7 +188,10 @@ class CuraApplication(QtApplication):
"SelectionTool",
"CameraTool",
"GCodeWriter",
"LocalFileOutputDevice"
"LocalFileOutputDevice",
"TranslateTool",
"FileLogger",
"XmlMaterialProfile"
])
self._physics = None
self._volume = None
@ -237,7 +247,7 @@ class CuraApplication(QtApplication):
ContainerRegistry.getInstance().load()
Preferences.getInstance().addPreference("cura/active_mode", "simple")
Preferences.getInstance().addPreference("cura/recent_files", "")
Preferences.getInstance().addPreference("cura/categories_expanded", "")
Preferences.getInstance().addPreference("cura/jobname_prefix", True)
Preferences.getInstance().addPreference("view/center_on_select", False)
@ -245,11 +255,14 @@ class CuraApplication(QtApplication):
Preferences.getInstance().addPreference("mesh/scale_tiny_meshes", True)
Preferences.getInstance().addPreference("cura/dialog_on_project_save", True)
Preferences.getInstance().addPreference("cura/asked_dialog_on_project_save", False)
Preferences.getInstance().addPreference("cura/choice_on_profile_override", 0)
Preferences.getInstance().addPreference("cura/choice_on_profile_override", "always_ask")
Preferences.getInstance().addPreference("cura/choice_on_open_project", "always_ask")
Preferences.getInstance().addPreference("cura/currency", "")
Preferences.getInstance().addPreference("cura/material_settings", "{}")
Preferences.getInstance().addPreference("view/invert_zoom", False)
for key in [
"dialog_load_path", # dialog_save_path is in LocalFileOutputDevicePlugin
"dialog_profile_path",
@ -310,20 +323,11 @@ class CuraApplication(QtApplication):
experimental
""".replace("\n", ";").replace(" ", ""))
JobQueue.getInstance().jobFinished.connect(self._onJobFinished)
self.applicationShuttingDown.connect(self.saveSettings)
self.engineCreatedSignal.connect(self._onEngineCreated)
self.globalContainerStackChanged.connect(self._onGlobalContainerChanged)
self._onGlobalContainerChanged()
self._recent_files = []
files = Preferences.getInstance().getValue("cura/recent_files").split(";")
for f in files:
if not os.path.isfile(f):
continue
self._recent_files.append(QUrl.fromLocalFile(f))
def _onEngineCreated(self):
self._engine.addImageProvider("camera", CameraImageProvider.CameraImageProvider())
@ -341,10 +345,10 @@ class CuraApplication(QtApplication):
def discardOrKeepProfileChanges(self):
choice = Preferences.getInstance().getValue("cura/choice_on_profile_override")
if choice == 1:
if choice == "always_discard":
# don't show dialog and DISCARD the profile
self.discardOrKeepProfileChangesClosed("discard")
elif choice == 2:
elif choice == "always_keep":
# don't show dialog and KEEP the profile
self.discardOrKeepProfileChangesClosed("keep")
else:
@ -588,6 +592,9 @@ class CuraApplication(QtApplication):
# The platform is a child of BuildVolume
self._volume = BuildVolume.BuildVolume(root)
# Set the build volume of the arranger to the used build volume
Arrange.build_volume = self._volume
self.getRenderer().setBackgroundColor(QColor(245, 245, 245))
self._physics = PlatformPhysics.PlatformPhysics(controller, self._volume)
@ -662,6 +669,7 @@ class CuraApplication(QtApplication):
#
# \param engine The QML engine.
def registerObjects(self, engine):
super().registerObjects(engine)
engine.rootContext().setContextProperty("Printer", self)
engine.rootContext().setContextProperty("CuraApplication", self)
self._print_information = PrintInformation.PrintInformation()
@ -695,14 +703,11 @@ class CuraApplication(QtApplication):
if type_name in ("Cura", "Actions"):
continue
qmlRegisterType(QUrl.fromLocalFile(path), "Cura", 1, 0, type_name)
# Ignore anything that is not a QML file.
if not path.endswith(".qml"):
continue
## Get the backend of the application (the program that does the heavy lifting).
# The backend is also a QObject, which can be used from qml.
# \returns Backend \type{Backend}
@pyqtSlot(result = "QObject*")
def getBackend(self):
return self._backend
qmlRegisterType(QUrl.fromLocalFile(path), "Cura", 1, 0, type_name)
def onSelectionChanged(self):
if Selection.hasSelection():
@ -720,7 +725,9 @@ class CuraApplication(QtApplication):
else:
# Default
self.getController().setActiveTool("TranslateTool")
if Preferences.getInstance().getValue("view/center_on_select"):
# Hack: QVector bindings are broken on PyQt 5.7.1 on Windows. This disables it being called at all.
if Preferences.getInstance().getValue("view/center_on_select") and not Platform.isWindows():
self._center_after_select = True
else:
if self.getController().getActiveTool():
@ -839,24 +846,14 @@ class CuraApplication(QtApplication):
op.push()
## Create a number of copies of existing object.
# \param object_id
# \param count number of copies
# \param min_offset minimum offset to other objects.
@pyqtSlot("quint64", int)
def multiplyObject(self, object_id, count):
node = self.getController().getScene().findObject(object_id)
if not node and object_id != 0: # Workaround for tool handles overlapping the selected object
node = Selection.getSelectedObject(0)
if node:
current_node = node
# Find the topmost group
while current_node.getParent() and current_node.getParent().callDecoration("isGroup"):
current_node = current_node.getParent()
op = GroupedOperation()
for _ in range(count):
new_node = copy.deepcopy(current_node)
op.addOperation(AddSceneNodeOperation(new_node, current_node.getParent()))
op.push()
def multiplyObject(self, object_id, count, min_offset = 8):
job = MultiplyObjectsJob(object_id, count, min_offset)
job.start()
return
## Center object on platform.
@pyqtSlot("quint64")
@ -974,6 +971,50 @@ class CuraApplication(QtApplication):
op.addOperation(SetTransformOperation(node, Vector(0, center_y, 0), Quaternion(), Vector(1, 1, 1)))
op.push()
## Arrange all objects.
@pyqtSlot()
def arrangeAll(self):
nodes = []
for node in DepthFirstIterator(self.getController().getScene().getRoot()):
if type(node) is not SceneNode:
continue
if not node.getMeshData() and not node.callDecoration("isGroup"):
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)
if not node.isSelectable():
continue # i.e. node with layer data
nodes.append(node)
self.arrange(nodes, fixed_nodes = [])
## Arrange Selection
@pyqtSlot()
def arrangeSelection(self):
nodes = Selection.getAllSelectedObjects()
# What nodes are on the build plate and are not being moved
fixed_nodes = []
for node in DepthFirstIterator(self.getController().getScene().getRoot()):
if type(node) is not SceneNode:
continue
if not node.getMeshData() and not node.callDecoration("isGroup"):
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)
if not node.isSelectable():
continue # i.e. node with layer data
if node in nodes: # exclude selected node from fixed_nodes
continue
fixed_nodes.append(node)
self.arrange(nodes, fixed_nodes)
## Arrange a set of nodes given a set of fixed nodes
# \param nodes nodes that we have to place
# \param fixed_nodes nodes that are placed in the arranger before finding spots for nodes
def arrange(self, nodes, fixed_nodes):
job = ArrangeObjectsJob(nodes, fixed_nodes)
job.start()
## Reload all mesh data on the screen from file.
@pyqtSlot()
def reloadAll(self):
@ -1009,12 +1050,6 @@ class CuraApplication(QtApplication):
return log
recentFilesChanged = pyqtSignal()
@pyqtProperty("QVariantList", notify = recentFilesChanged)
def recentFiles(self):
return self._recent_files
@pyqtSlot("QStringList")
def setExpandedCategories(self, categories):
categories = list(set(categories))
@ -1050,7 +1085,9 @@ class CuraApplication(QtApplication):
transformation.setTranslation(zero_translation)
transformed_mesh = mesh.getTransformed(transformation)
center = transformed_mesh.getCenterPosition()
if center is not None:
object_centers.append(center)
if object_centers and len(object_centers) > 0:
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)
@ -1120,25 +1157,6 @@ class CuraApplication(QtApplication):
fileLoaded = pyqtSignal(str)
def _onJobFinished(self, job):
if type(job) is not ReadMeshJob or not job.getResult():
return
f = QUrl.fromLocalFile(job.getFileName())
if f in self._recent_files:
self._recent_files.remove(f)
self._recent_files.insert(0, f)
if len(self._recent_files) > 10:
del self._recent_files[10]
pref = ""
for path in self._recent_files:
pref += path.toLocalFile() + ";"
Preferences.getInstance().setValue("cura/recent_files", pref)
self.recentFilesChanged.emit()
def _reloadMeshFinished(self, job):
# TODO; This needs to be fixed properly. We now make the assumption that we only load a single mesh!
mesh_data = job.getResult()[0].getMeshData()
@ -1233,6 +1251,10 @@ class CuraApplication(QtApplication):
filename = job.getFileName()
self._currently_loading_files.remove(filename)
root = self.getController().getScene().getRoot()
arranger = Arrange.create(scene_root = root)
min_offset = 8
for node in nodes:
node.setSelectable(True)
node.setName(os.path.basename(filename))
@ -1253,10 +1275,37 @@ class CuraApplication(QtApplication):
scene = self.getController().getScene()
# If there is no convex hull for the node, start calculating it and continue.
if not node.getDecorator(ConvexHullDecorator):
node.addDecorator(ConvexHullDecorator())
if node.callDecoration("isSliceable"):
# Find node location
offset_shape_arr, hull_shape_arr = ShapeArray.fromNode(node, min_offset = min_offset)
# Step is for skipping tests to make it a lot faster. it also makes the outcome somewhat rougher
node,_ = arranger.findNodePlacement(node, offset_shape_arr, hull_shape_arr, step = 10)
op = AddSceneNodeOperation(node, scene.getRoot())
op.push()
scene.sceneChanged.emit(node)
def addNonSliceableExtension(self, extension):
self._non_sliceable_extensions.append(extension)
@pyqtSlot(str, result=bool)
def checkIsValidProjectFile(self, file_url):
"""
Checks if the given file URL is a valid project file.
"""
try:
file_path = QUrl(file_url).toLocalFile()
workspace_reader = self.getWorkspaceFileHandler().getReaderForFile(file_path)
if workspace_reader is None:
return False # non-project files won't get a reader
result = workspace_reader.preRead(file_path, show_dialog=False)
return result == WorkspaceReader.PreReadResult.accepted
except Exception as e:
Logger.log("e", "Could not check file %s: %s", file_url, e)
return False

View file

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

View file

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

View file

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

View file

@ -0,0 +1,75 @@
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the AGPLv3 or higher.
from UM.Job import Job
from UM.Scene.SceneNode import SceneNode
from UM.Math.Vector import Vector
from UM.Operations.SetTransformOperation import SetTransformOperation
from UM.Operations.TranslateOperation import TranslateOperation
from UM.Operations.GroupedOperation import GroupedOperation
from UM.Logger import Logger
from UM.Message import Message
from UM.i18n import i18nCatalog
i18n_catalog = i18nCatalog("cura")
from cura.ZOffsetDecorator import ZOffsetDecorator
from cura.Arrange import Arrange
from cura.ShapeArray import ShapeArray
from typing import List
from UM.Application import Application
from UM.Scene.Selection import Selection
from UM.Operations.AddSceneNodeOperation import AddSceneNodeOperation
class MultiplyObjectsJob(Job):
def __init__(self, object_id, count, min_offset = 8):
super().__init__()
self._object_id = object_id
self._count = count
self._min_offset = min_offset
def run(self):
status_message = Message(i18n_catalog.i18nc("@info:status", "Multiplying and placing objects"), lifetime=0,
dismissable=False, progress=0)
status_message.show()
scene = Application.getInstance().getController().getScene()
node = scene.findObject(self._object_id)
if not node and self._object_id != 0: # Workaround for tool handles overlapping the selected object
node = Selection.getSelectedObject(0)
# If object is part of a group, multiply group
current_node = node
while current_node.getParent() and current_node.getParent().callDecoration("isGroup"):
current_node = current_node.getParent()
root = scene.getRoot()
arranger = Arrange.create(scene_root=root)
offset_shape_arr, hull_shape_arr = ShapeArray.fromNode(current_node, min_offset=self._min_offset)
nodes = []
found_solution_for_all = True
for i in range(self._count):
# We do place the nodes one by one, as we want to yield in between.
node, solution_found = arranger.findNodePlacement(current_node, offset_shape_arr, hull_shape_arr)
if not solution_found:
found_solution_for_all = False
new_location = node.getPosition()
new_location = new_location.set(z = 100 - i * 20)
node.setPosition(new_location)
nodes.append(node)
Job.yieldThread()
status_message.setProgress((i + 1) / self._count * 100)
if nodes:
op = GroupedOperation()
for new_node in nodes:
op.addOperation(AddSceneNodeOperation(new_node, current_node.getParent()))
op.push()
status_message.hide()
if not found_solution_for_all:
no_full_solution_message = Message(i18n_catalog.i18nc("@info:status", "Unable to find a location within the build volume for all objects"))
no_full_solution_message.show()

44
cura/PlatformPhysics.py Normal file → Executable file
View file

@ -3,10 +3,10 @@
from PyQt5.QtCore import QTimer
from UM.Application import Application
from UM.Scene.SceneNode import SceneNode
from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator
from UM.Math.Vector import Vector
from UM.Math.AxisAlignedBox import AxisAlignedBox
from UM.Scene.Selection import Selection
from UM.Preferences import Preferences
@ -51,10 +51,13 @@ class PlatformPhysics:
# 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))
# Only check nodes inside build area.
nodes = [node for node in nodes if (hasattr(node, "_outside_buildarea") and not node._outside_buildarea)]
random.shuffle(nodes)
for node in nodes:
if node is root or type(node) is not SceneNode or node.getBoundingBox() is None:
@ -62,24 +65,6 @@ class PlatformPhysics:
bbox = node.getBoundingBox()
# Ignore intersections with the bottom
build_volume_bounding_box = self._build_volume.getBoundingBox()
if build_volume_bounding_box:
# It's over 9000!
build_volume_bounding_box = build_volume_bounding_box.set(bottom=-9001)
else:
# No bounding box. This is triggered when running Cura from command line with a model for the first time
# In that situation there is a model, but no machine (and therefore no build volume.
return
node._outside_buildarea = False
# Mark the node as outside the build volume if the bounding box test fails.
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 Preferences.getInstance().getValue("physics/automatic_drop_down") and not (node.getParent() and node.getParent().callDecoration("isGroup")) and node.isEnabled(): #If an object is grouped, don't move it down
@ -145,27 +130,14 @@ class PlatformPhysics:
# Simply waiting for the next tick seems to resolve this correctly.
overlap = None
convex_hull = node.callDecoration("getConvexHull")
if convex_hull:
if not convex_hull.isValid():
return
# Check for collisions between disallowed areas and the object
for area in self._build_volume.getDisallowedAreas():
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
# After moving, we have to evaluate the boundary checks for nodes
build_volume = Application.getInstance().getBuildVolume()
build_volume.updateNodeBoundaryCheck()
def _onToolOperationStarted(self, tool):
self._enabled = False

View file

@ -75,6 +75,8 @@ class PrintInformation(QObject):
Application.getInstance().getMachineManager().activeMaterialChanged.connect(self._onActiveMaterialChanged)
self._onActiveMaterialChanged()
self._material_amounts = []
currentPrintTimeChanged = pyqtSignal()
preSlicedChanged = pyqtSignal()
@ -126,7 +128,7 @@ class PrintInformation(QObject):
return
# Material amount is sent as an amount of mm^3, so calculate length from that
r = Application.getInstance().getGlobalContainerStack().getProperty("material_diameter", "value") / 2
radius = Application.getInstance().getGlobalContainerStack().getProperty("material_diameter", "value") / 2
self._material_lengths = []
self._material_weights = []
self._material_costs = []
@ -161,8 +163,12 @@ class PrintInformation(QObject):
else:
cost = 0
if radius != 0:
length = round((amount / (math.pi * radius ** 2)) / 1000, 2)
else:
length = 0
self._material_weights.append(weight)
self._material_lengths.append(round((amount / (math.pi * r ** 2)) / 1000, 2))
self._material_lengths.append(length)
self._material_costs.append(cost)
self.materialLengthsChanged.emit()

View file

@ -255,6 +255,11 @@ class ExtruderManager(QObject):
preferred_materials = container_registry.findInstanceContainers(**search_criteria)
if len(preferred_materials) >= 1:
# In some cases we get multiple materials. In that case, prefer materials that are marked as read only.
read_only_preferred_materials = [preferred_material for preferred_material in preferred_materials if preferred_material.isReadOnly()]
if len(read_only_preferred_materials) >= 1:
material = read_only_preferred_materials[0]
else:
material = preferred_materials[0]
else:
Logger.log("w", "The preferred material \"%s\" of machine %s doesn't exist or is not a material profile.", preferred_material_id, machine_id)

View file

@ -2,7 +2,7 @@
# Cura is released under the terms of the AGPLv3 or higher.
from typing import Union
from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal
from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal, QTimer
from UM.FlameProfiler import pyqtSlot
from PyQt5.QtWidgets import QMessageBox
from UM import Util
@ -83,6 +83,11 @@ class MachineManager(QObject):
self._material_incompatible_message = Message(catalog.i18nc("@info:status",
"The selected material is incompatible with the selected machine or configuration."))
self._error_check_timer = QTimer()
self._error_check_timer.setInterval(250)
self._error_check_timer.setSingleShot(True)
self._error_check_timer.timeout.connect(self._updateStacksHaveErrors)
globalContainerChanged = pyqtSignal() # Emitted whenever the global stack is changed (ie: when changing between printers, changing a global profile, but not when changing a value)
activeMaterialChanged = pyqtSignal()
activeVariantChanged = pyqtSignal()
@ -306,33 +311,7 @@ class MachineManager(QObject):
self.activeStackValueChanged.emit()
elif property_name == "validationState":
if not self._stacks_have_errors:
# fast update, we only have to look at the current changed property
if self._global_container_stack.getProperty("machine_extruder_count", "value") > 1 and self._active_container_stack.getProperty(key, "settable_per_extruder"):
extruder_index = int(self._active_container_stack.getProperty(key, "limit_to_extruder"))
if extruder_index >= 0: #We have to look up the value from a different extruder.
stack = ExtruderManager.getInstance().getExtruderStack(str(extruder_index))
else:
stack = self._active_container_stack
else:
stack = self._global_container_stack
changed_validation_state = stack.getProperty(key, property_name)
if changed_validation_state is None:
# Setting is not validated. This can happen if there is only a setting definition.
# We do need to validate it, because a setting defintions value can be set by a function, which could
# be an invalid setting.
definition = self._active_container_stack.getSettingDefinition(key)
validator_type = SettingDefinition.getValidatorForType(definition.type)
if validator_type:
validator = validator_type(key)
changed_validation_state = validator(self._active_container_stack)
if changed_validation_state in (ValidatorState.Exception, ValidatorState.MaximumError, ValidatorState.MinimumError):
self._stacks_have_errors = True
self.stacksValidationChanged.emit()
else:
# Normal check
self._updateStacksHaveErrors()
self._error_check_timer.start()
@pyqtSlot(str)
def setActiveMachine(self, stack_id: str) -> None:
@ -813,6 +792,10 @@ class MachineManager(QObject):
Logger.log("e", "Tried to set quality to a container that is not of the right type")
return
# Check if it was at all possible to find new settings
if new_quality_settings_list is None:
return
name_changed_connect_stacks = [] # Connect these stacks to the name changed callback
for setting_info in new_quality_settings_list:
stack = setting_info["stack"]
@ -889,7 +872,12 @@ class MachineManager(QObject):
quality_changes_profiles = quality_manager.findQualityChangesByName(quality_changes_name,
global_machine_definition)
global_quality_changes = [qcp for qcp in quality_changes_profiles if qcp.getMetaDataEntry("extruder") is None][0]
global_quality_changes = [qcp for qcp in quality_changes_profiles if qcp.getMetaDataEntry("extruder") is None]
if global_quality_changes:
global_quality_changes = global_quality_changes[0]
else:
Logger.log("e", "Could not find the global quality changes container with name %s", quality_changes_name)
return None
material = global_container_stack.findContainer(type="material")
# For the global stack, find a quality which matches the quality_type in

111
cura/ShapeArray.py Executable file
View file

@ -0,0 +1,111 @@
import numpy
import copy
from UM.Math.Polygon import Polygon
## Polygon representation as an array for use with Arrange
class ShapeArray:
def __init__(self, arr, offset_x, offset_y, scale = 1):
self.arr = arr
self.offset_x = offset_x
self.offset_y = offset_y
self.scale = scale
## Instantiate from a bunch of vertices
# \param vertices
# \param scale scale the coordinates
@classmethod
def fromPolygon(cls, vertices, scale = 1):
# scale
vertices = vertices * scale
# flip y, x -> x, y
flip_vertices = numpy.zeros((vertices.shape))
flip_vertices[:, 0] = vertices[:, 1]
flip_vertices[:, 1] = vertices[:, 0]
flip_vertices = flip_vertices[::-1]
# offset, we want that all coordinates have positive values
offset_y = int(numpy.amin(flip_vertices[:, 0]))
offset_x = int(numpy.amin(flip_vertices[:, 1]))
flip_vertices[:, 0] = numpy.add(flip_vertices[:, 0], -offset_y)
flip_vertices[:, 1] = numpy.add(flip_vertices[:, 1], -offset_x)
shape = [int(numpy.amax(flip_vertices[:, 0])), int(numpy.amax(flip_vertices[:, 1]))]
arr = cls.arrayFromPolygon(shape, flip_vertices)
return cls(arr, offset_x, offset_y)
## Instantiate an offset and hull ShapeArray from a scene node.
# \param node source node where the convex hull must be present
# \param min_offset offset for the offset ShapeArray
# \param scale scale the coordinates
@classmethod
def fromNode(cls, node, min_offset, scale = 0.5):
transform = node._transformation
transform_x = transform._data[0][3]
transform_y = transform._data[2][3]
hull_verts = node.callDecoration("getConvexHull")
offset_verts = hull_verts.getMinkowskiHull(Polygon.approximatedCircle(min_offset))
offset_points = copy.deepcopy(offset_verts._points) # x, y
offset_points[:, 0] = numpy.add(offset_points[:, 0], -transform_x)
offset_points[:, 1] = numpy.add(offset_points[:, 1], -transform_y)
offset_shape_arr = ShapeArray.fromPolygon(offset_points, scale = scale)
hull_points = copy.deepcopy(hull_verts._points)
hull_points[:, 0] = numpy.add(hull_points[:, 0], -transform_x)
hull_points[:, 1] = numpy.add(hull_points[:, 1], -transform_y)
hull_shape_arr = ShapeArray.fromPolygon(hull_points, scale = scale) # x, y
return offset_shape_arr, hull_shape_arr
## Create np.array with dimensions defined by shape
# Fills polygon defined by vertices with ones, all other values zero
# Only works correctly for convex hull vertices
# Originally from: http://stackoverflow.com/questions/37117878/generating-a-filled-polygon-inside-a-numpy-array
# \param shape numpy format shape, [x-size, y-size]
# \param vertices
@classmethod
def arrayFromPolygon(cls, shape, vertices):
base_array = numpy.zeros(shape, dtype=float) # Initialize your array of zeros
fill = numpy.ones(base_array.shape) * True # Initialize boolean array defining shape fill
# Create check array for each edge segment, combine into fill array
for k in range(vertices.shape[0]):
fill = numpy.all([fill, cls._check(vertices[k - 1], vertices[k], base_array)], axis=0)
# Set all values inside polygon to one
base_array[fill] = 1
return base_array
## Return indices that mark one side of the line, used by arrayFromPolygon
# Uses the line defined by p1 and p2 to check array of
# input indices against interpolated value
# Returns boolean array, with True inside and False outside of shape
# Originally from: http://stackoverflow.com/questions/37117878/generating-a-filled-polygon-inside-a-numpy-array
# \param p1 2-tuple with x, y for point 1
# \param p2 2-tuple with x, y for point 2
# \param base_array boolean array to project the line on
@classmethod
def _check(cls, p1, p2, base_array):
if p1[0] == p2[0] and p1[1] == p2[1]:
return
idxs = numpy.indices(base_array.shape) # Create 3D array of indices
p1 = p1.astype(float)
p2 = p2.astype(float)
if p2[0] == p1[0]:
sign = numpy.sign(p2[1] - p1[1])
return idxs[1] * sign
if p2[1] == p1[1]:
sign = numpy.sign(p2[0] - p1[0])
return idxs[1] * sign
# Calculate max column idx for each row idx based on interpolated line between two points
max_col_idx = (idxs[0] - p1[0]) / (p2[0] - p1[0]) * (p2[1] - p1[1]) + p1[1]
sign = numpy.sign(p2[0] - p1[0])
return idxs[1] * sign <= max_col_idx * sign

View file

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

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

@ -16,6 +16,7 @@ from UM.Application import Application
from cura.Settings.ExtruderManager import ExtruderManager
from cura.QualityManager import QualityManager
from UM.Scene.SceneNode import SceneNode
from cura.SliceableObjectDecorator import SliceableObjectDecorator
MYPY = False
@ -144,6 +145,11 @@ class ThreeMFReader(MeshReader):
group_decorator = GroupDecorator()
um_node.addDecorator(group_decorator)
um_node.setSelectable(True)
if um_node.getMeshData():
# Assuming that all nodes with mesh data are printable objects
# affects (auto) slicing
sliceable_decorator = SliceableObjectDecorator()
um_node.addDecorator(sliceable_decorator)
return um_node
def read(self, file_name):

View file

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

View file

@ -12,15 +12,15 @@ UM.Dialog
{
title: catalog.i18nc("@title:window", "Open Project")
width: 550
minimumWidth: 550
maximumWidth: 550
width: 550 * Screen.devicePixelRatio
minimumWidth: 550 * Screen.devicePixelRatio
maximumWidth: minimumWidth
height: 400
minimumHeight: 400
maximumHeight: 400
property int comboboxHeight: 15
property int spacerHeight: 10
height: 400 * Screen.devicePixelRatio
minimumHeight: 400 * Screen.devicePixelRatio
maximumHeight: minimumHeight
property int comboboxHeight: 15 * Screen.devicePixelRatio
property int spacerHeight: 10 * Screen.devicePixelRatio
onClosing: manager.notifyClosed()
onVisibleChanged:
{
@ -33,20 +33,17 @@ UM.Dialog
}
Item
{
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.topMargin: 20
anchors.bottomMargin: 20
anchors.leftMargin:20
anchors.rightMargin: 20
anchors.fill: parent
anchors.margins: 20 * Screen.devicePixelRatio
UM.I18nCatalog
{
id: catalog;
name: "cura";
id: catalog
name: "cura"
}
SystemPalette
{
id: palette
}
ListModel
@ -70,12 +67,12 @@ UM.Dialog
{
id: titleLabel
text: catalog.i18nc("@action:title", "Summary - Cura Project")
font.pixelSize: 22
font.pointSize: 18
}
Rectangle
{
id: separator
color: "black"
color: palette.text
width: parent.width
height: 1
}
@ -360,7 +357,7 @@ UM.Dialog
height: width
source: UM.Theme.getIcon("notice")
color: "black"
color: palette.text
}
Label

View file

@ -1,9 +1,16 @@
# Copyright (c) 2015 Ultimaker B.V.
# Cura is released under the terms of the AGPLv3 or higher.
from typing import Dict
import sys
from UM.Logger import Logger
try:
from . import ThreeMFReader
except ImportError:
Logger.log("w", "Could not import ThreeMFReader; libSavitar may be missing")
from . import ThreeMFWorkspaceReader
from UM.i18n import i18nCatalog
from UM.Platform import Platform
catalog = i18nCatalog("cura")
@ -14,30 +21,36 @@ def getMetaData() -> Dict:
workspace_extension = "3mf"
else:
workspace_extension = "curaproject.3mf"
return {
metaData = {
"plugin": {
"name": catalog.i18nc("@label", "3MF Reader"),
"author": "Ultimaker",
"version": "1.0",
"description": catalog.i18nc("@info:whatsthis", "Provides support for reading 3MF files."),
"api": 3
},
"mesh_reader": [
}
}
if "3MFReader.ThreeMFReader" in sys.modules:
metaData["mesh_reader"] = [
{
"extension": "3mf",
"description": catalog.i18nc("@item:inlistbox", "3MF File")
}
],
"workspace_reader":
[
]
metaData["workspace_reader"] = [
{
"extension": workspace_extension,
"description": catalog.i18nc("@item:inlistbox", "3MF File")
}
]
}
return metaData
def register(app):
if "3MFReader.ThreeMFReader" in sys.modules:
return {"mesh_reader": ThreeMFReader.ThreeMFReader(),
"workspace_reader": ThreeMFWorkspaceReader.ThreeMFWorkspaceReader()}
else:
return {}

View file

@ -1,30 +1,39 @@
# Copyright (c) 2015 Ultimaker B.V.
# Uranium is released under the terms of the AGPLv3 or higher.
import sys
from UM.Logger import Logger
try:
from . import ThreeMFWriter
except ImportError:
Logger.log("w", "Could not import ThreeMFWriter; libSavitar may be missing")
from . import ThreeMFWorkspaceWriter
from UM.i18n import i18nCatalog
from . import ThreeMFWorkspaceWriter
from . import ThreeMFWriter
i18n_catalog = i18nCatalog("uranium")
def getMetaData():
return {
metaData = {
"plugin": {
"name": i18n_catalog.i18nc("@label", "3MF Writer"),
"author": "Ultimaker",
"version": "1.0",
"description": i18n_catalog.i18nc("@info:whatsthis", "Provides support for writing 3MF files."),
"api": 3
},
"mesh_writer": {
}
}
if "3MFWriter.ThreeMFWriter" in sys.modules:
metaData["mesh_writer"] = {
"output": [{
"extension": "3mf",
"description": i18n_catalog.i18nc("@item:inlistbox", "3MF file"),
"mime_type": "application/vnd.ms-package.3dmanufacturing-3dmodel+xml",
"mode": ThreeMFWriter.ThreeMFWriter.OutputMode.BinaryMode
}]
},
"workspace_writer": {
}
metaData["workspace_writer"] = {
"output": [{
"extension": "curaproject.3mf",
"description": i18n_catalog.i18nc("@item:inlistbox", "Cura Project 3MF file"),
@ -32,7 +41,12 @@ def getMetaData():
"mode": ThreeMFWorkspaceWriter.ThreeMFWorkspaceWriter.OutputMode.BinaryMode
}]
}
}
return metaData
def register(app):
return {"mesh_writer": ThreeMFWriter.ThreeMFWriter(), "workspace_writer": ThreeMFWorkspaceWriter.ThreeMFWorkspaceWriter()}
if "3MFWriter.ThreeMFWriter" in sys.modules:
return {"mesh_writer": ThreeMFWriter.ThreeMFWriter(),
"workspace_writer": ThreeMFWorkspaceWriter.ThreeMFWorkspaceWriter()}
else:
return {}

View file

@ -1,56 +1,54 @@
[2.5.0]
*Speed.
Weve given the system a tweak, to make changing printers, profiles, materials and print cores even quicker than ever. That means less hanging around, more time printing. Weve also adjusted the start-up speed, which is now five seconds faster.
*Improved speed
Weve made changing printers, profiles, materials, and print cores even faster. 3MF processing is also much faster now. Opening a 3MF file now takes one tenth of the time.
*Speedup engine Multi-threading.
This is one of the most significant improvements, making slicing even faster. Just like computers with multiple cores, Cura can process multiple operations at the same time. Hows that for efficient?
*Speedup engine Multithreading
Cura can process multiple operations at the same time during slicing. Supported by Windows and Linux operating systems only.
*Better layout for 3D layer view options.
Need things to be a bit clearer? Weve now incorporated an improved layer view for computers that support OpenGL 4.1. For OpenGL 2.0 we will automatically switch to the old layer view. Thanks to community member Aldo Hoeben for the fancy double handle slider.
*Preheat the build plate (with a connected printer)
Users can now set the Ultimaker 3 to preheat the build plate, which reduces the downtime, allowing to manually speed up the printing workflow.
*Disable automatic slicing.
Some users told us that slicing slowed down their workflow when it auto-starts, and to improve the user experience, we added an option to disable auto-slicing if required. Thanks to community member Aldo Hoeben for contributing to this one.
*Better layout for 3D layer view options
An improved layer view has been implemented for computers that support OpenGL 4.1. For OpenGL 2.0 to 4.0, we will automatically switch to the old layer view.
*Auto-scale off by default.
This change needs no explanation!
*Disable automatic slicing
An option to disable auto-slicing has been added for the better user experience.
*Preheat the build plate (with a connected printer).
You can now set your Ultimaker 3 to preheat the build plate, which reduces the downtime, letting you manually speed up your printing workflow. All you need to do is use the preheat function in the Print Monitor screen, and set the correct temperature for the active material(s).
*Auto-scale off by default
This change speaks for itself.
*G-code reader.
The g-code reader has been reintroduced, which means you can load g-code from file and display it in layer view. You can also print saved g-code files with Cura, share and re-use them, and you can check that your printed object looks right via the g-code viewer. Thanks to AlephObjects for this feature.
*Print cost calculation
The latest version of Cura now contains code to help users calculate the cost of their prints. To do so, users need to enter a cost per spool and an amount of materials per spool. It is also possible to set the cost per material and gain better control of the expenses. Thanks to our community member Aldo Hoeben for adding this feature.
*Switching profiles.
When you change a printing profile after customizing print settings, you have an option (shown in a popup) to transfer your customizations to the new profile or discard those modifications and continue with default settings instead. Weve made this dialog window more informative and intuitive.
*G-code reader
The g-code reader has been reintroduced, which means users can load g-code from file and display it in layer view. Users can also print saved g-code files with Cura, share and re-use them, as well as preview the printed object via the g-code viewer. Thanks to AlephObjects for this feature.
*Print cost calculation.
Cura now contains code to help you calculate the cost of your print. To do so, youll need to enter a cost per spool and an amount of materials per spool. You can also set the cost per material and gain better control of your expenses. Thanks to our community member Aldo Hoeben for adding this feature.
*Discard or Keep Changes popup
Weve changed the popup that appears when a user changes a printing profile after setting custom printing settings. It is now more informative and helpful.
*Bug fixes
Property renaming: Properties that start with get have been renamed to avoid confusion.
Window overflow: This is now fixed.
Multiple machine prefixes: Multiple machine prefixes are gone when loading and saving projects.
Removal of file extension: When you save a file or project (without changing the file type), no file extension is added to the name. Its only when you change to another file type that the extension is added.
Ultimaker 3 Extended connectivity: Selecting Ultimaker 3 Extended in Cura let you connect and print with Ultimaker 3, without any warning. This now has been fixed.
Different Y / Z colors: Y and Z colors in the tool menu are now different to the colors on the build plate.
No collision areas: No collision areas were generated for some models.
Perimeter gaps: Perimeter gaps are not filled often enough; weve now amended this.
File location after restart: The old version of Cura didnt remember the last opened file location after its been restarted. Now it does!
Project name: The project name changes after the project is opened.
Slicing when error value is given (print core 2): When a support is printed with extruder 2 (PVA), some support settings will trigger a slice when an error value is given. Weve now sorted this out.
Support Towers: Support Towers can now be disabled.
Support bottoms: When putting one object on top of another with some space in between, and selecting support with support bottom interface, no support bottom is printed. This has now been resolved.
Summary box size: Weve enlarged the summary box when saving your project.
Cubic subdivision infill: In the past, the cubic subdivision infill sometimes didnt produce the infill (WIN) this has now been addressed.
Spiralize outer contour and fill small gaps: When combining Fill Gaps Between Walls with Spiralize Outer Contour, the model gets a massive infill.
Experimental post-processing plugin: Since the TwaekAtZ post-processing plugin is not officially supported, we added the Experimental tag.
- Window overflow: On some configurations (OS and screen dependant), an overflow on the General (Preferences) panel and the credits list on the About window occurred. This is now fixed.
- “Center camera when the item is selected”: This is now set to off by default.
- Removal of file extension: When users save a file or project (without changing the file type), no file extension is added to the name. Its only when users change to another file type that the extension is added.
- Ultimaker 3 Extended connectivity. Selecting Ultimaker 3 Extended in Cura let you connect and print with Ultimaker 3, without any warning. This now has been fixed.
- Different Y / Z colors: Y and Z colors in the tool menu are now similar to the colors on the build plate.
- No collision areas: No collision areas used to be generated for some models when "keep models apart" was activated. This is now fixed.
- Perimeter gaps: Perimeter gaps are not filled often enough; weve now amended this.
- File location after restart: The old version of Cura didnt remember the last opened file location after its been restarted. Now it has been fixed.
- Project name: The project name changes after the project is opened. This now has been fixed.
- Slicing when error value is given (print core 2): When a support is printed with the Extruder 2 (PVA), some support settings will trigger a slice when an error value is given. Weve now sorted this out.
- Support Towers: Support Towers can now be disabled.
- Support bottoms: When putting one object on top of another with some space in between, and selecting support with support bottom interface, no support bottom is printed. This has now been resolved.
- Summary box size: Weve enlarged the summary box when saving the project.
- Cubic subdivision infill: In the past, the cubic subdivision infill sometimes didnt produce the infill (WIN) this has now been addressed.
- Spiralize outer contour and fill small gaps: When combining Fill Gaps Between Walls with Spiralize Outer Contour, the model gets a massive infill.
- Experimental post-processing plugin: Since the TweakAtZ post-processing plugin is not officially supported, we added the Experimental tag.
*3rd party printers (bug fixes)
Folgertech printer definition has been added
Hello BEE Prusa printer definition has been added
Material profiles for Cartesio printers have been updated
- Folgertech printer definition has been added.
- Hello BEE Prusa printer definition has been added.
- Velleman Vertex K8400 printer definitions have been added for both single-extrusion and dual-extrusion versions.
- Material profiles for Cartesio printers have been updated.
[2.4.0]
*Project saving & opening

View file

@ -2,6 +2,7 @@
# Cura is released under the terms of the AGPLv3 or higher.
from UM.Application import Application
from UM.Backend import Backend
from UM.Job import Job
from UM.Logger import Logger
from UM.Math.AxisAlignedBox import AxisAlignedBox
@ -37,7 +38,6 @@ class GCodeReader(MeshReader):
self._message = None
self._layer_number = 0
self._extruder_number = 0
self._layer_type = LayerPolygon.Inset0Type
self._clearValues()
self._scene_node = None
self._position = namedtuple('Position', ['x', 'y', 'z', 'e'])
@ -153,7 +153,6 @@ class GCodeReader(MeshReader):
self._previous_z = z
else:
path.append([x, y, z, LayerPolygon.MoveCombingType])
return self._position(x, y, z, e)
# G0 and G1 should be handled exactly the same.
@ -172,7 +171,6 @@ class GCodeReader(MeshReader):
def _gCode92(self, position, params, path):
if params.e is not None:
position.e[self._extruder_number] = params.e
return self._position(
params.x if params.x is not None else position.x,
params.y if params.y is not None else position.y,
@ -307,11 +305,11 @@ class GCodeReader(MeshReader):
G = self._getInt(line, "G")
if G is not None:
current_position = self._processGCode(G, line, current_position, current_path)
# < 2 is a heuristic for a movement only, that should not be counted as a layer
if current_position.z > last_z and abs(current_position.z - last_z) < 2:
if self._createPolygon(self._current_layer_thickness, current_path, self._extruder_offsets.get(self._extruder_number, [0, 0])):
current_path.clear()
if not self._is_layers_in_file:
self._layer_number += 1
@ -343,6 +341,8 @@ class GCodeReader(MeshReader):
gcode_list_decorator.setGCodeList(gcode_list)
scene_node.addDecorator(gcode_list_decorator)
Application.getInstance().getController().getScene().gcode_list = gcode_list
Logger.log("d", "Finished parsing %s" % file_name)
self._message.hide()
@ -364,4 +364,8 @@ class GCodeReader(MeshReader):
"Make sure the g-code is suitable for your printer and printer configuration before sending the file to it. The g-code representation may not be accurate."), lifetime=0)
caution_message.show()
# The "save/print" button's state is bound to the backend state.
backend = Application.getInstance().getBackend()
backend.backendStateChange.emit(Backend.BackendState.Disabled)
return scene_node

View file

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

View file

@ -1,6 +1,8 @@
# Copyright (c) 2015 Ultimaker B.V.
# Cura is released under the terms of the AGPLv3 or higher.
import sys
from UM.PluginRegistry import PluginRegistry
from UM.View.View import View
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
@ -253,8 +255,17 @@ class LayerView(View):
if not layer_data:
continue
if new_max_layers < len(layer_data.getLayers()):
new_max_layers = len(layer_data.getLayers()) - 1
min_layer_number = sys.maxsize
max_layer_number = -sys.maxsize
for layer_id in layer_data.getLayers():
if max_layer_number < layer_id:
max_layer_number = layer_id
if min_layer_number > layer_id:
min_layer_number = layer_id
layer_count = max_layer_number - min_layer_number
if new_max_layers < layer_count:
new_max_layers = layer_count
if new_max_layers > 0 and new_max_layers != self._old_max_layers:
self._max_layers = new_max_layers

View file

@ -351,7 +351,7 @@ Item
property bool roundValues: true
property var activeHandle: upperHandle
property bool layersVisible: UM.LayerView.layerActivity && Printer.platformActivity ? true : false
property bool layersVisible: UM.LayerView.layerActivity && CuraApplication.platformActivity ? true : false
function getUpperValueFromHandle()
{

View file

@ -91,7 +91,7 @@ class RemovableDriveOutputDevice(OutputDevice):
self.writeStarted.emit(self)
job._message = message
job.setMessage(message)
self._writing = True
job.start()
except PermissionError as e:
@ -118,8 +118,6 @@ class RemovableDriveOutputDevice(OutputDevice):
raise OutputDeviceError.WriteRequestFailedError("Could not find a file name when trying to write to {device}.".format(device = self.getName()))
def _onProgress(self, job, progress):
if hasattr(job, "_message"):
job._message.setProgress(progress)
self.writeProgress.emit(self, progress)
def _onFinished(self, job):
@ -128,10 +126,6 @@ class RemovableDriveOutputDevice(OutputDevice):
self._stream.close()
self._stream = None
if hasattr(job, "_message"):
job._message.hide()
job._message = None
self._writing = False
self.writeFinished.emit(self)
if job.getResult():

View file

@ -35,6 +35,7 @@ class DiscoverUM3Action(MachineAction):
@pyqtSlot()
def startDiscovery(self):
if not self._network_plugin:
Logger.log("d", "Starting printer discovery.")
self._network_plugin = Application.getInstance().getOutputDeviceManager().getOutputDevicePlugin("UM3NetworkPrinting")
self._network_plugin.printerListChanged.connect(self._onPrinterDiscoveryChanged)
self.printersChanged.emit()
@ -42,6 +43,7 @@ class DiscoverUM3Action(MachineAction):
## Re-filters the list of printers.
@pyqtSlot()
def reset(self):
Logger.log("d", "Reset the list of found printers.")
self.printersChanged.emit()
@pyqtSlot()
@ -95,12 +97,14 @@ class DiscoverUM3Action(MachineAction):
@pyqtSlot(str)
def setKey(self, key):
Logger.log("d", "Attempting to set the network key of the active machine to %s", key)
global_container_stack = Application.getInstance().getGlobalContainerStack()
if global_container_stack:
meta_data = global_container_stack.getMetaData()
if "um_network_key" in meta_data:
global_container_stack.setMetaDataEntry("um_network_key", key)
# Delete old authentication data.
Logger.log("d", "Removing old authentication id %s for device %s", global_container_stack.getMetaDataEntry("network_authentication_id", None), key)
global_container_stack.removeMetaDataEntry("network_authentication_id")
global_container_stack.removeMetaDataEntry("network_authentication_key")
else:

View file

@ -200,11 +200,11 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice):
def _onAuthenticationRequired(self, reply, authenticator):
if self._authentication_id is not None and self._authentication_key is not None:
Logger.log("d", "Authentication was required. Setting up authenticator with ID %s",self._authentication_id )
Logger.log("d", "Authentication was required. Setting up authenticator with ID %s and key", self._authentication_id, self._getSafeAuthKey())
authenticator.setUser(self._authentication_id)
authenticator.setPassword(self._authentication_key)
else:
Logger.log("d", "No authentication was required. The ID is: %s", self._authentication_id)
Logger.log("d", "No authentication is available to use, but we did got a request for it.")
def getProperties(self):
return self._properties
@ -619,10 +619,13 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice):
self._gcode = getattr(Application.getInstance().getController().getScene(), "gcode_list")
print_information = Application.getInstance().getPrintInformation()
warnings = [] # There might be multiple things wrong. Keep a list of all the stuff we need to warn about.
# Only check for mistakes if there is material length information.
if print_information.materialLengths:
# Check if print cores / materials are loaded at all. Any failure in these results in an Error.
for index in range(0, self._num_extruders):
if print_information.materialLengths[index] != 0:
if index < len(print_information.materialLengths) and print_information.materialLengths[index] != 0:
if self._json_printer_state["heads"][0]["extruders"][index]["hotend"]["id"] == "":
Logger.log("e", "No cartridge loaded in slot %s, unable to start print", index + 1)
self._error_message = Message(
@ -637,18 +640,16 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice):
self._error_message.show()
return
warnings = [] # There might be multiple things wrong. Keep a list of all the stuff we need to warn about.
for index in range(0, self._num_extruders):
# Check if there is enough material. Any failure in these results in a warning.
material_length = self._json_printer_state["heads"][0]["extruders"][index]["active_material"]["length_remaining"]
if material_length != -1 and print_information.materialLengths[index] > material_length:
if material_length != -1 and index < len(print_information.materialLengths) and print_information.materialLengths[index] > material_length:
Logger.log("w", "Printer reports that there is not enough material left for extruder %s. We need %s and the printer has %s", index + 1, print_information.materialLengths[index], material_length)
warnings.append(i18n_catalog.i18nc("@label", "Not enough material for spool {0}.").format(index+1))
# Check if the right cartridges are loaded. Any failure in these results in a warning.
extruder_manager = cura.Settings.ExtruderManager.ExtruderManager.getInstance()
if print_information.materialLengths[index] != 0:
if index < len(print_information.materialLengths) and print_information.materialLengths[index] != 0:
variant = extruder_manager.getExtruderStack(index).findContainer({"type": "variant"})
core_name = self._json_printer_state["heads"][0]["extruders"][index]["hotend"]["id"]
if variant:
@ -677,6 +678,8 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice):
if not is_offset_calibrated:
warnings.append(i18n_catalog.i18nc("@label", "Print core {0} is not properly calibrated. XY calibration needs to be performed on the printer.").format(index + 1))
else:
Logger.log("w", "There was no material usage found. No check to match used material with machine is done.")
if warnings:
text = i18n_catalog.i18nc("@label", "Are you sure you wish to print with the selected configuration?")
@ -713,7 +716,8 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice):
## Start requesting data from printer
def connect(self):
self.close() # Ensure that previous connection (if any) is killed.
if self.isConnected():
self.close() # Close previous connection
self._createNetworkManager()
@ -726,7 +730,12 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice):
## Check if this machine was authenticated before.
self._authentication_id = Application.getInstance().getGlobalContainerStack().getMetaDataEntry("network_authentication_id", None)
self._authentication_key = Application.getInstance().getGlobalContainerStack().getMetaDataEntry("network_authentication_key", None)
Logger.log("d", "Loaded authentication id %s from the metadata entry", self._authentication_id)
if self._authentication_id is None and self._authentication_key is None:
Logger.log("d", "No authentication found in metadata.")
else:
Logger.log("d", "Loaded authentication id %s and key %s from the metadata entry", self._authentication_id, self._getSafeAuthKey())
self._update_timer.start()
## Stop requesting data from printer
@ -788,19 +797,41 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice):
Logger.log("d", "Started sending g-code to remote printer.")
self._compressing_print = True
## Mash the data into single string
max_chars_per_line = 1024 * 1024 / 4 # 1 / 4 MB
byte_array_file_data = b""
batched_line = ""
def _compress_data_and_notify_qt(data_to_append):
compressed_data = gzip.compress(data_to_append.encode("utf-8"))
QCoreApplication.processEvents() # Ensure that the GUI does not freeze.
# Pretend that this is a response, as zipping might take a bit of time.
self._last_response_time = time()
return compressed_data
for line in self._gcode:
if not self._compressing_print:
self._progress_message.hide()
return # Stop trying to zip, abort was called.
if self._use_gzip:
byte_array_file_data += gzip.compress(line.encode("utf-8"))
QCoreApplication.processEvents() # Ensure that the GUI does not freeze.
# Pretend that this is a response, as zipping might take a bit of time.
self._last_response_time = time()
batched_line += line
# if the gcode was read from a gcode file, self._gcode will be a list of all lines in that file.
# Compressing line by line in this case is extremely slow, so we need to batch them.
if len(batched_line) < max_chars_per_line:
continue
byte_array_file_data += _compress_data_and_notify_qt(batched_line)
batched_line = ""
else:
byte_array_file_data += line.encode("utf-8")
# don't miss the last batch if it's there
if self._use_gzip:
if batched_line:
byte_array_file_data += _compress_data_and_notify_qt(batched_line)
if self._use_gzip:
file_name = "%s.gcode.gz" % Application.getInstance().getPrintInformation().jobName
else:
@ -842,7 +873,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice):
## Check if the authentication request was allowed by the printer.
def _checkAuthentication(self):
Logger.log("d", "Checking if authentication is correct for id %s", self._authentication_id)
Logger.log("d", "Checking if authentication is correct for id %s and key %s", self._authentication_id, self._getSafeAuthKey())
self._manager.get(QNetworkRequest(QUrl("http://" + self._address + self._api_prefix + "auth/check/" + str(self._authentication_id))))
## Request a authentication key from the printer so we can be authenticated
@ -850,8 +881,10 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice):
url = QUrl("http://" + self._address + self._api_prefix + "auth/request")
request = QNetworkRequest(url)
request.setHeader(QNetworkRequest.ContentTypeHeader, "application/json")
self.setAuthenticationState(AuthState.AuthenticationRequested)
self._authentication_key = None
self._authentication_id = None
self._manager.post(request, json.dumps({"application": "Cura-" + Application.getInstance().getVersion(), "user": self._getUserName()}).encode())
self.setAuthenticationState(AuthState.AuthenticationRequested)
## Send all material profiles to the printer.
def sendMaterialProfiles(self):
@ -921,7 +954,11 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice):
if status_code == 200:
if self._connection_state == ConnectionState.connecting:
self.setConnectionState(ConnectionState.connected)
try:
self._json_printer_state = json.loads(bytes(reply.readAll()).decode("utf-8"))
except json.decoder.JSONDecodeError:
Logger.log("w", "Received an invalid printer state message: Not valid JSON.")
return
self._spliceJSONData()
# Hide connection error message if the connection was restored
@ -933,7 +970,11 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice):
pass # TODO: Handle errors
elif "print_job" in reply_url: # Status update from print_job:
if status_code == 200:
try:
json_data = json.loads(bytes(reply.readAll()).decode("utf-8"))
except json.decoder.JSONDecodeError:
Logger.log("w", "Received an invalid print job state message: Not valid JSON.")
return
progress = json_data["progress"]
## If progress is 0 add a bit so another print can't be sent.
if progress == 0:
@ -1011,13 +1052,17 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice):
else:
global_container_stack.addMetaDataEntry("network_authentication_id", self._authentication_id)
Application.getInstance().saveStack(global_container_stack) # Force save so we are sure the data is not lost.
Logger.log("i", "Authentication succeeded for id %s", self._authentication_id)
Logger.log("i", "Authentication succeeded for id %s and key %s", self._authentication_id, self._getSafeAuthKey())
else: # Got a response that we didn't expect, so something went wrong.
Logger.log("e", "While trying to authenticate, we got an unexpected response: %s", reply.attribute(QNetworkRequest.HttpStatusCodeAttribute))
self.setAuthenticationState(AuthState.NotAuthenticated)
elif "auth/check" in reply_url: # Check if we are authenticated (user can refuse this!)
try:
data = json.loads(bytes(reply.readAll()).decode("utf-8"))
except json.decoder.JSONDecodeError:
Logger.log("w", "Received an invalid authentication check from printer: Not valid JSON.")
return
if data.get("message", "") == "authorized":
Logger.log("i", "Authentication was approved")
self._verifyAuthentication() # Ensure that the verification is really used and correct.
@ -1030,8 +1075,11 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice):
elif reply.operation() == QNetworkAccessManager.PostOperation:
if "/auth/request" in reply_url:
# We got a response to requesting authentication.
try:
data = json.loads(bytes(reply.readAll()).decode("utf-8"))
except json.decoder.JSONDecodeError:
Logger.log("w", "Received an invalid authentication request reply from printer: Not valid JSON.")
return
global_container_stack = Application.getInstance().getGlobalContainerStack()
if global_container_stack: # Remove any old data.
Logger.log("d", "Removing old network authentication data as a new one was requested.")
@ -1041,7 +1089,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice):
self._authentication_key = data["key"]
self._authentication_id = data["id"]
Logger.log("i", "Got a new authentication ID. Waiting for authorization: %s", self._authentication_id )
Logger.log("i", "Got a new authentication ID (%s) and KEY (%S). Waiting for authorization.", self._authentication_id, self._getSafeAuthKey())
# Check if the authentication is accepted.
self._checkAuthentication()
@ -1111,3 +1159,12 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice):
icon=QMessageBox.Question,
callback=callback
)
## Convenience function to "blur" out all but the last 5 characters of the auth key.
# This can be used to debug print the key, without it compromising the security.
def _getSafeAuthKey(self):
if self._authentication_key is not None:
result = self._authentication_key[-5:]
result = "********" + result
return result
return self._authentication_key

View file

@ -157,12 +157,14 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin):
for key in self._printers:
if key == active_machine.getMetaDataEntry("um_network_key"):
if not self._printers[key].isConnected():
Logger.log("d", "Connecting [%s]..." % key)
self._printers[key].connect()
self._printers[key].connectionStateChanged.connect(self._onPrinterConnectionStateChanged)
else:
if self._printers[key].isConnected():
Logger.log("d", "Closing connection [%s]..." % key)
self._printers[key].connectionStateChanged.disconnect(self._onPrinterConnectionStateChanged)
self._printers[key].close()
## Because the model needs to be created in the same thread as the QMLEngine, we use a signal.

View file

@ -19,8 +19,8 @@ from PyQt5.QtCore import QUrl, pyqtSlot, pyqtSignal, pyqtProperty
from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")
class USBPrinterOutputDevice(PrinterOutputDevice):
class USBPrinterOutputDevice(PrinterOutputDevice):
def __init__(self, serial_port):
super().__init__(serial_port)
self.setName(catalog.i18nc("@item:inmenu", "USB printing"))
@ -202,6 +202,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
try:
programmer.connect(self._serial_port)
except Exception:
programmer.close()
pass
# Give programmer some time to connect. Might need more in some cases, but this worked in all tested cases.
@ -312,8 +313,10 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
programmer.connect(self._serial_port) # Connect with the serial, if this succeeds, it's an arduino based usb device.
self._serial = programmer.leaveISP()
except ispBase.IspError as e:
programmer.close()
Logger.log("i", "Could not establish connection on %s: %s. Device is not arduino based." %(self._serial_port,str(e)))
except Exception as e:
programmer.close()
Logger.log("i", "Could not establish connection on %s, unknown reasons. Device is not arduino based." % self._serial_port)
# If the programmer connected, we know its an atmega based version.
@ -559,6 +562,11 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
if ";" in line:
line = line[:line.find(";")]
line = line.strip()
# Don't send empty lines. But we do have to send something, so send m105 instead.
if line == "":
line = "M105"
try:
if line == "M0" or line == "M1":
line = "M105" # Don't send the M0 or M1 to the machine, as M0 and M1 are handled as an LCD menu pause.

View file

@ -236,8 +236,7 @@ class USBPrinterOutputDeviceManager(QObject, OutputDevicePlugin, Extension):
self.getOutputDeviceManager().removeOutputDevice(serial_port)
self.connectionStateChanged.emit()
except KeyError:
pass # no output device by this device_id found in connection list.
Logger.log("w", "Connection state of %s changed, but it was not found in the list")
@pyqtProperty(QObject , notify = connectionStateChanged)
def connectedPrinterList(self):

View file

@ -12,6 +12,9 @@
"has_machine_materials": true,
"has_variants": true,
"variants_name": "Nozzle size",
"preferred_variant": "*0.4*",
"preferred_material": "*pla*",
"preferred_quality": "*draft*",
"machine_extruder_trains":
{
"0": "cartesio_extruder_0",
@ -29,16 +32,27 @@
"machine_extruder_count": { "default_value": 4 },
"machine_heated_bed": { "default_value": true },
"machine_center_is_zero": { "default_value": false },
"gantry_height": { "default_value": 35 },
"machine_height": { "default_value": 400 },
"machine_depth": { "default_value": 270 },
"machine_width": { "default_value": 430 },
"machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" },
"material_print_temp_wait": { "default_value": false },
"material_bed_temp_wait": { "default_value": false },
"infill_pattern": { "default_value": "grid"},
"prime_tower_enable": { "default_value": true },
"prime_tower_wall_thickness": { "resolve": 0.7 },
"prime_tower_position_x": { "default_value": 30 },
"prime_tower_position_y": { "default_value": 71 },
"machine_start_gcode": {
"default_value": "M92 E159\nG21\nG90\nM42 S255 P13;chamber lights\nM42 S255 P12;fume extraction\nM140 S{material_bed_temperature}\n\nM117 Homing Y ......\nG28 Y\nM117 Homing X ......\nG28 X\nM117 Homing Z ......\nG28 Z F100\nG1 Z10 F600\nG1 X70 Y20 F9000;go to wipe point\n\nM190 S{material_bed_temperature}\nM104 S120 T1\nM109 S{material_print_temperature} T0\nM104 S21 T1\n\nM117 purging nozzle....\n\nT0\nG92 E0;set E\nG1 E10 F100\nG92 E0\nG1 E-{retraction_amount} F600\nG92 E0\n\nM117 wiping nozzle....\n\nG1 X1 Y24 F3000\nG1 X70 F9000\n\nM117 Printing .....\n\nG1 E1 F100\nG92 E-1\n"
"default_value": "\nM104 S120 T1\nM104 S120 T2\nM104 S120 T3\n\nM92 E159\n\nG21\nG90\nM42 S255 P13;chamber lights\nM42 S255 P12;fume extraction\n\nM117 Homing Y ......\nG28 Y\nM117 Homing X ......\nG28 X\nM117 Homing Z ......\nG28 Z F100\nG1 Z10 F600\nG1 X70 Y20 F9000;go to wipe point\n\nM190 S{material_bed_temperature_layer_0}\n\nM117 Heating for 50 sec.\nG4 S20\nM117 Heating for 30 sec.\nG4 S20\nM117 Heating for 10 sec.\nM300 S600 P1000\nG4 S9\n\nM117 purging nozzle....\nT0\nG92 E0;set E\nG1 E10 F100\nG92 E0\nG1 E-1 F600\n\nM117 wiping nozzle....\nG1 X1 Y24 F3000\nG1 X70 F9000\n\nM104 S21 T1\nM104 S21 T2\nM104 S21 T3\n\nM117 Printing .....\n"
},
"machine_end_gcode": {
"default_value": "; -- END GCODE --\nM106 S255\nM140 S5\nM104 S5 T0\nM104 S5 T1\nG1 X20.0 Y260.0 F6000\nG4 S7\nM84\nG4 S90\nM107\nM42 P12 S0\nM42 P13 S0\nM84\n; -- end of END GCODE --"
"default_value": "; -- END GCODE --\nM106 S255\nM140 S5\nM104 S5 T0\nM104 S5 T1\nM104 S5 T2\nM104 S5 T3\nG1 X20.0 Y260.0 F6000\nG4 S7\nM84\nG4 S90\nM107\nM42 P12 S0\nM42 P13 S0\nM84\nT0\n; -- end of GCODE --"
},
"layer_height": { "maximum_value": "(0.8 * min(extruderValues('machine_nozzle_size')))" },
"layer_height_0": { "maximum_value": "(0.8 * min(extruderValues('machine_nozzle_size')))" },
"layer_height_0": { "resolve": "0.2 if min(extruderValues('machine_nozzle_size')) < 0.3 else 0.3 "},
"machine_nozzle_heat_up_speed": {"default_value": 20},
"machine_nozzle_cool_down_speed": {"default_value": 20},
"machine_min_cool_heat_time_window": {"default_value": 5}

View file

@ -635,7 +635,7 @@
"description": "Width of a single line. Generally, the width of each line should correspond to the width of the nozzle. However, slightly reducing this value could produce better prints.",
"unit": "mm",
"minimum_value": "0.001",
"minimum_value_warning": "0.5 * machine_nozzle_size",
"minimum_value_warning": "0.1 + 0.4 * machine_nozzle_size",
"maximum_value_warning": "2 * machine_nozzle_size",
"default_value": 0.4,
"type": "float",
@ -649,7 +649,7 @@
"description": "Width of a single wall line.",
"unit": "mm",
"minimum_value": "0.001",
"minimum_value_warning": "0.75 * machine_nozzle_size",
"minimum_value_warning": "0.1 + 0.4 * machine_nozzle_size",
"maximum_value_warning": "2 * machine_nozzle_size",
"value": "line_width",
"default_value": 0.4,
@ -663,7 +663,7 @@
"description": "Width of the outermost wall line. By lowering this value, higher levels of detail can be printed.",
"unit": "mm",
"minimum_value": "0.001",
"minimum_value_warning": "0.75 * machine_nozzle_size if outer_inset_first else 0.1 * machine_nozzle_size",
"minimum_value_warning": "(0.1 + 0.4 * machine_nozzle_size) if outer_inset_first else 0.1 * machine_nozzle_size",
"maximum_value_warning": "2 * machine_nozzle_size",
"default_value": 0.4,
"value": "wall_line_width",
@ -676,7 +676,7 @@
"description": "Width of a single wall line for all wall lines except the outermost one.",
"unit": "mm",
"minimum_value": "0.001",
"minimum_value_warning": "0.5 * machine_nozzle_size",
"minimum_value_warning": "0.1 + 0.4 * machine_nozzle_size",
"maximum_value_warning": "2 * machine_nozzle_size",
"default_value": 0.4,
"value": "wall_line_width",
@ -691,7 +691,7 @@
"description": "Width of a single top/bottom line.",
"unit": "mm",
"minimum_value": "0.001",
"minimum_value_warning": "0.1 * machine_nozzle_size",
"minimum_value_warning": "0.1 + 0.4 * machine_nozzle_size",
"maximum_value_warning": "2 * machine_nozzle_size",
"default_value": 0.4,
"type": "float",
@ -704,7 +704,7 @@
"description": "Width of a single infill line.",
"unit": "mm",
"minimum_value": "0.001",
"minimum_value_warning": "0.75 * machine_nozzle_size",
"minimum_value_warning": "0.1 + 0.4 * machine_nozzle_size",
"maximum_value_warning": "3 * machine_nozzle_size",
"default_value": 0.4,
"type": "float",
@ -718,7 +718,7 @@
"description": "Width of a single skirt or brim line.",
"unit": "mm",
"minimum_value": "0.001",
"minimum_value_warning": "0.75 * machine_nozzle_size",
"minimum_value_warning": "0.1 + 0.4 * machine_nozzle_size",
"maximum_value_warning": "3 * machine_nozzle_size",
"default_value": 0.4,
"type": "float",
@ -733,7 +733,7 @@
"description": "Width of a single support structure line.",
"unit": "mm",
"minimum_value": "0.001",
"minimum_value_warning": "0.75 * machine_nozzle_size",
"minimum_value_warning": "0.1 + 0.4 * machine_nozzle_size",
"maximum_value_warning": "3 * machine_nozzle_size",
"default_value": 0.4,
"type": "float",
@ -750,7 +750,7 @@
"unit": "mm",
"default_value": 0.4,
"minimum_value": "0.001",
"minimum_value_warning": "0.4 * machine_nozzle_size",
"minimum_value_warning": "0.1 + 0.4 * machine_nozzle_size",
"maximum_value_warning": "2 * machine_nozzle_size",
"type": "float",
"enabled": "support_enable and support_interface_enable",
@ -804,7 +804,7 @@
"default_value": 0.4,
"value": "line_width",
"minimum_value": "0.001",
"minimum_value_warning": "0.75 * machine_nozzle_size",
"minimum_value_warning": "0.1 + 0.4 * machine_nozzle_size",
"maximum_value_warning": "2 * machine_nozzle_size",
"settable_per_mesh": false,
"settable_per_extruder": true
@ -1122,7 +1122,7 @@
"default_value": 2,
"minimum_value": "0",
"minimum_value_warning": "infill_line_width",
"value": "0 if infill_sparse_density == 0 else (infill_line_width * 100) / infill_sparse_density * (2 if infill_pattern == 'grid' else (3 if infill_pattern == 'triangles' or infill_pattern == 'cubic' or infill_pattern == 'cubicsubdiv' else (4 if infill_pattern == 'tetrahedral' else 1)))",
"value": "0 if infill_sparse_density == 0 else (infill_line_width * 100) / infill_sparse_density * (2 if infill_pattern == 'grid' else (3 if infill_pattern == 'triangles' or infill_pattern == 'cubic' or infill_pattern == 'cubicsubdiv' else (2 if infill_pattern == 'tetrahedral' else 1)))",
"settable_per_mesh": true
}
}
@ -1156,6 +1156,65 @@
"type": "[int]",
"default_value": "[ ]",
"enabled": "infill_pattern != 'concentric' and infill_pattern != 'concentric_3d' and infill_pattern != 'cubicsubdiv'",
"enabled": "infill_sparse_density > 0",
"settable_per_mesh": true
},
"spaghetti_infill_enabled":
{
"label": "Spaghetti Infill",
"description": "Print the infill every so often, so that the filament will curl up chaotically inside the object. This reduces print time, but the behaviour is rather unpredictable.",
"type": "bool",
"default_value": false,
"enabled": "infill_sparse_density > 0",
"settable_per_mesh": true
},
"spaghetti_max_infill_angle":
{
"label": "Spaghetti Maximum Infill Angle",
"description": "The maximum angle w.r.t. the Z axis of the inside of the print for areas which are to be filled with spaghetti infill afterwards. Lowering this value causes more angled parts in your model to be filled on each layer.",
"unit": "°",
"type": "float",
"default_value": 10,
"minimum_value": "0",
"maximum_value": "90",
"maximum_value_warning": "45",
"enabled": "infill_sparse_density > 0 and spaghetti_infill_enabled",
"settable_per_mesh": true
},
"spaghetti_max_height":
{
"label": "Spaghetti Infill Maximum Height",
"description": "The maximum height of inside space which can be combined and filled from the top.",
"unit": "mm",
"type": "float",
"default_value": 2.0,
"minimum_value": "layer_height",
"maximum_value_warning": "10.0",
"enabled": "infill_sparse_density > 0 and spaghetti_infill_enabled",
"settable_per_mesh": true
},
"spaghetti_inset":
{
"label": "Spaghetti Inset",
"description": "The offset from the walls from where the spaghetti infill will be printed.",
"unit": "mm",
"type": "float",
"default_value": 0.2,
"minimum_value_warning": "0",
"maximum_value_warning": "5.0",
"enabled": "infill_sparse_density > 0 and spaghetti_infill_enabled",
"settable_per_mesh": true
},
"spaghetti_flow":
{
"label": "Spaghetti Flow",
"description": "Adjusts the density of the spaghetti infill. Note that the Infill Density only controls the line spacing of the filling pattern, not the amount of extrusion for spaghetti infill.",
"unit": "%",
"type": "float",
"default_value": 20,
"minimum_value": "0",
"maximum_value_warning": "100",
"enabled": "infill_sparse_density > 0 and spaghetti_infill_enabled",
"settable_per_mesh": true
},
"sub_div_rad_mult":
@ -1264,9 +1323,9 @@
"default_value": 0.1,
"minimum_value": "resolveOrValue('layer_height')",
"maximum_value_warning": "0.75 * machine_nozzle_size",
"maximum_value": "resolveOrValue('layer_height') * 8",
"maximum_value": "resolveOrValue('layer_height') * (1.45 if spaghetti_infill_enabled else 8)",
"value": "resolveOrValue('layer_height')",
"enabled": "infill_sparse_density > 0",
"enabled": "infill_sparse_density > 0 and not spaghetti_infill_enabled",
"settable_per_mesh": true
},
"gradual_infill_steps":
@ -1277,8 +1336,8 @@
"type": "int",
"minimum_value": "0",
"maximum_value_warning": "4",
"maximum_value": "(20 - math.log(infill_line_distance) / math.log(2)) if infill_line_distance > 0 else 0",
"enabled": "infill_sparse_density > 0 and infill_pattern != 'cubicsubdiv'",
"maximum_value": "(20 - math.log(infill_line_distance) / math.log(2)) if infill_line_distance > 0 and not spaghetti_infill_enabled else 0",
"enabled": "infill_sparse_density > 0 and infill_pattern != 'cubicsubdiv' and not spaghetti_infill_enabled",
"settable_per_mesh": true
},
"gradual_infill_step_height":
@ -1356,7 +1415,7 @@
"max_skin_angle_for_expansion":
{
"label": "Maximum Skin Angle for Expansion",
"description": "Top and or bottom surfaces of your object with an angle larger than this setting, won't have their top/bottom skin expanded. This avoids expanding the narrow skin areas that are created when the model surface has a near vertical slope. An angle of 0° is horizontal, while an angle of 90° is vertical.",
"description": "Top and/or bottom surfaces of your object with an angle larger than this setting, won't have their top/bottom skin expanded. This avoids expanding the narrow skin areas that are created when the model surface has a near vertical slope. An angle of 0° is horizontal, while an angle of 90° is vertical.",
"unit": "°",
"type": "float",
"minimum_value": "0",
@ -4405,6 +4464,40 @@
"settable_per_meshgroup": false,
"settable_globally": false
},
"mold_enabled":
{
"label": "Mold",
"description": "Print models as a mold, which can be cast in order to get a model which resembles the models on the build plate.",
"type": "bool",
"default_value": false,
"settable_per_mesh": true
},
"mold_width":
{
"label": "Minimal Mold Width",
"description": "The minimal distance between the ouside of the mold and the outside of the model.",
"unit": "mm",
"type": "float",
"minimum_value_warning": "wall_line_width_0 * 2",
"maximum_value_warning": "100",
"default_value": 5,
"settable_per_mesh": true,
"enabled": "mold_enabled"
},
"mold_angle":
{
"label": "Mold Angle",
"description": "The angle of overhang of the outer walls created for the mold. 0° will make the outer shell of the mold vertical, while 90° will make the outside of the model follow the contour of the model.",
"unit": "°",
"type": "float",
"minimum_value": "-89",
"minimum_value_warning": "0",
"maximum_value_warning": "support_angle",
"maximum_value": "90",
"default_value": 40,
"settable_per_mesh": true,
"enabled": "mold_enabled"
},
"infill_mesh_order":
{
"label": "Infill Mesh Order",
@ -4461,7 +4554,8 @@
"description": "Spiralize smooths out the Z move of the outer edge. This will create a steady Z increase over the whole print. This feature turns a solid model into a single walled print with a solid bottom. This feature used to be called Joris in older versions.",
"type": "bool",
"default_value": false,
"settable_per_mesh": true
"settable_per_mesh": false,
"settable_per_extruder": false
}
}
},

View file

@ -0,0 +1,41 @@
{
"id": "imade3d_jellybox",
"version": 2,
"name": "IMADE3D JellyBOX",
"inherits": "fdmprinter",
"metadata": {
"visible": true,
"author": "IMADE3D",
"manufacturer": "IMADE3D",
"category": "Other",
"platform": "imade3d_jellybox_platform.stl",
"platform_offset": [ 0, -0.3, 0],
"file_formats": "text/x-gcode",
"preferred_variant": "*0.4*",
"preferred_material": "*generic_pla*",
"preferred_quality": "*fast*",
"has_materials": true,
"has_variants": true,
"has_machine_materials": true,
"has_machine_quality": true
},
"overrides": {
"machine_head_with_fans_polygon": { "default_value": [[ 0, 0 ],[ 0, 0 ],[ 0, 0 ],[ 0, 0 ]]},
"machine_name": { "default_value": "IMADE3D JellyBOX" },
"machine_width": { "default_value": 170 },
"machine_height": { "default_value": 145 },
"machine_depth": { "default_value": 160 },
"machine_nozzle_size": { "default_value": 0.4 },
"material_diameter": { "default_value": 1.75 },
"machine_heated_bed": { "default_value": true },
"machine_center_is_zero": { "default_value": false },
"machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" },
"machine_start_gcode": {
"default_value": ";---------------------------------------\n; ; ; Jellybox Start Script Begin ; ; ;\n;_______________________________________\n; M92 E140 ;optionally adjust steps per mm for your filament\n\n; Print Settings Summary\n; (leave these alone: this is only a list of the slicing settings)\n; (overwriting these values will NOT change your printer's behavior)\n; sliced for : {machine_name}\n; nozzle diameter : {machine_nozzle_size}\n; filament diameter : {material_diameter}\n; layer height : {layer_height}\n; 1st layer height : {layer_height_0}\n; line width : {line_width}\n; outer wall wipe dist. : {wall_0_wipe_dist}\n; infill line width : {infill_line_width}\n; wall thickness : {wall_thickness}\n; top thickness : {top_thickness}\n; bottom thickness : {bottom_thickness}\n; infill density : {infill_sparse_density}\n; infill pattern : {infill_pattern}\n; print temperature : {material_print_temperature}\n; 1st layer print temp. : {material_print_temperature_layer_0}\n; heated bed temperature : {material_bed_temperature}\n; 1st layer bed temp. : {material_bed_temperature_layer_0}\n; regular fan speed : {cool_fan_speed_min}\n; max fan speed : {cool_fan_speed_max}\n; retraction amount : {retraction_amount}\n; retr. retract speed : {retraction_retract_speed}\n; retr. prime speed : {retraction_prime_speed}\n; build plate adhesion : {adhesion_type}\n; support ? {support_enable}\n; spiralized ? {magic_spiralize}\n\nM117 Preparing ;write Preparing\nM140 S{material_bed_temperature_layer_0} ;set bed temperature and move on\nM104 S{material_print_temperature_layer_0} ;set extruder temperature and move on\nM206 X10.0 Y0.0 ;set x homing offset for default bed leveling\nG21 ;metric values\nG90 ;absolute positioning\nM107 ;start with the fan off\nM82 ;set extruder to absolute mode\nG28 ;home all axes\nM203 Z4 ;slow Z speed down for greater accuracy when probing\nG29 ;auto bed leveling procedure\nM203 Z7 ;pick up z speed again for printing\nM190 S{material_bed_temperature_layer_0} ;wait for the bed to reach desired temperature\nM109 S{material_print_temperature_layer_0} ;wait for the extruder to reach desired temperature\nG92 E0 ;reset the extruder position\nG1 F1500 E15 ;extrude 15mm of feed stock\nG92 E0 ;reset the extruder position again\nM117 Print starting ;write Print starting\n;---------------------------------------------\n; ; ; Jellybox Printer Start Script End ; ; ;\n;_____________________________________________\n"
},
"machine_end_gcode": {
"default_value": "\n;---------------------------------\n;;; Jellybox End Script Begin ;;;\n;_________________________________\nM117 Finishing Up ;write Finishing Up\n\nM104 S0 ;extruder heater off\nM140 S0 ;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\nG90 ;absolute positioning\nG28 X ;home x, so the head is out of the way\nG1 Y100 ;move Y forward, so the print is more accessible\nM84 ;steppers off\n\nM117 Print finished ;write Print finished\n;---------------------------------------\n;;; Jellybox End Script End ;;;\n;_______________________________________"
}
}
}

View file

@ -1,35 +0,0 @@
{
"id": "jellybox",
"version": 2,
"name": "JellyBOX",
"inherits": "fdmprinter",
"metadata": {
"visible": true,
"author": "IMADE3D",
"manufacturer": "IMADE3D",
"category": "Other",
"platform": "jellybox_platform.stl",
"platform_offset": [ 0, -0.3, 0],
"file_formats": "text/x-gcode",
"has_materials": true,
"has_machine_materials": true
},
"overrides": {
"machine_name": { "default_value": "IMADE3D JellyBOX" },
"machine_width": { "default_value": 170 },
"machine_height": { "default_value": 145 },
"machine_depth": { "default_value": 160 },
"machine_nozzle_size": { "default_value": 0.4 },
"material_diameter": { "default_value": 1.75 },
"machine_heated_bed": { "default_value": true },
"machine_center_is_zero": { "default_value": false },
"machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" },
"machine_start_gcode": {
"default_value": ";---------------------------------------\n; ; ; Jellybox Start Script Begin ; ; ;\n;_______________________________________\n; M92 E140 ;optionally adjust steps per mm for your filament\n\n; Print Settings Summary\n; (overwriting these values will NOT change your printer's behavior)\n; sliced for: {machine_name}\n; nozzle diameter: {machine_nozzle_size}\n; filament diameter: {material_diameter}\n; layer height: {layer_height}\n; 1st layer height: {layer_height_0}\n; line width: {line_width}\n; wall thickness: {wall_thickness}\n; infill density: {infill_sparse_density}\n; infill pattern: {infill_pattern}\n; print temperature: {material_print_temperature}\n; heated bed temperature: {material_bed_temperature}\n; regular fan speed: {cool_fan_speed_min}\n; max fan speed: {cool_fan_speed_max}\n; support? {support_enable}\n; spiralized? {magic_spiralize}\n\nM117 Preparing ;write Preparing\nM140 S{material_bed_temperature} ;set bed temperature and move on\nM104 S{material_print_temperature} ;set extruder temperature and move on\nM206 X10.0 Y0.0 ;set x homing offset for default bed leveling\nG21 ;metric values\nG90 ;absolute positioning\nM107 ;start with the fan off\nM82 ;set extruder to absolute mode\nG28 ;home all axes\nM203 Z5 ;slow Z speed down for greater accuracy when probing\nG29 ;auto bed leveling procedure\nM203 Z7 ;pick up z speed again for printing\nM190 S{material_bed_temperature} ;wait for the bed to reach desired temperature\nM109 S{material_print_temperature} ;wait for the extruder to reach desired temperature\nG92 E0 ;reset the extruder position\nG1 F200 E5 ;extrude 5mm of feed stock\nG92 E0 ;reset the extruder position again\nM117 Print starting ;write Print starting\n;---------------------------------------------\n; ; ; Jellybox Printer Start Script End ; ; ;\n;_____________________________________________"
},
"machine_end_gcode": {
"default_value": "\n;---------------------------------\n;;; Jellybox End Script Begin ;;;\n;_________________________________\nM117 Finishing Up ;write Finishing Up\n\nM104 S0 ;extruder heater off\nM140 S0 ;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\nG90 ;absolute positioning\nG28 X ;home x, so the head is out of the way\nG1 Y100 ;move Y forward, so the print is more accessible\nM84 ;steppers off\n\nM117 Print finished ;write Print finished\n;---------------------------------------\n;;; Jellybox End Script End ;;;\n;_______________________________________"
}
}
}

View file

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

View file

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

View file

@ -76,7 +76,7 @@
"value": "100"
},
"material_bed_temperature": {
"visible": "False"
"enabled": false
},
"material_diameter": {
"value": "1.75"

View file

@ -16,10 +16,10 @@
"machine_nozzle_offset_x": { "default_value": 0.0 },
"machine_nozzle_offset_y": { "default_value": 0.0 },
"machine_extruder_start_code": {
"default_value": "\n;start extruder_0\nM117 Heating nozzles....\nM104 S190 T0\nG1 X70 Y20 F9000\nM109 S190 T0\n\nM117 purging nozzle\nG92 E0\nG1 E6 F90\nG92 E0\nG1 E-2 F300\nG92 E0\nM117 wiping nozzle\nG1 X1 Y28 F3000\nG1 X70 F6000\n\nM117 printing\n"
"default_value": "\n;start extruder_0\n\nM117 printing\n"
},
"machine_extruder_end_code": {
"default_value": "\nM104 T0 S155\n;end extruder_0\nM117 temp is {material_print_temp}"
"default_value": "\nM104 T0 S155\nG91\nG1 Z0.5 F900\nG90\nG1 X1 Y260 F9000\n;end extruder_0\nM117 temp is {material_print_temp}"
}
}
}

View file

@ -16,10 +16,10 @@
"machine_nozzle_offset_x": { "default_value": 24.0 },
"machine_nozzle_offset_y": { "default_value": 0.0 },
"machine_extruder_start_code": {
"default_value": "\n;start extruder_1\nM117 Heating nozzles....\nM104 S190 T1\nG1 X70 Y20 F9000\nM109 S190 T1\n\nM117 purging nozzle\nG92 E0\nG1 E6 F90\nG92 E0\nG1 E-2 F300\nG92 E0\n\nM117 wiping nozzle\nG1 X1 Y28 F3000\nG1 X70 F6000\n\nM117 printing\n"
"default_value": "\n;start extruder_1\n\nM117 printing\n"
},
"machine_extruder_end_code": {
"default_value": "\nM104 T1 S155\n;end extruder_1\n"
"default_value": "\nM104 T1 S155\nG91\nG1 Z0.5 F900\nG90\nG1 X1 Y260 F9000\n;end extruder_1\n"
}
}
}

View file

@ -16,10 +16,10 @@
"machine_nozzle_offset_x": { "default_value": 0.0 },
"machine_nozzle_offset_y": { "default_value": 60.0 },
"machine_extruder_start_code": {
"default_value": "\n;start extruder_2\nM117 Heating nozzles....\nM104 S190 T2\nG1 X70 Y20 F9000\nM109 S190 T2\n\nM117 purging nozzle\nG92 E0\nG1 E6 F90\nG92 E0\nG1 E-2 F300\nG92 E0\n\nM117 wiping nozzle\nG1 X1 Y28 F3000\nG1 X70 F6000\n\nM117 printing\n"
"default_value": "\n;start extruder_2\n\nM117 printing\n"
},
"machine_extruder_end_code": {
"default_value": "\nM104 T2 S155\n;end extruder_2\n"
"default_value": "\nM104 T2 S155\nG91\nG1 Z0.5 F900\nG90\nG1 X1 Y260 F9000\n;end extruder_2\n"
}
}
}

View file

@ -16,10 +16,10 @@
"machine_nozzle_offset_x": { "default_value": 24.0 },
"machine_nozzle_offset_y": { "default_value": 60.0 },
"machine_extruder_start_code": {
"default_value": "\n;start extruder_3\nM117 Heating nozzles....\nM104 S190 T3\nG1 X70 Y20 F9000\nM109 S190 T3\n\nM117 purging nozzle\nG92 E0\nG1 E6 F90\nG92 E0\nG1 E-2 F300\nG92 E0\n\nM117 wiping nozzle\nG1 X1 Y28 F3000\nG1 X70 F6000\n\nM117 printing\n"
"default_value": "\n;start extruder_3\n\nM117 printing\n"
},
"machine_extruder_end_code": {
"default_value": "\nM104 T3 S155\n;end extruder_3\n"
"default_value": "\nM104 T3 S155\nG91\nG1 Z0.5 F900\nG90\nG1 X1 Y260 F9000\n;end extruder_3\n"
}
}
}

View file

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

View file

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

View file

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

View file

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

View file

@ -23,7 +23,7 @@
"machine_extruder_end_pos_x": { "default_value": 213 },
"machine_extruder_end_pos_y": { "default_value": 207 },
"machine_nozzle_head_distance": { "default_value": 2.7 },
"extruder_prime_pos_x": { "default_value": 170 },
"extruder_prime_pos_x": { "default_value": 9 },
"extruder_prime_pos_y": { "default_value": 6 },
"extruder_prime_pos_z": { "default_value": 2 }
}

View file

@ -23,7 +23,7 @@
"machine_extruder_end_pos_x": { "default_value": 213 },
"machine_extruder_end_pos_y": { "default_value": 189 },
"machine_nozzle_head_distance": { "default_value": 4.2 },
"extruder_prime_pos_x": { "default_value": 182 },
"extruder_prime_pos_x": { "default_value": 222 },
"extruder_prime_pos_y": { "default_value": 6 },
"extruder_prime_pos_z": { "default_value": 2 }
}

View file

@ -23,7 +23,7 @@
"machine_extruder_end_pos_x": { "default_value": 213 },
"machine_extruder_end_pos_y": { "default_value": 207 },
"machine_nozzle_head_distance": { "default_value": 2.7 },
"extruder_prime_pos_x": { "default_value": 170 },
"extruder_prime_pos_x": { "default_value": 9 },
"extruder_prime_pos_y": { "default_value": 6 },
"extruder_prime_pos_z": { "default_value": 2 }
}

View file

@ -23,7 +23,7 @@
"machine_extruder_end_pos_x": { "default_value": 213 },
"machine_extruder_end_pos_y": { "default_value": 189 },
"machine_nozzle_head_distance": { "default_value": 4.2 },
"extruder_prime_pos_x": { "default_value": 182 },
"extruder_prime_pos_x": { "default_value": 222 },
"extruder_prime_pos_y": { "default_value": 6 },
"extruder_prime_pos_z": { "default_value": 2 }
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,13 +1,18 @@
# Cura JSON setting files
# Copyright (C) 2017 Ultimaker
# This file is distributed under the same license as the Cura package.
# Ruben Dulek <r.dulek@ultimaker.com>, 2017.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: Uranium json setting files\n"
"Report-Msgid-Bugs-To: http://github.com/ultimaker/uranium\n"
"POT-Creation-Date: 2016-12-28 10:51+0000\n"
"PO-Revision-Date: 2017-01-12 15:51+0100\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE\n"
"Language: \n"
"Project-Id-Version: Cura 2.5\n"
"Report-Msgid-Bugs-To: http://github.com/Ultimaker/Cura\n"
"POT-Creation-Date: 2017-03-27 17:27+0000\n"
"PO-Revision-Date: 2017-04-04 11:27+0200\n"
"Last-Translator: Bothof <info@bothof.nl>\n"
"Language-Team: Bothof <info@bothof.nl>\n"
"Language: de\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"

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

View file

@ -1,13 +1,18 @@
# Cura JSON setting files
# Copyright (C) 2017 Ultimaker
# This file is distributed under the same license as the Cura package.
# Ruben Dulek <r.dulek@ultimaker.com>, 2017.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: Uranium json setting files\n"
"Report-Msgid-Bugs-To: http://github.com/ultimaker/uranium\n"
"POT-Creation-Date: 2016-12-28 10:51+0000\n"
"PO-Revision-Date: 2017-01-12 15:51+0100\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE\n"
"Language: \n"
"Project-Id-Version: Cura 2.5\n"
"Report-Msgid-Bugs-To: http://github.com/Ultimaker/Cura\n"
"POT-Creation-Date: 2017-03-27 17:27+0000\n"
"PO-Revision-Date: 2017-04-04 11:27+0200\n"
"Last-Translator: Bothof <info@bothof.nl>\n"
"Language-Team: Bothof <info@bothof.nl>\n"
"Language: es\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"

File diff suppressed because it is too large Load diff

View file

@ -3,7 +3,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Uranium json setting files\n"
"Report-Msgid-Bugs-To: http://github.com/ultimaker/uranium\n"
"POT-Creation-Date: 2016-12-28 10:51+0000\n"
"POT-Creation-Date: 2017-03-27 17:27+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE\n"

View file

@ -3,7 +3,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Uranium json setting files\n"
"Report-Msgid-Bugs-To: http://github.com/ultimaker/uranium\n"
"POT-Creation-Date: 2016-12-28 10:51+0000\n"
"POT-Creation-Date: 2017-03-27 17:27+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE\n"
@ -268,6 +268,18 @@ msgid ""
"extruder is no longer used."
msgstr ""
#: fdmprinter.def.json
msgctxt "machine_nozzle_temp_enabled label"
msgid "Enable Nozzle Temperature Control"
msgstr ""
#: fdmprinter.def.json
msgctxt "machine_nozzle_temp_enabled description"
msgid ""
"Whether to control temperature from Cura. Turn this off to control nozzle "
"temperature from outside of Cura."
msgstr ""
#: fdmprinter.def.json
msgctxt "machine_nozzle_heat_up_speed label"
msgid "Heat up speed"
@ -856,6 +868,47 @@ msgctxt "top_bottom_pattern option zigzag"
msgid "Zig Zag"
msgstr ""
#: fdmprinter.def.json
msgctxt "top_bottom_pattern_0 label"
msgid "Bottom Pattern Initial Layer"
msgstr ""
#: fdmprinter.def.json
msgctxt "top_bottom_pattern_0 description"
msgid "The pattern on the bottom of the print on the first layer."
msgstr ""
#: fdmprinter.def.json
msgctxt "top_bottom_pattern_0 option lines"
msgid "Lines"
msgstr ""
#: fdmprinter.def.json
msgctxt "top_bottom_pattern_0 option concentric"
msgid "Concentric"
msgstr ""
#: fdmprinter.def.json
msgctxt "top_bottom_pattern_0 option zigzag"
msgid "Zig Zag"
msgstr ""
#: fdmprinter.def.json
msgctxt "skin_angles label"
msgid "Top/Bottom Line Directions"
msgstr ""
#: fdmprinter.def.json
msgctxt "skin_angles description"
msgid ""
"A list of integer line directions to use when the top/bottom layers use the "
"lines or zig zag pattern. Elements from the list are used sequentially as "
"the layers progress and when the end of the list is reached, it starts at "
"the beginning again. The list items are separated by commas and the whole "
"list is contained in square brackets. Default is an empty list which means "
"use the traditional default angles (45 and 135 degrees)."
msgstr ""
#: fdmprinter.def.json
msgctxt "wall_0_inset label"
msgid "Outer Wall Inset"
@ -1124,6 +1177,22 @@ msgctxt "infill_pattern option zigzag"
msgid "Zig Zag"
msgstr ""
#: fdmprinter.def.json
msgctxt "infill_angles label"
msgid "Infill Line Directions"
msgstr ""
#: fdmprinter.def.json
msgctxt "infill_angles description"
msgid ""
"A list of integer line directions to use. Elements from the list are used "
"sequentially as the layers progress and when the end of the list is reached, "
"it starts at the beginning again. The list items are separated by commas and "
"the whole list is contained in square brackets. Default is an empty list "
"which means use the traditional default angles (45 and 135 degrees for the "
"lines and zig zag patterns and 45 degrees for all other patterns)."
msgstr ""
#: fdmprinter.def.json
msgctxt "sub_div_rad_mult label"
msgid "Cubic Subdivision Radius"
@ -1262,6 +1331,97 @@ msgid ""
"through the surface."
msgstr ""
#: fdmprinter.def.json
msgctxt "min_infill_area label"
msgid "Minimum Infill Area"
msgstr ""
#: fdmprinter.def.json
msgctxt "min_infill_area description"
msgid "Don't generate areas of infill smaller than this (use skin instead)."
msgstr ""
#: fdmprinter.def.json
msgctxt "expand_skins_into_infill label"
msgid "Expand Skins Into Infill"
msgstr ""
#: fdmprinter.def.json
msgctxt "expand_skins_into_infill description"
msgid ""
"Expand skin areas of top and/or bottom skin of flat surfaces. By default, "
"skins stop under the wall lines that surround infill but this can lead to "
"holes appearing when the infill density is low. This setting extends the "
"skins beyond the wall lines so that the infill on the next layer rests on "
"skin."
msgstr ""
#: fdmprinter.def.json
msgctxt "expand_upper_skins label"
msgid "Expand Upper Skins"
msgstr ""
#: fdmprinter.def.json
msgctxt "expand_upper_skins description"
msgid ""
"Expand upper skin areas (areas with air above) so that they support infill "
"above."
msgstr ""
#: fdmprinter.def.json
msgctxt "expand_lower_skins label"
msgid "Expand Lower Skins"
msgstr ""
#: fdmprinter.def.json
msgctxt "expand_lower_skins description"
msgid ""
"Expand lower skin areas (areas with air below) so that they are anchored by "
"the infill layers above and below."
msgstr ""
#: fdmprinter.def.json
msgctxt "expand_skins_expand_distance label"
msgid "Skin Expand Distance"
msgstr ""
#: fdmprinter.def.json
msgctxt "expand_skins_expand_distance description"
msgid ""
"The distance the skins are expanded into the infill. The default distance is "
"enough to bridge the gap between the infill lines and will stop holes "
"appearing in the skin where it meets the wall when the infill density is "
"low. A smaller distance will often be sufficient."
msgstr ""
#: fdmprinter.def.json
msgctxt "max_skin_angle_for_expansion label"
msgid "Maximum Skin Angle for Expansion"
msgstr ""
#: fdmprinter.def.json
msgctxt "max_skin_angle_for_expansion description"
msgid ""
"Top and/or bottom surfaces of your object with an angle larger than this "
"setting, won't have their top/bottom skin expanded. This avoids expanding "
"the narrow skin areas that are created when the model surface has a near "
"vertical slope. An angle of 0° is horizontal, while an angle of 90° is "
"vertical."
msgstr ""
#: fdmprinter.def.json
msgctxt "min_skin_width_for_expansion label"
msgid "Minimum Skin Width for Expansion"
msgstr ""
#: fdmprinter.def.json
msgctxt "min_skin_width_for_expansion description"
msgid ""
"Skin areas narrower than this are not expanded. This avoids expanding the "
"narrow skin areas that are created when the model surface has a slope close "
"to the vertical."
msgstr ""
#: fdmprinter.def.json
msgctxt "material label"
msgid "Material"
@ -1304,8 +1464,7 @@ msgstr ""
#: fdmprinter.def.json
msgctxt "material_print_temperature description"
msgid ""
"The temperature used for printing. Set at 0 to pre-heat the printer manually."
msgid "The temperature used for printing."
msgstr ""
#: fdmprinter.def.json
@ -1376,8 +1535,8 @@ msgstr ""
#: fdmprinter.def.json
msgctxt "material_bed_temperature description"
msgid ""
"The temperature used for the heated build plate. Set at 0 to pre-heat the "
"printer manually."
"The temperature used for the heated build plate. If this is 0, the bed will "
"not heat up for this print."
msgstr ""
#: fdmprinter.def.json
@ -2216,6 +2375,16 @@ msgctxt "retraction_combing option noskin"
msgid "No Skin"
msgstr ""
#: fdmprinter.def.json
msgctxt "travel_retract_before_outer_wall label"
msgid "Retract Before Outer Wall"
msgstr ""
#: fdmprinter.def.json
msgctxt "travel_retract_before_outer_wall description"
msgid "Always retract when moving to start an outer wall."
msgstr ""
#: fdmprinter.def.json
msgctxt "travel_avoid_other_parts label"
msgid "Avoid Printed Parts When Traveling"
@ -2671,7 +2840,7 @@ msgctxt "support_z_distance description"
msgid ""
"Distance from the top/bottom of the support structure to the print. This gap "
"provides clearance to remove the supports after the model is printed. This "
"value is rounded down to a multiple of the layer height."
"value is rounded up to a multiple of the layer height."
msgstr ""
#: fdmprinter.def.json

File diff suppressed because it is too large Load diff

View file

@ -1,13 +1,18 @@
# Cura JSON setting files
# Copyright (C) 2017 Ultimaker
# This file is distributed under the same license as the Cura package.
# Ruben Dulek <r.dulek@ultimaker.com>, 2017.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: Uranium json setting files\n"
"Report-Msgid-Bugs-To: http://github.com/ultimaker/uranium\n"
"POT-Creation-Date: 2016-12-28 10:51+0000\n"
"PO-Revision-Date: 2017-01-12 15:51+0100\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE\n"
"Language: \n"
"Project-Id-Version: Cura 2.5\n"
"Report-Msgid-Bugs-To: http://github.com/Ultimaker/Cura\n"
"POT-Creation-Date: 2017-03-27 17:27+0000\n"
"PO-Revision-Date: 2017-04-04 11:27+0200\n"
"Last-Translator: Bothof <info@bothof.nl>\n"
"Language-Team: Bothof <info@bothof.nl>\n"
"Language: fi\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,13 +1,18 @@
# Cura JSON setting files
# Copyright (C) 2017 Ultimaker
# This file is distributed under the same license as the Cura package.
# Ruben Dulek <r.dulek@ultimaker.com>, 2017.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: Uranium json setting files\n"
"Report-Msgid-Bugs-To: http://github.com/ultimaker/uranium\n"
"POT-Creation-Date: 2016-12-28 10:51+0000\n"
"PO-Revision-Date: 2017-01-12 15:51+0100\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE\n"
"Language: \n"
"Project-Id-Version: Cura 2.5\n"
"Report-Msgid-Bugs-To: http://github.com/Ultimaker/Cura\n"
"POT-Creation-Date: 2017-03-27 17:27+0000\n"
"PO-Revision-Date: 2017-04-04 11:27+0200\n"
"Last-Translator: Bothof <info@bothof.nl>\n"
"Language-Team: Bothof <info@bothof.nl>\n"
"Language: fr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,13 +1,18 @@
# Cura JSON setting files
# Copyright (C) 2017 Ultimaker
# This file is distributed under the same license as the Cura package.
# Ruben Dulek <r.dulek@ultimaker.com>, 2017.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: Uranium json setting files\n"
"Report-Msgid-Bugs-To: http://github.com/ultimaker/uranium\n"
"POT-Creation-Date: 2016-12-28 10:51+0000\n"
"PO-Revision-Date: 2017-01-12 15:51+0100\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE\n"
"Language: \n"
"Project-Id-Version: Cura 2.5\n"
"Report-Msgid-Bugs-To: http://github.com/Ultimaker/Cura\n"
"POT-Creation-Date: 2017-03-27 17:27+0000\n"
"PO-Revision-Date: 2017-04-04 11:27+0200\n"
"Last-Translator: Bothof <info@bothof.nl>\n"
"Language-Team: Bothof <info@bothof.nl>\n"
"Language: it\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"

File diff suppressed because it is too large Load diff

3208
resources/i18n/jp/cura.po Normal file

File diff suppressed because it is too large Load diff

3330
resources/i18n/ko/cura.po Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,13 +1,18 @@
# Cura JSON setting files
# Copyright (C) 2017 Ultimaker
# This file is distributed under the same license as the Cura package.
# Ruben Dulek <r.dulek@ultimaker.com>, 2017.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: Uranium json setting files\n"
"Report-Msgid-Bugs-To: http://github.com/ultimaker/uranium\n"
"POT-Creation-Date: 2016-12-28 10:51+0000\n"
"PO-Revision-Date: 2017-01-12 15:51+0100\n"
"Last-Translator: Ruben Dulek <r.dulek@ultimaker.com>\n"
"Language-Team: Ultimaker\n"
"Language: \n"
"Project-Id-Version: Cura 2.5\n"
"Report-Msgid-Bugs-To: http://github.com/Ultimaker/Cura\n"
"POT-Creation-Date: 2017-03-27 17:27+0000\n"
"PO-Revision-Date: 2017-04-04 11:27+0200\n"
"Last-Translator: Bothof <info@bothof.nl>\n"
"Language-Team: Bothof <info@bothof.nl>\n"
"Language: nl\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -3,7 +3,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Uranium json setting files\n"
"Report-Msgid-Bugs-To: http://github.com/ultimaker/uranium\n"
"POT-Creation-Date: 2016-12-28 10:51+0000\n"
"POT-Creation-Date: 2017-03-27 17:27+0000\n"
"PO-Revision-Date: 2016-01-25 05:05-0300\n"
"Last-Translator: Cláudio Sampaio <patola@makerlinux.com.br>\n"
"Language-Team: LANGUAGE\n"
@ -70,12 +70,8 @@ msgstr "Posição de Início do Extrusor Absoluta"
#: fdmextruder.def.json
msgctxt "machine_extruder_start_pos_abs description"
msgid ""
"Make the extruder starting position absolute rather than relative to the "
"last-known location of the head."
msgstr ""
"Faz a posição de início do extrusor ser absoluta ao invés de relativa à "
"última posição conhecida da cabeça de impressão."
msgid "Make the extruder starting position absolute rather than relative to the last-known location of the head."
msgstr "Faz a posição de início do extrusor ser absoluta ao invés de relativa à última posição conhecida da cabeça de impressão."
#: fdmextruder.def.json
msgctxt "machine_extruder_start_pos_x label"
@ -114,12 +110,8 @@ msgstr "Posição Final do Extrusor Absoluta"
#: fdmextruder.def.json
msgctxt "machine_extruder_end_pos_abs description"
msgid ""
"Make the extruder ending position absolute rather than relative to the last-"
"known location of the head."
msgstr ""
"Faz a posição final do extrusor ser absoluta ao invés de relativa à última "
"posição conhecida da cabeça de impressão."
msgid "Make the extruder ending position absolute rather than relative to the last-known location of the head."
msgstr "Faz a posição final do extrusor ser absoluta ao invés de relativa à última posição conhecida da cabeça de impressão."
#: fdmextruder.def.json
msgctxt "machine_extruder_end_pos_x label"
@ -148,11 +140,8 @@ msgstr "Posição Z de Purga do Extrusor"
#: fdmextruder.def.json
msgctxt "extruder_prime_pos_z description"
msgid ""
"The Z coordinate of the position where the nozzle primes at the start of "
"printing."
msgstr ""
"A coordenada Z da posição onde o bico faz a purga no início da impressão."
msgid "The Z coordinate of the position where the nozzle primes at the start of printing."
msgstr "A coordenada Z da posição onde o bico faz a purga no início da impressão."
#: fdmextruder.def.json
msgctxt "platform_adhesion label"
@ -171,11 +160,8 @@ msgstr "Posição X de Purga do Extrusor"
#: fdmextruder.def.json
msgctxt "extruder_prime_pos_x description"
msgid ""
"The X coordinate of the position where the nozzle primes at the start of "
"printing."
msgstr ""
"A coordenada X da posição onde o bico faz a purga no início da impressão."
msgid "The X coordinate of the position where the nozzle primes at the start of printing."
msgstr "A coordenada X da posição onde o bico faz a purga no início da impressão."
#: fdmextruder.def.json
msgctxt "extruder_prime_pos_y label"
@ -184,8 +170,5 @@ msgstr "Posição Y de Purga do Extrusor"
#: fdmextruder.def.json
msgctxt "extruder_prime_pos_y description"
msgid ""
"The Y coordinate of the position where the nozzle primes at the start of "
"printing."
msgstr ""
"A coordenada Y da posição onde o bico faz a purga no início da impressão."
msgid "The Y coordinate of the position where the nozzle primes at the start of printing."
msgstr "A coordenada Y da posição onde o bico faz a purga no início da impressão."

File diff suppressed because it is too large Load diff

1587
resources/i18n/ru/cura.po Normal file → Executable file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

2554
resources/i18n/ru/fdmprinter.def.json.po Normal file → Executable file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,13 +1,18 @@
# Cura JSON setting files
# Copyright (C) 2017 Ultimaker
# This file is distributed under the same license as the Cura package.
# Ruben Dulek <r.dulek@ultimaker.com>, 2017.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: Uranium json setting files\n"
"Report-Msgid-Bugs-To: http://github.com/ultimaker/uranium\n"
"POT-Creation-Date: 2016-12-28 10:51+0000\n"
"PO-Revision-Date: 2017-01-12 15:51+0100\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE\n"
"Language: \n"
"Project-Id-Version: Cura 2.5\n"
"Report-Msgid-Bugs-To: http://github.com/Ultimaker/Cura\n"
"POT-Creation-Date: 2017-03-27 17:27+0000\n"
"PO-Revision-Date: 2017-04-04 11:27+0200\n"
"Last-Translator: Bothof <info@bothof.nl>\n"
"Language-Team: Bothof <info@bothof.nl>\n"
"Language: tr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"

File diff suppressed because it is too large Load diff

44
resources/qml/Actions.qml Normal file → Executable file
View file

@ -12,7 +12,6 @@ Item
{
property alias newProject: newProjectAction;
property alias open: openAction;
property alias loadWorkspace: loadWorkspaceAction;
property alias quit: quitAction;
property alias undo: undoAction;
@ -32,6 +31,8 @@ Item
property alias selectAll: selectAllAction;
property alias deleteAll: deleteAllAction;
property alias reloadAll: reloadAllAction;
property alias arrangeAll: arrangeAllAction;
property alias arrangeSelection: arrangeSelectionAction;
property alias resetAllTranslation: resetAllTranslationAction;
property alias resetAll: resetAllAction;
@ -184,7 +185,7 @@ Item
enabled: UM.Controller.toolsEnabled;
iconName: "edit-delete";
shortcut: StandardKey.Delete;
onTriggered: Printer.deleteSelection();
onTriggered: CuraApplication.deleteSelection();
}
Action
@ -208,7 +209,7 @@ Item
enabled: UM.Scene.numObjectsSelected > 1 ? true: false
iconName: "object-group"
shortcut: "Ctrl+G";
onTriggered: Printer.groupSelected();
onTriggered: CuraApplication.groupSelected();
}
Action
@ -218,7 +219,7 @@ Item
enabled: UM.Scene.isGroupSelected
iconName: "object-ungroup"
shortcut: "Ctrl+Shift+G";
onTriggered: Printer.ungroupSelected();
onTriggered: CuraApplication.ungroupSelected();
}
Action
@ -228,7 +229,7 @@ Item
enabled: UM.Scene.numObjectsSelected > 1 ? true: false
iconName: "merge";
shortcut: "Ctrl+Alt+G";
onTriggered: Printer.mergeSelected();
onTriggered: CuraApplication.mergeSelected();
}
Action
@ -245,7 +246,7 @@ Item
enabled: UM.Controller.toolsEnabled;
iconName: "edit-select-all";
shortcut: "Ctrl+A";
onTriggered: Printer.selectAll();
onTriggered: CuraApplication.selectAll();
}
Action
@ -255,7 +256,7 @@ Item
enabled: UM.Controller.toolsEnabled;
iconName: "edit-delete";
shortcut: "Ctrl+D";
onTriggered: Printer.deleteAll();
onTriggered: CuraApplication.deleteAll();
}
Action
@ -264,27 +265,42 @@ Item
text: catalog.i18nc("@action:inmenu menubar:file","Re&load All Models");
iconName: "document-revert";
shortcut: "F5"
onTriggered: Printer.reloadAll();
onTriggered: CuraApplication.reloadAll();
}
Action
{
id: arrangeAllAction;
text: catalog.i18nc("@action:inmenu menubar:edit","Arrange All Models");
onTriggered: Printer.arrangeAll();
shortcut: "Ctrl+R";
}
Action
{
id: arrangeSelectionAction;
text: catalog.i18nc("@action:inmenu menubar:edit","Arrange Selection");
onTriggered: Printer.arrangeSelection();
}
Action
{
id: resetAllTranslationAction;
text: catalog.i18nc("@action:inmenu menubar:edit","Reset All Model Positions");
onTriggered: Printer.resetAllTranslation();
onTriggered: CuraApplication.resetAllTranslation();
}
Action
{
id: resetAllAction;
text: catalog.i18nc("@action:inmenu menubar:edit","Reset All Model &Transformations");
onTriggered: Printer.resetAll();
onTriggered: CuraApplication.resetAll();
}
Action
{
id: openAction;
text: catalog.i18nc("@action:inmenu menubar:file","&Open File...");
text: catalog.i18nc("@action:inmenu menubar:file","&Open File(s)...");
iconName: "document-open";
shortcut: StandardKey.Open;
}
@ -296,12 +312,6 @@ Item
shortcut: StandardKey.New
}
Action
{
id: loadWorkspaceAction
text: catalog.i18nc("@action:inmenu menubar:file","&Open Project...");
}
Action
{
id: showEngineLogAction;

View file

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

View file

@ -21,7 +21,7 @@ UM.MainWindow
property bool monitoringPrint: false
Component.onCompleted:
{
Printer.setMinimumWindowSize(UM.Theme.getSize("window_minimum_size"))
CuraApplication.setMinimumWindowSize(UM.Theme.getSize("window_minimum_size"))
// Workaround silly issues with QML Action's shortcut property.
//
// Currently, there is no way to define shortcuts as "Application Shortcut".
@ -78,11 +78,6 @@ UM.MainWindow
RecentFilesMenu { }
MenuItem
{
action: Cura.Actions.loadWorkspace
}
MenuSeparator { }
MenuItem
@ -92,26 +87,18 @@ UM.MainWindow
iconName: "document-save-as";
onTriggered: UM.OutputDeviceManager.requestWriteSelectionToDevice("local_file", PrintInformation.jobName, { "filter_by_machine": false, "preferred_mimetype": "application/vnd.ms-package.3dmanufacturing-3dmodel+xml"});
}
Menu
{
id: saveAllMenu
title: catalog.i18nc("@title:menu menubar:file","Save &All")
iconName: "document-save-all";
enabled: devicesModel.rowCount() > 0 && UM.Backend.progress > 0.99;
Instantiator
{
model: UM.OutputDevicesModel { id: devicesModel; }
MenuItem
{
text: model.description;
onTriggered: UM.OutputDeviceManager.requestWriteToDevice(model.id, PrintInformation.jobName, { "filter_by_machine": false, "preferred_mimetype": "application/vnd.ms-package.3dmanufacturing-3dmodel+xml"});
}
onObjectAdded: saveAllMenu.insertItem(index, object)
onObjectRemoved: saveAllMenu.removeItem(object)
id: saveAsMenu
text: catalog.i18nc("@title:menu menubar:file", "Save &As...")
onTriggered:
{
var localDeviceId = "local_file";
UM.OutputDeviceManager.requestWriteToDevice(localDeviceId, PrintInformation.jobName, { "filter_by_machine": false, "preferred_mimetype": "application/vnd.ms-package.3dmanufacturing-3dmodel+xml"});
}
}
MenuItem
{
id: saveWorkspaceMenu
@ -144,6 +131,7 @@ UM.MainWindow
MenuItem { action: Cura.Actions.redo; }
MenuSeparator { }
MenuItem { action: Cura.Actions.selectAll; }
MenuItem { action: Cura.Actions.arrangeAll; }
MenuItem { action: Cura.Actions.deleteSelection; }
MenuItem { action: Cura.Actions.deleteAll; }
MenuItem { action: Cura.Actions.resetAllTranslation; }
@ -272,40 +260,7 @@ UM.MainWindow
{
if (drop.urls.length > 0)
{
// Import models
var imported_model = -1;
for (var i in drop.urls)
{
// There is no endsWith in this version of JS...
if ((drop.urls[i].length <= 12) || (drop.urls[i].substring(drop.urls[i].length-12) !== ".curaprofile")) {
// Drop an object
Printer.readLocalFile(drop.urls[i]);
if (imported_model == -1)
{
imported_model = i;
}
}
}
// Import profiles
var import_result = Cura.ContainerManager.importProfiles(drop.urls);
if (import_result.message !== "") {
messageDialog.text = import_result.message
if (import_result.status == "ok")
{
messageDialog.icon = StandardIcon.Information
}
else
{
messageDialog.icon = StandardIcon.Critical
}
messageDialog.open()
}
if (imported_model != -1)
{
var meshName = backgroundItem.getMeshName(drop.urls[imported_model].toString())
backgroundItem.hasMesh(decodeURIComponent(meshName))
}
openDialog.handleOpenFileUrls(drop.urls);
}
}
}
@ -548,7 +503,7 @@ UM.MainWindow
icon: StandardIcon.Question
onYes:
{
Printer.deleteAll();
CuraApplication.deleteAll();
Cura.Actions.resetProfile.trigger();
}
}
@ -649,6 +604,7 @@ UM.MainWindow
MenuItem { action: Cura.Actions.multiplyObject; }
MenuSeparator { }
MenuItem { action: Cura.Actions.selectAll; }
MenuItem { action: Cura.Actions.arrangeAll; }
MenuItem { action: Cura.Actions.deleteAll; }
MenuItem { action: Cura.Actions.reloadAll; }
MenuItem { action: Cura.Actions.resetAllTranslation; }
@ -665,7 +621,7 @@ UM.MainWindow
{
if(objectContextMenu.objectId != 0)
{
Printer.deleteObject(objectContextMenu.objectId);
CuraApplication.deleteObject(objectContextMenu.objectId);
objectContextMenu.objectId = 0;
}
}
@ -698,7 +654,7 @@ UM.MainWindow
{
if(objectContextMenu.objectId != 0)
{
Printer.centerObject(objectContextMenu.objectId);
CuraApplication.centerObject(objectContextMenu.objectId);
objectContextMenu.objectId = 0;
}
}
@ -709,6 +665,7 @@ UM.MainWindow
{
id: contextMenu;
MenuItem { action: Cura.Actions.selectAll; }
MenuItem { action: Cura.Actions.arrangeAll; }
MenuItem { action: Cura.Actions.deleteAll; }
MenuItem { action: Cura.Actions.reloadAll; }
MenuItem { action: Cura.Actions.resetAllTranslation; }
@ -752,7 +709,7 @@ UM.MainWindow
id: openDialog;
//: File open dialog title
title: catalog.i18nc("@title:window","Open file")
title: catalog.i18nc("@title:window","Open file(s)")
modality: UM.Application.platform == "linux" ? Qt.NonModal : Qt.WindowModal;
selectMultiple: true
nameFilters: UM.MeshFileHandler.supportedReadFileTypes;
@ -766,13 +723,105 @@ UM.MainWindow
CuraApplication.setDefaultPath("dialog_load_path", folder);
for(var i in fileUrls)
{
Printer.readLocalFile(fileUrls[i])
handleOpenFileUrls(fileUrls);
}
var meshName = backgroundItem.getMeshName(fileUrls[0].toString())
backgroundItem.hasMesh(decodeURIComponent(meshName))
// Yeah... I know... it is a mess to put all those things here.
// There are lots of user interactions in this part of the logic, such as showing a warning dialog here and there,
// etc. This means it will come back and forth from time to time between QML and Python. So, separating the logic
// and view here may require more effort but make things more difficult to understand.
function handleOpenFileUrls(fileUrlList)
{
// look for valid project files
var projectFileUrlList = [];
var hasGcode = false;
var nonGcodeFileList = [];
for (var i in fileUrlList)
{
var endsWithG = /\.g$/;
var endsWithGcode = /\.gcode$/;
if (endsWithG.test(fileUrlList[i]) || endsWithGcode.test(fileUrlList[i]))
{
continue;
}
else if (CuraApplication.checkIsValidProjectFile(fileUrlList[i]))
{
projectFileUrlList.push(fileUrlList[i]);
}
nonGcodeFileList.push(fileUrlList[i]);
}
hasGcode = nonGcodeFileList.length < fileUrlList.length;
// show a warning if selected multiple files together with Gcode
var hasProjectFile = projectFileUrlList.length > 0;
var selectedMultipleFiles = fileUrlList.length > 1;
if (selectedMultipleFiles && hasGcode)
{
infoMultipleFilesWithGcodeDialog.selectedMultipleFiles = selectedMultipleFiles;
infoMultipleFilesWithGcodeDialog.hasProjectFile = hasProjectFile;
infoMultipleFilesWithGcodeDialog.fileUrls = nonGcodeFileList.slice();
infoMultipleFilesWithGcodeDialog.projectFileUrlList = projectFileUrlList.slice();
infoMultipleFilesWithGcodeDialog.open();
}
else
{
handleOpenFiles(selectedMultipleFiles, hasProjectFile, fileUrlList, projectFileUrlList);
}
}
function handleOpenFiles(selectedMultipleFiles, hasProjectFile, fileUrlList, projectFileUrlList)
{
// we only allow opening one project file
if (selectedMultipleFiles && hasProjectFile)
{
openFilesIncludingProjectsDialog.fileUrls = fileUrlList.slice();
openFilesIncludingProjectsDialog.show();
return;
}
if (hasProjectFile)
{
var projectFile = projectFileUrlList[0];
// check preference
var choice = UM.Preferences.getValue("cura/choice_on_open_project");
if (choice == "open_as_project")
{
openFilesIncludingProjectsDialog.loadProjectFile(projectFile);
}
else if (choice == "open_as_model")
{
openFilesIncludingProjectsDialog.loadModelFiles([projectFile].slice());
}
else // always ask
{
// ask whether to open as project or as models
askOpenAsProjectOrModelsDialog.fileUrl = projectFile;
askOpenAsProjectOrModelsDialog.show();
}
}
else
{
openFilesIncludingProjectsDialog.loadModelFiles(fileUrlList.slice());
}
}
}
MessageDialog {
id: infoMultipleFilesWithGcodeDialog
title: catalog.i18nc("@title:window", "Open File(s)")
icon: StandardIcon.Information
standardButtons: StandardButton.Ok
text: catalog.i18nc("@text:window", "We have found one or more G-Code files within the files you have selected. You can only open one G-Code file at a time. If you want to open a G-Code file, please just select only one.")
property var selectedMultipleFiles
property var hasProjectFile
property var fileUrls
property var projectFileUrlList
onAccepted:
{
openDialog.handleOpenFiles(selectedMultipleFiles, hasProjectFile, fileUrls, projectFileUrlList);
}
}
@ -782,38 +831,14 @@ UM.MainWindow
onTriggered: openDialog.open()
}
FileDialog
OpenFilesIncludingProjectsDialog
{
id: openWorkspaceDialog;
//: File open dialog title
title: catalog.i18nc("@title:window","Open workspace")
modality: UM.Application.platform == "linux" ? Qt.NonModal : Qt.WindowModal;
selectMultiple: false
nameFilters: UM.WorkspaceFileHandler.supportedReadFileTypes;
folder: CuraApplication.getDefaultPath("dialog_load_path")
onAccepted:
{
//Because several implementations of the file dialog only update the folder
//when it is explicitly set.
var f = folder;
folder = f;
CuraApplication.setDefaultPath("dialog_load_path", folder);
for(var i in fileUrls)
{
UM.WorkspaceFileHandler.readLocalFile(fileUrls[i])
}
var meshName = backgroundItem.getMeshName(fileUrls[0].toString())
backgroundItem.hasMesh(decodeURIComponent(meshName))
}
id: openFilesIncludingProjectsDialog
}
Connections
AskOpenAsProjectOrModelsDialog
{
target: Cura.Actions.loadWorkspace
onTriggered: openWorkspaceDialog.open()
id: askOpenAsProjectOrModelsDialog
}
EngineLog
@ -876,14 +901,14 @@ UM.MainWindow
{
id: messageDialog
modality: Qt.ApplicationModal
onAccepted: Printer.messageBoxClosed(clickedButton)
onApply: Printer.messageBoxClosed(clickedButton)
onDiscard: Printer.messageBoxClosed(clickedButton)
onHelp: Printer.messageBoxClosed(clickedButton)
onNo: Printer.messageBoxClosed(clickedButton)
onRejected: Printer.messageBoxClosed(clickedButton)
onReset: Printer.messageBoxClosed(clickedButton)
onYes: Printer.messageBoxClosed(clickedButton)
onAccepted: CuraApplication.messageBoxClosed(clickedButton)
onApply: CuraApplication.messageBoxClosed(clickedButton)
onDiscard: CuraApplication.messageBoxClosed(clickedButton)
onHelp: CuraApplication.messageBoxClosed(clickedButton)
onNo: CuraApplication.messageBoxClosed(clickedButton)
onRejected: CuraApplication.messageBoxClosed(clickedButton)
onReset: CuraApplication.messageBoxClosed(clickedButton)
onYes: CuraApplication.messageBoxClosed(clickedButton)
}
Connections
@ -913,7 +938,6 @@ UM.MainWindow
{
discardOrKeepProfileChangesDialog.show()
}
}
Connections
@ -963,4 +987,3 @@ UM.MainWindow
}
}
}

View file

@ -4,6 +4,7 @@
import QtQuick 2.1
import QtQuick.Controls 1.1
import QtQuick.Dialogs 1.2
import QtQuick.Window 2.1
import UM 1.2 as UM
import Cura 1.1 as Cura
@ -13,17 +14,26 @@ UM.Dialog
id: base
title: catalog.i18nc("@title:window", "Discard or Keep changes")
width: 800
height: 400
width: 800 * Screen.devicePixelRatio
height: 400 * Screen.devicePixelRatio
property var changesModel: Cura.UserChangesModel{ id: userChangesModel}
onVisibilityChanged:
{
if(visible)
{
changesModel.forceUpdate()
}
discardOrKeepProfileChangesDropDownButton.currentIndex = UM.Preferences.getValue("cura/choice_on_profile_override")
discardOrKeepProfileChangesDropDownButton.currentIndex = 0;
for (var i = 0; i < discardOrKeepProfileChangesDropDownButton.model.count; ++i)
{
var code = discardOrKeepProfileChangesDropDownButton.model.get(i).code;
if (code == UM.Preferences.getValue("cura/choice_on_profile_override"))
{
discardOrKeepProfileChangesDropDownButton.currentIndex = i;
break;
}
}
}
}
Column
@ -59,7 +69,7 @@ UM.Dialog
anchors.margins: UM.Theme.getSize("default_margin").width
anchors.left: parent.left
anchors.right: parent.right
height: base.height - 200
height: base.height - 150 * Screen.devicePixelRatio
id: tableView
Component
{
@ -133,30 +143,35 @@ UM.Dialog
ComboBox
{
id: discardOrKeepProfileChangesDropDownButton
model: [
catalog.i18nc("@option:discardOrKeep", "Always ask me this"),
catalog.i18nc("@option:discardOrKeep", "Discard and never ask again"),
catalog.i18nc("@option:discardOrKeep", "Keep and never ask again")
]
width: 300
currentIndex: UM.Preferences.getValue("cura/choice_on_profile_override")
onCurrentIndexChanged:
model: ListModel
{
UM.Preferences.setValue("cura/choice_on_profile_override", currentIndex)
if (currentIndex == 1) {
// 1 == "Discard and never ask again", so only enable the "Discard" button
discardButton.enabled = true
keepButton.enabled = false
id: discardOrKeepProfileListModel
Component.onCompleted: {
append({ text: catalog.i18nc("@option:discardOrKeep", "Always ask me this"), code: "always_ask" })
append({ text: catalog.i18nc("@option:discardOrKeep", "Discard and never ask again"), code: "always_discard" })
append({ text: catalog.i18nc("@option:discardOrKeep", "Keep and never ask again"), code: "always_keep" })
}
else if (currentIndex == 2) {
// 2 == "Keep and never ask again", so only enable the "Keep" button
keepButton.enabled = true
discardButton.enabled = false
}
onActivated:
{
var code = model.get(index).code;
UM.Preferences.setValue("cura/choice_on_profile_override", code);
if (code == "always_keep") {
keepButton.enabled = true;
discardButton.enabled = false;
}
else if (code == "always_discard") {
keepButton.enabled = false;
discardButton.enabled = true;
}
else {
// 0 == "Always ask me this", so show both
keepButton.enabled = true
discardButton.enabled = true
keepButton.enabled = true;
discardButton.enabled = true;
}
}
}
@ -176,7 +191,7 @@ UM.Dialog
anchors.right: parent.right
onClicked:
{
Printer.discardOrKeepProfileChangesClosed("discard")
CuraApplication.discardOrKeepProfileChangesClosed("discard")
base.hide()
}
isDefault: true
@ -190,7 +205,7 @@ UM.Dialog
anchors.rightMargin: UM.Theme.getSize("default_margin").width
onClicked:
{
Printer.discardOrKeepProfileChangesClosed("keep")
CuraApplication.discardOrKeepProfileChangesClosed("keep")
base.hide()
}
}

View file

@ -27,7 +27,7 @@ UM.Dialog
interval: 1000;
running: false;
repeat: true;
onTriggered: textArea.text = Printer.getEngineLog();
onTriggered: textArea.text = CuraApplication.getEngineLog();
}
UM.I18nCatalog{id: catalog; name:"cura"}
}
@ -43,7 +43,7 @@ UM.Dialog
{
if(visible)
{
textArea.text = Printer.getEngineLog();
textArea.text = CuraApplication.getEngineLog();
updateTimer.start();
} else
{

View file

@ -12,7 +12,7 @@ import Cura 1.0 as Cura
Item {
id: base
property bool activity: Printer.platformActivity
property bool activity: CuraApplication.platformActivity
property string fileBaseName
property variant activeMachineName: Cura.MachineManager.activeMachineName
@ -141,7 +141,7 @@ Item {
verticalAlignment: Text.AlignVCenter
font: UM.Theme.getFont("small")
color: UM.Theme.getColor("text_subtext")
text: Printer.getSceneBoundingBoxString
text: CuraApplication.getSceneBoundingBoxString
}
Rectangle

View file

@ -4,7 +4,7 @@
import QtQuick 2.2
import QtQuick.Controls 1.1
import UM 1.2 as UM
import UM 1.3 as UM
import Cura 1.0 as Cura
Menu
@ -13,11 +13,11 @@ Menu
title: catalog.i18nc("@title:menu menubar:file", "Open &Recent")
iconName: "document-open-recent";
enabled: Printer.recentFiles.length > 0;
enabled: CuraApplication.recentFiles.length > 0;
Instantiator
{
model: Printer.recentFiles
model: CuraApplication.recentFiles
MenuItem
{
text:
@ -25,8 +25,44 @@ Menu
var path = modelData.toString()
return (index + 1) + ". " + path.slice(path.lastIndexOf("/") + 1);
}
onTriggered: {
Printer.readLocalFile(modelData);
onTriggered:
{
var toShowDialog = false;
var toOpenAsProject = false;
var toOpenAsModel = false;
if (CuraApplication.checkIsValidProjectFile(modelData)) {
// check preference
var choice = UM.Preferences.getValue("cura/choice_on_open_project");
if (choice == "open_as_project")
{
toOpenAsProject = true;
}else if (choice == "open_as_model"){
toOpenAsModel = true;
}else{
toShowDialog = true;
}
}
else {
toOpenAsModel = true;
}
if (toShowDialog) {
askOpenAsProjectOrModelsDialog.fileUrl = modelData;
askOpenAsProjectOrModelsDialog.show();
return;
}
// open file in the prefered way
if (toOpenAsProject)
{
UM.WorkspaceFileHandler.readLocalFile(modelData);
}
else if (toOpenAsModel)
{
CuraApplication.readLocalFile(modelData);
}
var meshName = backgroundItem.getMeshName(modelData.toString())
backgroundItem.hasMesh(decodeURIComponent(meshName))
}
@ -34,4 +70,9 @@ Menu
onObjectAdded: menu.insertItem(index, object)
onObjectRemoved: menu.removeItem(object)
}
Cura.AskOpenAsProjectOrModelsDialog
{
id: askOpenAsProjectOrModelsDialog
}
}

View file

@ -80,7 +80,7 @@ Item
}
}
property bool activity: Printer.platformActivity;
property bool activity: CuraApplication.platformActivity;
property int totalHeight: childrenRect.height + UM.Theme.getSize("default_margin").height
property string fileBaseName
property string statusText:
@ -205,8 +205,8 @@ Item
onAdditionalComponentsChanged:
{
if(areaId == "monitorButtons") {
for (var component in Printer.additionalComponents["monitorButtons"]) {
Printer.additionalComponents["monitorButtons"][component].parent = additionalComponentsRow
for (var component in CuraApplication.additionalComponents["monitorButtons"]) {
CuraApplication.additionalComponents["monitorButtons"][component].parent = additionalComponentsRow
}
}
}

View file

@ -20,7 +20,7 @@ UM.Dialog
height: minimumHeight
property var objectId: 0;
onAccepted: Printer.multiplyObject(base.objectId, parseInt(copiesField.text))
onAccepted: CuraApplication.multiplyObject(base.objectId, parseInt(copiesField.text))
property variant catalog: UM.I18nCatalog { name: "cura" }

View file

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

View file

@ -25,6 +25,30 @@ UM.PreferencesPage
}
}
function setDefaultDiscardOrKeepProfile(code)
{
for (var i = 0; i < choiceOnProfileOverrideDropDownButton.model.count; i++)
{
if (choiceOnProfileOverrideDropDownButton.model.get(i).code == code)
{
choiceOnProfileOverrideDropDownButton.currentIndex = i;
break;
}
}
}
function setDefaultOpenProjectOption(code)
{
for (var i = 0; i < choiceOnOpenProjectDropDownButton.model.count; ++i)
{
if (choiceOnOpenProjectDropDownButton.model.get(i).code == code)
{
choiceOnOpenProjectDropDownButton.currentIndex = i
break;
}
}
}
function reset()
{
UM.Preferences.resetPreference("general/language")
@ -49,8 +73,12 @@ UM.PreferencesPage
invertZoomCheckbox.checked = boolCheck(UM.Preferences.getValue("view/invert_zoom"))
UM.Preferences.resetPreference("view/top_layer_count");
topLayerCountCheckbox.checked = boolCheck(UM.Preferences.getValue("view/top_layer_count"))
UM.Preferences.resetPreference("cura/choice_on_profile_override")
choiceOnProfileOverrideDropDownButton.currentIndex = UM.Preferences.getValue("cura/choice_on_profile_override")
setDefaultDiscardOrKeepProfile(UM.Preferences.getValue("cura/choice_on_profile_override"))
UM.Preferences.resetPreference("cura/choice_on_open_project")
setDefaultOpenProjectOption(UM.Preferences.getValue("cura/choice_on_open_project"))
if (plugins.find("id", "SliceInfoPlugin") > -1) {
UM.Preferences.resetPreference("info/send_slice_info")
@ -105,6 +133,8 @@ UM.PreferencesPage
append({ text: "Suomi", code: "fi" })
append({ text: "Français", code: "fr" })
append({ text: "Italiano", code: "it" })
append({ text: "日本語", code: "jp" })
append({ text: "한국어", code: "ko" })
append({ text: "Nederlands", code: "nl" })
append({ text: "Português do Brasil", code: "ptbr" })
append({ text: "Русский", code: "ru" })
@ -231,6 +261,7 @@ UM.PreferencesPage
text: catalog.i18nc("@action:button","Center camera when item is selected");
checked: boolCheck(UM.Preferences.getValue("view/center_on_select"))
onClicked: UM.Preferences.setValue("view/center_on_select", checked)
enabled: Qt.platform.os != "windows" // Hack: disable the feature on windows as it's broken for pyqt 5.7.1.
}
}
@ -376,6 +407,56 @@ UM.PreferencesPage
}
}
UM.TooltipArea {
width: childrenRect.width
height: childrenRect.height
text: catalog.i18nc("@info:tooltip", "Default behavior when opening a project file")
Column
{
spacing: 4
Label
{
text: catalog.i18nc("@window:text", "Default behavior when opening a project file: ")
}
ComboBox
{
id: choiceOnOpenProjectDropDownButton
width: 200
model: ListModel
{
id: openProjectOptionModel
Component.onCompleted: {
append({ text: catalog.i18nc("@option:openProject", "Always ask"), code: "always_ask" })
append({ text: catalog.i18nc("@option:openProject", "Always open as a project"), code: "open_as_project" })
append({ text: catalog.i18nc("@option:openProject", "Always import models"), code: "open_as_model" })
}
}
currentIndex:
{
var index = 0;
var currentChoice = UM.Preferences.getValue("cura/choice_on_open_project");
for (var i = 0; i < model.count; ++i)
{
if (model.get(i).code == currentChoice)
{
index = i;
break;
}
}
return index;
}
onActivated: UM.Preferences.setValue("cura/choice_on_open_project", model.get(index).code)
}
}
}
Item
{
//: Spacer
@ -383,12 +464,6 @@ UM.PreferencesPage
width: UM.Theme.getSize("default_margin").width
}
Label
{
font.bold: true
text: catalog.i18nc("@label", "Override Profile")
}
UM.TooltipArea
{
width: childrenRect.width;
@ -396,18 +471,48 @@ UM.PreferencesPage
text: catalog.i18nc("@info:tooltip", "When you have made changes to a profile and switched to a different one, a dialog will be shown asking whether you want to keep your modifications or not, or you can choose a default behaviour and never show that dialog again.")
Column
{
spacing: 4
Label
{
font.bold: true
text: catalog.i18nc("@label", "Override Profile")
}
ComboBox
{
id: choiceOnProfileOverrideDropDownButton
width: 200
model: [
catalog.i18nc("@option:discardOrKeep", "Always ask me this"),
catalog.i18nc("@option:discardOrKeep", "Discard and never ask again"),
catalog.i18nc("@option:discardOrKeep", "Keep and never ask again")
]
width: 300
currentIndex: UM.Preferences.getValue("cura/choice_on_profile_override")
onCurrentIndexChanged: UM.Preferences.setValue("cura/choice_on_profile_override", currentIndex)
model: ListModel
{
id: discardOrKeepProfileListModel
Component.onCompleted: {
append({ text: catalog.i18nc("@option:discardOrKeep", "Always ask me this"), code: "always_ask" })
append({ text: catalog.i18nc("@option:discardOrKeep", "Discard and never ask again"), code: "always_discard" })
append({ text: catalog.i18nc("@option:discardOrKeep", "Keep and never ask again"), code: "always_keep" })
}
}
currentIndex:
{
var index = 0;
var code = UM.Preferences.getValue("cura/choice_on_profile_override");
for (var i = 0; i < model.count; ++i)
{
if (model.get(i).code == code)
{
index = i;
break;
}
}
return index;
}
onActivated: UM.Preferences.setValue("cura/choice_on_profile_override", model.get(index).code)
}
}
}

View file

@ -43,7 +43,7 @@ UM.ManagementPage
{
text: catalog.i18nc("@action:button", "Add");
iconName: "list-add";
onClicked: Printer.requestAddPrinter()
onClicked: CuraApplication.requestAddPrinter()
},
Button
{
@ -216,8 +216,8 @@ UM.ManagementPage
Component.onCompleted:
{
for (var component in Printer.additionalComponents["machinesDetailPane"]) {
Printer.additionalComponents["machinesDetailPane"][component].parent = additionalComponentsColumn
for (var component in CuraApplication.additionalComponents["machinesDetailPane"]) {
CuraApplication.additionalComponents["machinesDetailPane"][component].parent = additionalComponentsColumn
}
}
}
@ -227,8 +227,8 @@ UM.ManagementPage
onAdditionalComponentsChanged:
{
if(areaId == "machinesDetailPane") {
for (var component in Printer.additionalComponents["machinesDetailPane"]) {
Printer.additionalComponents["machinesDetailPane"][component].parent = additionalComponentsColumn
for (var component in CuraApplication.additionalComponents["machinesDetailPane"]) {
CuraApplication.additionalComponents["machinesDetailPane"][component].parent = additionalComponentsColumn
}
}
}

View file

@ -153,7 +153,7 @@ TabView
value: base.getMaterialPreferenceValue(properties.guid, "spool_cost")
prefix: base.currency + " "
decimals: 2
maximumValue: 1000
maximumValue: 100000000
onValueChanged: {
base.setMaterialPreferenceValue(properties.guid, "spool_cost", parseFloat(value))

Some files were not shown because too many files have changed in this diff Show more