Merge branch 'master' of github.com:Ultimaker/Cura

This commit is contained in:
Jaime van Kessel 2016-07-19 16:56:43 +02:00
commit f52a3c1b9d
8 changed files with 115 additions and 36 deletions

View file

@ -3,6 +3,7 @@
from cura.Settings.ExtruderManager import ExtruderManager from cura.Settings.ExtruderManager import ExtruderManager
from UM.i18n import i18nCatalog from UM.i18n import i18nCatalog
from UM.Scene.Platform import Platform
from UM.Scene.SceneNode import SceneNode from UM.Scene.SceneNode import SceneNode
from UM.Application import Application from UM.Application import Application
from UM.Resources import Resources from UM.Resources import Resources
@ -18,12 +19,32 @@ from UM.View.GL.OpenGL import OpenGL
catalog = i18nCatalog("cura") catalog = i18nCatalog("cura")
import numpy import numpy
import copy
# Setting for clearance around the prime # Setting for clearance around the prime
PRIME_CLEARANCE = 10 PRIME_CLEARANCE = 10
def approximatedCircleVertices(r):
"""
Return vertices from an approximated circle.
:param r: radius
:return: numpy 2-array with the vertices
"""
return numpy.array([
[-r, 0],
[-r * 0.707, r * 0.707],
[0, r],
[r * 0.707, r * 0.707],
[r, 0],
[r * 0.707, -r * 0.707],
[0, -r],
[-r * 0.707, -r * 0.707]
], numpy.float32)
## Build volume is a special kind of node that is responsible for rendering the printable area & disallowed areas. ## Build volume is a special kind of node that is responsible for rendering the printable area & disallowed areas.
class BuildVolume(SceneNode): class BuildVolume(SceneNode):
VolumeOutlineColor = Color(12, 169, 227, 255) VolumeOutlineColor = Color(12, 169, 227, 255)
@ -46,6 +67,11 @@ class BuildVolume(SceneNode):
self.setCalculateBoundingBox(False) self.setCalculateBoundingBox(False)
self._volume_aabb = None self._volume_aabb = None
self._raft_thickness = 0.0
self._adhesion_type = None
self._raft_mesh = None
self._platform = Platform(self)
self._active_container_stack = None self._active_container_stack = None
Application.getInstance().globalContainerStackChanged.connect(self._onGlobalContainerStackChanged) Application.getInstance().globalContainerStackChanged.connect(self._onGlobalContainerStackChanged)
self._onGlobalContainerStackChanged() self._onGlobalContainerStackChanged()
@ -77,6 +103,9 @@ class BuildVolume(SceneNode):
renderer.queueNode(self, mesh = self._grid_mesh, shader = self._grid_shader, backface_cull = True) renderer.queueNode(self, mesh = self._grid_mesh, shader = self._grid_shader, backface_cull = True)
if self._disallowed_area_mesh: if self._disallowed_area_mesh:
renderer.queueNode(self, mesh = self._disallowed_area_mesh, shader = self._shader, transparent = True, backface_cull = True, sort = -9) renderer.queueNode(self, mesh = self._disallowed_area_mesh, shader = self._shader, transparent = True, backface_cull = True, sort = -9)
if self._raft_mesh and self._adhesion_type == "raft":
renderer.queueNode(self, mesh=self._raft_mesh, transparent=True, backface_cull=True, sort=-9)
return True return True
## Recalculates the build volume & disallowed areas. ## Recalculates the build volume & disallowed areas.
@ -93,6 +122,7 @@ class BuildVolume(SceneNode):
mb = MeshBuilder() mb = MeshBuilder()
# Outline 'cube' of the build volume
mb.addLine(Vector(min_w, min_h, min_d), Vector(max_w, min_h, min_d), color = self.VolumeOutlineColor) mb.addLine(Vector(min_w, min_h, min_d), Vector(max_w, min_h, min_d), color = self.VolumeOutlineColor)
mb.addLine(Vector(min_w, min_h, min_d), Vector(min_w, max_h, min_d), color = self.VolumeOutlineColor) mb.addLine(Vector(min_w, min_h, min_d), Vector(min_w, max_h, min_d), color = self.VolumeOutlineColor)
mb.addLine(Vector(min_w, max_h, min_d), Vector(max_w, max_h, min_d), color = self.VolumeOutlineColor) mb.addLine(Vector(min_w, max_h, min_d), Vector(max_w, max_h, min_d), color = self.VolumeOutlineColor)
@ -117,11 +147,23 @@ class BuildVolume(SceneNode):
Vector(max_w, min_h - 0.2, max_d), Vector(max_w, min_h - 0.2, max_d),
Vector(min_w, min_h - 0.2, max_d) Vector(min_w, min_h - 0.2, max_d)
) )
for n in range(0, 6): for n in range(0, 6):
v = mb.getVertex(n) v = mb.getVertex(n)
mb.setVertexUVCoordinates(n, v[0], v[2]) mb.setVertexUVCoordinates(n, v[0], v[2])
self._grid_mesh = mb.build() self._grid_mesh = mb.build()
# Build raft mesh: a plane on the height of the raft.
mb = MeshBuilder()
mb.addQuad(
Vector(min_w, self._raft_thickness, min_d),
Vector(max_w, self._raft_thickness, min_d),
Vector(max_w, self._raft_thickness, max_d),
Vector(min_w, self._raft_thickness, max_d),
color=Color(128, 128, 128, 64)
)
self._raft_mesh = mb.build()
disallowed_area_height = 0.1 disallowed_area_height = 0.1
disallowed_area_size = 0 disallowed_area_size = 0
if self._disallowed_areas: if self._disallowed_areas:
@ -177,6 +219,21 @@ class BuildVolume(SceneNode):
" \"Print Sequence\" setting to prevent the gantry from colliding" " \"Print Sequence\" setting to prevent the gantry from colliding"
" with printed objects."), lifetime=10).show() " with printed objects."), lifetime=10).show()
def _updateRaftThickness(self):
old_raft_thickness = self._raft_thickness
self._adhesion_type = self._active_container_stack.getProperty("adhesion_type", "value")
self._raft_thickness = 0.0
if self._adhesion_type == "raft":
self._raft_thickness = (
self._active_container_stack.getProperty("raft_base_thickness", "value") +
self._active_container_stack.getProperty("raft_interface_thickness", "value") +
self._active_container_stack.getProperty("raft_surface_layers", "value") *
self._active_container_stack.getProperty("raft_surface_thickness", "value") +
self._active_container_stack.getProperty("raft_airgap", "value"))
# Rounding errors do not matter, we check if raft_thickness has changed at all
if old_raft_thickness != self._raft_thickness:
self.setPosition(Vector(0, -self._raft_thickness, 0), SceneNode.TransformSpace.World)
def _onGlobalContainerStackChanged(self): def _onGlobalContainerStackChanged(self):
if self._active_container_stack: if self._active_container_stack:
self._active_container_stack.propertyChanged.disconnect(self._onSettingPropertyChanged) self._active_container_stack.propertyChanged.disconnect(self._onSettingPropertyChanged)
@ -195,6 +252,7 @@ class BuildVolume(SceneNode):
self._depth = self._active_container_stack.getProperty("machine_depth", "value") self._depth = self._active_container_stack.getProperty("machine_depth", "value")
self._updateDisallowedAreas() self._updateDisallowedAreas()
self._updateRaftThickness()
self.rebuild() self.rebuild()
@ -202,32 +260,40 @@ class BuildVolume(SceneNode):
if property_name != "value": if property_name != "value":
return return
rebuild_me = False
if setting_key == "print_sequence": if setting_key == "print_sequence":
if Application.getInstance().getGlobalContainerStack().getProperty("print_sequence", "value") == "one_at_a_time": if Application.getInstance().getGlobalContainerStack().getProperty("print_sequence", "value") == "one_at_a_time":
self._height = self._active_container_stack.getProperty("gantry_height", "value") self._height = self._active_container_stack.getProperty("gantry_height", "value")
self._buildVolumeMessage() self._buildVolumeMessage()
else: else:
self._height = self._active_container_stack.getProperty("machine_height", "value") self._height = self._active_container_stack.getProperty("machine_height", "value")
self.rebuild() rebuild_me = True
if setting_key in self._skirt_settings: if setting_key in self._skirt_settings:
self._updateDisallowedAreas() self._updateDisallowedAreas()
rebuild_me = True
if setting_key in self._raft_settings:
self._updateRaftThickness()
rebuild_me = True
if rebuild_me:
self.rebuild() self.rebuild()
def _updateDisallowedAreas(self): def _updateDisallowedAreas(self):
if not self._active_container_stack: if not self._active_container_stack:
return return
disallowed_areas = self._active_container_stack.getProperty("machine_disallowed_areas", "value") disallowed_areas = copy.deepcopy(
self._active_container_stack.getProperty("machine_disallowed_areas", "value"))
areas = [] areas = []
# Add extruder prime locations as disallowed areas. # Add extruder prime locations as disallowed areas.
# Probably needs some rework after coordinate system change. # Probably needs some rework after coordinate system change.
machine_definition = self._active_container_stack.getBottom()
current_machine_id = machine_definition.getId()
extruder_manager = ExtruderManager.getInstance() extruder_manager = ExtruderManager.getInstance()
extruders = extruder_manager.getMachineExtruders(current_machine_id) extruders = extruder_manager.getMachineExtruders(self._active_container_stack.getId())
machine_width = machine_definition.getProperty("machine_width", "value") machine_width = self._active_container_stack.getProperty("machine_width", "value")
machine_depth = machine_definition.getProperty("machine_depth", "value") machine_depth = self._active_container_stack.getProperty("machine_depth", "value")
for single_extruder in extruders: for single_extruder in extruders:
extruder_prime_pos_x = single_extruder.getProperty("extruder_prime_pos_x", "value") extruder_prime_pos_x = single_extruder.getProperty("extruder_prime_pos_x", "value")
extruder_prime_pos_y = single_extruder.getProperty("extruder_prime_pos_y", "value") extruder_prime_pos_y = single_extruder.getProperty("extruder_prime_pos_y", "value")
@ -249,16 +315,7 @@ class BuildVolume(SceneNode):
# Extend every area already in the disallowed_areas with the skirt size. # Extend every area already in the disallowed_areas with the skirt size.
for area in disallowed_areas: for area in disallowed_areas:
poly = Polygon(numpy.array(area, numpy.float32)) poly = Polygon(numpy.array(area, numpy.float32))
poly = poly.getMinkowskiHull(Polygon(numpy.array([ poly = poly.getMinkowskiHull(Polygon(approximatedCircleVertices(skirt_size)))
[-skirt_size, 0],
[-skirt_size * 0.707, skirt_size * 0.707],
[0, skirt_size],
[skirt_size * 0.707, skirt_size * 0.707],
[skirt_size, 0],
[skirt_size * 0.707, -skirt_size * 0.707],
[0, -skirt_size],
[-skirt_size * 0.707, -skirt_size * 0.707]
], numpy.float32)))
areas.append(poly) areas.append(poly)
@ -297,7 +354,7 @@ class BuildVolume(SceneNode):
self._disallowed_areas = areas self._disallowed_areas = areas
## Convenience function to calculate the size of the bed adhesion. ## Convenience function to calculate the size of the bed adhesion in directions x, y.
def _getSkirtSize(self, container_stack): def _getSkirtSize(self, container_stack):
skirt_size = 0.0 skirt_size = 0.0
@ -323,3 +380,4 @@ class BuildVolume(SceneNode):
return max(min(value, max_value), min_value) return max(min(value, max_value), min_value)
_skirt_settings = ["adhesion_type", "skirt_gap", "skirt_line_count", "skirt_line_width", "brim_width", "brim_line_count", "raft_margin", "draft_shield_enabled", "draft_shield_dist", "xy_offset"] _skirt_settings = ["adhesion_type", "skirt_gap", "skirt_line_count", "skirt_line_width", "brim_width", "brim_line_count", "raft_margin", "draft_shield_enabled", "draft_shield_dist", "xy_offset"]
_raft_settings = ["adhesion_type", "raft_base_thickness", "raft_interface_thickness", "raft_surface_layers", "raft_surface_thickness", "raft_airgap"]

