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(${CMAKE_SOURCE_DIR}/cura.desktop.in ${CMAKE_BINARY_DIR}/cura.desktop @ONLY)
configure_file(cura/CuraVersion.py.in CuraVersion.py @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 "") if(NOT ${URANIUM_SCRIPTS_DIR} STREQUAL "")
list(APPEND CMAKE_MODULE_PATH ${URANIUM_DIR}/cmake) list(APPEND CMAKE_MODULE_PATH ${URANIUM_DIR}/cmake)
include(UraniumTranslationTools) include(UraniumTranslationTools)
@ -64,5 +67,3 @@ else()
install(FILES ${CMAKE_BINARY_DIR}/CuraVersion.py install(FILES ${CMAKE_BINARY_DIR}/CuraVersion.py
DESTINATION lib/python${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR}/site-packages/cura) DESTINATION lib/python${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR}/site-packages/cura)
endif() 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") catalog = i18nCatalog("cura")
import numpy import numpy
import copy
import math import math
# Setting for clearance around the prime # Setting for clearance around the prime
@ -110,10 +109,11 @@ class BuildVolume(SceneNode):
def _onChangeTimerFinished(self): def _onChangeTimerFinished(self):
root = Application.getInstance().getController().getScene().getRoot() 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: if new_scene_objects != self._scene_objects:
for node in new_scene_objects - self._scene_objects: #Nodes that were added to the scene. 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. for node in self._scene_objects - new_scene_objects: #Nodes that were removed from the scene.
per_mesh_stack = node.callDecoration("getStack") per_mesh_stack = node.callDecoration("getStack")
if per_mesh_stack: if per_mesh_stack:
@ -121,7 +121,7 @@ class BuildVolume(SceneNode):
active_extruder_changed = node.callDecoration("getActiveExtruderChangedSignal") active_extruder_changed = node.callDecoration("getActiveExtruderChangedSignal")
if active_extruder_changed is not None: if active_extruder_changed is not None:
node.callDecoration("getActiveExtruderChangedSignal").disconnect(self._updateDisallowedAreasAndRebuild) node.callDecoration("getActiveExtruderChangedSignal").disconnect(self._updateDisallowedAreasAndRebuild)
node.decoratorsChanged.disconnect(self._onNodeDecoratorChanged) node.decoratorsChanged.disconnect(self._updateNodeListeners)
self._scene_objects = new_scene_objects self._scene_objects = new_scene_objects
self._onSettingPropertyChanged("print_sequence", "value") # Create fake event, so right settings are triggered. 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. ## Updates the listeners that listen for changes in per-mesh stacks.
# #
# \param node The node for which the decorators changed. # \param node The node for which the decorators changed.
def _onNodeDecoratorChanged(self, node): def _updateNodeListeners(self, node):
per_mesh_stack = node.callDecoration("getStack") per_mesh_stack = node.callDecoration("getStack")
if per_mesh_stack: if per_mesh_stack:
per_mesh_stack.propertyChanged.connect(self._onSettingPropertyChanged) per_mesh_stack.propertyChanged.connect(self._onSettingPropertyChanged)
@ -179,6 +179,54 @@ class BuildVolume(SceneNode):
return True 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. ## Recalculates the build volume & disallowed areas.
def rebuild(self): def rebuild(self):
if not self._width or not self._height or not self._depth: 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 Application.getInstance().getController().getScene()._maximum_bounds = scale_to_max_bounds
self.updateNodeBoundaryCheck()
def getBoundingBox(self): def getBoundingBox(self):
return self._volume_aabb return self._volume_aabb
@ -629,11 +679,12 @@ class BuildVolume(SceneNode):
if not self._global_container_stack.getProperty("machine_center_is_zero", "value"): 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_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 = 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.getMinkowskiHull(Polygon.approximatedCircle(border_size))
prime_polygon = prime_polygon.translate(prime_x, prime_y)
result[extruder.getId()] = [prime_polygon] result[extruder.getId()] = [prime_polygon]
return result return result

View file

@ -258,12 +258,16 @@ class ConvexHullDecorator(SceneNodeDecorator):
# influences the collision area. # influences the collision area.
def _offsetHull(self, convex_hull): def _offsetHull(self, convex_hull):
horizontal_expansion = self._getSettingProperty("xy_offset", "value") 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([ expansion_polygon = Polygon(numpy.array([
[-horizontal_expansion, -horizontal_expansion], [-hull_offset, -hull_offset],
[-horizontal_expansion, horizontal_expansion], [-hull_offset, hull_offset],
[horizontal_expansion, horizontal_expansion], [hull_offset, hull_offset],
[horizontal_expansion, -horizontal_expansion] [hull_offset, -hull_offset]
], numpy.float32)) ], numpy.float32))
return convex_hull.getMinkowskiHull(expansion_polygon) return convex_hull.getMinkowskiHull(expansion_polygon)
else: else:
@ -331,4 +335,4 @@ class ConvexHullDecorator(SceneNodeDecorator):
## Settings that change the convex hull. ## Settings that change the convex hull.
# #
# If these settings change, the convex hull should be recalculated. # 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 from UM.View.GL.OpenGL import OpenGL
class ConvexHullNode(SceneNode): 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 ## 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 # 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 # 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.setCalculateBoundingBox(False)
self._shader = None
self._original_parent = parent self._original_parent = parent
# Color of the drawn convex hull # Color of the drawn convex hull
@ -59,16 +60,16 @@ class ConvexHullNode(SceneNode):
return self._node return self._node
def render(self, renderer): def render(self, renderer):
if not self._shader: if not ConvexHullNode.shader:
self._shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "transparent_object.shader")) ConvexHullNode.shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "transparent_object.shader"))
self._shader.setUniformValue("u_diffuseColor", self._color) ConvexHullNode.shader.setUniformValue("u_diffuseColor", self._color)
self._shader.setUniformValue("u_opacity", 0.6) ConvexHullNode.shader.setUniformValue("u_opacity", 0.6)
if self.getParent(): if self.getParent():
if self.getMeshData(): 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: 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 return True

View file