View file

@ -4,7 +4,6 @@
from UM.Qt.QtApplication import QtApplication from UM.Qt.QtApplication import QtApplication
from UM.Scene.SceneNode import SceneNode from UM.Scene.SceneNode import SceneNode
from UM.Scene.Camera import Camera from UM.Scene.Camera import Camera
from UM.Scene.Platform import Platform as Scene_Platform
from UM.Math.Vector import Vector from UM.Math.Vector import Vector
from UM.Math.Quaternion import Quaternion from UM.Math.Quaternion import Quaternion
from UM.Math.AxisAlignedBox import AxisAlignedBox from UM.Math.AxisAlignedBox import AxisAlignedBox
@ -144,7 +143,6 @@ class CuraApplication(QtApplication):
]) ])
self._physics = None self._physics = None
self._volume = None self._volume = None
self._platform = None
self._output_devices = {} self._output_devices = {}
self._print_information = None self._print_information = None
self._previous_active_tool = None self._previous_active_tool = None
@ -196,6 +194,14 @@ class CuraApplication(QtApplication):
Preferences.getInstance().addPreference("view/center_on_select", True) Preferences.getInstance().addPreference("view/center_on_select", True)
Preferences.getInstance().addPreference("mesh/scale_to_fit", True) Preferences.getInstance().addPreference("mesh/scale_to_fit", True)
Preferences.getInstance().addPreference("mesh/scale_tiny_meshes", True) Preferences.getInstance().addPreference("mesh/scale_tiny_meshes", True)
for key in [
"dialog_load_path", # dialog_save_path is in LocalFileOutputDevicePlugin
"dialog_profile_path",
"dialog_material_path"]:
Preferences.getInstance().addPreference("local_file/%s" % key, "~/")
Preferences.getInstance().setDefault("local_file/last_used_type", "text/x-gcode") Preferences.getInstance().setDefault("local_file/last_used_type", "text/x-gcode")
Preferences.getInstance().setDefault("general/visible_settings", """ Preferences.getInstance().setDefault("general/visible_settings", """
@ -334,10 +340,16 @@ class CuraApplication(QtApplication):
f.write(data) f.write(data)
@pyqtSlot(result = QUrl) @pyqtSlot(str, result = QUrl)
def getDefaultPath(self): def getDefaultPath(self, key):
return QUrl.fromLocalFile(os.path.expanduser("~/")) #return QUrl.fromLocalFile(os.path.expanduser("~/"))
default_path = Preferences.getInstance().getValue("local_file/%s" % key)
return QUrl.fromLocalFile(default_path)
@pyqtSlot(str, str)
def setDefaultPath(self, key, default_path):
Preferences.getInstance().setValue("local_file/%s" % key, default_path)
## Handle loading of all plugin types (and the backend explicitly) ## Handle loading of all plugin types (and the backend explicitly)
# \sa PluginRegistery # \sa PluginRegistery
def _loadPlugins(self): def _loadPlugins(self):
@ -377,8 +389,8 @@ class CuraApplication(QtApplication):
Selection.selectionChanged.connect(self.onSelectionChanged) Selection.selectionChanged.connect(self.onSelectionChanged)
root = controller.getScene().getRoot() root = controller.getScene().getRoot()
self._platform = Scene_Platform(root)
# The platform is a child of BuildVolume
self._volume = BuildVolume.BuildVolume(root) self._volume = BuildVolume.BuildVolume(root)
self.getRenderer().setBackgroundColor(QColor(245, 245, 245)) self.getRenderer().setBackgroundColor(QColor(245, 245, 245))
@ -857,3 +869,6 @@ class CuraApplication(QtApplication):
@pyqtSlot("QSize") @pyqtSlot("QSize")
def setMinimumWindowSize(self, size): def setMinimumWindowSize(self, size):
self.getMainWindow().setMinimumSize(size) self.getMainWindow().setMinimumSize(size)
def getBuildVolume(self):
return self._volume

View file

@ -40,6 +40,7 @@ class PlatformPhysics:
return return
root = self._controller.getScene().getRoot() root = self._controller.getScene().getRoot()
for node in BreadthFirstIterator(root): for node in BreadthFirstIterator(root):
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:
continue continue
@ -58,10 +59,7 @@ class PlatformPhysics:
move_vector = Vector() move_vector = Vector()
if not (node.getParent() and node.getParent().callDecoration("isGroup")): #If an object is grouped, don't move it down if not (node.getParent() and node.getParent().callDecoration("isGroup")): #If an object is grouped, don't move it down
z_offset = node.callDecoration("getZOffset") if node.getDecorator(ZOffsetDecorator.ZOffsetDecorator) else 0 z_offset = node.callDecoration("getZOffset") if node.getDecorator(ZOffsetDecorator.ZOffsetDecorator) else 0
if bbox.bottom > 0: move_vector = move_vector.set(y=-bbox.bottom + z_offset)
move_vector = move_vector.set(y=-bbox.bottom + z_offset)
elif bbox.bottom < z_offset:
move_vector = move_vector.set(y=(-bbox.bottom) - z_offset)
# If there is no convex hull for the node, start calculating it and continue. # If there is no convex hull for the node, start calculating it and continue.
if not node.getDecorator(ConvexHullDecorator): if not node.getDecorator(ConvexHullDecorator):

View file

@ -130,7 +130,10 @@ class ProcessSlicedLayersJob(Job):
new_node.addDecorator(decorator) new_node.addDecorator(decorator)
new_node.setMeshData(mesh) new_node.setMeshData(mesh)
new_node.setParent(self._scene.getRoot()) # Note: After this we can no longer abort! # Set build volume as parent, the build volume can move as a result of raft settings.
# It makes sense to set the build volume as parent: the print is actually printed on it.
new_node_parent = Application.getInstance().getBuildVolume()
new_node.setParent(new_node_parent) # Note: After this we can no longer abort!
settings = Application.getInstance().getGlobalContainerStack() settings = Application.getInstance().getGlobalContainerStack()
if not settings.getProperty("machine_center_is_zero", "value"): if not settings.getProperty("machine_center_is_zero", "value"):

View file

@ -1000,7 +1000,7 @@
"type": "int", "type": "int",
"minimum_value": "0", "minimum_value": "0",
"maximum_value_warning": "4", "maximum_value_warning": "4",
"maximum_value": "17", "maximum_value": "20 - math.log(infill_line_distance) / math.log(2)",
"settable_per_mesh": true "settable_per_mesh": true
}, },
"gradual_infill_step_height": "gradual_infill_step_height":

View file

@ -638,7 +638,7 @@ UM.MainWindow
//TODO: Support multiple file selection, workaround bug in KDE file dialog //TODO: Support multiple file selection, workaround bug in KDE file dialog
//selectMultiple: true //selectMultiple: true
nameFilters: UM.MeshFileHandler.supportedReadFileTypes; nameFilters: UM.MeshFileHandler.supportedReadFileTypes;
folder: Printer.getDefaultPath() 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
@ -646,6 +646,7 @@ UM.MainWindow
var f = folder; var f = folder;
folder = f; folder = f;
CuraApplication.setDefaultPath("dialog_load_path", folder);
UM.MeshFileHandler.readLocalFile(fileUrl) UM.MeshFileHandler.readLocalFile(fileUrl)
var meshName = backgroundItem.getMeshName(fileUrl.toString()) var meshName = backgroundItem.getMeshName(fileUrl.toString())
backgroundItem.hasMesh(decodeURIComponent(meshName)) backgroundItem.hasMesh(decodeURIComponent(meshName))

View file

@ -200,7 +200,7 @@ UM.ManagementPage
title: catalog.i18nc("@title:window", "Import Material"); title: catalog.i18nc("@title:window", "Import Material");
selectExisting: true; selectExisting: true;
nameFilters: Cura.ContainerManager.getContainerNameFilters("material") nameFilters: Cura.ContainerManager.getContainerNameFilters("material")
folder: CuraApplication.getDefaultPath() folder: CuraApplication.getDefaultPath("dialog_material_path")
onAccepted: onAccepted:
{ {
var result = Cura.ContainerManager.importContainer(fileUrl) var result = Cura.ContainerManager.importContainer(fileUrl)
@ -221,6 +221,7 @@ UM.ManagementPage
messageDialog.icon = StandardIcon.Critical messageDialog.icon = StandardIcon.Critical
} }
messageDialog.open() messageDialog.open()
CuraApplication.setDefaultPath("dialog_material_path", folder)
} }
} }
@ -230,7 +231,7 @@ UM.ManagementPage
title: catalog.i18nc("@title:window", "Export Material"); title: catalog.i18nc("@title:window", "Export Material");
selectExisting: false; selectExisting: false;
nameFilters: Cura.ContainerManager.getContainerNameFilters("material") nameFilters: Cura.ContainerManager.getContainerNameFilters("material")
folder: CuraApplication.getDefaultPath() folder: CuraApplication.getDefaultPath("dialog_material_path")
onAccepted: onAccepted:
{ {
if(base.currentItem.metadata.base_file) if(base.currentItem.metadata.base_file)
@ -255,6 +256,7 @@ UM.ManagementPage
messageDialog.text = catalog.i18nc("@info:status", "Successfully exported material to <filename>%1</filename>").arg(fileUrl) messageDialog.text = catalog.i18nc("@info:status", "Successfully exported material to <filename>%1</filename>").arg(fileUrl)
messageDialog.open() messageDialog.open()
} }
CuraApplication.setDefaultPath("dialog_material_path", folder)
} }
} }