@ -16,7 +16,6 @@ from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Mesh.ReadMeshJob import ReadMeshJob from UM.Mesh.ReadMeshJob import ReadMeshJob
from UM.Logger import Logger from UM.Logger import Logger
from UM.Preferences import Preferences from UM.Preferences import Preferences
from UM.JobQueue import JobQueue
from UM.SaveFile import SaveFile from UM.SaveFile import SaveFile
from UM.Scene.Selection import Selection from UM.Scene.Selection import Selection
from UM.Scene.GroupDecorator import GroupDecorator from UM.Scene.GroupDecorator import GroupDecorator
@ -25,15 +24,23 @@ from UM.Settings.InstanceContainer import InstanceContainer
from UM.Settings.Validator import Validator from UM.Settings.Validator import Validator
from UM.Message import Message from UM.Message import Message
from UM.i18n import i18nCatalog 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.AddSceneNodeOperation import AddSceneNodeOperation
from UM.Operations.RemoveSceneNodeOperation import RemoveSceneNodeOperation from UM.Operations.RemoveSceneNodeOperation import RemoveSceneNodeOperation
from UM.Operations.GroupedOperation import GroupedOperation from UM.Operations.GroupedOperation import GroupedOperation
from UM.Operations.SetTransformOperation import SetTransformOperation 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.SetParentOperation import SetParentOperation
from cura.SliceableObjectDecorator import SliceableObjectDecorator from cura.SliceableObjectDecorator import SliceableObjectDecorator
from cura.BlockSlicingDecorator import BlockSlicingDecorator 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.SettingDefinition import SettingDefinition, DefinitionPropertyType
from UM.Settings.ContainerRegistry import ContainerRegistry from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.Settings.SettingFunction import SettingFunction from UM.Settings.SettingFunction import SettingFunction
@ -87,6 +94,7 @@ if not MYPY:
CuraVersion = "master" # [CodeStyle: Reflecting imported value] CuraVersion = "master" # [CodeStyle: Reflecting imported value]
CuraBuildType = "" CuraBuildType = ""
class CuraApplication(QtApplication): class CuraApplication(QtApplication):
class ResourceTypes: class ResourceTypes:
QmlFiles = Resources.UserType + 1 QmlFiles = Resources.UserType + 1
@ -101,7 +109,6 @@ class CuraApplication(QtApplication):
Q_ENUMS(ResourceTypes) Q_ENUMS(ResourceTypes)
def __init__(self): def __init__(self):
Resources.addSearchPath(os.path.join(QtApplication.getInstallPrefix(), "share", "cura", "resources")) Resources.addSearchPath(os.path.join(QtApplication.getInstallPrefix(), "share", "cura", "resources"))
if not hasattr(sys, "frozen"): if not hasattr(sys, "frozen"):
Resources.addSearchPath(os.path.join(os.path.abspath(os.path.dirname(__file__)), "..", "resources")) Resources.addSearchPath(os.path.join(os.path.abspath(os.path.dirname(__file__)), "..", "resources"))
@ -181,7 +188,10 @@ class CuraApplication(QtApplication):
"SelectionTool", "SelectionTool",
"CameraTool", "CameraTool",
"GCodeWriter", "GCodeWriter",
"LocalFileOutputDevice" "LocalFileOutputDevice",
"TranslateTool",
"FileLogger",
"XmlMaterialProfile"
]) ])
self._physics = None self._physics = None
self._volume = None self._volume = None
@ -237,7 +247,7 @@ class CuraApplication(QtApplication):
ContainerRegistry.getInstance().load() ContainerRegistry.getInstance().load()
Preferences.getInstance().addPreference("cura/active_mode", "simple") Preferences.getInstance().addPreference("cura/active_mode", "simple")
Preferences.getInstance().addPreference("cura/recent_files", "")
Preferences.getInstance().addPreference("cura/categories_expanded", "") Preferences.getInstance().addPreference("cura/categories_expanded", "")
Preferences.getInstance().addPreference("cura/jobname_prefix", True) Preferences.getInstance().addPreference("cura/jobname_prefix", True)
Preferences.getInstance().addPreference("view/center_on_select", False) 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("mesh/scale_tiny_meshes", True)
Preferences.getInstance().addPreference("cura/dialog_on_project_save", True) Preferences.getInstance().addPreference("cura/dialog_on_project_save", True)
Preferences.getInstance().addPreference("cura/asked_dialog_on_project_save", False) 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/currency", "")
Preferences.getInstance().addPreference("cura/material_settings", "{}") Preferences.getInstance().addPreference("cura/material_settings", "{}")
Preferences.getInstance().addPreference("view/invert_zoom", False)
for key in [ for key in [
"dialog_load_path", # dialog_save_path is in LocalFileOutputDevicePlugin "dialog_load_path", # dialog_save_path is in LocalFileOutputDevicePlugin
"dialog_profile_path", "dialog_profile_path",
@ -310,20 +323,11 @@ class CuraApplication(QtApplication):
experimental experimental
""".replace("\n", ";").replace(" ", "")) """.replace("\n", ";").replace(" ", ""))
JobQueue.getInstance().jobFinished.connect(self._onJobFinished)
self.applicationShuttingDown.connect(self.saveSettings) self.applicationShuttingDown.connect(self.saveSettings)
self.engineCreatedSignal.connect(self._onEngineCreated) self.engineCreatedSignal.connect(self._onEngineCreated)
self.globalContainerStackChanged.connect(self._onGlobalContainerChanged) self.globalContainerStackChanged.connect(self._onGlobalContainerChanged)
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): def _onEngineCreated(self):
self._engine.addImageProvider("camera", CameraImageProvider.CameraImageProvider()) self._engine.addImageProvider("camera", CameraImageProvider.CameraImageProvider())
@ -341,10 +345,10 @@ class CuraApplication(QtApplication):
def discardOrKeepProfileChanges(self): def discardOrKeepProfileChanges(self):
choice = Preferences.getInstance().getValue("cura/choice_on_profile_override") choice = Preferences.getInstance().getValue("cura/choice_on_profile_override")
if choice == 1: if choice == "always_discard":
# don't show dialog and DISCARD the profile # don't show dialog and DISCARD the profile
self.discardOrKeepProfileChangesClosed("discard") self.discardOrKeepProfileChangesClosed("discard")
elif choice == 2: elif choice == "always_keep":
# don't show dialog and KEEP the profile # don't show dialog and KEEP the profile
self.discardOrKeepProfileChangesClosed("keep") self.discardOrKeepProfileChangesClosed("keep")
else: else:
@ -588,6 +592,9 @@ class CuraApplication(QtApplication):
# The platform is a child of BuildVolume # The platform is a child of BuildVolume
self._volume = BuildVolume.BuildVolume(root) 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.getRenderer().setBackgroundColor(QColor(245, 245, 245))
self._physics = PlatformPhysics.PlatformPhysics(controller, self._volume) self._physics = PlatformPhysics.PlatformPhysics(controller, self._volume)
@ -662,6 +669,7 @@ class CuraApplication(QtApplication):
# #
# \param engine The QML engine. # \param engine The QML engine.
def registerObjects(self, engine): def registerObjects(self, engine):
super().registerObjects(engine)
engine.rootContext().setContextProperty("Printer", self) engine.rootContext().setContextProperty("Printer", self)
engine.rootContext().setContextProperty("CuraApplication", self) engine.rootContext().setContextProperty("CuraApplication", self)
self._print_information = PrintInformation.PrintInformation() self._print_information = PrintInformation.PrintInformation()
@ -695,14 +703,11 @@ class CuraApplication(QtApplication):
if type_name in ("Cura", "Actions"): if type_name in ("Cura", "Actions"):
continue 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). qmlRegisterType(QUrl.fromLocalFile(path), "Cura", 1, 0, type_name)
# 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
def onSelectionChanged(self): def onSelectionChanged(self):
if Selection.hasSelection(): if Selection.hasSelection():
@ -720,7 +725,9 @@ class CuraApplication(QtApplication):
else: else:
# Default # Default
self.getController().setActiveTool("TranslateTool") 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 self._center_after_select = True
else: else:
if self.getController().getActiveTool(): if self.getController().getActiveTool():
@ -839,24 +846,14 @@ class CuraApplication(QtApplication):
op.push() op.push()
## Create a number of copies of existing object. ## 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) @pyqtSlot("quint64", int)
def multiplyObject(self, object_id, count): def multiplyObject(self, object_id, count, min_offset = 8):
node = self.getController().getScene().findObject(object_id) job = MultiplyObjectsJob(object_id, count, min_offset)
job.start()
if not node and object_id != 0: # Workaround for tool handles overlapping the selected object return
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()
## Center object on platform. ## Center object on platform.
@pyqtSlot("quint64") @pyqtSlot("quint64")
@ -974,6 +971,50 @@ class CuraApplication(QtApplication):
op.addOperation(SetTransformOperation(node, Vector(0, center_y, 0), Quaternion(), Vector(1, 1, 1))) op.addOperation(SetTransformOperation(node, Vector(0, center_y, 0), Quaternion(), Vector(1, 1, 1)))
op.push() 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. ## Reload all mesh data on the screen from file.
@pyqtSlot() @pyqtSlot()
def reloadAll(self): def reloadAll(self):
@ -1009,12 +1050,6 @@ class CuraApplication(QtApplication):
return log return log
recentFilesChanged = pyqtSignal()
@pyqtProperty("QVariantList", notify = recentFilesChanged)
def recentFiles(self):
return self._recent_files
@pyqtSlot("QStringList") @pyqtSlot("QStringList")
def setExpandedCategories(self, categories): def setExpandedCategories(self, categories):
categories = list(set(categories)) categories = list(set(categories))
@ -1050,7 +1085,9 @@ class CuraApplication(QtApplication):
transformation.setTranslation(zero_translation) transformation.setTranslation(zero_translation)
transformed_mesh = mesh.getTransformed(transformation) transformed_mesh = mesh.getTransformed(transformation)
center = transformed_mesh.getCenterPosition() center = transformed_mesh.getCenterPosition()
object_centers.append(center) if center is not None:
object_centers.append(center)
if object_centers and len(object_centers) > 0: if object_centers and len(object_centers) > 0:
middle_x = sum([v.x for v in object_centers]) / len(object_centers) middle_x = sum([v.x for v in object_centers]) / len(object_centers)
middle_y = sum([v.y for v in object_centers]) / len(object_centers) middle_y = sum([v.y for v in object_centers]) / len(object_centers)
@ -1120,25 +1157,6 @@ class CuraApplication(QtApplication):
fileLoaded = pyqtSignal(str) 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): def _reloadMeshFinished(self, job):
# TODO; This needs to be fixed properly. We now make the assumption that we only load a single mesh! # 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() mesh_data = job.getResult()[0].getMeshData()
@ -1233,6 +1251,10 @@ class CuraApplication(QtApplication):
filename = job.getFileName() filename = job.getFileName()
self._currently_loading_files.remove(filename) self._currently_loading_files.remove(filename)
root = self.getController().getScene().getRoot()
arranger = Arrange.create(scene_root = root)
min_offset = 8
for node in nodes: for node in nodes:
node.setSelectable(True) node.setSelectable(True)
node.setName(os.path.basename(filename)) node.setName(os.path.basename(filename))
@ -1253,10 +1275,37 @@ class CuraApplication(QtApplication):
scene = self.getController().getScene() 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 = AddSceneNodeOperation(node, scene.getRoot())
op.push() op.push()
scene.sceneChanged.emit(node) scene.sceneChanged.emit(node)
def addNonSliceableExtension(self, extension): def addNonSliceableExtension(self, extension):
self._non_sliceable_extensions.append(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 from UM.Mesh.MeshBuilder import MeshBuilder
import numpy import numpy
class Layer: class Layer:
def __init__(self, layer_id): def __init__(self, layer_id):
self._id = layer_id self._id = layer_id
@ -80,8 +78,7 @@ class Layer:
else: else:
for polygon in self._polygons: for polygon in self._polygons:
line_count += polygon.jumpCount line_count += polygon.jumpCount
# Reserve the neccesary space for the data upfront # Reserve the neccesary space for the data upfront
builder.reserveFaceAndVertexCount(2 * line_count, 4 * line_count) builder.reserveFaceAndVertexCount(2 * line_count, 4 * line_count)
@ -94,7 +91,7 @@ class Layer:
# Line types of the points we want to draw # Line types of the points we want to draw
line_types = polygon.types[index_mask] line_types = polygon.types[index_mask]
# Shift the z-axis according to previous implementation. # Shift the z-axis according to previous implementation.
if make_mesh: if make_mesh:
points[polygon.isInfillOrSkinType(line_types), 1::3] -= 0.01 points[polygon.isInfillOrSkinType(line_types), 1::3] -= 0.01
else: else:
@ -106,13 +103,14 @@ class Layer:
# Scale all normals by the line width of the current line so we can easily offset. # Scale all normals by the line width of the current line so we can easily offset.
normals *= (polygon.lineWidths[index_mask.ravel()] / 2) 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)) 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
# __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_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) f_colors = numpy.repeat(polygon.mapLineTypeToColor(line_types), 4, 0)
builder.addFacesWithColor(f_points, f_indices, f_colors) builder.addFacesWithColor(f_points, f_indices, f_colors)
return builder.build() return builder.build()

View file

@ -2,11 +2,12 @@
# Cura is released under the terms of the AGPLv3 or higher. # Cura is released under the terms of the AGPLv3 or higher.
from UM.Mesh.MeshData import MeshData from UM.Mesh.MeshData import MeshData
## Class to holds the layer mesh and information about the layers. ## Class to holds the layer mesh and information about the layers.
# Immutable, use LayerDataBuilder to create one of these. # Immutable, use LayerDataBuilder to create one of these.
class LayerData(MeshData): class LayerData(MeshData):
def __init__(self, vertices = None, normals = None, indices = None, colors = None, uvs = None, file_name = None, def __init__(self, vertices = None, normals = None, indices = None, colors = None, uvs = None, file_name = None,
center_position = None, layers=None, element_counts=None, attributes=None): center_position = None, layers=None, element_counts=None, attributes=None):
super().__init__(vertices=vertices, normals=normals, indices=indices, colors=colors, uvs=uvs, super().__init__(vertices=vertices, normals=normals, indices=indices, colors=colors, uvs=uvs,
file_name=file_name, center_position=center_position, attributes=attributes) file_name=file_name, center_position=center_position, attributes=attributes)
self._layers = layers self._layers = layers

View file

@ -8,6 +8,7 @@ from .LayerData import LayerData
import numpy import numpy
## Builder class for constructing a LayerData object ## Builder class for constructing a LayerData object
class LayerDataBuilder(MeshBuilder): class LayerDataBuilder(MeshBuilder):
def __init__(self): 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 PyQt5.QtCore import QTimer
from UM.Application import Application
from UM.Scene.SceneNode import SceneNode from UM.Scene.SceneNode import SceneNode
from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator
from UM.Math.Vector import Vector from UM.Math.Vector import Vector
from UM.Math.AxisAlignedBox import AxisAlignedBox
from UM.Scene.Selection import Selection from UM.Scene.Selection import Selection
from UM.Preferences import Preferences from UM.Preferences import Preferences
@ -51,10 +51,13 @@ class PlatformPhysics:
# same direction. # same direction.
transformed_nodes = [] transformed_nodes = []
group_nodes = []
# We try to shuffle all the nodes to prevent "locked" situations, where iteration B inverts iteration A. # 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. # By shuffling the order of the nodes, this might happen a few times, but at some point it will resolve.
nodes = list(BreadthFirstIterator(root)) 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) random.shuffle(nodes)
for node in nodes: for node in nodes:
if node is root or type(node) is not SceneNode or node.getBoundingBox() is None: if node is root or type(node) is not SceneNode or node.getBoundingBox() is None:
@ -62,24 +65,6 @@ class PlatformPhysics:
bbox = node.getBoundingBox() 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 it downwards if bottom is above platform
move_vector = Vector() 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 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. # Simply waiting for the next tick seems to resolve this correctly.
overlap = None 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): if not Vector.Null.equals(move_vector, epsilon=1e-5):
transformed_nodes.append(node) transformed_nodes.append(node)
op = PlatformPhysicsOperation.PlatformPhysicsOperation(node, move_vector) op = PlatformPhysicsOperation.PlatformPhysicsOperation(node, move_vector)
op.push() op.push()
# Group nodes should override the _outside_buildarea property of their children. # After moving, we have to evaluate the boundary checks for nodes
for group_node in group_nodes: build_volume = Application.getInstance().getBuildVolume()
for child_node in group_node.getAllChildren(): build_volume.updateNodeBoundaryCheck()
child_node._outside_buildarea = group_node._outside_buildarea
def _onToolOperationStarted(self, tool): def _onToolOperationStarted(self, tool):
self._enabled = False self._enabled = False

View file

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

View file

@ -255,7 +255,12 @@ class ExtruderManager(QObject):
preferred_materials = container_registry.findInstanceContainers(**search_criteria) preferred_materials = container_registry.findInstanceContainers(**search_criteria)
if len(preferred_materials) >= 1: if len(preferred_materials) >= 1:
material = preferred_materials[0] # 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: 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) Logger.log("w", "The preferred material \"%s\" of machine %s doesn't exist or is not a material profile.", preferred_material_id, machine_id)
# And leave it at the default material. # And leave it at the default material.

View file

@ -2,7 +2,7 @@
# Cura is released under the terms of the AGPLv3 or higher. # Cura is released under the terms of the AGPLv3 or higher.
from typing import Union 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 UM.FlameProfiler import pyqtSlot
from PyQt5.QtWidgets import QMessageBox from PyQt5.QtWidgets import QMessageBox
from UM import Util from UM import Util
@ -83,6 +83,11 @@ class MachineManager(QObject):
self._material_incompatible_message = Message(catalog.i18nc("@info:status", self._material_incompatible_message = Message(catalog.i18nc("@info:status",
"The selected material is incompatible with the selected machine or configuration.")) "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) 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() activeMaterialChanged = pyqtSignal()
activeVariantChanged = pyqtSignal() activeVariantChanged = pyqtSignal()
@ -306,33 +311,7 @@ class MachineManager(QObject):
self.activeStackValueChanged.emit() self.activeStackValueChanged.emit()
elif property_name == "validationState": elif property_name == "validationState":
if not self._stacks_have_errors: self._error_check_timer.start()
# 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()
@pyqtSlot(str) @pyqtSlot(str)
def setActiveMachine(self, stack_id: str) -> None: 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") Logger.log("e", "Tried to set quality to a container that is not of the right type")
return 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 name_changed_connect_stacks = [] # Connect these stacks to the name changed callback
for setting_info in new_quality_settings_list: for setting_info in new_quality_settings_list:
stack = setting_info["stack"] stack = setting_info["stack"]
@ -889,7 +872,12 @@ class MachineManager(QObject):
quality_changes_profiles = quality_manager.findQualityChangesByName(quality_changes_name, quality_changes_profiles = quality_manager.findQualityChangesByName(quality_changes_name,
global_machine_definition) 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") material = global_container_stack.findContainer(type="material")
# For the global stack, find a quality which matches the quality_type in # 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 # first seems to prevent Sip from going into a state where it
# tries to create PyQt objects on a non-main thread. # tries to create PyQt objects on a non-main thread.
import Arcus #@UnusedImport import Arcus #@UnusedImport
from UM.Platform import Platform
import cura.CuraApplication import cura.CuraApplication
import cura.Settings.CuraContainerRegistry 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.Settings.ExtruderManager import ExtruderManager
from cura.QualityManager import QualityManager from cura.QualityManager import QualityManager
from UM.Scene.SceneNode import SceneNode from UM.Scene.SceneNode import SceneNode
from cura.SliceableObjectDecorator import SliceableObjectDecorator
MYPY = False MYPY = False
@ -144,6 +145,11 @@ class ThreeMFReader(MeshReader):
group_decorator = GroupDecorator() group_decorator = GroupDecorator()
um_node.addDecorator(group_decorator) um_node.addDecorator(group_decorator)
um_node.setSelectable(True) 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 return um_node
def read(self, file_name): 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) self._id_mapping[old_id] = self._container_registry.uniqueName(old_id)
return self._id_mapping[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) 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: if self._3mf_mesh_reader and self._3mf_mesh_reader.preRead(file_name) == WorkspaceReader.PreReadResult.accepted:
pass pass
@ -167,6 +167,10 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
Logger.log("w", "File %s is not a valid workspace.", file_name) Logger.log("w", "File %s is not a valid workspace.", file_name)
return WorkspaceReader.PreReadResult.failed 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. # Show the dialog, informing the user what is about to happen.
self._dialog.setMachineConflict(machine_conflict) self._dialog.setMachineConflict(machine_conflict)
self._dialog.setQualityChangesConflict(quality_changes_conflict) self._dialog.setQualityChangesConflict(quality_changes_conflict)

View file

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

View file

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

View file

@ -1,30 +1,39 @@
# Copyright (c) 2015 Ultimaker B.V. # Copyright (c) 2015 Ultimaker B.V.
# Uranium is released under the terms of the AGPLv3 or higher. # 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 UM.i18n import i18nCatalog
from . import ThreeMFWorkspaceWriter
from . import ThreeMFWriter
i18n_catalog = i18nCatalog("uranium") i18n_catalog = i18nCatalog("uranium")
def getMetaData(): def getMetaData():
return { metaData = {
"plugin": { "plugin": {
"name": i18n_catalog.i18nc("@label", "3MF Writer"), "name": i18n_catalog.i18nc("@label", "3MF Writer"),
"author": "Ultimaker", "author": "Ultimaker",
"version": "1.0", "version": "1.0",
"description": i18n_catalog.i18nc("@info:whatsthis", "Provides support for writing 3MF files."), "description": i18n_catalog.i18nc("@info:whatsthis", "Provides support for writing 3MF files."),
"api": 3 "api": 3
}, }
"mesh_writer": { }
if "3MFWriter.ThreeMFWriter" in sys.modules:
metaData["mesh_writer"] = {
"output": [{ "output": [{
"extension": "3mf", "extension": "3mf",
"description": i18n_catalog.i18nc("@item:inlistbox", "3MF file"), "description": i18n_catalog.i18nc("@item:inlistbox", "3MF file"),
"mime_type": "application/vnd.ms-package.3dmanufacturing-3dmodel+xml", "mime_type": "application/vnd.ms-package.3dmanufacturing-3dmodel+xml",
"mode": ThreeMFWriter.ThreeMFWriter.OutputMode.BinaryMode "mode": ThreeMFWriter.ThreeMFWriter.OutputMode.BinaryMode
}] }]
}, }
"workspace_writer": { metaData["workspace_writer"] = {
"output": [{ "output": [{
"extension": "curaproject.3mf", "extension": "curaproject.3mf",
"description": i18n_catalog.i18nc("@item:inlistbox", "Cura Project 3MF file"), "description": i18n_catalog.i18nc("@item:inlistbox", "Cura Project 3MF file"),
@ -32,7 +41,12 @@ def getMetaData():
"mode": ThreeMFWorkspaceWriter.ThreeMFWorkspaceWriter.OutputMode.BinaryMode "mode": ThreeMFWorkspaceWriter.ThreeMFWorkspaceWriter.OutputMode.BinaryMode
}] }]
} }
}
return metaData
def register(app): 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] [2.5.0]
*Speed. *Improved 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. 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. *Speedup engine Multithreading
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? 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. *Preheat the build plate (with a connected printer)
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. 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. *Better layout for 3D layer view options
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. 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. *Disable automatic slicing
This change needs no explanation! An option to disable auto-slicing has been added for the better user experience.
*Preheat the build plate (with a connected printer). *Auto-scale off by default
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). This change speaks for itself.
*G-code reader. *Print cost calculation
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. 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. *G-code reader
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. 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. *Discard or Keep Changes popup
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. 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 *Bug fixes
- 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.
Property renaming: Properties that start with get have been renamed to avoid confusion. - “Center camera when the item is selected”: This is now set to off by default.
Window overflow: This is now fixed. - 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.
Multiple machine prefixes: Multiple machine prefixes are gone when loading and saving projects. - 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.
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. - Different Y / Z colors: Y and Z colors in the tool menu are now similar to the colors on the build plate.
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. - No collision areas: No collision areas used to be generated for some models when "keep models apart" was activated. This is now fixed.
Different Y / Z colors: Y and Z colors in the tool menu are now different to the colors on the build plate. - Perimeter gaps: Perimeter gaps are not filled often enough; weve now amended this.
No collision areas: No collision areas were generated for some models. - 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.
Perimeter gaps: Perimeter gaps are not filled often enough; weve now amended this. - Project name: The project name changes after the project is opened. This now has been fixed.
File location after restart: The old version of Cura didnt remember the last opened file location after its been restarted. Now it does! - 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.
Project name: The project name changes after the project is opened. - Support Towers: Support Towers can now be disabled.
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 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.
Support Towers: Support Towers can now be disabled. - Summary box size: Weve enlarged the summary box when saving the project.
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. - Cubic subdivision infill: In the past, the cubic subdivision infill sometimes didnt produce the infill (WIN) this has now been addressed.
Summary box size: Weve enlarged the summary box when saving your project. - Spiralize outer contour and fill small gaps: When combining Fill Gaps Between Walls with Spiralize Outer Contour, the model gets a massive infill.
Cubic subdivision infill: In the past, the cubic subdivision infill sometimes didnt produce the infill (WIN) this has now been addressed. - Experimental post-processing plugin: Since the TweakAtZ post-processing plugin is not officially supported, we added the Experimental tag.
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.
*3rd party printers (bug fixes) *3rd party printers (bug fixes)
- Folgertech printer definition has been added.
Folgertech printer definition has been added - Hello BEE Prusa 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 - Material profiles for Cartesio printers have been updated.
[2.4.0] [2.4.0]
*Project saving & opening *Project saving & opening

View file