View file

@ -291,7 +291,7 @@ UM.ManagementPage
title: catalog.i18nc("@title:window", "Import Profile"); title: catalog.i18nc("@title:window", "Import Profile");
selectExisting: true; selectExisting: true;
nameFilters: base.model.getFileNameFilters("profile_reader") nameFilters: base.model.getFileNameFilters("profile_reader")
folder: base.model.getDefaultPath() folder: CuraApplication.getDefaultPath("dialog_profile_path")
onAccepted: onAccepted:
{ {
var result = base.model.importProfile(fileUrl) var result = base.model.importProfile(fileUrl)
@ -309,6 +309,7 @@ UM.ManagementPage
messageDialog.icon = StandardIcon.Critical messageDialog.icon = StandardIcon.Critical
} }
messageDialog.open() messageDialog.open()
CuraApplication.setDefaultPath("dialog_profile_path", folder)
} }
} }
@ -318,7 +319,7 @@ UM.ManagementPage
title: catalog.i18nc("@title:window", "Export Profile"); title: catalog.i18nc("@title:window", "Export Profile");
selectExisting: false; selectExisting: false;
nameFilters: base.model.getFileNameFilters("profile_writer") nameFilters: base.model.getFileNameFilters("profile_writer")
folder: base.model.getDefaultPath() folder: CuraApplication.getDefaultPath("dialog_profile_path")
onAccepted: onAccepted:
{ {
var result = base.model.exportProfile(base.currentItem.id, fileUrl, selectedNameFilter) var result = base.model.exportProfile(base.currentItem.id, fileUrl, selectedNameFilter)
@ -329,6 +330,7 @@ UM.ManagementPage
messageDialog.open() messageDialog.open()
} }
// else pop-up Message thing from python code // else pop-up Message thing from python code
CuraApplication.setDefaultPath("dialog_profile_path", folder)
} }
} }
} }