@ -2,6 +2,7 @@
# Cura is released under the terms of the AGPLv3 or higher. # Cura is released under the terms of the AGPLv3 or higher.
from UM.Application import Application from UM.Application import Application
from UM.Backend import Backend
from UM.Job import Job from UM.Job import Job
from UM.Logger import Logger from UM.Logger import Logger
from UM.Math.AxisAlignedBox import AxisAlignedBox from UM.Math.AxisAlignedBox import AxisAlignedBox
@ -37,7 +38,6 @@ class GCodeReader(MeshReader):
self._message = None self._message = None
self._layer_number = 0 self._layer_number = 0
self._extruder_number = 0 self._extruder_number = 0
self._layer_type = LayerPolygon.Inset0Type
self._clearValues() self._clearValues()
self._scene_node = None self._scene_node = None
self._position = namedtuple('Position', ['x', 'y', 'z', 'e']) self._position = namedtuple('Position', ['x', 'y', 'z', 'e'])
@ -153,7 +153,6 @@ class GCodeReader(MeshReader):
self._previous_z = z self._previous_z = z
else: else:
path.append([x, y, z, LayerPolygon.MoveCombingType]) path.append([x, y, z, LayerPolygon.MoveCombingType])
return self._position(x, y, z, e) return self._position(x, y, z, e)
# G0 and G1 should be handled exactly the same. # G0 and G1 should be handled exactly the same.
@ -172,7 +171,6 @@ class GCodeReader(MeshReader):
def _gCode92(self, position, params, path): def _gCode92(self, position, params, path):
if params.e is not None: if params.e is not None:
position.e[self._extruder_number] = params.e position.e[self._extruder_number] = params.e
return self._position( return self._position(
params.x if params.x is not None else position.x, params.x if params.x is not None else position.x,
params.y if params.y is not None else position.y, params.y if params.y is not None else position.y,
@ -307,11 +305,11 @@ class GCodeReader(MeshReader):
G = self._getInt(line, "G") G = self._getInt(line, "G")
if G is not None: if G is not None:
current_position = self._processGCode(G, line, current_position, current_path) 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 # < 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 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])): if self._createPolygon(self._current_layer_thickness, current_path, self._extruder_offsets.get(self._extruder_number, [0, 0])):
current_path.clear() current_path.clear()
if not self._is_layers_in_file: if not self._is_layers_in_file:
self._layer_number += 1 self._layer_number += 1
@ -343,6 +341,8 @@ class GCodeReader(MeshReader):
gcode_list_decorator.setGCodeList(gcode_list) gcode_list_decorator.setGCodeList(gcode_list)
scene_node.addDecorator(gcode_list_decorator) scene_node.addDecorator(gcode_list_decorator)
Application.getInstance().getController().getScene().gcode_list = gcode_list
Logger.log("d", "Finished parsing %s" % file_name) Logger.log("d", "Finished parsing %s" % file_name)
self._message.hide() 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) "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() 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 return scene_node

View file

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

View file

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

View file

@ -351,7 +351,7 @@ Item
property bool roundValues: true property bool roundValues: true
property var activeHandle: upperHandle 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() function getUpperValueFromHandle()
{ {

View file

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

View file

@ -35,6 +35,7 @@ class DiscoverUM3Action(MachineAction):
@pyqtSlot() @pyqtSlot()
def startDiscovery(self): def startDiscovery(self):
if not self._network_plugin: if not self._network_plugin:
Logger.log("d", "Starting printer discovery.")
self._network_plugin = Application.getInstance().getOutputDeviceManager().getOutputDevicePlugin("UM3NetworkPrinting") self._network_plugin = Application.getInstance().getOutputDeviceManager().getOutputDevicePlugin("UM3NetworkPrinting")
self._network_plugin.printerListChanged.connect(self._onPrinterDiscoveryChanged) self._network_plugin.printerListChanged.connect(self._onPrinterDiscoveryChanged)
self.printersChanged.emit() self.printersChanged.emit()
@ -42,6 +43,7 @@ class DiscoverUM3Action(MachineAction):
## Re-filters the list of printers. ## Re-filters the list of printers.
@pyqtSlot() @pyqtSlot()
def reset(self): def reset(self):
Logger.log("d", "Reset the list of found printers.")
self.printersChanged.emit() self.printersChanged.emit()
@pyqtSlot() @pyqtSlot()
@ -95,12 +97,14 @@ class DiscoverUM3Action(MachineAction):
@pyqtSlot(str) @pyqtSlot(str)
def setKey(self, key): 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() global_container_stack = Application.getInstance().getGlobalContainerStack()
if global_container_stack: if global_container_stack:
meta_data = global_container_stack.getMetaData() meta_data = global_container_stack.getMetaData()
if "um_network_key" in meta_data: if "um_network_key" in meta_data:
global_container_stack.setMetaDataEntry("um_network_key", key) global_container_stack.setMetaDataEntry("um_network_key", key)
# Delete old authentication data. # 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_id")
global_container_stack.removeMetaDataEntry("network_authentication_key") global_container_stack.removeMetaDataEntry("network_authentication_key")
else: else:

193
plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py Normal file → Executable file
View file

@ -200,11 +200,11 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice):
def _onAuthenticationRequired(self, reply, authenticator): def _onAuthenticationRequired(self, reply, authenticator):
if self._authentication_id is not None and self._authentication_key is not None: 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.setUser(self._authentication_id)
authenticator.setPassword(self._authentication_key) authenticator.setPassword(self._authentication_key)
else: 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): def getProperties(self):
return self._properties return self._properties
@ -619,64 +619,67 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice):
self._gcode = getattr(Application.getInstance().getController().getScene(), "gcode_list") self._gcode = getattr(Application.getInstance().getController().getScene(), "gcode_list")
print_information = Application.getInstance().getPrintInformation() print_information = Application.getInstance().getPrintInformation()
# 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 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(
i18n_catalog.i18nc("@info:status", "Unable to start a new print job. No PrinterCore loaded in slot {0}".format(index + 1)))
self._error_message.show()
return
if self._json_printer_state["heads"][0]["extruders"][index]["active_material"]["guid"] == "":
Logger.log("e", "No material loaded in slot %s, unable to start print", index + 1)
self._error_message = Message(
i18n_catalog.i18nc("@info:status",
"Unable to start a new print job. No material loaded in slot {0}".format(index + 1)))
self._error_message.show()
return
warnings = [] # There might be multiple things wrong. Keep a list of all the stuff we need to warn about. 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): # Only check for mistakes if there is material length information.
# Check if there is enough material. Any failure in these results in a warning. if print_information.materialLengths:
material_length = self._json_printer_state["heads"][0]["extruders"][index]["active_material"]["length_remaining"] # Check if print cores / materials are loaded at all. Any failure in these results in an Error.
if material_length != -1 and print_information.materialLengths[index] > material_length: for index in range(0, self._num_extruders):
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) if index < len(print_information.materialLengths) and print_information.materialLengths[index] != 0:
warnings.append(i18n_catalog.i18nc("@label", "Not enough material for spool {0}.").format(index+1)) 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(
i18n_catalog.i18nc("@info:status", "Unable to start a new print job. No PrinterCore loaded in slot {0}".format(index + 1)))
self._error_message.show()
return
if self._json_printer_state["heads"][0]["extruders"][index]["active_material"]["guid"] == "":
Logger.log("e", "No material loaded in slot %s, unable to start print", index + 1)
self._error_message = Message(
i18n_catalog.i18nc("@info:status",
"Unable to start a new print job. No material loaded in slot {0}".format(index + 1)))
self._error_message.show()
return
# Check if the right cartridges are loaded. Any failure in these results in a warning. for index in range(0, self._num_extruders):
extruder_manager = cura.Settings.ExtruderManager.ExtruderManager.getInstance() # Check if there is enough material. Any failure in these results in a warning.
if print_information.materialLengths[index] != 0: material_length = self._json_printer_state["heads"][0]["extruders"][index]["active_material"]["length_remaining"]
variant = extruder_manager.getExtruderStack(index).findContainer({"type": "variant"}) if material_length != -1 and index < len(print_information.materialLengths) and print_information.materialLengths[index] > material_length:
core_name = self._json_printer_state["heads"][0]["extruders"][index]["hotend"]["id"] 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)
if variant: warnings.append(i18n_catalog.i18nc("@label", "Not enough material for spool {0}.").format(index+1))
if variant.getName() != core_name:
Logger.log("w", "Extruder %s has a different Cartridge (%s) as Cura (%s)", index + 1, core_name, variant.getName())
warnings.append(i18n_catalog.i18nc("@label", "Different print core (Cura: {0}, Printer: {1}) selected for extruder {2}".format(variant.getName(), core_name, index + 1)))
material = extruder_manager.getExtruderStack(index).findContainer({"type": "material"}) # Check if the right cartridges are loaded. Any failure in these results in a warning.
if material: extruder_manager = cura.Settings.ExtruderManager.ExtruderManager.getInstance()
remote_material_guid = self._json_printer_state["heads"][0]["extruders"][index]["active_material"]["guid"] if index < len(print_information.materialLengths) and print_information.materialLengths[index] != 0:
if material.getMetaDataEntry("GUID") != remote_material_guid: variant = extruder_manager.getExtruderStack(index).findContainer({"type": "variant"})
Logger.log("w", "Extruder %s has a different material (%s) as Cura (%s)", index + 1, core_name = self._json_printer_state["heads"][0]["extruders"][index]["hotend"]["id"]
remote_material_guid, if variant:
material.getMetaDataEntry("GUID")) if variant.getName() != core_name:
Logger.log("w", "Extruder %s has a different Cartridge (%s) as Cura (%s)", index + 1, core_name, variant.getName())
warnings.append(i18n_catalog.i18nc("@label", "Different print core (Cura: {0}, Printer: {1}) selected for extruder {2}".format(variant.getName(), core_name, index + 1)))
remote_materials = UM.Settings.ContainerRegistry.ContainerRegistry.getInstance().findInstanceContainers(type = "material", GUID = remote_material_guid, read_only = True) material = extruder_manager.getExtruderStack(index).findContainer({"type": "material"})
remote_material_name = "Unknown" if material:
if remote_materials: remote_material_guid = self._json_printer_state["heads"][0]["extruders"][index]["active_material"]["guid"]
remote_material_name = remote_materials[0].getName() if material.getMetaDataEntry("GUID") != remote_material_guid:
warnings.append(i18n_catalog.i18nc("@label", "Different material (Cura: {0}, Printer: {1}) selected for extruder {2}").format(material.getName(), remote_material_name, index + 1)) Logger.log("w", "Extruder %s has a different material (%s) as Cura (%s)", index + 1,
remote_material_guid,
material.getMetaDataEntry("GUID"))
try: remote_materials = UM.Settings.ContainerRegistry.ContainerRegistry.getInstance().findInstanceContainers(type = "material", GUID = remote_material_guid, read_only = True)
is_offset_calibrated = self._json_printer_state["heads"][0]["extruders"][index]["hotend"]["offset"]["state"] == "valid" remote_material_name = "Unknown"
except KeyError: # Older versions of the API don't expose the offset property, so we must asume that all is well. if remote_materials:
is_offset_calibrated = True remote_material_name = remote_materials[0].getName()
warnings.append(i18n_catalog.i18nc("@label", "Different material (Cura: {0}, Printer: {1}) selected for extruder {2}").format(material.getName(), remote_material_name, index + 1))
if not is_offset_calibrated: try:
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)) is_offset_calibrated = self._json_printer_state["heads"][0]["extruders"][index]["hotend"]["offset"]["state"] == "valid"
except KeyError: # Older versions of the API don't expose the offset property, so we must asume that all is well.
is_offset_calibrated = True
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: if warnings:
text = i18n_catalog.i18nc("@label", "Are you sure you wish to print with the selected configuration?") 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 ## Start requesting data from printer
def connect(self): def connect(self):
self.close() # Ensure that previous connection (if any) is killed. if self.isConnected():
self.close() # Close previous connection
self._createNetworkManager() self._createNetworkManager()
@ -726,7 +730,12 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice):
## Check if this machine was authenticated before. ## Check if this machine was authenticated before.
self._authentication_id = Application.getInstance().getGlobalContainerStack().getMetaDataEntry("network_authentication_id", None) self._authentication_id = Application.getInstance().getGlobalContainerStack().getMetaDataEntry("network_authentication_id", None)
self._authentication_key = Application.getInstance().getGlobalContainerStack().getMetaDataEntry("network_authentication_key", 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() self._update_timer.start()
## Stop requesting data from printer ## Stop requesting data from printer
@ -788,19 +797,41 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice):
Logger.log("d", "Started sending g-code to remote printer.") Logger.log("d", "Started sending g-code to remote printer.")
self._compressing_print = True self._compressing_print = True
## Mash the data into single string ## Mash the data into single string
max_chars_per_line = 1024 * 1024 / 4 # 1 / 4 MB
byte_array_file_data = b"" 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: for line in self._gcode:
if not self._compressing_print: if not self._compressing_print:
self._progress_message.hide() self._progress_message.hide()
return # Stop trying to zip, abort was called. return # Stop trying to zip, abort was called.
if self._use_gzip: if self._use_gzip:
byte_array_file_data += gzip.compress(line.encode("utf-8")) batched_line += line
QCoreApplication.processEvents() # Ensure that the GUI does not freeze. # if the gcode was read from a gcode file, self._gcode will be a list of all lines in that file.
# Pretend that this is a response, as zipping might take a bit of time. # Compressing line by line in this case is extremely slow, so we need to batch them.
self._last_response_time = time() if len(batched_line) < max_chars_per_line:
continue
byte_array_file_data += _compress_data_and_notify_qt(batched_line)
batched_line = ""
else: else:
byte_array_file_data += line.encode("utf-8") 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: if self._use_gzip:
file_name = "%s.gcode.gz" % Application.getInstance().getPrintInformation().jobName file_name = "%s.gcode.gz" % Application.getInstance().getPrintInformation().jobName
else: else:
@ -842,7 +873,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice):
## Check if the authentication request was allowed by the printer. ## Check if the authentication request was allowed by the printer.
def _checkAuthentication(self): 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)))) 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 ## 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") url = QUrl("http://" + self._address + self._api_prefix + "auth/request")
request = QNetworkRequest(url) request = QNetworkRequest(url)
request.setHeader(QNetworkRequest.ContentTypeHeader, "application/json") 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._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. ## Send all material profiles to the printer.
def sendMaterialProfiles(self): def sendMaterialProfiles(self):
@ -921,7 +954,11 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice):
if status_code == 200: if status_code == 200:
if self._connection_state == ConnectionState.connecting: if self._connection_state == ConnectionState.connecting:
self.setConnectionState(ConnectionState.connected) self.setConnectionState(ConnectionState.connected)
self._json_printer_state = json.loads(bytes(reply.readAll()).decode("utf-8")) 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() self._spliceJSONData()
# Hide connection error message if the connection was restored # Hide connection error message if the connection was restored
@ -933,7 +970,11 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice):
pass # TODO: Handle errors pass # TODO: Handle errors
elif "print_job" in reply_url: # Status update from print_job: elif "print_job" in reply_url: # Status update from print_job:
if status_code == 200: if status_code == 200:
json_data = json.loads(bytes(reply.readAll()).decode("utf-8")) 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"] progress = json_data["progress"]
## If progress is 0 add a bit so another print can't be sent. ## If progress is 0 add a bit so another print can't be sent.
if progress == 0: if progress == 0:
@ -1011,13 +1052,17 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice):
else: else:
global_container_stack.addMetaDataEntry("network_authentication_id", self._authentication_id) 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. 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. 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)) Logger.log("e", "While trying to authenticate, we got an unexpected response: %s", reply.attribute(QNetworkRequest.HttpStatusCodeAttribute))
self.setAuthenticationState(AuthState.NotAuthenticated) self.setAuthenticationState(AuthState.NotAuthenticated)
elif "auth/check" in reply_url: # Check if we are authenticated (user can refuse this!) elif "auth/check" in reply_url: # Check if we are authenticated (user can refuse this!)
data = json.loads(bytes(reply.readAll()).decode("utf-8")) 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": if data.get("message", "") == "authorized":
Logger.log("i", "Authentication was approved") Logger.log("i", "Authentication was approved")
self._verifyAuthentication() # Ensure that the verification is really used and correct. self._verifyAuthentication() # Ensure that the verification is really used and correct.
@ -1030,8 +1075,11 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice):
elif reply.operation() == QNetworkAccessManager.PostOperation: elif reply.operation() == QNetworkAccessManager.PostOperation:
if "/auth/request" in reply_url: if "/auth/request" in reply_url:
# We got a response to requesting authentication. # We got a response to requesting authentication.
data = json.loads(bytes(reply.readAll()).decode("utf-8")) 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() global_container_stack = Application.getInstance().getGlobalContainerStack()
if global_container_stack: # Remove any old data. if global_container_stack: # Remove any old data.
Logger.log("d", "Removing old network authentication data as a new one was requested.") 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_key = data["key"]
self._authentication_id = data["id"] 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. # Check if the authentication is accepted.
self._checkAuthentication() self._checkAuthentication()
@ -1111,3 +1159,12 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice):
icon=QMessageBox.Question, icon=QMessageBox.Question,
callback=callback 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: for key in self._printers:
if key == active_machine.getMetaDataEntry("um_network_key"): if key == active_machine.getMetaDataEntry("um_network_key"):
Logger.log("d", "Connecting [%s]..." % key) if not self._printers[key].isConnected():
self._printers[key].connect() Logger.log("d", "Connecting [%s]..." % key)
self._printers[key].connectionStateChanged.connect(self._onPrinterConnectionStateChanged) self._printers[key].connect()
self._printers[key].connectionStateChanged.connect(self._onPrinterConnectionStateChanged)
else: else:
if self._printers[key].isConnected(): if self._printers[key].isConnected():
Logger.log("d", "Closing connection [%s]..." % key) Logger.log("d", "Closing connection [%s]..." % key)
self._printers[key].connectionStateChanged.disconnect(self._onPrinterConnectionStateChanged)
self._printers[key].close() self._printers[key].close()
## Because the model needs to be created in the same thread as the QMLEngine, we use a signal. ## 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 from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura") catalog = i18nCatalog("cura")
class USBPrinterOutputDevice(PrinterOutputDevice):
class USBPrinterOutputDevice(PrinterOutputDevice):
def __init__(self, serial_port): def __init__(self, serial_port):
super().__init__(serial_port) super().__init__(serial_port)
self.setName(catalog.i18nc("@item:inmenu", "USB printing")) self.setName(catalog.i18nc("@item:inmenu", "USB printing"))
@ -202,6 +202,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
try: try:
programmer.connect(self._serial_port) programmer.connect(self._serial_port)
except Exception: except Exception:
programmer.close()
pass pass
# Give programmer some time to connect. Might need more in some cases, but this worked in all tested cases. # 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. programmer.connect(self._serial_port) # Connect with the serial, if this succeeds, it's an arduino based usb device.
self._serial = programmer.leaveISP() self._serial = programmer.leaveISP()
except ispBase.IspError as e: 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))) Logger.log("i", "Could not establish connection on %s: %s. Device is not arduino based." %(self._serial_port,str(e)))
except Exception as 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) 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. # If the programmer connected, we know its an atmega based version.
@ -559,6 +562,11 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
if ";" in line: if ";" in line:
line = line[:line.find(";")] line = line[:line.find(";")]
line = line.strip() line = line.strip()
# Don't send empty lines. But we do have to send something, so send m105 instead.
if line == "":
line = "M105"
try: try:
if line == "M0" or line == "M1": 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. 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.getOutputDeviceManager().removeOutputDevice(serial_port)
self.connectionStateChanged.emit() self.connectionStateChanged.emit()
except KeyError: 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) @pyqtProperty(QObject , notify = connectionStateChanged)
def connectedPrinterList(self): def connectedPrinterList(self):

View file

@ -12,6 +12,9 @@
"has_machine_materials": true, "has_machine_materials": true,
"has_variants": true, "has_variants": true,
"variants_name": "Nozzle size", "variants_name": "Nozzle size",
"preferred_variant": "*0.4*",
"preferred_material": "*pla*",
"preferred_quality": "*draft*",
"machine_extruder_trains": "machine_extruder_trains":
{ {
"0": "cartesio_extruder_0", "0": "cartesio_extruder_0",
@ -29,16 +32,27 @@
"machine_extruder_count": { "default_value": 4 }, "machine_extruder_count": { "default_value": 4 },
"machine_heated_bed": { "default_value": true }, "machine_heated_bed": { "default_value": true },
"machine_center_is_zero": { "default_value": false }, "machine_center_is_zero": { "default_value": false },
"gantry_height": { "default_value": 35 },
"machine_height": { "default_value": 400 }, "machine_height": { "default_value": 400 },
"machine_depth": { "default_value": 270 }, "machine_depth": { "default_value": 270 },
"machine_width": { "default_value": 430 }, "machine_width": { "default_value": 430 },
"machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" }, "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": { "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": { "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_heat_up_speed": {"default_value": 20},
"machine_nozzle_cool_down_speed": {"default_value": 20}, "machine_nozzle_cool_down_speed": {"default_value": 20},
"machine_min_cool_heat_time_window": {"default_value": 5} "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.", "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", "unit": "mm",
"minimum_value": "0.001", "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", "maximum_value_warning": "2 * machine_nozzle_size",
"default_value": 0.4, "default_value": 0.4,
"type": "float", "type": "float",
@ -649,7 +649,7 @@
"description": "Width of a single wall line.", "description": "Width of a single wall line.",
"unit": "mm", "unit": "mm",
"minimum_value": "0.001", "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", "maximum_value_warning": "2 * machine_nozzle_size",
"value": "line_width", "value": "line_width",
"default_value": 0.4, "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.", "description": "Width of the outermost wall line. By lowering this value, higher levels of detail can be printed.",
"unit": "mm", "unit": "mm",
"minimum_value": "0.001", "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", "maximum_value_warning": "2 * machine_nozzle_size",
"default_value": 0.4, "default_value": 0.4,
"value": "wall_line_width", "value": "wall_line_width",
@ -676,7 +676,7 @@
"description": "Width of a single wall line for all wall lines except the outermost one.", "description": "Width of a single wall line for all wall lines except the outermost one.",
"unit": "mm", "unit": "mm",
"minimum_value": "0.001", "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", "maximum_value_warning": "2 * machine_nozzle_size",
"default_value": 0.4, "default_value": 0.4,
"value": "wall_line_width", "value": "wall_line_width",
@ -691,7 +691,7 @@
"description": "Width of a single top/bottom line.", "description": "Width of a single top/bottom line.",
"unit": "mm", "unit": "mm",
"minimum_value": "0.001", "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", "maximum_value_warning": "2 * machine_nozzle_size",
"default_value": 0.4, "default_value": 0.4,
"type": "float", "type": "float",
@ -704,7 +704,7 @@
"description": "Width of a single infill line.", "description": "Width of a single infill line.",
"unit": "mm", "unit": "mm",
"minimum_value": "0.001", "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", "maximum_value_warning": "3 * machine_nozzle_size",
"default_value": 0.4, "default_value": 0.4,
"type": "float", "type": "float",
@ -718,7 +718,7 @@
"description": "Width of a single skirt or brim line.", "description": "Width of a single skirt or brim line.",
"unit": "mm", "unit": "mm",
"minimum_value": "0.001", "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", "maximum_value_warning": "3 * machine_nozzle_size",
"default_value": 0.4, "default_value": 0.4,
"type": "float", "type": "float",
@ -733,7 +733,7 @@
"description": "Width of a single support structure line.", "description": "Width of a single support structure line.",
"unit": "mm", "unit": "mm",
"minimum_value": "0.001", "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", "maximum_value_warning": "3 * machine_nozzle_size",
"default_value": 0.4, "default_value": 0.4,
"type": "float", "type": "float",
@ -750,7 +750,7 @@
"unit": "mm", "unit": "mm",
"default_value": 0.4, "default_value": 0.4,
"minimum_value": "0.001", "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", "maximum_value_warning": "2 * machine_nozzle_size",
"type": "float", "type": "float",
"enabled": "support_enable and support_interface_enable", "enabled": "support_enable and support_interface_enable",
@ -804,7 +804,7 @@
"default_value": 0.4, "default_value": 0.4,
"value": "line_width", "value": "line_width",
"minimum_value": "0.001", "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", "maximum_value_warning": "2 * machine_nozzle_size",
"settable_per_mesh": false, "settable_per_mesh": false,
"settable_per_extruder": true "settable_per_extruder": true
@ -1122,7 +1122,7 @@
"default_value": 2, "default_value": 2,
"minimum_value": "0", "minimum_value": "0",
"minimum_value_warning": "infill_line_width", "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 "settable_per_mesh": true
} }
} }
@ -1156,6 +1156,65 @@
"type": "[int]", "type": "[int]",
"default_value": "[ ]", "default_value": "[ ]",
"enabled": "infill_pattern != 'concentric' and infill_pattern != 'concentric_3d' and infill_pattern != 'cubicsubdiv'", "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 "settable_per_mesh": true
}, },
"sub_div_rad_mult": "sub_div_rad_mult":
@ -1264,9 +1323,9 @@
"default_value": 0.1, "default_value": 0.1,
"minimum_value": "resolveOrValue('layer_height')", "minimum_value": "resolveOrValue('layer_height')",
"maximum_value_warning": "0.75 * machine_nozzle_size", "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')", "value": "resolveOrValue('layer_height')",
"enabled": "infill_sparse_density > 0", "enabled": "infill_sparse_density > 0 and not spaghetti_infill_enabled",
"settable_per_mesh": true "settable_per_mesh": true
}, },
"gradual_infill_steps": "gradual_infill_steps":
@ -1277,8 +1336,8 @@
"type": "int", "type": "int",
"minimum_value": "0", "minimum_value": "0",
"maximum_value_warning": "4", "maximum_value_warning": "4",
"maximum_value": "(20 - math.log(infill_line_distance) / math.log(2)) if infill_line_distance > 0 else 0", "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'", "enabled": "infill_sparse_density > 0 and infill_pattern != 'cubicsubdiv' and not spaghetti_infill_enabled",
"settable_per_mesh": true "settable_per_mesh": true
}, },
"gradual_infill_step_height": "gradual_infill_step_height":
@ -1356,7 +1415,7 @@
"max_skin_angle_for_expansion": "max_skin_angle_for_expansion":
{ {
"label": "Maximum 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": "°", "unit": "°",
"type": "float", "type": "float",
"minimum_value": "0", "minimum_value": "0",
@ -4405,6 +4464,40 @@
"settable_per_meshgroup": false, "settable_per_meshgroup": false,
"settable_globally": 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": "infill_mesh_order":
{ {
"label": "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.", "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", "type": "bool",
"default_value": false, "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" "value": "100"
}, },
"material_bed_temperature": { "material_bed_temperature": {
"visible": "False" "enabled": false
}, },
"material_diameter": { "material_diameter": {
"value": "1.75" "value": "1.75"

View file

@ -16,10 +16,10 @@
"machine_nozzle_offset_x": { "default_value": 0.0 }, "machine_nozzle_offset_x": { "default_value": 0.0 },
"machine_nozzle_offset_y": { "default_value": 0.0 }, "machine_nozzle_offset_y": { "default_value": 0.0 },
"machine_extruder_start_code": { "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": { "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_x": { "default_value": 24.0 },
"machine_nozzle_offset_y": { "default_value": 0.0 }, "machine_nozzle_offset_y": { "default_value": 0.0 },
"machine_extruder_start_code": { "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": { "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_x": { "default_value": 0.0 },
"machine_nozzle_offset_y": { "default_value": 60.0 }, "machine_nozzle_offset_y": { "default_value": 60.0 },
"machine_extruder_start_code": { "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": { "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_x": { "default_value": 24.0 },
"machine_nozzle_offset_y": { "default_value": 60.0 }, "machine_nozzle_offset_y": { "default_value": 60.0 },
"machine_extruder_start_code": { "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": { "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_x": { "default_value": 213 },
"machine_extruder_end_pos_y": { "default_value": 207 }, "machine_extruder_end_pos_y": { "default_value": 207 },
"machine_nozzle_head_distance": { "default_value": 2.7 }, "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_y": { "default_value": 6 },
"extruder_prime_pos_z": { "default_value": 2 } "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_x": { "default_value": 213 },
"machine_extruder_end_pos_y": { "default_value": 189 }, "machine_extruder_end_pos_y": { "default_value": 189 },
"machine_nozzle_head_distance": { "default_value": 4.2 }, "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_y": { "default_value": 6 },
"extruder_prime_pos_z": { "default_value": 2 } "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_x": { "default_value": 213 },
"machine_extruder_end_pos_y": { "default_value": 207 }, "machine_extruder_end_pos_y": { "default_value": 207 },
"machine_nozzle_head_distance": { "default_value": 2.7 }, "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_y": { "default_value": 6 },
"extruder_prime_pos_z": { "default_value": 2 } "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_x": { "default_value": 213 },
"machine_extruder_end_pos_y": { "default_value": 189 }, "machine_extruder_end_pos_y": { "default_value": 189 },
"machine_nozzle_head_distance": { "default_value": 4.2 }, "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_y": { "default_value": 6 },
"extruder_prime_pos_z": { "default_value": 2 } "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 #, fuzzy
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: Uranium json setting files\n" "Project-Id-Version: Cura 2.5\n"
"Report-Msgid-Bugs-To: http://github.com/ultimaker/uranium\n" "Report-Msgid-Bugs-To: http://github.com/Ultimaker/Cura\n"
"POT-Creation-Date: 2016-12-28 10:51+0000\n" "POT-Creation-Date: 2017-03-27 17:27+0000\n"
"PO-Revision-Date: 2017-01-12 15:51+0100\n" "PO-Revision-Date: 2017-04-04 11:27+0200\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: Bothof <info@bothof.nl>\n"
"Language-Team: LANGUAGE\n" "Language-Team: Bothof <info@bothof.nl>\n"
"Language: \n" "Language: de\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\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 #, fuzzy
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: Uranium json setting files\n" "Project-Id-Version: Cura 2.5\n"
"Report-Msgid-Bugs-To: http://github.com/ultimaker/uranium\n" "Report-Msgid-Bugs-To: http://github.com/Ultimaker/Cura\n"
"POT-Creation-Date: 2016-12-28 10:51+0000\n" "POT-Creation-Date: 2017-03-27 17:27+0000\n"
"PO-Revision-Date: 2017-01-12 15:51+0100\n" "PO-Revision-Date: 2017-04-04 11:27+0200\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: Bothof <info@bothof.nl>\n"
"Language-Team: LANGUAGE\n" "Language-Team: Bothof <info@bothof.nl>\n"
"Language: \n" "Language: es\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"

File diff suppressed because it is too large Load diff

View file

@ -3,7 +3,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: Uranium json setting files\n" "Project-Id-Version: Uranium json setting files\n"
"Report-Msgid-Bugs-To: http://github.com/ultimaker/uranium\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" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE\n" "Language-Team: LANGUAGE\n"

View file

@ -3,7 +3,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: Uranium json setting files\n" "Project-Id-Version: Uranium json setting files\n"
"Report-Msgid-Bugs-To: http://github.com/ultimaker/uranium\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" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE\n" "Language-Team: LANGUAGE\n"
@ -268,6 +268,18 @@ msgid ""
"extruder is no longer used." "extruder is no longer used."
msgstr "" 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 #: fdmprinter.def.json
msgctxt "machine_nozzle_heat_up_speed label" msgctxt "machine_nozzle_heat_up_speed label"
msgid "Heat up speed" msgid "Heat up speed"
@ -856,6 +868,47 @@ msgctxt "top_bottom_pattern option zigzag"
msgid "Zig Zag" msgid "Zig Zag"
msgstr "" 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 #: fdmprinter.def.json
msgctxt "wall_0_inset label" msgctxt "wall_0_inset label"
msgid "Outer Wall Inset" msgid "Outer Wall Inset"
@ -1124,6 +1177,22 @@ msgctxt "infill_pattern option zigzag"
msgid "Zig Zag" msgid "Zig Zag"
msgstr "" 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 #: fdmprinter.def.json
msgctxt "sub_div_rad_mult label" msgctxt "sub_div_rad_mult label"
msgid "Cubic Subdivision Radius" msgid "Cubic Subdivision Radius"
@ -1262,6 +1331,97 @@ msgid ""
"through the surface." "through the surface."
msgstr "" 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 #: fdmprinter.def.json
msgctxt "material label" msgctxt "material label"
msgid "Material" msgid "Material"
@ -1304,8 +1464,7 @@ msgstr ""
#: fdmprinter.def.json #: fdmprinter.def.json
msgctxt "material_print_temperature description" msgctxt "material_print_temperature description"
msgid "" msgid "The temperature used for printing."
"The temperature used for printing. Set at 0 to pre-heat the printer manually."
msgstr "" msgstr ""
#: fdmprinter.def.json #: fdmprinter.def.json
@ -1376,8 +1535,8 @@ msgstr ""
#: fdmprinter.def.json #: fdmprinter.def.json
msgctxt "material_bed_temperature description" msgctxt "material_bed_temperature description"
msgid "" msgid ""
"The temperature used for the heated build plate. Set at 0 to pre-heat the " "The temperature used for the heated build plate. If this is 0, the bed will "
"printer manually." "not heat up for this print."
msgstr "" msgstr ""
#: fdmprinter.def.json #: fdmprinter.def.json
@ -2216,6 +2375,16 @@ msgctxt "retraction_combing option noskin"
msgid "No Skin" msgid "No Skin"
msgstr "" 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 #: fdmprinter.def.json
msgctxt "travel_avoid_other_parts label" msgctxt "travel_avoid_other_parts label"
msgid "Avoid Printed Parts When Traveling" msgid "Avoid Printed Parts When Traveling"
@ -2671,7 +2840,7 @@ msgctxt "support_z_distance description"
msgid "" msgid ""
"Distance from the top/bottom of the support structure to the print. This gap " "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 " "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 "" msgstr ""
#: fdmprinter.def.json #: 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 #, fuzzy
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: Uranium json setting files\n" "Project-Id-Version: Cura 2.5\n"
"Report-Msgid-Bugs-To: http://github.com/ultimaker/uranium\n" "Report-Msgid-Bugs-To: http://github.com/Ultimaker/Cura\n"
"POT-Creation-Date: 2016-12-28 10:51+0000\n" "POT-Creation-Date: 2017-03-27 17:27+0000\n"
"PO-Revision-Date: 2017-01-12 15:51+0100\n" "PO-Revision-Date: 2017-04-04 11:27+0200\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: Bothof <info@bothof.nl>\n"
"Language-Team: LANGUAGE\n" "Language-Team: Bothof <info@bothof.nl>\n"
"Language: \n" "Language: fi\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\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 #, fuzzy
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: Uranium json setting files\n" "Project-Id-Version: Cura 2.5\n"
"Report-Msgid-Bugs-To: http://github.com/ultimaker/uranium\n" "Report-Msgid-Bugs-To: http://github.com/Ultimaker/Cura\n"
"POT-Creation-Date: 2016-12-28 10:51+0000\n" "POT-Creation-Date: 2017-03-27 17:27+0000\n"
"PO-Revision-Date: 2017-01-12 15:51+0100\n" "PO-Revision-Date: 2017-04-04 11:27+0200\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: Bothof <info@bothof.nl>\n"
"Language-Team: LANGUAGE\n" "Language-Team: Bothof <info@bothof.nl>\n"
"Language: \n" "Language: fr\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\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 #, fuzzy
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: Uranium json setting files\n" "Project-Id-Version: Cura 2.5\n"
"Report-Msgid-Bugs-To: http://github.com/ultimaker/uranium\n" "Report-Msgid-Bugs-To: http://github.com/Ultimaker/Cura\n"
"POT-Creation-Date: 2016-12-28 10:51+0000\n" "POT-Creation-Date: 2017-03-27 17:27+0000\n"
"PO-Revision-Date: 2017-01-12 15:51+0100\n" "PO-Revision-Date: 2017-04-04 11:27+0200\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: Bothof <info@bothof.nl>\n"
"Language-Team: LANGUAGE\n" "Language-Team: Bothof <info@bothof.nl>\n"
"Language: \n" "Language: it\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\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 #, fuzzy
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: Uranium json setting files\n" "Project-Id-Version: Cura 2.5\n"
"Report-Msgid-Bugs-To: http://github.com/ultimaker/uranium\n" "Report-Msgid-Bugs-To: http://github.com/Ultimaker/Cura\n"
"POT-Creation-Date: 2016-12-28 10:51+0000\n" "POT-Creation-Date: 2017-03-27 17:27+0000\n"
"PO-Revision-Date: 2017-01-12 15:51+0100\n" "PO-Revision-Date: 2017-04-04 11:27+0200\n"
"Last-Translator: Ruben Dulek <r.dulek@ultimaker.com>\n" "Last-Translator: Bothof <info@bothof.nl>\n"
"Language-Team: Ultimaker\n" "Language-Team: Bothof <info@bothof.nl>\n"
"Language: \n" "Language: nl\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\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 "" msgstr ""
"Project-Id-Version: Uranium json setting files\n" "Project-Id-Version: Uranium json setting files\n"
"Report-Msgid-Bugs-To: http://github.com/ultimaker/uranium\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" "PO-Revision-Date: 2016-01-25 05:05-0300\n"
"Last-Translator: Cláudio Sampaio <patola@makerlinux.com.br>\n" "Last-Translator: Cláudio Sampaio <patola@makerlinux.com.br>\n"
"Language-Team: LANGUAGE\n" "Language-Team: LANGUAGE\n"
@ -70,12 +70,8 @@ msgstr "Posição de Início do Extrusor Absoluta"
#: fdmextruder.def.json #: fdmextruder.def.json
msgctxt "machine_extruder_start_pos_abs description" msgctxt "machine_extruder_start_pos_abs description"
msgid "" msgid "Make the extruder starting position absolute rather than relative to the last-known location of the head."
"Make the extruder starting position absolute rather than relative to the " 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."
"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 #: fdmextruder.def.json
msgctxt "machine_extruder_start_pos_x label" msgctxt "machine_extruder_start_pos_x label"
@ -114,12 +110,8 @@ msgstr "Posição Final do Extrusor Absoluta"
#: fdmextruder.def.json #: fdmextruder.def.json
msgctxt "machine_extruder_end_pos_abs description" msgctxt "machine_extruder_end_pos_abs description"
msgid "" msgid "Make the extruder ending position absolute rather than relative to the last-known location of the head."
"Make the extruder ending position absolute rather than relative to the last-" 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."
"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 #: fdmextruder.def.json
msgctxt "machine_extruder_end_pos_x label" msgctxt "machine_extruder_end_pos_x label"
@ -148,11 +140,8 @@ msgstr "Posição Z de Purga do Extrusor"
#: fdmextruder.def.json #: fdmextruder.def.json
msgctxt "extruder_prime_pos_z description" msgctxt "extruder_prime_pos_z description"
msgid "" msgid "The Z coordinate of the position where the nozzle primes at the start of printing."
"The Z coordinate of the position where the nozzle primes at the start of " msgstr "A coordenada Z da posição onde o bico faz a purga no início da impressão."
"printing."
msgstr ""
"A coordenada Z da posição onde o bico faz a purga no início da impressão."
#: fdmextruder.def.json #: fdmextruder.def.json
msgctxt "platform_adhesion label" msgctxt "platform_adhesion label"
@ -171,11 +160,8 @@ msgstr "Posição X de Purga do Extrusor"
#: fdmextruder.def.json #: fdmextruder.def.json
msgctxt "extruder_prime_pos_x description" msgctxt "extruder_prime_pos_x description"
msgid "" msgid "The X coordinate of the position where the nozzle primes at the start of printing."
"The X coordinate of the position where the nozzle primes at the start of " msgstr "A coordenada X da posição onde o bico faz a purga no início da impressão."
"printing."
msgstr ""
"A coordenada X da posição onde o bico faz a purga no início da impressão."
#: fdmextruder.def.json #: fdmextruder.def.json
msgctxt "extruder_prime_pos_y label" msgctxt "extruder_prime_pos_y label"
@ -184,8 +170,5 @@ msgstr "Posição Y de Purga do Extrusor"
#: fdmextruder.def.json #: fdmextruder.def.json
msgctxt "extruder_prime_pos_y description" msgctxt "extruder_prime_pos_y description"
msgid "" msgid "The Y coordinate of the position where the nozzle primes at the start of printing."
"The Y coordinate of the position where the nozzle primes at the start of " msgstr "A coordenada Y da posição onde o bico faz a purga no início da impressão."
"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 #, fuzzy
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: Uranium json setting files\n" "Project-Id-Version: Cura 2.5\n"
"Report-Msgid-Bugs-To: http://github.com/ultimaker/uranium\n" "Report-Msgid-Bugs-To: http://github.com/Ultimaker/Cura\n"
"POT-Creation-Date: 2016-12-28 10:51+0000\n" "POT-Creation-Date: 2017-03-27 17:27+0000\n"
"PO-Revision-Date: 2017-01-12 15:51+0100\n" "PO-Revision-Date: 2017-04-04 11:27+0200\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: Bothof <info@bothof.nl>\n"
"Language-Team: LANGUAGE\n" "Language-Team: Bothof <info@bothof.nl>\n"
"Language: \n" "Language: tr\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\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 newProject: newProjectAction;
property alias open: openAction; property alias open: openAction;
property alias loadWorkspace: loadWorkspaceAction;
property alias quit: quitAction; property alias quit: quitAction;
property alias undo: undoAction; property alias undo: undoAction;
@ -32,6 +31,8 @@ Item
property alias selectAll: selectAllAction; property alias selectAll: selectAllAction;
property alias deleteAll: deleteAllAction; property alias deleteAll: deleteAllAction;
property alias reloadAll: reloadAllAction; property alias reloadAll: reloadAllAction;
property alias arrangeAll: arrangeAllAction;
property alias arrangeSelection: arrangeSelectionAction;
property alias resetAllTranslation: resetAllTranslationAction; property alias resetAllTranslation: resetAllTranslationAction;
property alias resetAll: resetAllAction; property alias resetAll: resetAllAction;
@ -184,7 +185,7 @@ Item
enabled: UM.Controller.toolsEnabled; enabled: UM.Controller.toolsEnabled;
iconName: "edit-delete"; iconName: "edit-delete";
shortcut: StandardKey.Delete; shortcut: StandardKey.Delete;
onTriggered: Printer.deleteSelection(); onTriggered: CuraApplication.deleteSelection();
} }
Action Action
@ -208,7 +209,7 @@ Item
enabled: UM.Scene.numObjectsSelected > 1 ? true: false enabled: UM.Scene.numObjectsSelected > 1 ? true: false
iconName: "object-group" iconName: "object-group"
shortcut: "Ctrl+G"; shortcut: "Ctrl+G";
onTriggered: Printer.groupSelected(); onTriggered: CuraApplication.groupSelected();
} }
Action Action
@ -218,7 +219,7 @@ Item
enabled: UM.Scene.isGroupSelected enabled: UM.Scene.isGroupSelected
iconName: "object-ungroup" iconName: "object-ungroup"
shortcut: "Ctrl+Shift+G"; shortcut: "Ctrl+Shift+G";
onTriggered: Printer.ungroupSelected(); onTriggered: CuraApplication.ungroupSelected();
} }
Action Action
@ -228,7 +229,7 @@ Item
enabled: UM.Scene.numObjectsSelected > 1 ? true: false enabled: UM.Scene.numObjectsSelected > 1 ? true: false
iconName: "merge"; iconName: "merge";
shortcut: "Ctrl+Alt+G"; shortcut: "Ctrl+Alt+G";
onTriggered: Printer.mergeSelected(); onTriggered: CuraApplication.mergeSelected();
} }
Action Action
@ -245,7 +246,7 @@ Item
enabled: UM.Controller.toolsEnabled; enabled: UM.Controller.toolsEnabled;
iconName: "edit-select-all"; iconName: "edit-select-all";
shortcut: "Ctrl+A"; shortcut: "Ctrl+A";
onTriggered: Printer.selectAll(); onTriggered: CuraApplication.selectAll();
} }
Action Action
@ -255,7 +256,7 @@ Item
enabled: UM.Controller.toolsEnabled; enabled: UM.Controller.toolsEnabled;
iconName: "edit-delete"; iconName: "edit-delete";
shortcut: "Ctrl+D"; shortcut: "Ctrl+D";
onTriggered: Printer.deleteAll(); onTriggered: CuraApplication.deleteAll();
} }
Action Action
@ -264,27 +265,42 @@ Item
text: catalog.i18nc("@action:inmenu menubar:file","Re&load All Models"); text: catalog.i18nc("@action:inmenu menubar:file","Re&load All Models");
iconName: "document-revert"; iconName: "document-revert";
shortcut: "F5" 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 Action
{ {
id: resetAllTranslationAction; id: resetAllTranslationAction;
text: catalog.i18nc("@action:inmenu menubar:edit","Reset All Model Positions"); text: catalog.i18nc("@action:inmenu menubar:edit","Reset All Model Positions");
onTriggered: Printer.resetAllTranslation(); onTriggered: CuraApplication.resetAllTranslation();
} }
Action Action
{ {
id: resetAllAction; id: resetAllAction;
text: catalog.i18nc("@action:inmenu menubar:edit","Reset All Model &Transformations"); text: catalog.i18nc("@action:inmenu menubar:edit","Reset All Model &Transformations");
onTriggered: Printer.resetAll(); onTriggered: CuraApplication.resetAll();
} }
Action Action
{ {
id: openAction; id: openAction;
text: catalog.i18nc("@action:inmenu menubar:file","&Open File..."); text: catalog.i18nc("@action:inmenu menubar:file","&Open File(s)...");
iconName: "document-open"; iconName: "document-open";
shortcut: StandardKey.Open; shortcut: StandardKey.Open;
} }
@ -296,12 +312,6 @@ Item
shortcut: StandardKey.New shortcut: StandardKey.New
} }
Action
{
id: loadWorkspaceAction
text: catalog.i18nc("@action:inmenu menubar:file","&Open Project...");
}
Action Action
{ {
id: showEngineLogAction; 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 property bool monitoringPrint: false
Component.onCompleted: 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. // Workaround silly issues with QML Action's shortcut property.
// //
// Currently, there is no way to define shortcuts as "Application Shortcut". // Currently, there is no way to define shortcuts as "Application Shortcut".
@ -78,11 +78,6 @@ UM.MainWindow
RecentFilesMenu { } RecentFilesMenu { }
MenuItem
{
action: Cura.Actions.loadWorkspace
}
MenuSeparator { } MenuSeparator { }
MenuItem MenuItem
@ -92,26 +87,18 @@ UM.MainWindow
iconName: "document-save-as"; 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"}); onTriggered: UM.OutputDeviceManager.requestWriteSelectionToDevice("local_file", PrintInformation.jobName, { "filter_by_machine": false, "preferred_mimetype": "application/vnd.ms-package.3dmanufacturing-3dmodel+xml"});
} }
Menu
MenuItem
{ {
id: saveAllMenu id: saveAsMenu
title: catalog.i18nc("@title:menu menubar:file","Save &All") text: catalog.i18nc("@title:menu menubar:file", "Save &As...")
iconName: "document-save-all"; onTriggered:
enabled: devicesModel.rowCount() > 0 && UM.Backend.progress > 0.99;
Instantiator
{ {
model: UM.OutputDevicesModel { id: devicesModel; } var localDeviceId = "local_file";
UM.OutputDeviceManager.requestWriteToDevice(localDeviceId, PrintInformation.jobName, { "filter_by_machine": false, "preferred_mimetype": "application/vnd.ms-package.3dmanufacturing-3dmodel+xml"});
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)
} }
} }
MenuItem MenuItem
{ {
id: saveWorkspaceMenu id: saveWorkspaceMenu
@ -144,6 +131,7 @@ UM.MainWindow
MenuItem { action: Cura.Actions.redo; } MenuItem { action: Cura.Actions.redo; }
MenuSeparator { } MenuSeparator { }
MenuItem { action: Cura.Actions.selectAll; } MenuItem { action: Cura.Actions.selectAll; }
MenuItem { action: Cura.Actions.arrangeAll; }
MenuItem { action: Cura.Actions.deleteSelection; } MenuItem { action: Cura.Actions.deleteSelection; }
MenuItem { action: Cura.Actions.deleteAll; } MenuItem { action: Cura.Actions.deleteAll; }
MenuItem { action: Cura.Actions.resetAllTranslation; } MenuItem { action: Cura.Actions.resetAllTranslation; }
@ -272,40 +260,7 @@ UM.MainWindow
{ {
if (drop.urls.length > 0) if (drop.urls.length > 0)
{ {
// Import models openDialog.handleOpenFileUrls(drop.urls);
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))
}
} }
} }
} }
@ -548,7 +503,7 @@ UM.MainWindow
icon: StandardIcon.Question icon: StandardIcon.Question
onYes: onYes:
{ {
Printer.deleteAll(); CuraApplication.deleteAll();
Cura.Actions.resetProfile.trigger(); Cura.Actions.resetProfile.trigger();
} }
} }
@ -649,6 +604,7 @@ UM.MainWindow
MenuItem { action: Cura.Actions.multiplyObject; } MenuItem { action: Cura.Actions.multiplyObject; }
MenuSeparator { } MenuSeparator { }
MenuItem { action: Cura.Actions.selectAll; } MenuItem { action: Cura.Actions.selectAll; }
MenuItem { action: Cura.Actions.arrangeAll; }
MenuItem { action: Cura.Actions.deleteAll; } MenuItem { action: Cura.Actions.deleteAll; }
MenuItem { action: Cura.Actions.reloadAll; } MenuItem { action: Cura.Actions.reloadAll; }
MenuItem { action: Cura.Actions.resetAllTranslation; } MenuItem { action: Cura.Actions.resetAllTranslation; }
@ -665,7 +621,7 @@ UM.MainWindow
{ {
if(objectContextMenu.objectId != 0) if(objectContextMenu.objectId != 0)
{ {
Printer.deleteObject(objectContextMenu.objectId); CuraApplication.deleteObject(objectContextMenu.objectId);
objectContextMenu.objectId = 0; objectContextMenu.objectId = 0;
} }
} }
@ -698,7 +654,7 @@ UM.MainWindow
{ {
if(objectContextMenu.objectId != 0) if(objectContextMenu.objectId != 0)
{ {
Printer.centerObject(objectContextMenu.objectId); CuraApplication.centerObject(objectContextMenu.objectId);
objectContextMenu.objectId = 0; objectContextMenu.objectId = 0;
} }
} }
@ -709,6 +665,7 @@ UM.MainWindow
{ {
id: contextMenu; id: contextMenu;
MenuItem { action: Cura.Actions.selectAll; } MenuItem { action: Cura.Actions.selectAll; }
MenuItem { action: Cura.Actions.arrangeAll; }
MenuItem { action: Cura.Actions.deleteAll; } MenuItem { action: Cura.Actions.deleteAll; }
MenuItem { action: Cura.Actions.reloadAll; } MenuItem { action: Cura.Actions.reloadAll; }
MenuItem { action: Cura.Actions.resetAllTranslation; } MenuItem { action: Cura.Actions.resetAllTranslation; }
@ -752,27 +709,119 @@ UM.MainWindow
id: openDialog; id: openDialog;
//: File open dialog title //: 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; modality: UM.Application.platform == "linux" ? Qt.NonModal : Qt.WindowModal;
selectMultiple: true selectMultiple: true
nameFilters: UM.MeshFileHandler.supportedReadFileTypes; nameFilters: UM.MeshFileHandler.supportedReadFileTypes;
folder: CuraApplication.getDefaultPath("dialog_load_path") folder: CuraApplication.getDefaultPath("dialog_load_path")
onAccepted: onAccepted:
{ {
//Because several implementations of the file dialog only update the folder // Because several implementations of the file dialog only update the folder
//when it is explicitly set. // when it is explicitly set.
var f = folder; var f = folder;
folder = f; folder = f;
CuraApplication.setDefaultPath("dialog_load_path", folder); CuraApplication.setDefaultPath("dialog_load_path", folder);
for(var i in fileUrls) handleOpenFileUrls(fileUrls);
}
// 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)
{ {
Printer.readLocalFile(fileUrls[i]) 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;
} }
var meshName = backgroundItem.getMeshName(fileUrls[0].toString()) if (hasProjectFile)
backgroundItem.hasMesh(decodeURIComponent(meshName)) {
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() onTriggered: openDialog.open()
} }
FileDialog OpenFilesIncludingProjectsDialog
{ {
id: openWorkspaceDialog; id: openFilesIncludingProjectsDialog
//: 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))
}
} }
Connections AskOpenAsProjectOrModelsDialog
{ {
target: Cura.Actions.loadWorkspace id: askOpenAsProjectOrModelsDialog
onTriggered: openWorkspaceDialog.open()
} }
EngineLog EngineLog
@ -854,7 +879,7 @@ UM.MainWindow
function start(id) function start(id)
{ {
var actions = Cura.MachineActionManager.getFirstStartActions(id) var actions = Cura.MachineActionManager.getFirstStartActions(id)
resetPages() // Remove previous pages resetPages() // Remove previous pages
for (var i = 0; i < actions.length; i++) for (var i = 0; i < actions.length; i++)
@ -876,14 +901,14 @@ UM.MainWindow
{ {
id: messageDialog id: messageDialog
modality: Qt.ApplicationModal modality: Qt.ApplicationModal
onAccepted: Printer.messageBoxClosed(clickedButton) onAccepted: CuraApplication.messageBoxClosed(clickedButton)
onApply: Printer.messageBoxClosed(clickedButton) onApply: CuraApplication.messageBoxClosed(clickedButton)
onDiscard: Printer.messageBoxClosed(clickedButton) onDiscard: CuraApplication.messageBoxClosed(clickedButton)
onHelp: Printer.messageBoxClosed(clickedButton) onHelp: CuraApplication.messageBoxClosed(clickedButton)
onNo: Printer.messageBoxClosed(clickedButton) onNo: CuraApplication.messageBoxClosed(clickedButton)
onRejected: Printer.messageBoxClosed(clickedButton) onRejected: CuraApplication.messageBoxClosed(clickedButton)
onReset: Printer.messageBoxClosed(clickedButton) onReset: CuraApplication.messageBoxClosed(clickedButton)
onYes: Printer.messageBoxClosed(clickedButton) onYes: CuraApplication.messageBoxClosed(clickedButton)
} }
Connections Connections
@ -913,7 +938,6 @@ UM.MainWindow
{ {
discardOrKeepProfileChangesDialog.show() discardOrKeepProfileChangesDialog.show()
} }
} }
Connections Connections
@ -963,4 +987,3 @@ UM.MainWindow
} }
} }
} }

View file

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

View file

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

View file

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

View file

@ -4,7 +4,7 @@
import QtQuick 2.2 import QtQuick 2.2
import QtQuick.Controls 1.1 import QtQuick.Controls 1.1
import UM 1.2 as UM import UM 1.3 as UM
import Cura 1.0 as Cura import Cura 1.0 as Cura
Menu Menu
@ -13,11 +13,11 @@ Menu
title: catalog.i18nc("@title:menu menubar:file", "Open &Recent") title: catalog.i18nc("@title:menu menubar:file", "Open &Recent")
iconName: "document-open-recent"; iconName: "document-open-recent";
enabled: Printer.recentFiles.length > 0; enabled: CuraApplication.recentFiles.length > 0;
Instantiator Instantiator
{ {
model: Printer.recentFiles model: CuraApplication.recentFiles
MenuItem MenuItem
{ {
text: text:
@ -25,8 +25,44 @@ Menu
var path = modelData.toString() var path = modelData.toString()
return (index + 1) + ". " + path.slice(path.lastIndexOf("/") + 1); return (index + 1) + ". " + path.slice(path.lastIndexOf("/") + 1);
} }
onTriggered: { onTriggered:
Printer.readLocalFile(modelData); {
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()) var meshName = backgroundItem.getMeshName(modelData.toString())
backgroundItem.hasMesh(decodeURIComponent(meshName)) backgroundItem.hasMesh(decodeURIComponent(meshName))
} }
@ -34,4 +70,9 @@ Menu
onObjectAdded: menu.insertItem(index, object) onObjectAdded: menu.insertItem(index, object)
onObjectRemoved: menu.removeItem(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 int totalHeight: childrenRect.height + UM.Theme.getSize("default_margin").height
property string fileBaseName property string fileBaseName
property string statusText: property string statusText:
@ -205,8 +205,8 @@ Item
onAdditionalComponentsChanged: onAdditionalComponentsChanged:
{ {
if(areaId == "monitorButtons") { if(areaId == "monitorButtons") {
for (var component in Printer.additionalComponents["monitorButtons"]) { for (var component in CuraApplication.additionalComponents["monitorButtons"]) {
Printer.additionalComponents["monitorButtons"][component].parent = additionalComponentsRow CuraApplication.additionalComponents["monitorButtons"][component].parent = additionalComponentsRow
} }
} }
} }

View file

@ -20,7 +20,7 @@ UM.Dialog
height: minimumHeight height: minimumHeight
property var objectId: 0; 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" } 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() function reset()
{ {
UM.Preferences.resetPreference("general/language") UM.Preferences.resetPreference("general/language")
@ -49,8 +73,12 @@ UM.PreferencesPage
invertZoomCheckbox.checked = boolCheck(UM.Preferences.getValue("view/invert_zoom")) invertZoomCheckbox.checked = boolCheck(UM.Preferences.getValue("view/invert_zoom"))
UM.Preferences.resetPreference("view/top_layer_count"); UM.Preferences.resetPreference("view/top_layer_count");
topLayerCountCheckbox.checked = boolCheck(UM.Preferences.getValue("view/top_layer_count")) topLayerCountCheckbox.checked = boolCheck(UM.Preferences.getValue("view/top_layer_count"))
UM.Preferences.resetPreference("cura/choice_on_profile_override") 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) { if (plugins.find("id", "SliceInfoPlugin") > -1) {
UM.Preferences.resetPreference("info/send_slice_info") UM.Preferences.resetPreference("info/send_slice_info")
@ -105,6 +133,8 @@ UM.PreferencesPage
append({ text: "Suomi", code: "fi" }) append({ text: "Suomi", code: "fi" })
append({ text: "Français", code: "fr" }) append({ text: "Français", code: "fr" })
append({ text: "Italiano", code: "it" }) append({ text: "Italiano", code: "it" })
append({ text: "日本語", code: "jp" })
append({ text: "한국어", code: "ko" })
append({ text: "Nederlands", code: "nl" }) append({ text: "Nederlands", code: "nl" })
append({ text: "Português do Brasil", code: "ptbr" }) append({ text: "Português do Brasil", code: "ptbr" })
append({ text: "Русский", code: "ru" }) append({ text: "Русский", code: "ru" })
@ -231,6 +261,7 @@ UM.PreferencesPage
text: catalog.i18nc("@action:button","Center camera when item is selected"); text: catalog.i18nc("@action:button","Center camera when item is selected");
checked: boolCheck(UM.Preferences.getValue("view/center_on_select")) checked: boolCheck(UM.Preferences.getValue("view/center_on_select"))
onClicked: UM.Preferences.setValue("view/center_on_select", checked) 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 Item
{ {
//: Spacer //: Spacer
@ -383,12 +464,6 @@ UM.PreferencesPage
width: UM.Theme.getSize("default_margin").width width: UM.Theme.getSize("default_margin").width
} }
Label
{
font.bold: true
text: catalog.i18nc("@label", "Override Profile")
}
UM.TooltipArea UM.TooltipArea
{ {
width: childrenRect.width; 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.") 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.")
ComboBox Column
{ {
id: choiceOnProfileOverrideDropDownButton spacing: 4
model: [ Label
catalog.i18nc("@option:discardOrKeep", "Always ask me this"), {
catalog.i18nc("@option:discardOrKeep", "Discard and never ask again"), font.bold: true
catalog.i18nc("@option:discardOrKeep", "Keep and never ask again") text: catalog.i18nc("@label", "Override Profile")
] }
width: 300
currentIndex: UM.Preferences.getValue("cura/choice_on_profile_override") ComboBox
onCurrentIndexChanged: UM.Preferences.setValue("cura/choice_on_profile_override", currentIndex) {
id: choiceOnProfileOverrideDropDownButton
width: 200
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"); text: catalog.i18nc("@action:button", "Add");
iconName: "list-add"; iconName: "list-add";
onClicked: Printer.requestAddPrinter() onClicked: CuraApplication.requestAddPrinter()
}, },
Button Button
{ {
@ -216,8 +216,8 @@ UM.ManagementPage
Component.onCompleted: Component.onCompleted:
{ {
for (var component in Printer.additionalComponents["machinesDetailPane"]) { for (var component in CuraApplication.additionalComponents["machinesDetailPane"]) {
Printer.additionalComponents["machinesDetailPane"][component].parent = additionalComponentsColumn CuraApplication.additionalComponents["machinesDetailPane"][component].parent = additionalComponentsColumn
} }
} }
} }
@ -227,8 +227,8 @@ UM.ManagementPage
onAdditionalComponentsChanged: onAdditionalComponentsChanged:
{ {
if(areaId == "machinesDetailPane") { if(areaId == "machinesDetailPane") {
for (var component in Printer.additionalComponents["machinesDetailPane"]) { for (var component in CuraApplication.additionalComponents["machinesDetailPane"]) {
Printer.additionalComponents["machinesDetailPane"][component].parent = additionalComponentsColumn CuraApplication.additionalComponents["machinesDetailPane"][component].parent = additionalComponentsColumn
} }
} }
} }

View file

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

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