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

This commit is contained in:
Jaime van Kessel 2018-01-08 10:56:12 +01:00
commit ed9634ebe0
77 changed files with 2675 additions and 791 deletions

25
cura/Arrange.py → cura/Arranging/Arrange.py Executable file → Normal file
View file

@ -1,8 +1,8 @@
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Logger import Logger from UM.Logger import Logger
from UM.Math.Vector import Vector from UM.Math.Vector import Vector
from cura.ShapeArray import ShapeArray from cura.Arranging.ShapeArray import ShapeArray
from cura import ZOffsetDecorator from cura.Scene import ZOffsetDecorator
from collections import namedtuple from collections import namedtuple
@ -30,6 +30,7 @@ class Arrange:
self._offset_x = offset_x self._offset_x = offset_x
self._offset_y = offset_y self._offset_y = offset_y
self._last_priority = 0 self._last_priority = 0
self._is_empty = True
## Helper to create an Arranger instance ## Helper to create an Arranger instance
# #
@ -38,8 +39,8 @@ class Arrange:
# \param scene_root Root for finding all scene nodes # \param scene_root Root for finding all scene nodes
# \param fixed_nodes Scene nodes to be placed # \param fixed_nodes Scene nodes to be placed
@classmethod @classmethod
def create(cls, scene_root = None, fixed_nodes = None, scale = 0.5): def create(cls, scene_root = None, fixed_nodes = None, scale = 0.5, x = 220, y = 220):
arranger = Arrange(220, 220, 110, 110, scale = scale) arranger = Arrange(x, y, x // 2, y // 2, scale = scale)
arranger.centerFirst() arranger.centerFirst()
if fixed_nodes is None: if fixed_nodes is None:
@ -64,7 +65,7 @@ class Arrange:
for area in disallowed_areas: for area in disallowed_areas:
points = copy.deepcopy(area._points) points = copy.deepcopy(area._points)
shape_arr = ShapeArray.fromPolygon(points, scale = scale) shape_arr = ShapeArray.fromPolygon(points, scale = scale)
arranger.place(0, 0, shape_arr) arranger.place(0, 0, shape_arr, update_empty = False)
return arranger return arranger
## Find placement for a node (using offset shape) and place it (using hull shape) ## Find placement for a node (using offset shape) and place it (using hull shape)
@ -168,7 +169,8 @@ class Arrange:
# \param x x-coordinate # \param x x-coordinate
# \param y y-coordinate # \param y y-coordinate
# \param shape_arr ShapeArray object # \param shape_arr ShapeArray object
def place(self, x, y, shape_arr): # \param update_empty updates the _is_empty, used when adding disallowed areas
def place(self, x, y, shape_arr, update_empty = True):
x = int(self._scale * x) x = int(self._scale * x)
y = int(self._scale * y) y = int(self._scale * y)
offset_x = x + self._offset_x + shape_arr.offset_x offset_x = x + self._offset_x + shape_arr.offset_x
@ -181,10 +183,17 @@ class Arrange:
max_y = min(max(offset_y + shape_arr.arr.shape[0], 0), shape_y - 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] 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 # we use a slice of shape because it can be out of bounds
occupied_slice[numpy.where(shape_arr.arr[ new_occupied = numpy.where(shape_arr.arr[
min_y - offset_y:max_y - offset_y, min_x - offset_x:max_x - offset_x] == 1)] = 1 min_y - offset_y:max_y - offset_y, min_x - offset_x:max_x - offset_x] == 1)
if update_empty and new_occupied:
self._is_empty = False
occupied_slice[new_occupied] = 1
# Set priority to low (= high number), so it won't get picked at trying out. # 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 = self._priority[min_y:max_y, min_x:max_x]
prio_slice[numpy.where(shape_arr.arr[ 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 min_y - offset_y:max_y - offset_y, min_x - offset_x:max_x - offset_x] == 1)] = 999
@property
def isEmpty(self):
return self._is_empty

View file

@ -0,0 +1,154 @@
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from UM.Job import Job
from UM.Scene.SceneNode import SceneNode
from UM.Math.Vector import Vector
from UM.Operations.TranslateOperation import TranslateOperation
from UM.Operations.GroupedOperation import GroupedOperation
from UM.Message import Message
from UM.i18n import i18nCatalog
i18n_catalog = i18nCatalog("cura")
from cura.Scene.ZOffsetDecorator import ZOffsetDecorator
from cura.Arranging.Arrange import Arrange
from cura.Arranging.ShapeArray import ShapeArray
from typing import List
class ArrangeArray:
def __init__(self, x: int, y: int, fixed_nodes: List[SceneNode]):
self._x = x
self._y = y
self._fixed_nodes = fixed_nodes
self._count = 0
self._first_empty = None
self._has_empty = False
self._arrange = []
def _update_first_empty(self):
for i, a in enumerate(self._arrange):
if a.isEmpty:
self._first_empty = i
self._has_empty = True
return
self._first_empty = None
self._has_empty = False
def add(self):
new_arrange = Arrange.create(x = self._x, y = self._y, fixed_nodes = self._fixed_nodes)
self._arrange.append(new_arrange)
self._count += 1
self._update_first_empty()
def count(self):
return self._count
def get(self, index):
return self._arrange[index]
def getFirstEmpty(self):
if not self._is_empty:
self.add()
return self._arrange[self._first_empty]
class ArrangeObjectsAllBuildPlatesJob(Job):
def __init__(self, nodes: List[SceneNode], min_offset = 8):
super().__init__()
self._nodes = 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,
title = i18n_catalog.i18nc("@info:title", "Finding Location"))
status_message.show()
# 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()
x, y = 200, 200
arrange_array = ArrangeArray(x = x, y = y, fixed_nodes = [])
arrange_array.add()
# Place nodes one at a time
start_priority = 0
grouped_operation = GroupedOperation()
found_solution_for_all = True
left_over_nodes = [] # nodes that do not fit on an empty build plate
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)
try_placement = True
current_build_plate_number = 0 # always start with the first one
# # Only for first build plate
# if last_size == size and last_build_plate_number == current_build_plate_number:
# # This optimization works if many of the objects have the same size
# # Continue with same build plate number
# start_priority = last_priority
# else:
# start_priority = 0
while try_placement:
# make sure that current_build_plate_number is not going crazy or you'll have a lot of arrange objects
while current_build_plate_number >= arrange_array.count():
arrange_array.add()
arranger = arrange_array.get(current_build_plate_number)
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
arranger.place(x, y, hull_shape_arr) # place the object in the arranger
node.callDecoration("setBuildPlateNumber", current_build_plate_number)
grouped_operation.addOperation(TranslateOperation(node, Vector(x, center_y, y), set_position = True))
try_placement = False
else:
# very naive, because we skip to the next build plate if one model doesn't fit.
if arranger.isEmpty:
# apparently we can never place this object
left_over_nodes.append(node)
try_placement = False
else:
# try next build plate
current_build_plate_number += 1
try_placement = True
status_message.setProgress((idx + 1) / len(nodes_arr) * 100)
Job.yieldThread()
for node in left_over_nodes:
node.callDecoration("setBuildPlateNumber", -1) # these are not on any build plate
found_solution_for_all = False
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"),
title = i18n_catalog.i18nc("@info:title", "Can't Find Location"))
no_full_solution_message.show()

View file

@ -4,7 +4,6 @@
from UM.Job import Job from UM.Job import Job
from UM.Scene.SceneNode import SceneNode from UM.Scene.SceneNode import SceneNode
from UM.Math.Vector import Vector from UM.Math.Vector import Vector
from UM.Operations.SetTransformOperation import SetTransformOperation
from UM.Operations.TranslateOperation import TranslateOperation from UM.Operations.TranslateOperation import TranslateOperation
from UM.Operations.GroupedOperation import GroupedOperation from UM.Operations.GroupedOperation import GroupedOperation
from UM.Logger import Logger from UM.Logger import Logger
@ -12,9 +11,9 @@ from UM.Message import Message
from UM.i18n import i18nCatalog from UM.i18n import i18nCatalog
i18n_catalog = i18nCatalog("cura") i18n_catalog = i18nCatalog("cura")
from cura.ZOffsetDecorator import ZOffsetDecorator from cura.Scene.ZOffsetDecorator import ZOffsetDecorator
from cura.Arrange import Arrange from cura.Arranging.Arrange import Arrange
from cura.ShapeArray import ShapeArray from cura.Arranging.ShapeArray import ShapeArray
from typing import List from typing import List

6
cura/ShapeArray.py → cura/Arranging/ShapeArray.py Executable file → Normal file
View file

@ -29,8 +29,12 @@ class ShapeArray:
offset_x = int(numpy.amin(flip_vertices[:, 1])) offset_x = int(numpy.amin(flip_vertices[:, 1]))
flip_vertices[:, 0] = numpy.add(flip_vertices[:, 0], -offset_y) flip_vertices[:, 0] = numpy.add(flip_vertices[:, 0], -offset_y)
flip_vertices[:, 1] = numpy.add(flip_vertices[:, 1], -offset_x) flip_vertices[:, 1] = numpy.add(flip_vertices[:, 1], -offset_x)
shape = [int(numpy.amax(flip_vertices[:, 0])), int(numpy.amax(flip_vertices[:, 1]))] shape = numpy.array([int(numpy.amax(flip_vertices[:, 0])), int(numpy.amax(flip_vertices[:, 1]))])
shape[numpy.where(shape == 0)] = 1
arr = cls.arrayFromPolygon(shape, flip_vertices) arr = cls.arrayFromPolygon(shape, flip_vertices)
if not numpy.ndarray.any(arr):
# set at least 1 pixel
arr[0][0] = 1
return cls(arr, offset_x, offset_y) return cls(arr, offset_x, offset_y)
## Instantiate an offset and hull ShapeArray from a scene node. ## Instantiate an offset and hull ShapeArray from a scene node.

View file

53
cura/BuildPlateModel.py Normal file
View file

@ -0,0 +1,53 @@
from PyQt5.QtCore import pyqtSignal, pyqtProperty, pyqtSlot
from UM.Qt.ListModel import ListModel
from UM.Scene.Selection import Selection
from UM.Logger import Logger
from UM.Application import Application
class BuildPlateModel(ListModel):
maxBuildPlateChanged = pyqtSignal()
activeBuildPlateChanged = pyqtSignal()
selectionChanged = pyqtSignal()
def __init__(self):
super().__init__()
Application.getInstance().getController().getScene().sceneChanged.connect(self._updateSelectedObjectBuildPlateNumbers)
Selection.selectionChanged.connect(self._updateSelectedObjectBuildPlateNumbers)
self._max_build_plate = 1 # default
self._active_build_plate = -1
self._selection_build_plates = []
def setMaxBuildPlate(self, max_build_plate):
self._max_build_plate = max_build_plate
self.maxBuildPlateChanged.emit()
## Return the highest build plate number
@pyqtProperty(int, notify = maxBuildPlateChanged)
def maxBuildPlate(self):
return self._max_build_plate
def setActiveBuildPlate(self, nr):
self._active_build_plate = nr
self.activeBuildPlateChanged.emit()
@pyqtProperty(int, notify = activeBuildPlateChanged)
def activeBuildPlate(self):
return self._active_build_plate
@staticmethod
def createBuildPlateModel():
return BuildPlateModel()
def _updateSelectedObjectBuildPlateNumbers(self, *args):
result = set()
for node in Selection.getAllSelectedObjects():
result.add(node.callDecoration("getBuildPlateNumber"))
self._selection_build_plates = list(result)
self.selectionChanged.emit()
@pyqtProperty("QVariantList", notify = selectionChanged)
def selectionBuildPlates(self):
return self._selection_build_plates

View file

@ -13,12 +13,18 @@ from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator
from UM.Operations.GroupedOperation import GroupedOperation from UM.Operations.GroupedOperation import GroupedOperation
from UM.Operations.RemoveSceneNodeOperation import RemoveSceneNodeOperation from UM.Operations.RemoveSceneNodeOperation import RemoveSceneNodeOperation
from UM.Operations.SetTransformOperation import SetTransformOperation from UM.Operations.SetTransformOperation import SetTransformOperation
from UM.Operations.TranslateOperation import TranslateOperation
from cura.SetParentOperation import SetParentOperation from cura.Operations.SetParentOperation import SetParentOperation
from cura.MultiplyObjectsJob import MultiplyObjectsJob from cura.MultiplyObjectsJob import MultiplyObjectsJob
from cura.Settings.SetObjectExtruderOperation import SetObjectExtruderOperation from cura.Settings.SetObjectExtruderOperation import SetObjectExtruderOperation
from cura.Settings.ExtruderManager import ExtruderManager from cura.Settings.ExtruderManager import ExtruderManager
from cura.Operations.SetBuildPlateNumberOperation import SetBuildPlateNumberOperation
from UM.Logger import Logger
class CuraActions(QObject): class CuraActions(QObject):
def __init__(self, parent = None): def __init__(self, parent = None):
super().__init__(parent) super().__init__(parent)
@ -54,7 +60,11 @@ class CuraActions(QObject):
while current_node.getParent() and current_node.getParent().callDecoration("isGroup"): while current_node.getParent() and current_node.getParent().callDecoration("isGroup"):
current_node = current_node.getParent() current_node = current_node.getParent()
center_operation = SetTransformOperation(current_node, Vector()) # This was formerly done with SetTransformOperation but because of
# unpredictable matrix deconstruction it was possible that mirrors
# could manifest as rotations. Centering is therefore done by
# moving the node to negative whatever its position is:
center_operation = TranslateOperation(current_node, -current_node._position)
operation.addOperation(center_operation) operation.addOperation(center_operation)
operation.push() operation.push()
@ -124,5 +134,31 @@ class CuraActions(QObject):
operation.addOperation(SetObjectExtruderOperation(node, extruder_id)) operation.addOperation(SetObjectExtruderOperation(node, extruder_id))
operation.push() operation.push()
@pyqtSlot(int)
def setBuildPlateForSelection(self, build_plate_nr: int) -> None:
Logger.log("d", "Setting build plate number... %d" % build_plate_nr)
operation = GroupedOperation()
root = Application.getInstance().getController().getScene().getRoot()
nodes_to_change = []
for node in Selection.getAllSelectedObjects():
parent_node = node # Find the parent node to change instead
while parent_node.getParent() != root:
parent_node = parent_node.getParent()
for single_node in BreadthFirstIterator(parent_node):
nodes_to_change.append(single_node)
if not nodes_to_change:
Logger.log("d", "Nothing to change.")
return
for node in nodes_to_change:
operation.addOperation(SetBuildPlateNumberOperation(node, build_plate_nr))
operation.push()
Selection.clear()
def _openUrl(self, url): def _openUrl(self, url):
QDesktopServices.openUrl(url) QDesktopServices.openUrl(url)

View file

@ -1,4 +1,5 @@
# Copyright (c) 2017 Ultimaker B.V. # Copyright (c) 2017 Ultimaker B.V.
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtNetwork import QLocalServer from PyQt5.QtNetwork import QLocalServer
from PyQt5.QtNetwork import QLocalSocket from PyQt5.QtNetwork import QLocalSocket
@ -16,7 +17,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.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
from UM.Settings.ContainerStack import ContainerStack from UM.Settings.ContainerStack import ContainerStack
@ -32,15 +32,19 @@ 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.Arranging.Arrange import Arrange
from cura.ShapeArray import ShapeArray from cura.Arranging.ArrangeObjectsJob import ArrangeObjectsJob
from cura.ConvexHullDecorator import ConvexHullDecorator from cura.Arranging.ArrangeObjectsAllBuildPlatesJob import ArrangeObjectsAllBuildPlatesJob
from cura.SetParentOperation import SetParentOperation from cura.Arranging.ShapeArray import ShapeArray
from cura.SliceableObjectDecorator import SliceableObjectDecorator
from cura.BlockSlicingDecorator import BlockSlicingDecorator
from cura.ArrangeObjectsJob import ArrangeObjectsJob
from cura.MultiplyObjectsJob import MultiplyObjectsJob from cura.MultiplyObjectsJob import MultiplyObjectsJob
from cura.Scene.ConvexHullDecorator import ConvexHullDecorator
from cura.Operations.SetParentOperation import SetParentOperation
from cura.Scene.SliceableObjectDecorator import SliceableObjectDecorator
from cura.Scene.BlockSlicingDecorator import BlockSlicingDecorator
from cura.Scene.BuildPlateDecorator import BuildPlateDecorator
from cura.Scene.CuraSceneNode import CuraSceneNode
from cura.Scene.CuraSceneController import CuraSceneController
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
@ -53,12 +57,13 @@ from cura.Settings.SettingInheritanceManager import SettingInheritanceManager
from cura.Settings.UserProfilesModel import UserProfilesModel from cura.Settings.UserProfilesModel import UserProfilesModel
from cura.Settings.SimpleModeSettingsManager import SimpleModeSettingsManager from cura.Settings.SimpleModeSettingsManager import SimpleModeSettingsManager
from . import PlatformPhysics from . import PlatformPhysics
from . import BuildVolume from . import BuildVolume
from . import CameraAnimation from . import CameraAnimation
from . import PrintInformation from . import PrintInformation
from . import CuraActions from . import CuraActions
from . import ZOffsetDecorator from cura.Scene import ZOffsetDecorator
from . import CuraSplashScreen from . import CuraSplashScreen
from . import CameraImageProvider from . import CameraImageProvider
from . import MachineActionManager from . import MachineActionManager
@ -72,8 +77,9 @@ from cura.Settings.ContainerSettingsModel import ContainerSettingsModel
from cura.Settings.MaterialSettingsVisibilityHandler import MaterialSettingsVisibilityHandler from cura.Settings.MaterialSettingsVisibilityHandler import MaterialSettingsVisibilityHandler
from cura.Settings.QualitySettingsModel import QualitySettingsModel from cura.Settings.QualitySettingsModel import QualitySettingsModel
from cura.Settings.ContainerManager import ContainerManager from cura.Settings.ContainerManager import ContainerManager
from cura.Settings.GlobalStack import GlobalStack
from cura.Settings.ExtruderStack import ExtruderStack from cura.ObjectsModel import ObjectsModel
from cura.BuildPlateModel import BuildPlateModel
from PyQt5.QtCore import QUrl, pyqtSignal, pyqtProperty, QEvent, Q_ENUMS from PyQt5.QtCore import QUrl, pyqtSignal, pyqtProperty, QEvent, Q_ENUMS
from UM.FlameProfiler import pyqtSlot from UM.FlameProfiler import pyqtSlot
@ -85,7 +91,6 @@ import sys
import os.path import os.path
import numpy import numpy
import copy import copy
import urllib.parse
import os import os
import argparse import argparse
import json import json
@ -203,8 +208,11 @@ class CuraApplication(QtApplication):
self._machine_manager = None # This is initialized on demand. self._machine_manager = None # This is initialized on demand.
self._extruder_manager = None self._extruder_manager = None
self._material_manager = None self._material_manager = None
self._object_manager = None
self._build_plate_model = None
self._setting_inheritance_manager = None self._setting_inheritance_manager = None
self._simple_mode_settings_manager = None self._simple_mode_settings_manager = None
self._cura_scene_controller = None
self._additional_components = {} # Components to add to certain areas in the interface self._additional_components = {} # Components to add to certain areas in the interface
@ -311,11 +319,14 @@ class CuraApplication(QtApplication):
preferences.addPreference("cura/asked_dialog_on_project_save", False) preferences.addPreference("cura/asked_dialog_on_project_save", False)
preferences.addPreference("cura/choice_on_profile_override", "always_ask") preferences.addPreference("cura/choice_on_profile_override", "always_ask")
preferences.addPreference("cura/choice_on_open_project", "always_ask") preferences.addPreference("cura/choice_on_open_project", "always_ask")
preferences.addPreference("cura/arrange_objects_on_load", True)
preferences.addPreference("cura/use_multi_build_plate", False)
preferences.addPreference("cura/currency", "") preferences.addPreference("cura/currency", "")
preferences.addPreference("cura/material_settings", "{}") preferences.addPreference("cura/material_settings", "{}")
preferences.addPreference("view/invert_zoom", False) preferences.addPreference("view/invert_zoom", False)
preferences.addPreference("view/filter_current_build_plate", False)
preferences.addPreference("cura/sidebar_collapsed", False) preferences.addPreference("cura/sidebar_collapsed", False)
self._need_to_show_user_agreement = not Preferences.getInstance().getValue("general/accepted_user_agreement") self._need_to_show_user_agreement = not Preferences.getInstance().getValue("general/accepted_user_agreement")
@ -389,6 +400,8 @@ class CuraApplication(QtApplication):
self._plugin_registry.addSupportedPluginExtension("curaplugin", "Cura Plugin") self._plugin_registry.addSupportedPluginExtension("curaplugin", "Cura Plugin")
self.getCuraSceneController().setActiveBuildPlate(0) # Initialize
def _onEngineCreated(self): def _onEngineCreated(self):
self._engine.addImageProvider("camera", CameraImageProvider.CameraImageProvider()) self._engine.addImageProvider("camera", CameraImageProvider.CameraImageProvider())
@ -684,8 +697,16 @@ class CuraApplication(QtApplication):
qmlRegisterSingletonType(ExtruderManager, "Cura", 1, 0, "ExtruderManager", self.getExtruderManager) qmlRegisterSingletonType(ExtruderManager, "Cura", 1, 0, "ExtruderManager", self.getExtruderManager)
qmlRegisterSingletonType(MachineManager, "Cura", 1, 0, "MachineManager", self.getMachineManager) qmlRegisterSingletonType(MachineManager, "Cura", 1, 0, "MachineManager", self.getMachineManager)
qmlRegisterSingletonType(MaterialManager, "Cura", 1, 0, "MaterialManager", self.getMaterialManager) qmlRegisterSingletonType(MaterialManager, "Cura", 1, 0, "MaterialManager", self.getMaterialManager)
qmlRegisterSingletonType(SettingInheritanceManager, "Cura", 1, 0, "SettingInheritanceManager", self.getSettingInheritanceManager)
qmlRegisterSingletonType(SimpleModeSettingsManager, "Cura", 1, 2, "SimpleModeSettingsManager", self.getSimpleModeSettingsManager) qmlRegisterSingletonType(SettingInheritanceManager, "Cura", 1, 0, "SettingInheritanceManager",
self.getSettingInheritanceManager)
qmlRegisterSingletonType(SimpleModeSettingsManager, "Cura", 1, 2, "SimpleModeSettingsManager",
self.getSimpleModeSettingsManager)
qmlRegisterSingletonType(ObjectsModel, "Cura", 1, 2, "ObjectsModel", self.getObjectsModel)
qmlRegisterSingletonType(BuildPlateModel, "Cura", 1, 2, "BuildPlateModel", self.getBuildPlateModel)
qmlRegisterSingletonType(CuraSceneController, "Cura", 1, 2, "SceneController", self.getCuraSceneController)
qmlRegisterSingletonType(MachineActionManager.MachineActionManager, "Cura", 1, 0, "MachineActionManager", self.getMachineActionManager) qmlRegisterSingletonType(MachineActionManager.MachineActionManager, "Cura", 1, 0, "MachineActionManager", self.getMachineActionManager)
self.setMainQml(Resources.getPath(self.ResourceTypes.QmlFiles, "Cura.qml")) self.setMainQml(Resources.getPath(self.ResourceTypes.QmlFiles, "Cura.qml"))
@ -723,6 +744,22 @@ class CuraApplication(QtApplication):
self._material_manager = MaterialManager.createMaterialManager() self._material_manager = MaterialManager.createMaterialManager()
return self._material_manager return self._material_manager
def getObjectsModel(self, *args):
if self._object_manager is None:
self._object_manager = ObjectsModel.createObjectsModel()
return self._object_manager
def getBuildPlateModel(self, *args):
if self._build_plate_model is None:
self._build_plate_model = BuildPlateModel.createBuildPlateModel()
return self._build_plate_model
def getCuraSceneController(self, *args):
if self._cura_scene_controller is None:
self._cura_scene_controller = CuraSceneController.createCuraSceneController()
return self._cura_scene_controller
def getSettingInheritanceManager(self, *args): def getSettingInheritanceManager(self, *args):
if self._setting_inheritance_manager is None: if self._setting_inheritance_manager is None:
self._setting_inheritance_manager = SettingInheritanceManager.createSettingInheritanceManager() self._setting_inheritance_manager = SettingInheritanceManager.createSettingInheritanceManager()
@ -858,7 +895,7 @@ class CuraApplication(QtApplication):
scene_bounding_box = None scene_bounding_box = None
is_block_slicing_node = False is_block_slicing_node = False
for node in DepthFirstIterator(self.getController().getScene().getRoot()): for node in DepthFirstIterator(self.getController().getScene().getRoot()):
if type(node) is not SceneNode or (not node.getMeshData() and not node.callDecoration("getLayerData")): if not issubclass(type(node), SceneNode) or (not node.getMeshData() and not node.callDecoration("getLayerData")):
continue continue
if node.callDecoration("isBlockSlicing"): if node.callDecoration("isBlockSlicing"):
is_block_slicing_node = True is_block_slicing_node = True
@ -975,7 +1012,7 @@ class CuraApplication(QtApplication):
Selection.clear() Selection.clear()
for node in DepthFirstIterator(self.getController().getScene().getRoot()): for node in DepthFirstIterator(self.getController().getScene().getRoot()):
if type(node) is not SceneNode: if not issubclass(type(node), SceneNode):
continue continue
if not node.getMeshData() and not node.callDecoration("isGroup"): if not node.getMeshData() and not node.callDecoration("isGroup"):
continue # Node that doesnt have a mesh and is not a group. continue # Node that doesnt have a mesh and is not a group.
@ -983,6 +1020,9 @@ class CuraApplication(QtApplication):
continue # Grouped nodes don't need resetting as their parent (the group) is resetted) continue # Grouped nodes don't need resetting as their parent (the group) is resetted)
if not node.isSelectable(): if not node.isSelectable():
continue # i.e. node with layer data continue # i.e. node with layer data
if not node.callDecoration("isSliceable") and not node.callDecoration("isGroup"):
continue # i.e. node with layer data
Selection.add(node) Selection.add(node)
## Delete all nodes containing mesh data in the scene. ## Delete all nodes containing mesh data in the scene.
@ -994,7 +1034,7 @@ class CuraApplication(QtApplication):
nodes = [] nodes = []
for node in DepthFirstIterator(self.getController().getScene().getRoot()): for node in DepthFirstIterator(self.getController().getScene().getRoot()):
if type(node) is not SceneNode: if type(node) not in {SceneNode, CuraSceneNode}:
continue continue
if (not node.getMeshData() and not node.callDecoration("getLayerData")) and not node.callDecoration("isGroup"): if (not node.getMeshData() and not node.callDecoration("getLayerData")) and not node.callDecoration("isGroup"):
continue # Node that doesnt have a mesh and is not a group. continue # Node that doesnt have a mesh and is not a group.
@ -1010,13 +1050,15 @@ class CuraApplication(QtApplication):
op.push() op.push()
Selection.clear() Selection.clear()
## Reset all translation on nodes with mesh data. self.getCuraSceneController().setActiveBuildPlate(0) # Select first build plate
## Reset all translation on nodes with mesh data.
@pyqtSlot() @pyqtSlot()
def resetAllTranslation(self): def resetAllTranslation(self):
Logger.log("i", "Resetting all scene translations") Logger.log("i", "Resetting all scene translations")
nodes = [] nodes = []
for node in DepthFirstIterator(self.getController().getScene().getRoot()): for node in DepthFirstIterator(self.getController().getScene().getRoot()):
if type(node) is not SceneNode: if not issubclass(type(node), SceneNode):
continue continue
if not node.getMeshData() and not node.callDecoration("isGroup"): if not node.getMeshData() and not node.callDecoration("isGroup"):
continue # Node that doesnt have a mesh and is not a group. continue # Node that doesnt have a mesh and is not a group.
@ -1044,13 +1086,13 @@ class CuraApplication(QtApplication):
Logger.log("i", "Resetting all scene transformations") Logger.log("i", "Resetting all scene transformations")
nodes = [] nodes = []
for node in DepthFirstIterator(self.getController().getScene().getRoot()): for node in DepthFirstIterator(self.getController().getScene().getRoot()):
if type(node) is not SceneNode: if not issubclass(type(node), SceneNode):
continue continue
if not node.getMeshData() and not node.callDecoration("isGroup"): if not node.getMeshData() and not node.callDecoration("isGroup"):
continue # Node that doesnt have a mesh and is not a group. continue # Node that doesnt have a mesh and is not a group.
if node.getParent() and node.getParent().callDecoration("isGroup"): if node.getParent() and node.getParent().callDecoration("isGroup"):
continue # Grouped nodes don't need resetting as their parent (the group) is resetted) continue # Grouped nodes don't need resetting as their parent (the group) is resetted)
if not node.isSelectable(): if not node.callDecoration("isSliceable") and not node.callDecoration("isGroup"):
continue # i.e. node with layer data continue # i.e. node with layer data
nodes.append(node) nodes.append(node)
@ -1068,10 +1110,31 @@ class CuraApplication(QtApplication):
## Arrange all objects. ## Arrange all objects.
@pyqtSlot() @pyqtSlot()
def arrangeAll(self): def arrangeObjectsToAllBuildPlates(self):
nodes = [] nodes = []
for node in DepthFirstIterator(self.getController().getScene().getRoot()): for node in DepthFirstIterator(self.getController().getScene().getRoot()):
if type(node) is not SceneNode: if not issubclass(type(node), 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.callDecoration("isSliceable") and not node.callDecoration("isGroup"):
continue # i.e. node with layer data
# Skip nodes that are too big
if node.getBoundingBox().width < self._volume.getBoundingBox().width or node.getBoundingBox().depth < self._volume.getBoundingBox().depth:
nodes.append(node)
job = ArrangeObjectsAllBuildPlatesJob(nodes)
job.start()
self.getCuraSceneController().setActiveBuildPlate(0) # Select first build plate
# Single build plate
@pyqtSlot()
def arrangeAll(self):
nodes = []
active_build_plate = self.getBuildPlateModel().activeBuildPlate
for node in DepthFirstIterator(self.getController().getScene().getRoot()):
if not issubclass(type(node), SceneNode):
continue continue
if not node.getMeshData() and not node.callDecoration("isGroup"): if not node.getMeshData() and not node.callDecoration("isGroup"):
continue # Node that doesnt have a mesh and is not a group. continue # Node that doesnt have a mesh and is not a group.
@ -1079,9 +1142,12 @@ class CuraApplication(QtApplication):
continue # Grouped nodes don't need resetting as their parent (the group) is resetted) continue # Grouped nodes don't need resetting as their parent (the group) is resetted)
if not node.isSelectable(): if not node.isSelectable():
continue # i.e. node with layer data continue # i.e. node with layer data
# Skip nodes that are too big if not node.callDecoration("isSliceable") and not node.callDecoration("isGroup"):
if node.getBoundingBox().width < self._volume.getBoundingBox().width or node.getBoundingBox().depth < self._volume.getBoundingBox().depth: continue # i.e. node with layer data
nodes.append(node) if node.callDecoration("getBuildPlateNumber") == active_build_plate:
# Skip nodes that are too big
if node.getBoundingBox().width < self._volume.getBoundingBox().width or node.getBoundingBox().depth < self._volume.getBoundingBox().depth:
nodes.append(node)
self.arrange(nodes, fixed_nodes = []) self.arrange(nodes, fixed_nodes = [])
## Arrange Selection ## Arrange Selection
@ -1092,7 +1158,7 @@ class CuraApplication(QtApplication):
# What nodes are on the build plate and are not being moved # What nodes are on the build plate and are not being moved
fixed_nodes = [] fixed_nodes = []
for node in DepthFirstIterator(self.getController().getScene().getRoot()): for node in DepthFirstIterator(self.getController().getScene().getRoot()):
if type(node) is not SceneNode: if not issubclass(type(node), SceneNode):
continue continue
if not node.getMeshData() and not node.callDecoration("isGroup"): if not node.getMeshData() and not node.callDecoration("isGroup"):
continue # Node that doesnt have a mesh and is not a group. continue # Node that doesnt have a mesh and is not a group.
@ -1100,6 +1166,8 @@ class CuraApplication(QtApplication):
continue # Grouped nodes don't need resetting as their parent (the group) is resetted) continue # Grouped nodes don't need resetting as their parent (the group) is resetted)
if not node.isSelectable(): if not node.isSelectable():
continue # i.e. node with layer data continue # i.e. node with layer data
if not node.callDecoration("isSliceable") and not node.callDecoration("isGroup"):
continue # i.e. node with layer data
if node in nodes: # exclude selected node from fixed_nodes if node in nodes: # exclude selected node from fixed_nodes
continue continue
fixed_nodes.append(node) fixed_nodes.append(node)
@ -1118,7 +1186,7 @@ class CuraApplication(QtApplication):
Logger.log("i", "Reloading all loaded mesh data.") Logger.log("i", "Reloading all loaded mesh data.")
nodes = [] nodes = []
for node in DepthFirstIterator(self.getController().getScene().getRoot()): for node in DepthFirstIterator(self.getController().getScene().getRoot()):
if type(node) is not SceneNode or not node.getMeshData(): if not issubclass(type(node), SceneNode) or not node.getMeshData():
continue continue
nodes.append(node) nodes.append(node)
@ -1135,7 +1203,7 @@ class CuraApplication(QtApplication):
job.start() job.start()
else: else:
Logger.log("w", "Unable to reload data because we don't have a filename.") Logger.log("w", "Unable to reload data because we don't have a filename.")
## Get logging data of the backend engine ## Get logging data of the backend engine
# \returns \type{string} Logging data # \returns \type{string} Logging data
@pyqtSlot(result = str) @pyqtSlot(result = str)
@ -1209,10 +1277,11 @@ class CuraApplication(QtApplication):
@pyqtSlot() @pyqtSlot()
def groupSelected(self): def groupSelected(self):
# Create a group-node # Create a group-node
group_node = SceneNode() group_node = CuraSceneNode()
group_decorator = GroupDecorator() group_decorator = GroupDecorator()
group_node.addDecorator(group_decorator) group_node.addDecorator(group_decorator)
group_node.addDecorator(ConvexHullDecorator()) group_node.addDecorator(ConvexHullDecorator())
group_node.addDecorator(BuildPlateDecorator(self.getBuildPlateModel().activeBuildPlate))
group_node.setParent(self.getController().getScene().getRoot()) group_node.setParent(self.getController().getScene().getRoot())
group_node.setSelectable(True) group_node.setSelectable(True)
center = Selection.getSelectionCenter() center = Selection.getSelectionCenter()
@ -1357,8 +1426,15 @@ class CuraApplication(QtApplication):
min_offset = 8 min_offset = 8
self.fileLoaded.emit(filename) self.fileLoaded.emit(filename)
arrange_objects_on_load = (
not Preferences.getInstance().getValue("cura/use_multi_build_plate") or
Preferences.getInstance().getValue("cura/arrange_objects_on_load"))
target_build_plate = self.getBuildPlateModel().activeBuildPlate if arrange_objects_on_load else -1
for original_node in nodes:
node = CuraSceneNode() # We want our own CuraSceneNode
node.setMeshData(original_node.getMeshData())
for node in nodes:
node.setSelectable(True) node.setSelectable(True)
node.setName(os.path.basename(filename)) node.setName(os.path.basename(filename))
@ -1385,21 +1461,23 @@ class CuraApplication(QtApplication):
if not child.getDecorator(ConvexHullDecorator): if not child.getDecorator(ConvexHullDecorator):
child.addDecorator(ConvexHullDecorator()) child.addDecorator(ConvexHullDecorator())
if node.callDecoration("isSliceable"): if arrange_objects_on_load:
# Only check position if it's not already blatantly obvious that it won't fit. if node.callDecoration("isSliceable"):
if node.getBoundingBox() is None or self._volume.getBoundingBox() is None or node.getBoundingBox().width < self._volume.getBoundingBox().width or node.getBoundingBox().depth < self._volume.getBoundingBox().depth: # Only check position if it's not already blatantly obvious that it won't fit.
# Find node location if node.getBoundingBox() is None or self._volume.getBoundingBox() is None or node.getBoundingBox().width < self._volume.getBoundingBox().width or node.getBoundingBox().depth < self._volume.getBoundingBox().depth:
offset_shape_arr, hull_shape_arr = ShapeArray.fromNode(node, min_offset = min_offset) # Find node location
offset_shape_arr, hull_shape_arr = ShapeArray.fromNode(node, min_offset = min_offset)
# If a model is to small then it will not contain any points # If a model is to small then it will not contain any points
if offset_shape_arr is None and hull_shape_arr is None: if offset_shape_arr is None and hull_shape_arr is None:
Message(self._i18n_catalog.i18nc("@info:status", "The selected model was too small to load."), Message(self._i18n_catalog.i18nc("@info:status", "The selected model was too small to load."),
title=self._i18n_catalog.i18nc("@info:title", "Warning") title=self._i18n_catalog.i18nc("@info:title", "Warning")).show()
).show() return
return
# Step is for skipping tests to make it a lot faster. it also makes the outcome somewhat rougher # 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) node, _ = arranger.findNodePlacement(node, offset_shape_arr, hull_shape_arr, step = 10)
node.addDecorator(BuildPlateDecorator(target_build_plate))
op = AddSceneNodeOperation(node, scene.getRoot()) op = AddSceneNodeOperation(node, scene.getRoot())
op.push() op.push()

View file

@ -2,24 +2,15 @@
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
from UM.Job import Job 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.Operations.GroupedOperation import GroupedOperation
from UM.Logger import Logger
from UM.Message import Message from UM.Message import Message
from UM.i18n import i18nCatalog from UM.i18n import i18nCatalog
i18n_catalog = i18nCatalog("cura") i18n_catalog = i18nCatalog("cura")
from cura.ZOffsetDecorator import ZOffsetDecorator from cura.Arranging.Arrange import Arrange
from cura.Arrange import Arrange from cura.Arranging.ShapeArray import ShapeArray
from cura.ShapeArray import ShapeArray
from typing import List
from UM.Application import Application from UM.Application import Application
from UM.Scene.Selection import Selection
from UM.Operations.AddSceneNodeOperation import AddSceneNodeOperation from UM.Operations.AddSceneNodeOperation import AddSceneNodeOperation
@ -65,6 +56,10 @@ class MultiplyObjectsJob(Job):
new_location = new_location.set(z = 100 - i * 20) new_location = new_location.set(z = 100 - i * 20)
node.setPosition(new_location) node.setPosition(new_location)
# Same build plate
build_plate_number = current_node.callDecoration("getBuildPlateNumber")
node.callDecoration("setBuildPlateNumber", build_plate_number)
nodes.append(node) nodes.append(node)
current_progress += 1 current_progress += 1
status_message.setProgress((current_progress / total_progress) * 100) status_message.setProgress((current_progress / total_progress) * 100)

63
cura/ObjectsModel.py Normal file
View file

@ -0,0 +1,63 @@
from UM.Application import Application
from UM.Qt.ListModel import ListModel
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Scene.SceneNode import SceneNode
from UM.Scene.Selection import Selection
from UM.Preferences import Preferences
from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")
## Keep track of all objects in the project
class ObjectsModel(ListModel):
def __init__(self):
super().__init__()
Application.getInstance().getController().getScene().sceneChanged.connect(self._update)
Preferences.getInstance().preferenceChanged.connect(self._update)
self._build_plate_number = -1
def setActiveBuildPlate(self, nr):
self._build_plate_number = nr
self._update()
def _update(self, *args):
nodes = []
filter_current_build_plate = Preferences.getInstance().getValue("view/filter_current_build_plate")
active_build_plate_number = self._build_plate_number
group_nr = 1
for node in DepthFirstIterator(Application.getInstance().getController().getScene().getRoot()):
if not issubclass(type(node), SceneNode):
continue
if (not node.getMeshData() and not node.callDecoration("getLayerData")) and not node.callDecoration("isGroup"):
continue
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.callDecoration("isSliceable") and not node.callDecoration("isGroup"):
continue
node_build_plate_number = node.callDecoration("getBuildPlateNumber")
if filter_current_build_plate and node_build_plate_number != active_build_plate_number:
continue
if not node.callDecoration("isGroup"):
name = node.getName()
else:
name = catalog.i18nc("@label", "Group #{group_nr}").format(group_nr = str(group_nr))
group_nr += 1
nodes.append({
"name": name,
"isSelected": Selection.isSelected(node),
"isOutsideBuildArea": node.isOutsideBuildArea(),
"buildPlateNumber": node_build_plate_number,
"node": node
})
nodes = sorted(nodes, key=lambda n: n["name"])
self.setItems(nodes)
self.itemsChanged.emit()
@staticmethod
def createObjectsModel():
return ObjectsModel()

View file

@ -0,0 +1,29 @@
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from UM.Scene.SceneNode import SceneNode
from UM.Operations.Operation import Operation
from cura.Settings.SettingOverrideDecorator import SettingOverrideDecorator
## Simple operation to set the buildplate number of a scenenode.
class SetBuildPlateNumberOperation(Operation):
def __init__(self, node: SceneNode, build_plate_nr: int) -> None:
super().__init__()
self._node = node
self._build_plate_nr = build_plate_nr
self._previous_build_plate_nr = None
self._decorator_added = False
def undo(self):
if self._previous_build_plate_nr:
self._node.callDecoration("setBuildPlateNumber", self._previous_build_plate_nr)
def redo(self):
stack = self._node.callDecoration("getStack") #Don't try to get the active extruder since it may be None anyway.
if not stack:
self._node.addDecorator(SettingOverrideDecorator())
self._previous_build_plate_nr = self._node.callDecoration("getBuildPlateNumber")
self._node.callDecoration("setBuildPlateNumber", self._build_plate_nr)

View file

View file

@ -10,10 +10,10 @@ from UM.Math.Vector import Vector
from UM.Scene.Selection import Selection from UM.Scene.Selection import Selection
from UM.Preferences import Preferences from UM.Preferences import Preferences
from cura.ConvexHullDecorator import ConvexHullDecorator from cura.Scene.ConvexHullDecorator import ConvexHullDecorator
from . import PlatformPhysicsOperation from cura.Operations import PlatformPhysicsOperation
from . import ZOffsetDecorator from cura.Scene import ZOffsetDecorator
import random # used for list shuffling import random # used for list shuffling
@ -34,7 +34,7 @@ class PlatformPhysics:
self._change_timer.timeout.connect(self._onChangeTimerFinished) self._change_timer.timeout.connect(self._onChangeTimerFinished)
self._move_factor = 1.1 # By how much should we multiply overlap to calculate a new spot? self._move_factor = 1.1 # By how much should we multiply overlap to calculate a new spot?
self._max_overlap_checks = 10 # How many times should we try to find a new spot per tick? self._max_overlap_checks = 10 # How many times should we try to find a new spot per tick?
self._minimum_gap = 2 # It is a minimum distance between two models, applicable for small models self._minimum_gap = 2 # It is a minimum distance (in mm) between two models, applicable for small models
Preferences.getInstance().addPreference("physics/automatic_push_free", True) Preferences.getInstance().addPreference("physics/automatic_push_free", True)
Preferences.getInstance().addPreference("physics/automatic_drop_down", True) Preferences.getInstance().addPreference("physics/automatic_drop_down", True)
@ -42,7 +42,7 @@ class PlatformPhysics:
def _onSceneChanged(self, source): def _onSceneChanged(self, source):
self._change_timer.start() self._change_timer.start()
def _onChangeTimerFinished(self, was_triggered_by_tool=False): def _onChangeTimerFinished(self):
if not self._enabled: if not self._enabled:
return return
@ -61,7 +61,7 @@ class PlatformPhysics:
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 not issubclass(type(node), SceneNode) or node.getBoundingBox() is None:
continue continue
bbox = node.getBoundingBox() bbox = node.getBoundingBox()
@ -71,7 +71,7 @@ class PlatformPhysics:
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
z_offset = node.callDecoration("getZOffset") if node.getDecorator(ZOffsetDecorator.ZOffsetDecorator) else 0 z_offset = node.callDecoration("getZOffset") if node.getDecorator(ZOffsetDecorator.ZOffsetDecorator) else 0
move_vector = move_vector.set(y=-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):
@ -82,7 +82,7 @@ class PlatformPhysics:
# Check for collisions between convex hulls # Check for collisions between convex hulls
for other_node in BreadthFirstIterator(root): for other_node in BreadthFirstIterator(root):
# Ignore root, ourselves and anything that is not a normal SceneNode. # Ignore root, ourselves and anything that is not a normal SceneNode.
if other_node is root or type(other_node) is not SceneNode or other_node is node: if other_node is root or not issubclass(type(other_node), SceneNode) or other_node is node or other_node.callDecoration("getBuildPlateNumber") != node.callDecoration("getBuildPlateNumber"):
continue continue
# Ignore collisions of a group with it's own children # Ignore collisions of a group with it's own children
@ -130,17 +130,14 @@ class PlatformPhysics:
overlap = own_convex_hull.translate(move_vector.x, move_vector.z).intersectsPolygon(other_convex_hull) overlap = own_convex_hull.translate(move_vector.x, move_vector.z).intersectsPolygon(other_convex_hull)
if overlap: # Moving ensured that overlap was still there. Try anew! if overlap: # Moving ensured that overlap was still there. Try anew!
temp_move_vector = move_vector.set(x = move_vector.x + overlap[0] * self._move_factor, temp_move_vector = move_vector.set(x = move_vector.x + overlap[0] * self._move_factor,
z = move_vector.z + overlap[1] * self._move_factor) z = move_vector.z + overlap[1] * self._move_factor)
# if the distance between two models less than 2mm then try to find a new factor # if the distance between two models less than 2mm then try to find a new factor
if abs(temp_move_vector.x - overlap[0]) < self._minimum_gap and abs(temp_move_vector.y - overlap[1]) < self._minimum_gap: if abs(temp_move_vector.x - overlap[0]) < self._minimum_gap and abs(temp_move_vector.y - overlap[1]) < self._minimum_gap:
temp_scale_factor = self._move_factor
temp_x_factor = (abs(overlap[0]) + self._minimum_gap) / overlap[0] if overlap[0] != 0 else 0 # find x move_factor, like (3.4 + 2) / 3.4 = 1.58 temp_x_factor = (abs(overlap[0]) + self._minimum_gap) / overlap[0] if overlap[0] != 0 else 0 # find x move_factor, like (3.4 + 2) / 3.4 = 1.58
temp_y_factor = (abs(overlap[1]) + self._minimum_gap) / overlap[1] if overlap[1] != 0 else 0 # find y move_factor temp_y_factor = (abs(overlap[1]) + self._minimum_gap) / overlap[1] if overlap[1] != 0 else 0 # find y move_factor
if abs(temp_x_factor) > abs(temp_y_factor):
temp_scale_factor = temp_x_factor temp_scale_factor = temp_x_factor if abs(temp_x_factor) > abs(temp_y_factor) else temp_y_factor
else:
temp_scale_factor = temp_y_factor
move_vector = move_vector.set(x = move_vector.x + overlap[0] * temp_scale_factor, move_vector = move_vector.set(x = move_vector.x + overlap[0] * temp_scale_factor,
z = move_vector.z + overlap[1] * temp_scale_factor) z = move_vector.z + overlap[1] * temp_scale_factor)
@ -148,7 +145,7 @@ class PlatformPhysics:
move_vector = temp_move_vector move_vector = temp_move_vector
else: else:
# This can happen in some cases if the object is not yet done with being loaded. # This can happen in some cases if the object is not yet done with being loaded.
# Simply waiting for the next tick seems to resolve this correctly. # Simply waiting for the next tick seems to resolve this correctly.
overlap = None overlap = None
if not Vector.Null.equals(move_vector, epsilon = 1e-5): if not Vector.Null.equals(move_vector, epsilon = 1e-5):
@ -180,4 +177,4 @@ class PlatformPhysics:
node.removeDecorator(ZOffsetDecorator.ZOffsetDecorator) node.removeDecorator(ZOffsetDecorator.ZOffsetDecorator)
self._enabled = True self._enabled = True
self._onChangeTimerFinished(True) self._onChangeTimerFinished()

View file

@ -54,10 +54,10 @@ class PrintInformation(QObject):
self.initializeCuraMessagePrintTimeProperties() self.initializeCuraMessagePrintTimeProperties()
self._material_lengths = [] self._material_lengths = {} # indexed by build plate number
self._material_weights = [] self._material_weights = {}
self._material_costs = [] self._material_costs = {}
self._material_names = [] self._material_names = {}
self._pre_sliced = False self._pre_sliced = False
@ -68,10 +68,15 @@ class PrintInformation(QObject):
self._base_name = "" self._base_name = ""
self._abbr_machine = "" self._abbr_machine = ""
self._job_name = "" self._job_name = ""
self._project_name = ""
self._active_build_plate = 0
self._initVariablesWithBuildPlate(self._active_build_plate)
Application.getInstance().globalContainerStackChanged.connect(self._updateJobName) Application.getInstance().globalContainerStackChanged.connect(self._updateJobName)
Application.getInstance().fileLoaded.connect(self.setBaseName) Application.getInstance().fileLoaded.connect(self.setBaseName)
Application.getInstance().getBuildPlateModel().activeBuildPlateChanged.connect(self._onActiveBuildPlateChanged)
Application.getInstance().workspaceLoaded.connect(self.setProjectName) Application.getInstance().workspaceLoaded.connect(self.setProjectName)
Preferences.getInstance().preferenceChanged.connect(self._onPreferencesChanged) Preferences.getInstance().preferenceChanged.connect(self._onPreferencesChanged)
self._active_material_container = None self._active_material_container = None
@ -83,7 +88,7 @@ class PrintInformation(QObject):
# Crate cura message translations and using translation keys initialize empty time Duration object for total time # Crate cura message translations and using translation keys initialize empty time Duration object for total time
# and time for each feature # and time for each feature
def initializeCuraMessagePrintTimeProperties(self): def initializeCuraMessagePrintTimeProperties(self):
self._current_print_time = Duration(None, self) self._current_print_time = {} # Duration(None, self)
self._print_time_message_translations = { self._print_time_message_translations = {
"inset_0": catalog.i18nc("@tooltip", "Outer Wall"), "inset_0": catalog.i18nc("@tooltip", "Outer Wall"),
@ -101,10 +106,26 @@ class PrintInformation(QObject):
self._print_time_message_values = {} self._print_time_message_values = {}
# Full fill message values using keys from _print_time_message_translations
for key in self._print_time_message_translations.keys():
self._print_time_message_values[key] = Duration(None, self)
def _initPrintTimeMessageValues(self, build_plate_number):
# Full fill message values using keys from _print_time_message_translations
self._print_time_message_values[build_plate_number] = {}
for key in self._print_time_message_translations.keys():
self._print_time_message_values[build_plate_number][key] = Duration(None, self)
def _initVariablesWithBuildPlate(self, build_plate_number):
if build_plate_number not in self._print_time_message_values:
self._initPrintTimeMessageValues(build_plate_number)
if self._active_build_plate not in self._material_lengths:
self._material_lengths[self._active_build_plate] = []
if self._active_build_plate not in self._material_weights:
self._material_weights[self._active_build_plate] = []
if self._active_build_plate not in self._material_costs:
self._material_costs[self._active_build_plate] = []
if self._active_build_plate not in self._material_names:
self._material_names[self._active_build_plate] = []
if self._active_build_plate not in self._current_print_time:
self._current_print_time[self._active_build_plate] = Duration(None, self)
currentPrintTimeChanged = pyqtSignal() currentPrintTimeChanged = pyqtSignal()
@ -120,64 +141,71 @@ class PrintInformation(QObject):
@pyqtProperty(Duration, notify = currentPrintTimeChanged) @pyqtProperty(Duration, notify = currentPrintTimeChanged)
def currentPrintTime(self): def currentPrintTime(self):
return self._current_print_time return self._current_print_time[self._active_build_plate]
materialLengthsChanged = pyqtSignal() materialLengthsChanged = pyqtSignal()
@pyqtProperty("QVariantList", notify = materialLengthsChanged) @pyqtProperty("QVariantList", notify = materialLengthsChanged)
def materialLengths(self): def materialLengths(self):
return self._material_lengths return self._material_lengths[self._active_build_plate]
materialWeightsChanged = pyqtSignal() materialWeightsChanged = pyqtSignal()
@pyqtProperty("QVariantList", notify = materialWeightsChanged) @pyqtProperty("QVariantList", notify = materialWeightsChanged)
def materialWeights(self): def materialWeights(self):
return self._material_weights return self._material_weights[self._active_build_plate]
materialCostsChanged = pyqtSignal() materialCostsChanged = pyqtSignal()
@pyqtProperty("QVariantList", notify = materialCostsChanged) @pyqtProperty("QVariantList", notify = materialCostsChanged)
def materialCosts(self): def materialCosts(self):
return self._material_costs return self._material_costs[self._active_build_plate]
materialNamesChanged = pyqtSignal() materialNamesChanged = pyqtSignal()
@pyqtProperty("QVariantList", notify = materialNamesChanged) @pyqtProperty("QVariantList", notify = materialNamesChanged)
def materialNames(self): def materialNames(self):
return self._material_names return self._material_names[self._active_build_plate]
def _onPrintDurationMessage(self, print_time, material_amounts): def printTimes(self):
return self._print_time_message_values[self._active_build_plate]
self._updateTotalPrintTimePerFeature(print_time) def _onPrintDurationMessage(self, build_plate_number, print_time, material_amounts):
self._updateTotalPrintTimePerFeature(build_plate_number, print_time)
self.currentPrintTimeChanged.emit() self.currentPrintTimeChanged.emit()
self._material_amounts = material_amounts self._material_amounts = material_amounts
self._calculateInformation() self._calculateInformation(build_plate_number)
def _updateTotalPrintTimePerFeature(self, print_time): def _updateTotalPrintTimePerFeature(self, build_plate_number, print_time):
total_estimated_time = 0 total_estimated_time = 0
if build_plate_number not in self._print_time_message_values:
self._initPrintTimeMessageValues(build_plate_number)
for feature, time in print_time.items(): for feature, time in print_time.items():
if time != time: # Check for NaN. Engine can sometimes give us weird values. if time != time: # Check for NaN. Engine can sometimes give us weird values.
self._print_time_message_values.get(feature).setDuration(0) self._print_time_message_values[build_plate_number].get(feature).setDuration(0)
Logger.log("w", "Received NaN for print duration message") Logger.log("w", "Received NaN for print duration message")
continue continue
total_estimated_time += time total_estimated_time += time
self._print_time_message_values.get(feature).setDuration(time) self._print_time_message_values[build_plate_number].get(feature).setDuration(time)
self._current_print_time.setDuration(total_estimated_time) if build_plate_number not in self._current_print_time:
self._current_print_time[build_plate_number] = Duration(None, self)
self._current_print_time[build_plate_number].setDuration(total_estimated_time)
def _calculateInformation(self): def _calculateInformation(self, build_plate_number):
if Application.getInstance().getGlobalContainerStack() is None: if Application.getInstance().getGlobalContainerStack() is None:
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
radius = Application.getInstance().getGlobalContainerStack().getProperty("material_diameter", "value") / 2 radius = Application.getInstance().getGlobalContainerStack().getProperty("material_diameter", "value") / 2
self._material_lengths = [] self._material_lengths[build_plate_number] = []
self._material_weights = [] self._material_weights[build_plate_number] = []
self._material_costs = [] self._material_costs[build_plate_number] = []
self._material_names = [] self._material_names[build_plate_number] = []
material_preference_values = json.loads(Preferences.getInstance().getValue("cura/material_settings")) material_preference_values = json.loads(Preferences.getInstance().getValue("cura/material_settings"))
@ -215,10 +243,10 @@ class PrintInformation(QObject):
length = round((amount / (math.pi * radius ** 2)) / 1000, 2) length = round((amount / (math.pi * radius ** 2)) / 1000, 2)
else: else:
length = 0 length = 0
self._material_weights.append(weight) self._material_weights[build_plate_number].append(weight)
self._material_lengths.append(length) self._material_lengths[build_plate_number].append(length)
self._material_costs.append(cost) self._material_costs[build_plate_number].append(cost)
self._material_names.append(material_name) self._material_names[build_plate_number].append(material_name)
self.materialLengthsChanged.emit() self.materialLengthsChanged.emit()
self.materialWeightsChanged.emit() self.materialWeightsChanged.emit()
@ -229,7 +257,8 @@ class PrintInformation(QObject):
if preference != "cura/material_settings": if preference != "cura/material_settings":
return return
self._calculateInformation() for build_plate_number in range(Application.getInstance().getBuildPlateModel().maxBuildPlate + 1):
self._calculateInformation(build_plate_number)
def _onActiveMaterialChanged(self): def _onActiveMaterialChanged(self):
if self._active_material_container: if self._active_material_container:
@ -245,8 +274,22 @@ class PrintInformation(QObject):
self._active_material_container = active_material_containers[0] self._active_material_container = active_material_containers[0]
self._active_material_container.metaDataChanged.connect(self._onMaterialMetaDataChanged) self._active_material_container.metaDataChanged.connect(self._onMaterialMetaDataChanged)
def _onActiveBuildPlateChanged(self):
new_active_build_plate = Application.getInstance().getBuildPlateModel().activeBuildPlate
if new_active_build_plate != self._active_build_plate:
self._active_build_plate = new_active_build_plate
self._initVariablesWithBuildPlate(self._active_build_plate)
self.materialLengthsChanged.emit()
self.materialWeightsChanged.emit()
self.materialCostsChanged.emit()
self.materialNamesChanged.emit()
self.currentPrintTimeChanged.emit()
def _onMaterialMetaDataChanged(self, *args, **kwargs): def _onMaterialMetaDataChanged(self, *args, **kwargs):
self._calculateInformation() for build_plate_number in range(Application.getInstance().getBuildPlateModel().maxBuildPlate + 1):
self._calculateInformation(build_plate_number)
@pyqtSlot(str) @pyqtSlot(str)
def setJobName(self, name): def setJobName(self, name):
@ -340,7 +383,9 @@ class PrintInformation(QObject):
@pyqtSlot(result = "QVariantMap") @pyqtSlot(result = "QVariantMap")
def getFeaturePrintTimes(self): def getFeaturePrintTimes(self):
result = {} result = {}
for feature, time in self._print_time_message_values.items(): if self._active_build_plate not in self._print_time_message_values:
self._initPrintTimeMessageValues(self._active_build_plate)
for feature, time in self._print_time_message_values[self._active_build_plate].items():
if feature in self._print_time_message_translations: if feature in self._print_time_message_translations:
result[self._print_time_message_translations[feature]] = time result[self._print_time_message_translations[feature]] = time
else: else:
@ -348,10 +393,12 @@ class PrintInformation(QObject):
return result return result
# Simulate message with zero time duration # Simulate message with zero time duration
def setToZeroPrintInformation(self): def setToZeroPrintInformation(self, build_plate_number):
temp_message = {} temp_message = {}
for key in self._print_time_message_values.keys(): if build_plate_number not in self._print_time_message_values:
self._print_time_message_values[build_plate_number] = {}
for key in self._print_time_message_values[build_plate_number].keys():
temp_message[key] = 0 temp_message[key] = 0
temp_material_amounts = [0] temp_material_amounts = [0]
self._onPrintDurationMessage(temp_message, temp_material_amounts) self._onPrintDurationMessage(build_plate_number, temp_message, temp_material_amounts)

View file

@ -0,0 +1,26 @@
from UM.Scene.SceneNodeDecorator import SceneNodeDecorator
from cura.Scene.CuraSceneNode import CuraSceneNode
## Make a SceneNode build plate aware CuraSceneNode objects all have this decorator.
class BuildPlateDecorator(SceneNodeDecorator):
def __init__(self, build_plate_number = -1):
super().__init__()
self._build_plate_number = None
self.setBuildPlateNumber(build_plate_number)
def setBuildPlateNumber(self, nr):
# Make sure that groups are set correctly
# setBuildPlateForSelection in CuraActions makes sure that no single childs are set.
self._build_plate_number = nr
if issubclass(type(self._node), CuraSceneNode):
self._node.transformChanged() # trigger refresh node without introducing a new signal
if self._node and self._node.callDecoration("isGroup"):
for child in self._node.getChildren():
child.callDecoration("setBuildPlateNumber", nr)
def getBuildPlateNumber(self):
return self._build_plate_number
def __deepcopy__(self, memo):
return BuildPlateDecorator()

View file

@ -7,7 +7,7 @@ from UM.Scene.SceneNodeDecorator import SceneNodeDecorator
from UM.Settings.ContainerRegistry import ContainerRegistry from UM.Settings.ContainerRegistry import ContainerRegistry
from cura.Settings.ExtruderManager import ExtruderManager from cura.Settings.ExtruderManager import ExtruderManager
from . import ConvexHullNode from cura.Scene import ConvexHullNode
import numpy import numpy

View file

@ -6,7 +6,6 @@ from UM.Scene.SceneNode import SceneNode
from UM.Resources import Resources from UM.Resources import Resources
from UM.Math.Color import Color from UM.Math.Color import Color
from UM.Mesh.MeshBuilder import MeshBuilder # To create a mesh to display the convex hull with. from UM.Mesh.MeshBuilder import MeshBuilder # To create a mesh to display the convex hull with.
from UM.View.GL.OpenGL import OpenGL from UM.View.GL.OpenGL import OpenGL
@ -66,7 +65,7 @@ class ConvexHullNode(SceneNode):
ConvexHullNode.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() and issubclass(type(self._node), SceneNode) and self._node.callDecoration("getBuildPlateNumber") == Application.getInstance().getBuildPlateModel().activeBuildPlate:
renderer.queueNode(self, transparent = True, shader = ConvexHullNode.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 = ConvexHullNode.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)

View file

@ -0,0 +1,101 @@
from UM.Logger import Logger
from PyQt5.QtCore import Qt, pyqtSlot, QObject
from PyQt5.QtWidgets import QApplication
from cura.ObjectsModel import ObjectsModel
from cura.BuildPlateModel import BuildPlateModel
from UM.Application import Application
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Scene.SceneNode import SceneNode
from UM.Scene.Selection import Selection
class CuraSceneController(QObject):
def __init__(self, objects_model: ObjectsModel, build_plate_model: BuildPlateModel):
super().__init__()
self._objects_model = objects_model
self._build_plate_model = build_plate_model
self._active_build_plate = -1
self._last_selected_index = 0
self._max_build_plate = 1 # default
Application.getInstance().getController().getScene().sceneChanged.connect(self.updateMaxBuildPlate) # it may be a bit inefficient when changing a lot simultaneously
def updateMaxBuildPlate(self, *args):
if args:
source = args[0]
else:
source = None
if not issubclass(type(source), SceneNode):
return
max_build_plate = self._calcMaxBuildPlate()
changed = False
if max_build_plate != self._max_build_plate:
self._max_build_plate = max_build_plate
changed = True
if changed:
self._build_plate_model.setMaxBuildPlate(self._max_build_plate)
build_plates = [{"name": "Build Plate %d" % (i + 1), "buildPlateNumber": i} for i in range(self._max_build_plate + 1)]
self._build_plate_model.setItems(build_plates)
# self.buildPlateItemsChanged.emit() # TODO: necessary after setItems?
def _calcMaxBuildPlate(self):
max_build_plate = 0
for node in DepthFirstIterator(Application.getInstance().getController().getScene().getRoot()):
if node.callDecoration("isSliceable"):
build_plate_number = node.callDecoration("getBuildPlateNumber")
max_build_plate = max(build_plate_number, max_build_plate)
return max_build_plate
## Either select or deselect an item
@pyqtSlot(int)
def changeSelection(self, index):
modifiers = QApplication.keyboardModifiers()
ctrl_is_active = modifiers & Qt.ControlModifier
shift_is_active = modifiers & Qt.ShiftModifier
if ctrl_is_active:
item = self._objects_model.getItem(index)
node = item["node"]
if Selection.isSelected(node):
Selection.remove(node)
else:
Selection.add(node)
elif shift_is_active:
polarity = 1 if index + 1 > self._last_selected_index else -1
for i in range(self._last_selected_index, index + polarity, polarity):
item = self._objects_model.getItem(i)
node = item["node"]
Selection.add(node)
else:
# Single select
item = self._objects_model.getItem(index)
node = item["node"]
Selection.clear()
Selection.add(node)
build_plate_number = node.callDecoration("getBuildPlateNumber")
if build_plate_number is not None and build_plate_number != -1:
self._build_plate_model.setActiveBuildPlate(build_plate_number)
self._last_selected_index = index
@pyqtSlot(int)
def setActiveBuildPlate(self, nr):
if nr == self._active_build_plate:
return
Logger.log("d", "Select build plate: %s" % nr)
self._active_build_plate = nr
Selection.clear()
self._build_plate_model.setActiveBuildPlate(nr)
self._objects_model.setActiveBuildPlate(nr)
@staticmethod
def createCuraSceneController():
objects_model = Application.getInstance().getObjectsModel()
build_plate_model = Application.getInstance().getBuildPlateModel()
return CuraSceneController(objects_model = objects_model, build_plate_model = build_plate_model)

View file

@ -0,0 +1,43 @@
from UM.Application import Application
from UM.Logger import Logger
from UM.Scene.SceneNode import SceneNode
from copy import deepcopy
## Scene nodes that are models are only seen when selecting the corresponding build plate
# Note that many other nodes can just be UM SceneNode objects.
class CuraSceneNode(SceneNode):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._outside_buildarea = True
def setOutsideBuildArea(self, new_value):
self._outside_buildarea = new_value
def isOutsideBuildArea(self):
return self._outside_buildarea or self.callDecoration("getBuildPlateNumber") < 0
def isVisible(self):
return super().isVisible() and self.callDecoration("getBuildPlateNumber") == Application.getInstance().getBuildPlateModel().activeBuildPlate
def isSelectable(self) -> bool:
return super().isSelectable() and self.callDecoration("getBuildPlateNumber") == Application.getInstance().getBuildPlateModel().activeBuildPlate
## Taken from SceneNode, but replaced SceneNode with CuraSceneNode
def __deepcopy__(self, memo):
copy = CuraSceneNode()
copy.setTransformation(self.getLocalTransformation())
copy.setMeshData(self._mesh_data)
copy.setVisible(deepcopy(self._visible, memo))
copy._selectable = deepcopy(self._selectable, memo)
copy._name = deepcopy(self._name, memo)
for decorator in self._decorators:
copy.addDecorator(deepcopy(decorator, memo))
for child in self._children:
copy.addChild(deepcopy(child, memo))
self.calculateBoundingBoxMesh()
return copy
def transformChanged(self) -> None:
self._transformChanged()

0
cura/Scene/__init__.py Normal file
View file

View file

@ -270,7 +270,7 @@ class ExtruderManager(QObject):
return [] return []
# Get the extruders of all printable meshes in the scene # Get the extruders of all printable meshes in the scene
meshes = [node for node in DepthFirstIterator(scene_root) if type(node) is SceneNode and node.isSelectable()] meshes = [node for node in DepthFirstIterator(scene_root) if isinstance(node, SceneNode) and node.isSelectable()]
for mesh in meshes: for mesh in meshes:
extruder_stack_id = mesh.callDecoration("getActiveExtruder") extruder_stack_id = mesh.callDecoration("getActiveExtruder")
if not extruder_stack_id: if not extruder_stack_id:

View file

@ -40,11 +40,11 @@ import faulthandler
if Platform.isLinux(): # Needed for platform.linux_distribution, which is not available on Windows and OSX if Platform.isLinux(): # Needed for platform.linux_distribution, which is not available on Windows and OSX
# For Ubuntu: https://bugs.launchpad.net/ubuntu/+source/python-qt4/+bug/941826 # For Ubuntu: https://bugs.launchpad.net/ubuntu/+source/python-qt4/+bug/941826
linux_distro_name = platform.linux_distribution()[0].lower() linux_distro_name = platform.linux_distribution()[0].lower()
if linux_distro_name in ("debian", "ubuntu", "linuxmint", "fedora"): # TODO: Needs a "if X11_GFX == 'nvidia'" here. The workaround is only needed on Ubuntu+NVidia drivers. Other drivers are not affected, but fine with this fix. # TODO: Needs a "if X11_GFX == 'nvidia'" here. The workaround is only needed on Ubuntu+NVidia drivers. Other drivers are not affected, but fine with this fix.
import ctypes import ctypes
from ctypes.util import find_library from ctypes.util import find_library
libGL = find_library("GL") libGL = find_library("GL")
ctypes.CDLL(libGL, ctypes.RTLD_GLOBAL) ctypes.CDLL(libGL, ctypes.RTLD_GLOBAL)
# When frozen, i.e. installer version, don't let PYTHONPATH mess up the search path for DLLs. # When frozen, i.e. installer version, don't let PYTHONPATH mess up the search path for DLLs.
if Platform.isWindows() and hasattr(sys, "frozen"): if Platform.isWindows() and hasattr(sys, "frozen"):

View file

@ -4,7 +4,6 @@
import os.path import os.path
import zipfile import zipfile
from UM.Job import Job
from UM.Logger import Logger from UM.Logger import Logger
from UM.Math.Matrix import Matrix from UM.Math.Matrix import Matrix
from UM.Math.Vector import Vector from UM.Math.Vector import Vector
@ -15,9 +14,10 @@ from cura.Settings.SettingOverrideDecorator import SettingOverrideDecorator
from UM.Application import Application 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 cura.Scene.CuraSceneNode import CuraSceneNode
from cura.SliceableObjectDecorator import SliceableObjectDecorator from cura.Scene.BuildPlateDecorator import BuildPlateDecorator
from cura.ZOffsetDecorator import ZOffsetDecorator from cura.Scene.SliceableObjectDecorator import SliceableObjectDecorator
from cura.Scene.ZOffsetDecorator import ZOffsetDecorator
MYPY = False MYPY = False
@ -43,6 +43,7 @@ class ThreeMFReader(MeshReader):
} }
self._base_name = "" self._base_name = ""
self._unit = None self._unit = None
self._object_count = 0 # Used to name objects as there is no node name yet.
def _createMatrixFromTransformationString(self, transformation): def _createMatrixFromTransformationString(self, transformation):
if transformation == "": if transformation == "":
@ -77,7 +78,12 @@ class ThreeMFReader(MeshReader):
## Convenience function that converts a SceneNode object (as obtained from libSavitar) to a Uranium scene node. ## Convenience function that converts a SceneNode object (as obtained from libSavitar) to a Uranium scene node.
# \returns Uranium scene node. # \returns Uranium scene node.
def _convertSavitarNodeToUMNode(self, savitar_node): def _convertSavitarNodeToUMNode(self, savitar_node):
um_node = SceneNode() self._object_count += 1
node_name = "Object %s" % self._object_count
um_node = CuraSceneNode()
um_node.addDecorator(BuildPlateDecorator(0))
um_node.setName(node_name)
transformation = self._createMatrixFromTransformationString(savitar_node.getTransformation()) transformation = self._createMatrixFromTransformationString(savitar_node.getTransformation())
um_node.setTransformation(transformation) um_node.setTransformation(transformation)
mesh_builder = MeshBuilder() mesh_builder = MeshBuilder()
@ -147,6 +153,7 @@ class ThreeMFReader(MeshReader):
def read(self, file_name): def read(self, file_name):
result = [] result = []
self._object_count = 0 # Used to name objects as there is no node name yet.
# The base object of 3mf is a zipped archive. # The base object of 3mf is a zipped archive.
try: try:
archive = zipfile.ZipFile(file_name, "r") archive = zipfile.ZipFile(file_name, "r")

View file

@ -7,6 +7,7 @@ from UM.Logger import Logger
from UM.Math.Matrix import Matrix from UM.Math.Matrix import Matrix
from UM.Application import Application from UM.Application import Application
import UM.Scene.SceneNode import UM.Scene.SceneNode
from cura.Scene.CuraSceneNode import CuraSceneNode
import Savitar import Savitar
@ -63,7 +64,7 @@ class ThreeMFWriter(MeshWriter):
## Convenience function that converts an Uranium SceneNode object to a SavitarSceneNode ## Convenience function that converts an Uranium SceneNode object to a SavitarSceneNode
# \returns Uranium Scenen node. # \returns Uranium Scenen node.
def _convertUMNodeToSavitarNode(self, um_node, transformation = Matrix()): def _convertUMNodeToSavitarNode(self, um_node, transformation = Matrix()):
if type(um_node) is not UM.Scene.SceneNode.SceneNode: if type(um_node) not in [UM.Scene.SceneNode.SceneNode, CuraSceneNode]:
return None return None
savitar_node = Savitar.SceneNode() savitar_node = Savitar.SceneNode()

View file

@ -16,6 +16,7 @@ from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Qt.Duration import DurationFormat from UM.Qt.Duration import DurationFormat
from PyQt5.QtCore import QObject, pyqtSlot from PyQt5.QtCore import QObject, pyqtSlot
from collections import defaultdict
from cura.Settings.ExtruderManager import ExtruderManager from cura.Settings.ExtruderManager import ExtruderManager
from . import ProcessSlicedLayersJob from . import ProcessSlicedLayersJob
from . import StartSliceJob from . import StartSliceJob
@ -69,9 +70,10 @@ class CuraEngineBackend(QObject, Backend):
# Workaround to disable layer view processing if layer view is not active. # Workaround to disable layer view processing if layer view is not active.
self._layer_view_active = False self._layer_view_active = False
Application.getInstance().getController().activeViewChanged.connect(self._onActiveViewChanged) Application.getInstance().getController().activeViewChanged.connect(self._onActiveViewChanged)
Application.getInstance().getBuildPlateModel().activeBuildPlateChanged.connect(self._onActiveViewChanged)
self._onActiveViewChanged() self._onActiveViewChanged()
self._stored_layer_data = [] self._stored_layer_data = []
self._stored_optimized_layer_data = [] self._stored_optimized_layer_data = {} # key is build plate number, then arrays are stored until they go to the ProcessSlicesLayersJob
self._scene = Application.getInstance().getController().getScene() self._scene = Application.getInstance().getController().getScene()
self._scene.sceneChanged.connect(self._onSceneChanged) self._scene.sceneChanged.connect(self._onSceneChanged)
@ -105,17 +107,18 @@ class CuraEngineBackend(QObject, Backend):
self._message_handlers["cura.proto.SlicingFinished"] = self._onSlicingFinishedMessage self._message_handlers["cura.proto.SlicingFinished"] = self._onSlicingFinishedMessage
self._start_slice_job = None self._start_slice_job = None
self._start_slice_job_build_plate = None
self._slicing = False # Are we currently slicing? self._slicing = False # Are we currently slicing?
self._restart = False # Back-end is currently restarting? self._restart = False # Back-end is currently restarting?
self._tool_active = False # If a tool is active, some tasks do not have to do anything self._tool_active = False # If a tool is active, some tasks do not have to do anything
self._always_restart = True # Always restart the engine when starting a new slice. Don't keep the process running. TODO: Fix engine statelessness. self._always_restart = True # Always restart the engine when starting a new slice. Don't keep the process running. TODO: Fix engine statelessness.
self._process_layers_job = None # The currently active job to process layers, or None if it is not processing layers. self._process_layers_job = None # The currently active job to process layers, or None if it is not processing layers.
self._need_slicing = False self._build_plates_to_be_sliced = [] # what needs slicing?
self._engine_is_fresh = True # Is the newly started engine used before or not? self._engine_is_fresh = True # Is the newly started engine used before or not?
self._backend_log_max_lines = 20000 # Maximum number of lines to buffer self._backend_log_max_lines = 20000 # Maximum number of lines to buffer
self._error_message = None # Pop-up message that shows errors. self._error_message = None # Pop-up message that shows errors.
self._last_num_objects = 0 # Count number of objects to see if there is something changed self._last_num_objects = defaultdict(int) # Count number of objects to see if there is something changed
self._postponed_scene_change_sources = [] # scene change is postponed (by a tool) self._postponed_scene_change_sources = [] # scene change is postponed (by a tool)
self.backendQuit.connect(self._onBackendQuit) self.backendQuit.connect(self._onBackendQuit)
@ -174,6 +177,7 @@ class CuraEngineBackend(QObject, Backend):
self._createSocket() self._createSocket()
if self._process_layers_job: # We were processing layers. Stop that, the layers are going to change soon. if self._process_layers_job: # We were processing layers. Stop that, the layers are going to change soon.
Logger.log("d", "Aborting process layers job...")
self._process_layers_job.abort() self._process_layers_job.abort()
self._process_layers_job = None self._process_layers_job = None
@ -190,17 +194,35 @@ class CuraEngineBackend(QObject, Backend):
## Perform a slice of the scene. ## Perform a slice of the scene.
def slice(self): def slice(self):
Logger.log("d", "starting to slice!")
self._slice_start_time = time() self._slice_start_time = time()
if not self._need_slicing: if not self._build_plates_to_be_sliced:
self.processingProgress.emit(1.0) self.processingProgress.emit(1.0)
self.backendStateChange.emit(BackendState.Done)
Logger.log("w", "Slice unnecessary, nothing has changed that needs reslicing.") Logger.log("w", "Slice unnecessary, nothing has changed that needs reslicing.")
return return
if Application.getInstance().getPrintInformation():
Application.getInstance().getPrintInformation().setToZeroPrintInformation() if self._process_layers_job:
Logger.log("d", " ## Process layers job still busy, trying later")
return
if not hasattr(self._scene, "gcode_list"):
self._scene.gcode_list = {}
# see if we really have to slice
active_build_plate = Application.getInstance().getBuildPlateModel().activeBuildPlate
build_plate_to_be_sliced = self._build_plates_to_be_sliced.pop(0)
Logger.log("d", "Going to slice build plate [%s]!" % build_plate_to_be_sliced)
num_objects = self._numObjects()
if build_plate_to_be_sliced not in num_objects or num_objects[build_plate_to_be_sliced] == 0:
self._scene.gcode_list[build_plate_to_be_sliced] = []
Logger.log("d", "Build plate %s has 0 objects to be sliced, skipping", build_plate_to_be_sliced)
return
self._stored_layer_data = [] self._stored_layer_data = []
self._stored_optimized_layer_data = [] self._stored_optimized_layer_data[build_plate_to_be_sliced] = []
if Application.getInstance().getPrintInformation() and build_plate_to_be_sliced == active_build_plate:
Application.getInstance().getPrintInformation().setToZeroPrintInformation(build_plate_to_be_sliced)
if self._process is None: if self._process is None:
self._createSocket() self._createSocket()
@ -210,12 +232,14 @@ class CuraEngineBackend(QObject, Backend):
self.processingProgress.emit(0.0) self.processingProgress.emit(0.0)
self.backendStateChange.emit(BackendState.NotStarted) self.backendStateChange.emit(BackendState.NotStarted)
self._scene.gcode_list = [] self._scene.gcode_list[build_plate_to_be_sliced] = [] #[] indexed by build plate number
self._slicing = True self._slicing = True
self.slicingStarted.emit() self.slicingStarted.emit()
slice_message = self._socket.createMessage("cura.proto.Slice") slice_message = self._socket.createMessage("cura.proto.Slice")
self._start_slice_job = StartSliceJob.StartSliceJob(slice_message) self._start_slice_job = StartSliceJob.StartSliceJob(slice_message)
self._start_slice_job_build_plate = build_plate_to_be_sliced
self._start_slice_job.setBuildPlate(self._start_slice_job_build_plate)
self._start_slice_job.start() self._start_slice_job.start()
self._start_slice_job.finished.connect(self._onStartSliceCompleted) self._start_slice_job.finished.connect(self._onStartSliceCompleted)
@ -224,7 +248,8 @@ class CuraEngineBackend(QObject, Backend):
def _terminate(self): def _terminate(self):
self._slicing = False self._slicing = False
self._stored_layer_data = [] self._stored_layer_data = []
self._stored_optimized_layer_data = [] if self._start_slice_job_build_plate in self._stored_optimized_layer_data:
del self._stored_optimized_layer_data[self._start_slice_job_build_plate]
if self._start_slice_job is not None: if self._start_slice_job is not None:
self._start_slice_job.cancel() self._start_slice_job.cancel()
@ -339,7 +364,10 @@ class CuraEngineBackend(QObject, Backend):
self.backendStateChange.emit(BackendState.Error) self.backendStateChange.emit(BackendState.Error)
else: else:
self.backendStateChange.emit(BackendState.NotStarted) self.backendStateChange.emit(BackendState.NotStarted)
pass
self._invokeSlice()
return return
# Preparation completed, send it to the backend. # Preparation completed, send it to the backend.
self._socket.sendMessage(job.getSliceMessage()) self._socket.sendMessage(job.getSliceMessage())
@ -363,7 +391,7 @@ class CuraEngineBackend(QObject, Backend):
self.backendStateChange.emit(BackendState.Disabled) self.backendStateChange.emit(BackendState.Disabled)
gcode_list = node.callDecoration("getGCodeList") gcode_list = node.callDecoration("getGCodeList")
if gcode_list is not None: if gcode_list is not None:
self._scene.gcode_list = gcode_list self._scene.gcode_list[node.callDecoration("getBuildPlateNumber")] = gcode_list
if self._use_timer == enable_timer: if self._use_timer == enable_timer:
return self._use_timer return self._use_timer
@ -375,33 +403,48 @@ class CuraEngineBackend(QObject, Backend):
self.disableTimer() self.disableTimer()
return False return False
## Return a dict with number of objects per build plate
def _numObjects(self):
num_objects = defaultdict(int)
for node in DepthFirstIterator(self._scene.getRoot()):
# Only count sliceable objects
if node.callDecoration("isSliceable"):
build_plate_number = node.callDecoration("getBuildPlateNumber")
num_objects[build_plate_number] += 1
return num_objects
## Listener for when the scene has changed. ## Listener for when the scene has changed.
# #
# This should start a slice if the scene is now ready to slice. # This should start a slice if the scene is now ready to slice.
# #
# \param source The scene node that was changed. # \param source The scene node that was changed.
def _onSceneChanged(self, source): def _onSceneChanged(self, source):
if type(source) is not SceneNode: if not issubclass(type(source), SceneNode):
return return
root_scene_nodes_changed = False build_plate_changed = set()
source_build_plate_number = source.callDecoration("getBuildPlateNumber")
if source == self._scene.getRoot(): if source == self._scene.getRoot():
num_objects = 0 # we got the root node
for node in DepthFirstIterator(self._scene.getRoot()): num_objects = self._numObjects()
# Only count sliceable objects for build_plate_number in list(self._last_num_objects.keys()) + list(num_objects.keys()):
if node.callDecoration("isSliceable"): if build_plate_number not in self._last_num_objects or num_objects[build_plate_number] != self._last_num_objects[build_plate_number]:
num_objects += 1 self._last_num_objects[build_plate_number] = num_objects[build_plate_number]
if num_objects != self._last_num_objects: build_plate_changed.add(build_plate_number)
self._last_num_objects = num_objects else:
root_scene_nodes_changed = True # we got a single scenenode
else: if not source.callDecoration("isGroup"):
return if source.getMeshData() is None:
return
if source.getMeshData().getVertices() is None:
return
if not source.callDecoration("isGroup") and not root_scene_nodes_changed: build_plate_changed.add(source_build_plate_number)
if source.getMeshData() is None:
return build_plate_changed.discard(None)
if source.getMeshData().getVertices() is None: build_plate_changed.discard(-1) # object not on build plate
return if not build_plate_changed:
return
if self._tool_active: if self._tool_active:
# do it later, each source only has to be done once # do it later, each source only has to be done once
@ -409,9 +452,17 @@ class CuraEngineBackend(QObject, Backend):
self._postponed_scene_change_sources.append(source) self._postponed_scene_change_sources.append(source)
return return
self.needsSlicing()
self.stopSlicing() self.stopSlicing()
self._onChanged() for build_plate_number in build_plate_changed:
if build_plate_number not in self._build_plates_to_be_sliced:
self._build_plates_to_be_sliced.append(build_plate_number)
self.processingProgress.emit(0.0)
self.backendStateChange.emit(BackendState.NotStarted)
# if not self._use_timer:
# With manually having to slice, we want to clear the old invalid layer data.
self._clearLayerData(build_plate_changed)
self._invokeSlice()
## Called when an error occurs in the socket connection towards the engine. ## Called when an error occurs in the socket connection towards the engine.
# #
@ -431,16 +482,21 @@ class CuraEngineBackend(QObject, Backend):
Logger.log("w", "A socket error caused the connection to be reset") Logger.log("w", "A socket error caused the connection to be reset")
## Remove old layer data (if any) ## Remove old layer data (if any)
def _clearLayerData(self): def _clearLayerData(self, build_plate_numbers = set()):
for node in DepthFirstIterator(self._scene.getRoot()): for node in DepthFirstIterator(self._scene.getRoot()):
if node.callDecoration("getLayerData"): if node.callDecoration("getLayerData"):
node.getParent().removeChild(node) if not build_plate_numbers or node.callDecoration("getBuildPlateNumber") in build_plate_numbers:
break node.getParent().removeChild(node)
## Convenient function: set need_slicing, emit state and clear layer data def markSliceAll(self):
for build_plate_number in range(Application.getInstance().getBuildPlateModel().maxBuildPlate + 1):
if build_plate_number not in self._build_plates_to_be_sliced:
self._build_plates_to_be_sliced.append(build_plate_number)
## Convenient function: mark everything to slice, emit state and clear layer data
def needsSlicing(self): def needsSlicing(self):
self.stopSlicing() self.stopSlicing()
self._need_slicing = True self.markSliceAll()
self.processingProgress.emit(0.0) self.processingProgress.emit(0.0)
self.backendStateChange.emit(BackendState.NotStarted) self.backendStateChange.emit(BackendState.NotStarted)
if not self._use_timer: if not self._use_timer:
@ -462,7 +518,7 @@ class CuraEngineBackend(QObject, Backend):
def _onStackErrorCheckFinished(self): def _onStackErrorCheckFinished(self):
self._is_error_check_scheduled = False self._is_error_check_scheduled = False
if not self._slicing and self._need_slicing: if not self._slicing and self._build_plates_to_be_sliced: #self._need_slicing:
self.needsSlicing() self.needsSlicing()
self._onChanged() self._onChanged()
@ -476,7 +532,7 @@ class CuraEngineBackend(QObject, Backend):
# #
# \param message The protobuf message containing sliced layer data. # \param message The protobuf message containing sliced layer data.
def _onOptimizedLayerMessage(self, message): def _onOptimizedLayerMessage(self, message):
self._stored_optimized_layer_data.append(message) self._stored_optimized_layer_data[self._start_slice_job_build_plate].append(message)
## Called when a progress message is received from the engine. ## Called when a progress message is received from the engine.
# #
@ -485,6 +541,16 @@ class CuraEngineBackend(QObject, Backend):
self.processingProgress.emit(message.amount) self.processingProgress.emit(message.amount)
self.backendStateChange.emit(BackendState.Processing) self.backendStateChange.emit(BackendState.Processing)
# testing
def _invokeSlice(self):
if self._use_timer:
# if the error check is scheduled, wait for the error check finish signal to trigger auto-slice,
# otherwise business as usual
if self._is_error_check_scheduled:
self._change_timer.stop()
else:
self._change_timer.start()
## Called when the engine sends a message that slicing is finished. ## Called when the engine sends a message that slicing is finished.
# #
# \param message The protobuf message signalling that slicing is finished. # \param message The protobuf message signalling that slicing is finished.
@ -492,36 +558,44 @@ class CuraEngineBackend(QObject, Backend):
self.backendStateChange.emit(BackendState.Done) self.backendStateChange.emit(BackendState.Done)
self.processingProgress.emit(1.0) self.processingProgress.emit(1.0)
for line in self._scene.gcode_list: gcode_list = self._scene.gcode_list[self._start_slice_job_build_plate]
for index, line in enumerate(gcode_list):
replaced = line.replace("{print_time}", str(Application.getInstance().getPrintInformation().currentPrintTime.getDisplayString(DurationFormat.Format.ISO8601))) replaced = line.replace("{print_time}", str(Application.getInstance().getPrintInformation().currentPrintTime.getDisplayString(DurationFormat.Format.ISO8601)))
replaced = replaced.replace("{filament_amount}", str(Application.getInstance().getPrintInformation().materialLengths)) replaced = replaced.replace("{filament_amount}", str(Application.getInstance().getPrintInformation().materialLengths))
replaced = replaced.replace("{filament_weight}", str(Application.getInstance().getPrintInformation().materialWeights)) replaced = replaced.replace("{filament_weight}", str(Application.getInstance().getPrintInformation().materialWeights))
replaced = replaced.replace("{filament_cost}", str(Application.getInstance().getPrintInformation().materialCosts)) replaced = replaced.replace("{filament_cost}", str(Application.getInstance().getPrintInformation().materialCosts))
replaced = replaced.replace("{jobname}", str(Application.getInstance().getPrintInformation().jobName)) replaced = replaced.replace("{jobname}", str(Application.getInstance().getPrintInformation().jobName))
self._scene.gcode_list[self._scene.gcode_list.index(line)] = replaced gcode_list[index] = replaced
self._slicing = False self._slicing = False
self._need_slicing = False
Logger.log("d", "Slicing took %s seconds", time() - self._slice_start_time ) Logger.log("d", "Slicing took %s seconds", time() - self._slice_start_time )
if self._layer_view_active and (self._process_layers_job is None or not self._process_layers_job.isRunning()):
self._process_layers_job = ProcessSlicedLayersJob.ProcessSlicedLayersJob(self._stored_optimized_layer_data) # See if we need to process the sliced layers job.
self._process_layers_job.finished.connect(self._onProcessLayersFinished) active_build_plate = Application.getInstance().getBuildPlateModel().activeBuildPlate
self._process_layers_job.start() if self._layer_view_active and (self._process_layers_job is None or not self._process_layers_job.isRunning()) and active_build_plate == self._start_slice_job_build_plate:
self._stored_optimized_layer_data = [] self._startProcessSlicedLayersJob(active_build_plate)
# self._onActiveViewChanged()
self._start_slice_job_build_plate = None
Logger.log("d", "See if there is more to slice...")
# Somehow this results in an Arcus Error
# self.slice()
# Testing call slice again, allow backend to restart by using the timer
self._invokeSlice()
## Called when a g-code message is received from the engine. ## Called when a g-code message is received from the engine.
# #
# \param message The protobuf message containing g-code, encoded as UTF-8. # \param message The protobuf message containing g-code, encoded as UTF-8.
def _onGCodeLayerMessage(self, message): def _onGCodeLayerMessage(self, message):
self._scene.gcode_list.append(message.data.decode("utf-8", "replace")) self._scene.gcode_list[self._start_slice_job_build_plate].append(message.data.decode("utf-8", "replace"))
## Called when a g-code prefix message is received from the engine. ## Called when a g-code prefix message is received from the engine.
# #
# \param message The protobuf message containing the g-code prefix, # \param message The protobuf message containing the g-code prefix,
# encoded as UTF-8. # encoded as UTF-8.
def _onGCodePrefixMessage(self, message): def _onGCodePrefixMessage(self, message):
self._scene.gcode_list.insert(0, message.data.decode("utf-8", "replace")) self._scene.gcode_list[self._start_slice_job_build_plate].insert(0, message.data.decode("utf-8", "replace"))
## Creates a new socket connection. ## Creates a new socket connection.
def _createSocket(self): def _createSocket(self):
@ -551,7 +625,7 @@ class CuraEngineBackend(QObject, Backend):
material_amounts.append(message.getRepeatedMessage("materialEstimates", index).material_amount) material_amounts.append(message.getRepeatedMessage("materialEstimates", index).material_amount)
times = self._parseMessagePrintTimes(message) times = self._parseMessagePrintTimes(message)
self.printDurationMessage.emit(times, material_amounts) self.printDurationMessage.emit(self._start_slice_job_build_plate, times, material_amounts)
## Called for parsing message to retrieve estimated time per feature ## Called for parsing message to retrieve estimated time per feature
# #
@ -605,19 +679,25 @@ class CuraEngineBackend(QObject, Backend):
source = self._postponed_scene_change_sources.pop(0) source = self._postponed_scene_change_sources.pop(0)
self._onSceneChanged(source) self._onSceneChanged(source)
def _startProcessSlicedLayersJob(self, build_plate_number):
self._process_layers_job = ProcessSlicedLayersJob.ProcessSlicedLayersJob(self._stored_optimized_layer_data[build_plate_number])
self._process_layers_job.setBuildPlate(build_plate_number)
self._process_layers_job.finished.connect(self._onProcessLayersFinished)
self._process_layers_job.start()
## Called when the user changes the active view mode. ## Called when the user changes the active view mode.
def _onActiveViewChanged(self): def _onActiveViewChanged(self):
if Application.getInstance().getController().getActiveView(): application = Application.getInstance()
view = Application.getInstance().getController().getActiveView() view = application.getController().getActiveView()
if view:
active_build_plate = application.getBuildPlateModel().activeBuildPlate
if view.getPluginId() == "SimulationView": # If switching to layer view, we should process the layers if that hasn't been done yet. if view.getPluginId() == "SimulationView": # If switching to layer view, we should process the layers if that hasn't been done yet.
self._layer_view_active = True self._layer_view_active = True
# There is data and we're not slicing at the moment # There is data and we're not slicing at the moment
# if we are slicing, there is no need to re-calculate the data as it will be invalid in a moment. # if we are slicing, there is no need to re-calculate the data as it will be invalid in a moment.
if self._stored_optimized_layer_data and not self._slicing: # TODO: what build plate I am slicing
self._process_layers_job = ProcessSlicedLayersJob.ProcessSlicedLayersJob(self._stored_optimized_layer_data) if active_build_plate in self._stored_optimized_layer_data and not self._slicing and not self._process_layers_job:
self._process_layers_job.finished.connect(self._onProcessLayersFinished) self._startProcessSlicedLayersJob(active_build_plate)
self._process_layers_job.start()
self._stored_optimized_layer_data = []
else: else:
self._layer_view_active = False self._layer_view_active = False
@ -653,7 +733,10 @@ class CuraEngineBackend(QObject, Backend):
self._onChanged() self._onChanged()
def _onProcessLayersFinished(self, job): def _onProcessLayersFinished(self, job):
del self._stored_optimized_layer_data[job.getBuildPlate()]
self._process_layers_job = None self._process_layers_job = None
Logger.log("d", "See if there is more to slice(2)...")
self._invokeSlice()
## Connect slice function to timer. ## Connect slice function to timer.
def enableTimer(self): def enableTimer(self):

View file

@ -4,7 +4,6 @@
import gc import gc
from UM.Job import Job from UM.Job import Job
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Scene.SceneNode import SceneNode from UM.Scene.SceneNode import SceneNode
from UM.Application import Application from UM.Application import Application
from UM.Mesh.MeshData import MeshData from UM.Mesh.MeshData import MeshData
@ -17,6 +16,7 @@ from UM.Logger import Logger
from UM.Math.Vector import Vector from UM.Math.Vector import Vector
from cura.Scene.BuildPlateDecorator import BuildPlateDecorator
from cura.Settings.ExtruderManager import ExtruderManager from cura.Settings.ExtruderManager import ExtruderManager
from cura import LayerDataBuilder from cura import LayerDataBuilder
from cura import LayerDataDecorator from cura import LayerDataDecorator
@ -49,6 +49,7 @@ class ProcessSlicedLayersJob(Job):
self._scene = Application.getInstance().getController().getScene() self._scene = Application.getInstance().getController().getScene()
self._progress_message = Message(catalog.i18nc("@info:status", "Processing Layers"), 0, False, -1) self._progress_message = Message(catalog.i18nc("@info:status", "Processing Layers"), 0, False, -1)
self._abort_requested = False self._abort_requested = False
self._build_plate_number = None
## Aborts the processing of layers. ## Aborts the processing of layers.
# #
@ -59,7 +60,14 @@ class ProcessSlicedLayersJob(Job):
def abort(self): def abort(self):
self._abort_requested = True self._abort_requested = True
def setBuildPlate(self, new_value):
self._build_plate_number = new_value
def getBuildPlate(self):
return self._build_plate_number
def run(self): def run(self):
Logger.log("d", "Processing new layer for build plate %s..." % self._build_plate_number)
start_time = time() start_time = time()
view = Application.getInstance().getController().getActiveView() view = Application.getInstance().getController().getActiveView()
if view.getPluginId() == "SimulationView": if view.getPluginId() == "SimulationView":
@ -74,16 +82,7 @@ class ProcessSlicedLayersJob(Job):
Application.getInstance().getController().activeViewChanged.connect(self._onActiveViewChanged) Application.getInstance().getController().activeViewChanged.connect(self._onActiveViewChanged)
new_node = SceneNode() new_node = SceneNode()
new_node.addDecorator(BuildPlateDecorator(self._build_plate_number))
## Remove old layer data (if any)
for node in DepthFirstIterator(self._scene.getRoot()):
if node.callDecoration("getLayerData"):
node.getParent().removeChild(node)
break
if self._abort_requested:
if self._progress_message:
self._progress_message.hide()
return
# Force garbage collection. # Force garbage collection.
# For some reason, Python has a tendency to keep the layer data # For some reason, Python has a tendency to keep the layer data

View file

@ -10,12 +10,12 @@ from UM.Job import Job
from UM.Application import Application from UM.Application import Application
from UM.Logger import Logger from UM.Logger import Logger
from UM.Scene.SceneNode import SceneNode
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Settings.Validator import ValidatorState from UM.Settings.Validator import ValidatorState
from UM.Settings.SettingRelation import RelationType from UM.Settings.SettingRelation import RelationType
from cura.Scene.CuraSceneNode import CuraSceneNode as SceneNode
from cura.OneAtATimeIterator import OneAtATimeIterator from cura.OneAtATimeIterator import OneAtATimeIterator
from cura.Settings.ExtruderManager import ExtruderManager from cura.Settings.ExtruderManager import ExtruderManager
@ -36,9 +36,32 @@ class StartJobResult(IntEnum):
## Formatter class that handles token expansion in start/end gcod ## Formatter class that handles token expansion in start/end gcod
class GcodeStartEndFormatter(Formatter): class GcodeStartEndFormatter(Formatter):
def get_value(self, key, args, kwargs): # [CodeStyle: get_value is an overridden function from the Formatter class] def get_value(self, key, args, kwargs): # [CodeStyle: get_value is an overridden function from the Formatter class]
# The kwargs dictionary contains a dictionary for each stack (with a string of the extruder_nr as their key),
# and a default_extruder_nr to use when no extruder_nr is specified
if isinstance(key, str): if isinstance(key, str):
try: try:
return kwargs[key] extruder_nr = kwargs["default_extruder_nr"]
except ValueError:
extruder_nr = -1
key_fragments = [fragment.strip() for fragment in key.split(',')]
if len(key_fragments) == 2:
try:
extruder_nr = int(key_fragments[1])
except ValueError:
try:
extruder_nr = int(kwargs["-1"][key_fragments[1]]) # get extruder_nr values from the global stack
except (KeyError, ValueError):
# either the key does not exist, or the value is not an int
Logger.log("w", "Unable to determine stack nr '%s' for key '%s' in start/end gcode, using global stack", key_fragments[1], key_fragments[0])
elif len(key_fragments) != 1:
Logger.log("w", "Incorrectly formatted placeholder '%s' in start/end gcode", key)
return "{" + str(key) + "}"
key = key_fragments[0]
try:
return kwargs[str(extruder_nr)][key]
except KeyError: except KeyError:
Logger.log("w", "Unable to replace '%s' placeholder in start/end gcode", key) Logger.log("w", "Unable to replace '%s' placeholder in start/end gcode", key)
return "{" + key + "}" return "{" + key + "}"
@ -55,10 +78,16 @@ class StartSliceJob(Job):
self._scene = Application.getInstance().getController().getScene() self._scene = Application.getInstance().getController().getScene()
self._slice_message = slice_message self._slice_message = slice_message
self._is_cancelled = False self._is_cancelled = False
self._build_plate_number = None
self._all_extruders_settings = None # cache for all setting values from all stacks (global & extruder) for the current machine
def getSliceMessage(self): def getSliceMessage(self):
return self._slice_message return self._slice_message
def setBuildPlate(self, build_plate_number):
self._build_plate_number = build_plate_number
## Check if a stack has any errors. ## Check if a stack has any errors.
## returns true if it has errors, false otherwise. ## returns true if it has errors, false otherwise.
def _checkStackForErrors(self, stack): def _checkStackForErrors(self, stack):
@ -75,6 +104,10 @@ class StartSliceJob(Job):
## Runs the job that initiates the slicing. ## Runs the job that initiates the slicing.
def run(self): def run(self):
if self._build_plate_number is None:
self.setResult(StartJobResult.Error)
return
stack = Application.getInstance().getGlobalContainerStack() stack = Application.getInstance().getGlobalContainerStack()
if not stack: if not stack:
self.setResult(StartJobResult.Error) self.setResult(StartJobResult.Error)
@ -108,7 +141,7 @@ class StartSliceJob(Job):
with self._scene.getSceneLock(): with self._scene.getSceneLock():
# Remove old layer data. # Remove old layer data.
for node in DepthFirstIterator(self._scene.getRoot()): for node in DepthFirstIterator(self._scene.getRoot()):
if node.callDecoration("getLayerData"): if node.callDecoration("getLayerData") and node.callDecoration("getBuildPlateNumber") == self._build_plate_number:
node.getParent().removeChild(node) node.getParent().removeChild(node)
break break
@ -143,10 +176,11 @@ class StartSliceJob(Job):
if per_object_stack: if per_object_stack:
is_non_printing_mesh = any(per_object_stack.getProperty(key, "value") for key in NON_PRINTING_MESH_SETTINGS) is_non_printing_mesh = any(per_object_stack.getProperty(key, "value") for key in NON_PRINTING_MESH_SETTINGS)
if not getattr(node, "_outside_buildarea", False) or is_non_printing_mesh: if (node.callDecoration("getBuildPlateNumber") == self._build_plate_number):
temp_list.append(node) if not getattr(node, "_outside_buildarea", False) or is_non_printing_mesh:
if not is_non_printing_mesh: temp_list.append(node)
has_printing_mesh = True if not is_non_printing_mesh:
has_printing_mesh = True
Job.yieldThread() Job.yieldThread()
@ -233,16 +267,33 @@ class StartSliceJob(Job):
result["date"] = time.strftime("%d-%m-%Y") result["date"] = time.strftime("%d-%m-%Y")
result["day"] = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"][int(time.strftime("%w"))] result["day"] = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"][int(time.strftime("%w"))]
initial_extruder_stack = Application.getInstance().getExtruderManager().getUsedExtruderStacks()[0]
initial_extruder_nr = initial_extruder_stack.getProperty("extruder_nr", "value")
result["initial_extruder_nr"] = initial_extruder_nr
return result return result
## Replace setting tokens in a piece of g-code. ## Replace setting tokens in a piece of g-code.
# \param value A piece of g-code to replace tokens in. # \param value A piece of g-code to replace tokens in.
# \param settings A dictionary of tokens to replace and their respective # \param default_extruder_nr Stack nr to use when no stack nr is specified, defaults to the global stack
# replacement strings. def _expandGcodeTokens(self, value: str, default_extruder_nr: int = -1):
def _expandGcodeTokens(self, value: str, settings: dict): if not self._all_extruders_settings:
global_stack = Application.getInstance().getGlobalContainerStack()
# NB: keys must be strings for the string formatter
self._all_extruders_settings = {
"-1": self._buildReplacementTokens(global_stack)
}
for extruder_stack in ExtruderManager.getInstance().getMachineExtruders(global_stack.getId()):
extruder_nr = extruder_stack.getProperty("extruder_nr", "value")
self._all_extruders_settings[str(extruder_nr)] = self._buildReplacementTokens(extruder_stack)
try: try:
# any setting can be used as a token # any setting can be used as a token
fmt = GcodeStartEndFormatter() fmt = GcodeStartEndFormatter()
settings = self._all_extruders_settings.copy()
settings["default_extruder_nr"] = default_extruder_nr
return str(fmt.format(value, **settings)) return str(fmt.format(value, **settings))
except: except:
Logger.logException("w", "Unable to do token replacement on start/end gcode") Logger.logException("w", "Unable to do token replacement on start/end gcode")
@ -259,8 +310,9 @@ class StartSliceJob(Job):
settings["material_guid"] = stack.material.getMetaDataEntry("GUID", "") settings["material_guid"] = stack.material.getMetaDataEntry("GUID", "")
# Replace the setting tokens in start and end g-code. # Replace the setting tokens in start and end g-code.
settings["machine_extruder_start_code"] = self._expandGcodeTokens(settings["machine_extruder_start_code"], settings) extruder_nr = stack.getProperty("extruder_nr", "value")
settings["machine_extruder_end_code"] = self._expandGcodeTokens(settings["machine_extruder_end_code"], settings) settings["machine_extruder_start_code"] = self._expandGcodeTokens(settings["machine_extruder_start_code"], extruder_nr)
settings["machine_extruder_end_code"] = self._expandGcodeTokens(settings["machine_extruder_end_code"], extruder_nr)
for key, value in settings.items(): for key, value in settings.items():
# Do not send settings that are not settable_per_extruder. # Do not send settings that are not settable_per_extruder.
@ -285,13 +337,13 @@ class StartSliceJob(Job):
print_temperature_settings = {"material_print_temperature", "material_print_temperature_layer_0", "default_material_print_temperature", "material_initial_print_temperature", "material_final_print_temperature", "material_standby_temperature"} print_temperature_settings = {"material_print_temperature", "material_print_temperature_layer_0", "default_material_print_temperature", "material_initial_print_temperature", "material_final_print_temperature", "material_standby_temperature"}
settings["material_print_temp_prepend"] = all(("{" + setting + "}" not in start_gcode for setting in print_temperature_settings)) settings["material_print_temp_prepend"] = all(("{" + setting + "}" not in start_gcode for setting in print_temperature_settings))
# Find the correct temperatures from the first used extruder
extruder_stack = Application.getInstance().getExtruderManager().getUsedExtruderStacks()[0]
extruder_0_settings = self._buildReplacementTokens(extruder_stack)
# Replace the setting tokens in start and end g-code. # Replace the setting tokens in start and end g-code.
settings["machine_start_gcode"] = self._expandGcodeTokens(settings["machine_start_gcode"], extruder_0_settings) # Use values from the first used extruder by default so we get the expected temperatures
settings["machine_end_gcode"] = self._expandGcodeTokens(settings["machine_end_gcode"], extruder_0_settings) initial_extruder_stack = Application.getInstance().getExtruderManager().getUsedExtruderStacks()[0]
initial_extruder_nr = initial_extruder_stack.getProperty("extruder_nr", "value")
settings["machine_start_gcode"] = self._expandGcodeTokens(settings["machine_start_gcode"], initial_extruder_nr)
settings["machine_end_gcode"] = self._expandGcodeTokens(settings["machine_end_gcode"], initial_extruder_nr)
# Add all sub-messages for each individual setting. # Add all sub-messages for each individual setting.
for key, value in settings.items(): for key, value in settings.items():

View file

@ -17,7 +17,7 @@ catalog = i18nCatalog("cura")
from cura import LayerDataBuilder from cura import LayerDataBuilder
from cura import LayerDataDecorator from cura import LayerDataDecorator
from cura.LayerPolygon import LayerPolygon from cura.LayerPolygon import LayerPolygon
from cura.GCodeListDecorator import GCodeListDecorator from cura.Scene.GCodeListDecorator import GCodeListDecorator
from cura.Settings.ExtruderManager import ExtruderManager from cura.Settings.ExtruderManager import ExtruderManager
import numpy import numpy

View file

@ -59,8 +59,9 @@ class GCodeWriter(MeshWriter):
Logger.log("e", "GCode Writer does not support non-text mode.") Logger.log("e", "GCode Writer does not support non-text mode.")
return False return False
active_build_plate = Application.getInstance().getBuildPlateModel().activeBuildPlate
scene = Application.getInstance().getController().getScene() scene = Application.getInstance().getController().getScene()
gcode_list = getattr(scene, "gcode_list") gcode_list = getattr(scene, "gcode_list")[active_build_plate]
if gcode_list: if gcode_list:
for gcode in gcode_list: for gcode in gcode_list:
stream.write(gcode) stream.write(gcode)

View file

@ -8,12 +8,13 @@ from PyQt5.QtCore import Qt
from UM.Mesh.MeshReader import MeshReader from UM.Mesh.MeshReader import MeshReader
from UM.Mesh.MeshBuilder import MeshBuilder from UM.Mesh.MeshBuilder import MeshBuilder
from UM.Scene.SceneNode import SceneNode
from UM.Math.Vector import Vector from UM.Math.Vector import Vector
from UM.Job import Job from UM.Job import Job
from UM.Logger import Logger from UM.Logger import Logger
from .ImageReaderUI import ImageReaderUI from .ImageReaderUI import ImageReaderUI
from cura.Scene.CuraSceneNode import CuraSceneNode as SceneNode
class ImageReader(MeshReader): class ImageReader(MeshReader):
def __init__(self): def __init__(self):

View file

@ -27,7 +27,9 @@ class MachineSettingsAction(MachineAction):
self._qml_url = "MachineSettingsAction.qml" self._qml_url = "MachineSettingsAction.qml"
self._global_container_stack = None self._global_container_stack = None
self._container_index = 0
from cura.Settings.CuraContainerStack import _ContainerIndexes
self._container_index = _ContainerIndexes.DefinitionChanges
self._container_registry = ContainerRegistry.getInstance() self._container_registry = ContainerRegistry.getInstance()
self._container_registry.containerAdded.connect(self._onContainerAdded) self._container_registry.containerAdded.connect(self._onContainerAdded)
@ -241,6 +243,7 @@ class MachineSettingsAction(MachineAction):
"type": "material", "type": "material",
"approximate_diameter": machine_approximate_diameter, "approximate_diameter": machine_approximate_diameter,
"material": old_material.getMetaDataEntry("material", "value"), "material": old_material.getMetaDataEntry("material", "value"),
"brand": old_material.getMetaDataEntry("brand", "value"),
"supplier": old_material.getMetaDataEntry("supplier", "value"), "supplier": old_material.getMetaDataEntry("supplier", "value"),
"color_name": old_material.getMetaDataEntry("color_name", "value"), "color_name": old_material.getMetaDataEntry("color_name", "value"),
"definition": materials_definition "definition": materials_definition
@ -251,6 +254,7 @@ class MachineSettingsAction(MachineAction):
if old_material == self._empty_container: if old_material == self._empty_container:
search_criteria.pop("material", None) search_criteria.pop("material", None)
search_criteria.pop("supplier", None) search_criteria.pop("supplier", None)
search_criteria.pop("brand", None)
search_criteria.pop("definition", None) search_criteria.pop("definition", None)
search_criteria["id"] = extruder_stack.getMetaDataEntry("preferred_material") search_criteria["id"] = extruder_stack.getMetaDataEntry("preferred_material")
@ -258,6 +262,7 @@ class MachineSettingsAction(MachineAction):
if not materials: if not materials:
# Same material with new diameter is not found, search for generic version of the same material type # Same material with new diameter is not found, search for generic version of the same material type
search_criteria.pop("supplier", None) search_criteria.pop("supplier", None)
search_criteria.pop("brand", None)
search_criteria["color_name"] = "Generic" search_criteria["color_name"] = "Generic"
materials = self._container_registry.findInstanceContainers(**search_criteria) materials = self._container_registry.findInstanceContainers(**search_criteria)
if not materials: if not materials:
@ -274,6 +279,6 @@ class MachineSettingsAction(MachineAction):
# Just use empty material as a final fallback # Just use empty material as a final fallback
materials = [self._empty_container] materials = [self._empty_container]
Logger.log("i", "Selecting new material: %s" % materials[0].getId()) Logger.log("i", "Selecting new material: %s", materials[0].getId())
extruder_stack.material = materials[0] extruder_stack.material = materials[0]

View file

@ -393,7 +393,7 @@ Cura.MachineAction
property string label: catalog.i18nc("@label", "Material diameter") property string label: catalog.i18nc("@label", "Material diameter")
property string unit: catalog.i18nc("@label", "mm") property string unit: catalog.i18nc("@label", "mm")
property string tooltip: catalog.i18nc("@tooltip", "The nominal diameter of filament supported by the printer. The exact diameter will be overridden by the material and/or the profile.") property string tooltip: catalog.i18nc("@tooltip", "The nominal diameter of filament supported by the printer. The exact diameter will be overridden by the material and/or the profile.")
property var afterOnEditingFinished: function afterOnEditingFinished()
{ {
if (settingsTabs.currentIndex > 0) if (settingsTabs.currentIndex > 0)
{ {

View file

@ -111,7 +111,7 @@ Item {
ScrollView ScrollView
{ {
height: parent.height height: parent.height
width: UM.Theme.getSize("setting").width width: UM.Theme.getSize("setting").width + UM.Theme.getSize("default_margin").width
style: UM.Theme.styles.scrollview style: UM.Theme.styles.scrollview
ListView ListView

View file

@ -93,6 +93,7 @@ class SimulationPass(RenderPass):
self.bind() self.bind()
tool_handle_batch = RenderBatch(self._tool_handle_shader, type = RenderBatch.RenderType.Overlay, backface_cull = True) tool_handle_batch = RenderBatch(self._tool_handle_shader, type = RenderBatch.RenderType.Overlay, backface_cull = True)
active_build_plate = Application.getInstance().getBuildPlateModel().activeBuildPlate
head_position = None # Indicates the current position of the print head head_position = None # Indicates the current position of the print head
nozzle_node = None nozzle_node = None
@ -105,7 +106,7 @@ class SimulationPass(RenderPass):
nozzle_node = node nozzle_node = node
nozzle_node.setVisible(False) nozzle_node.setVisible(False)
elif isinstance(node, SceneNode) and (node.getMeshData() or node.callDecoration("isBlockSlicing")) and node.isVisible(): elif issubclass(type(node), SceneNode) and (node.getMeshData() or node.callDecoration("isBlockSlicing")) and node.isVisible() and node.callDecoration("getBuildPlateNumber") == active_build_plate:
layer_data = node.callDecoration("getLayerData") layer_data = node.callDecoration("getLayerData")
if not layer_data: if not layer_data:
continue continue

View file

@ -25,7 +25,7 @@ from UM.View.GL.OpenGL import OpenGL
from UM.View.GL.OpenGLContext import OpenGLContext from UM.View.GL.OpenGLContext import OpenGLContext
from UM.View.View import View from UM.View.View import View
from UM.i18n import i18nCatalog from UM.i18n import i18nCatalog
from cura.ConvexHullNode import ConvexHullNode from cura.Scene.ConvexHullNode import ConvexHullNode
from cura.CuraApplication import CuraApplication from cura.CuraApplication import CuraApplication
from .NozzleNode import NozzleNode from .NozzleNode import NozzleNode

View file

@ -145,35 +145,42 @@ geometry41core =
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz + g_vertex_offset_vert)); myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz + g_vertex_offset_vert));
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz + g_vertex_offset_vert)); myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz + g_vertex_offset_vert));
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz_head + g_vertex_offset_vert)); myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz_head + g_vertex_offset_vert));
//And reverse so that the line is also visible from the back side.
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz + g_vertex_offset_vert));
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz + g_vertex_offset_vert));
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz + g_vertex_offset_vert));
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz + g_vertex_offset_vert));
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz_head + g_vertex_offset_vert));
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz + g_vertex_offset_vert));
EndPrimitive(); EndPrimitive();
} else { } else {
// All normal lines are rendered as 3d tubes. // All normal lines are rendered as 3d tubes.
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz));
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz));
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_vert));
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_vert));
myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz)); myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz));
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz)); myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz));
myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_vert)); myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_vert));
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_vert)); myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_vert));
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz)); myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz));
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz)); myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz));
myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_vert));
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_vert));
myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz));
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz));
EndPrimitive(); EndPrimitive();
// left side // left side
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz)); myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz));
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_vert)); myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_vert));
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz_head, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz_head)); myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz_head, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz_head));
myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz)); myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz));
EndPrimitive(); EndPrimitive();
myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz)); myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz));
myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_vert)); myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_vert, u_viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_vert));
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz_head, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz_head)); myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz_head, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz_head));
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz)); myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_horz, u_viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz));
EndPrimitive(); EndPrimitive();

View file

@ -166,7 +166,7 @@ class SliceInfo(Extension):
data["models"].append(model) data["models"].append(model)
print_times = print_information._print_time_message_values print_times = print_information.printTimes()
data["print_times"] = {"travel": int(print_times["travel"].getDisplayString(DurationFormat.Format.Seconds)), data["print_times"] = {"travel": int(print_times["travel"].getDisplayString(DurationFormat.Format.Seconds)),
"support": int(print_times["support"].getDisplayString(DurationFormat.Format.Seconds)), "support": int(print_times["support"].getDisplayString(DurationFormat.Format.Seconds)),
"infill": int(print_times["infill"].getDisplayString(DurationFormat.Format.Seconds)), "infill": int(print_times["infill"].getDisplayString(DurationFormat.Format.Seconds)),

View file

@ -11,7 +11,7 @@ from UM.Math.Matrix import Matrix
from UM.Math.Vector import Vector from UM.Math.Vector import Vector
from UM.Mesh.MeshBuilder import MeshBuilder from UM.Mesh.MeshBuilder import MeshBuilder
from UM.Mesh.MeshReader import MeshReader from UM.Mesh.MeshReader import MeshReader
from UM.Scene.SceneNode import SceneNode from cura.Scene.CuraSceneNode import CuraSceneNode as SceneNode
MYPY = False MYPY = False
try: try:
@ -19,63 +19,63 @@ try:
import xml.etree.cElementTree as ET import xml.etree.cElementTree as ET
except ImportError: except ImportError:
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
# TODO: preserve the structure of scenes that contain several objects # TODO: preserve the structure of scenes that contain several objects
# Use CADPart, for example, to distinguish between separate objects # Use CADPart, for example, to distinguish between separate objects
DEFAULT_SUBDIV = 16 # Default subdivision factor for spheres, cones, and cylinders DEFAULT_SUBDIV = 16 # Default subdivision factor for spheres, cones, and cylinders
EPSILON = 0.000001 EPSILON = 0.000001
class Shape: class Shape:
# Expects verts in MeshBuilder-ready format, as a n by 3 mdarray # Expects verts in MeshBuilder-ready format, as a n by 3 mdarray
# with vertices stored in rows # with vertices stored in rows
def __init__(self, verts, faces, index_base, name): def __init__(self, verts, faces, index_base, name):
self.verts = verts self.verts = verts
self.faces = faces self.faces = faces
# Those are here for debugging purposes only # Those are here for debugging purposes only
self.index_base = index_base self.index_base = index_base
self.name = name self.name = name
class X3DReader(MeshReader): class X3DReader(MeshReader):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self._supported_extensions = [".x3d"] self._supported_extensions = [".x3d"]
self._namespaces = {} self._namespaces = {}
# Main entry point # Main entry point
# Reads the file, returns a SceneNode (possibly with nested ones), or None # Reads the file, returns a SceneNode (possibly with nested ones), or None
def read(self, file_name): def read(self, file_name):
try: try:
self.defs = {} self.defs = {}
self.shapes = [] self.shapes = []
tree = ET.parse(file_name) tree = ET.parse(file_name)
xml_root = tree.getroot() xml_root = tree.getroot()
if xml_root.tag != "X3D": if xml_root.tag != "X3D":
return None return None
scale = 1000 # Default X3D unit it one meter, while Cura's is one millimeters scale = 1000 # Default X3D unit it one meter, while Cura's is one millimeters
if xml_root[0].tag == "head": if xml_root[0].tag == "head":
for head_node in xml_root[0]: for head_node in xml_root[0]:
if head_node.tag == "unit" and head_node.attrib.get("category") == "length": if head_node.tag == "unit" and head_node.attrib.get("category") == "length":
scale *= float(head_node.attrib["conversionFactor"]) scale *= float(head_node.attrib["conversionFactor"])
break break
xml_scene = xml_root[1] xml_scene = xml_root[1]
else: else:
xml_scene = xml_root[0] xml_scene = xml_root[0]
if xml_scene.tag != "Scene": if xml_scene.tag != "Scene":
return None return None
self.transform = Matrix() self.transform = Matrix()
self.transform.setByScaleFactor(scale) self.transform.setByScaleFactor(scale)
self.index_base = 0 self.index_base = 0
# Traverse the scene tree, populate the shapes list # Traverse the scene tree, populate the shapes list
self.processChildNodes(xml_scene) self.processChildNodes(xml_scene)
if self.shapes: if self.shapes:
builder = MeshBuilder() builder = MeshBuilder()
builder.setVertices(numpy.concatenate([shape.verts for shape in self.shapes])) builder.setVertices(numpy.concatenate([shape.verts for shape in self.shapes]))
@ -95,20 +95,20 @@ class X3DReader(MeshReader):
else: else:
return None return None
except Exception: except Exception:
Logger.logException("e", "Exception in X3D reader") Logger.logException("e", "Exception in X3D reader")
return None return None
return node return node
# ------------------------- XML tree traversal # ------------------------- XML tree traversal
def processNode(self, xml_node): def processNode(self, xml_node):
xml_node = self.resolveDefUse(xml_node) xml_node = self.resolveDefUse(xml_node)
if xml_node is None: if xml_node is None:
return return
tag = xml_node.tag tag = xml_node.tag
if tag in ("Group", "StaticGroup", "CADAssembly", "CADFace", "CADLayer", "Collision"): if tag in ("Group", "StaticGroup", "CADAssembly", "CADFace", "CADLayer", "Collision"):
self.processChildNodes(xml_node) self.processChildNodes(xml_node)
@ -120,8 +120,8 @@ class X3DReader(MeshReader):
self.processTransform(xml_node) self.processTransform(xml_node)
elif tag == "Shape": elif tag == "Shape":
self.processShape(xml_node) self.processShape(xml_node)
def processShape(self, xml_node): def processShape(self, xml_node):
# Find the geometry and the appearance inside the Shape # Find the geometry and the appearance inside the Shape
geometry = appearance = None geometry = appearance = None
@ -130,21 +130,21 @@ class X3DReader(MeshReader):
appearance = self.resolveDefUse(sub_node) appearance = self.resolveDefUse(sub_node)
elif sub_node.tag in self.geometry_importers and not geometry: elif sub_node.tag in self.geometry_importers and not geometry:
geometry = self.resolveDefUse(sub_node) geometry = self.resolveDefUse(sub_node)
# TODO: appearance is completely ignored. At least apply the material color... # TODO: appearance is completely ignored. At least apply the material color...
if not geometry is None: if not geometry is None:
try: try:
self.verts = self.faces = [] # Safeguard self.verts = self.faces = [] # Safeguard
self.geometry_importers[geometry.tag](self, geometry) self.geometry_importers[geometry.tag](self, geometry)
m = self.transform.getData() m = self.transform.getData()
verts = m.dot(self.verts)[:3].transpose() verts = m.dot(self.verts)[:3].transpose()
self.shapes.append(Shape(verts, self.faces, self.index_base, geometry.tag)) self.shapes.append(Shape(verts, self.faces, self.index_base, geometry.tag))
self.index_base += len(verts) self.index_base += len(verts)
except Exception: except Exception:
Logger.logException("e", "Exception in X3D reader while reading %s", geometry.tag) Logger.logException("e", "Exception in X3D reader while reading %s", geometry.tag)
# Returns the referenced node if the node has USE, the same node otherwise. # Returns the referenced node if the node has USE, the same node otherwise.
# May return None is USE points at a nonexistent node # May return None is USE points at a nonexistent node
# In X3DOM, when both DEF and USE are in the same node, DEF is ignored. # In X3DOM, when both DEF and USE are in the same node, DEF is ignored.
@ -155,34 +155,34 @@ class X3DReader(MeshReader):
if USE: if USE:
return self.defs.get(USE, None) return self.defs.get(USE, None)
DEF = node.attrib.get("DEF") DEF = node.attrib.get("DEF")
if DEF: if DEF:
self.defs[DEF] = node self.defs[DEF] = node
return node return node
def processChildNodes(self, node): def processChildNodes(self, node):
for c in node: for c in node:
self.processNode(c) self.processNode(c)
Job.yieldThread() Job.yieldThread()
# Since this is a grouping node, will recurse down the tree. # Since this is a grouping node, will recurse down the tree.
# According to the spec, the final transform matrix is: # According to the spec, the final transform matrix is:
# T * C * R * SR * S * -SR * -C # T * C * R * SR * S * -SR * -C
# Where SR corresponds to the rotation matrix to scaleOrientation # Where SR corresponds to the rotation matrix to scaleOrientation
# C and SR are rather exotic. S, slightly less so. # C and SR are rather exotic. S, slightly less so.
def processTransform(self, node): def processTransform(self, node):
rot = readRotation(node, "rotation", (0, 0, 1, 0)) # (angle, axisVactor) tuple rot = readRotation(node, "rotation", (0, 0, 1, 0)) # (angle, axisVactor) tuple
trans = readVector(node, "translation", (0, 0, 0)) # Vector trans = readVector(node, "translation", (0, 0, 0)) # Vector
scale = readVector(node, "scale", (1, 1, 1)) # Vector scale = readVector(node, "scale", (1, 1, 1)) # Vector
center = readVector(node, "center", (0, 0, 0)) # Vector center = readVector(node, "center", (0, 0, 0)) # Vector
scale_orient = readRotation(node, "scaleOrientation", (0, 0, 1, 0)) # (angle, axisVactor) tuple scale_orient = readRotation(node, "scaleOrientation", (0, 0, 1, 0)) # (angle, axisVactor) tuple
# Store the previous transform; in Cura, the default matrix multiplication is in place # Store the previous transform; in Cura, the default matrix multiplication is in place
prev = Matrix(self.transform.getData()) # It's deep copy, I've checked prev = Matrix(self.transform.getData()) # It's deep copy, I've checked
# The rest of transform manipulation will be applied in place # The rest of transform manipulation will be applied in place
got_center = (center.x != 0 or center.y != 0 or center.z != 0) got_center = (center.x != 0 or center.y != 0 or center.z != 0)
T = self.transform T = self.transform
if trans.x != 0 or trans.y != 0 or trans.z != 0: if trans.x != 0 or trans.y != 0 or trans.z != 0:
T.translate(trans) T.translate(trans)
@ -202,13 +202,13 @@ class X3DReader(MeshReader):
T.rotateByAxis(-scale_orient[0], scale_orient[1]) T.rotateByAxis(-scale_orient[0], scale_orient[1])
if got_center: if got_center:
T.translate(-center) T.translate(-center)
self.processChildNodes(node) self.processChildNodes(node)
self.transform = prev self.transform = prev
# ------------------------- Geometry importers # ------------------------- Geometry importers
# They are supposed to fill the self.verts and self.faces arrays, the caller will do the rest # They are supposed to fill the self.verts and self.faces arrays, the caller will do the rest
# Primitives # Primitives
def processGeometryBox(self, node): def processGeometryBox(self, node):
@ -228,14 +228,14 @@ class X3DReader(MeshReader):
self.addVertex(-dx, -dy, dz) self.addVertex(-dx, -dy, dz)
self.addVertex(-dx, -dy, -dz) self.addVertex(-dx, -dy, -dz)
self.addVertex(dx, -dy, -dz) self.addVertex(dx, -dy, -dz)
self.addQuad(0, 1, 2, 3) # +y self.addQuad(0, 1, 2, 3) # +y
self.addQuad(4, 0, 3, 7) # +x self.addQuad(4, 0, 3, 7) # +x
self.addQuad(7, 3, 2, 6) # -z self.addQuad(7, 3, 2, 6) # -z
self.addQuad(6, 2, 1, 5) # -x self.addQuad(6, 2, 1, 5) # -x
self.addQuad(5, 1, 0, 4) # +z self.addQuad(5, 1, 0, 4) # +z
self.addQuad(7, 6, 5, 4) # -y self.addQuad(7, 6, 5, 4) # -y
# The sphere is subdivided into nr rings and ns segments # The sphere is subdivided into nr rings and ns segments
def processGeometrySphere(self, node): def processGeometrySphere(self, node):
r = readFloat(node, "radius", 0.5) r = readFloat(node, "radius", 0.5)
@ -247,16 +247,16 @@ class X3DReader(MeshReader):
(nr, ns) = subdiv (nr, ns) = subdiv
else: else:
nr = ns = DEFAULT_SUBDIV nr = ns = DEFAULT_SUBDIV
lau = pi / nr # Unit angle of latitude (rings) for the given tesselation lau = pi / nr # Unit angle of latitude (rings) for the given tesselation
lou = 2 * pi / ns # Unit angle of longitude (segments) lou = 2 * pi / ns # Unit angle of longitude (segments)
self.reserveFaceAndVertexCount(ns*(nr*2 - 2), 2 + (nr - 1)*ns) self.reserveFaceAndVertexCount(ns*(nr*2 - 2), 2 + (nr - 1)*ns)
# +y and -y poles # +y and -y poles
self.addVertex(0, r, 0) self.addVertex(0, r, 0)
self.addVertex(0, -r, 0) self.addVertex(0, -r, 0)
# The non-polar vertices go from x=0, negative z plane counterclockwise - # The non-polar vertices go from x=0, negative z plane counterclockwise -
# to -x, to +z, to +x, back to -z # to -x, to +z, to +x, back to -z
for ring in range(1, nr): for ring in range(1, nr):
@ -264,12 +264,12 @@ class X3DReader(MeshReader):
self.addVertex(-r*sin(lou * seg) * sin(lau * ring), self.addVertex(-r*sin(lou * seg) * sin(lau * ring),
r*cos(lau * ring), r*cos(lau * ring),
-r*cos(lou * seg) * sin(lau * ring)) -r*cos(lou * seg) * sin(lau * ring))
vb = 2 + (nr - 2) * ns # First vertex index for the bottom cap vb = 2 + (nr - 2) * ns # First vertex index for the bottom cap
# Faces go in order: top cap, sides, bottom cap. # Faces go in order: top cap, sides, bottom cap.
# Sides go by ring then by segment. # Sides go by ring then by segment.
# Caps # Caps
# Top cap face vertices go in order: down right up # Top cap face vertices go in order: down right up
# (starting from +y pole) # (starting from +y pole)
@ -277,7 +277,7 @@ class X3DReader(MeshReader):
for seg in range(ns): for seg in range(ns):
self.addTri(0, seg + 2, (seg + 1) % ns + 2) self.addTri(0, seg + 2, (seg + 1) % ns + 2)
self.addTri(1, vb + (seg + 1) % ns, vb + seg) self.addTri(1, vb + (seg + 1) % ns, vb + seg)
# Sides # Sides
# Side face vertices go in order: down right upleft, downright up left # Side face vertices go in order: down right upleft, downright up left
for ring in range(nr - 2): for ring in range(nr - 2):
@ -288,24 +288,24 @@ class X3DReader(MeshReader):
for seg in range(ns): for seg in range(ns):
nseg = (seg + 1) % ns nseg = (seg + 1) % ns
self.addQuad(tvb + seg, bvb + seg, bvb + nseg, tvb + nseg) self.addQuad(tvb + seg, bvb + seg, bvb + nseg, tvb + nseg)
def processGeometryCone(self, node): def processGeometryCone(self, node):
r = readFloat(node, "bottomRadius", 1) r = readFloat(node, "bottomRadius", 1)
height = readFloat(node, "height", 2) height = readFloat(node, "height", 2)
bottom = readBoolean(node, "bottom", True) bottom = readBoolean(node, "bottom", True)
side = readBoolean(node, "side", True) side = readBoolean(node, "side", True)
n = readInt(node, "subdivision", DEFAULT_SUBDIV) n = readInt(node, "subdivision", DEFAULT_SUBDIV)
d = height / 2 d = height / 2
angle = 2 * pi / n angle = 2 * pi / n
self.reserveFaceAndVertexCount((n if side else 0) + (n-2 if bottom else 0), n+1) self.reserveFaceAndVertexCount((n if side else 0) + (n-2 if bottom else 0), n+1)
# Vertex 0 is the apex, vertices 1..n are the bottom # Vertex 0 is the apex, vertices 1..n are the bottom
self.addVertex(0, d, 0) self.addVertex(0, d, 0)
for i in range(n): for i in range(n):
self.addVertex(-r * sin(angle * i), -d, -r * cos(angle * i)) self.addVertex(-r * sin(angle * i), -d, -r * cos(angle * i))
# Side face vertices go: up down right # Side face vertices go: up down right
if side: if side:
for i in range(n): for i in range(n):
@ -313,7 +313,7 @@ class X3DReader(MeshReader):
if bottom: if bottom:
for i in range(2, n): for i in range(2, n):
self.addTri(1, i, i+1) self.addTri(1, i, i+1)
def processGeometryCylinder(self, node): def processGeometryCylinder(self, node):
r = readFloat(node, "radius", 1) r = readFloat(node, "radius", 1)
height = readFloat(node, "height", 2) height = readFloat(node, "height", 2)
@ -321,13 +321,13 @@ class X3DReader(MeshReader):
side = readBoolean(node, "side", True) side = readBoolean(node, "side", True)
top = readBoolean(node, "top", True) top = readBoolean(node, "top", True)
n = readInt(node, "subdivision", DEFAULT_SUBDIV) n = readInt(node, "subdivision", DEFAULT_SUBDIV)
nn = n * 2 nn = n * 2
angle = 2 * pi / n angle = 2 * pi / n
hh = height/2 hh = height/2
self.reserveFaceAndVertexCount((nn if side else 0) + (n - 2 if top else 0) + (n - 2 if bottom else 0), nn) self.reserveFaceAndVertexCount((nn if side else 0) + (n - 2 if top else 0) + (n - 2 if bottom else 0), nn)
# The seam is at x=0, z=-r, vertices go ccw - # The seam is at x=0, z=-r, vertices go ccw -
# to pos x, to neg z, to neg x, back to neg z # to pos x, to neg z, to neg x, back to neg z
for i in range(n): for i in range(n):
@ -335,18 +335,18 @@ class X3DReader(MeshReader):
rc = -r * cos(angle * i) rc = -r * cos(angle * i)
self.addVertex(rs, hh, rc) self.addVertex(rs, hh, rc)
self.addVertex(rs, -hh, rc) self.addVertex(rs, -hh, rc)
if side: if side:
for i in range(n): for i in range(n):
ni = (i + 1) % n ni = (i + 1) % n
self.addQuad(ni * 2 + 1, ni * 2, i * 2, i * 2 + 1) self.addQuad(ni * 2 + 1, ni * 2, i * 2, i * 2 + 1)
for i in range(2, nn-3, 2): for i in range(2, nn-3, 2):
if top: if top:
self.addTri(0, i, i+2) self.addTri(0, i, i+2)
if bottom: if bottom:
self.addTri(1, i+1, i+3) self.addTri(1, i+1, i+3)
# Semi-primitives # Semi-primitives
def processGeometryElevationGrid(self, node): def processGeometryElevationGrid(self, node):
@ -356,21 +356,21 @@ class X3DReader(MeshReader):
nz = readInt(node, "zDimension", 0) nz = readInt(node, "zDimension", 0)
height = readFloatArray(node, "height", False) height = readFloatArray(node, "height", False)
ccw = readBoolean(node, "ccw", True) ccw = readBoolean(node, "ccw", True)
if nx <= 0 or nz <= 0 or len(height) < nx*nz: if nx <= 0 or nz <= 0 or len(height) < nx*nz:
return # That's weird, the wording of the standard suggests grids with zero quads are somehow valid return # That's weird, the wording of the standard suggests grids with zero quads are somehow valid
self.reserveFaceAndVertexCount(2*(nx-1)*(nz-1), nx*nz) self.reserveFaceAndVertexCount(2*(nx-1)*(nz-1), nx*nz)
for z in range(nz): for z in range(nz):
for x in range(nx): for x in range(nx):
self.addVertex(x * dx, height[z*nx + x], z * dz) self.addVertex(x * dx, height[z*nx + x], z * dz)
for z in range(1, nz): for z in range(1, nz):
for x in range(1, nx): for x in range(1, nx):
self.addTriFlip((z - 1)*nx + x - 1, z*nx + x, (z - 1)*nx + x, ccw) self.addTriFlip((z - 1)*nx + x - 1, z*nx + x, (z - 1)*nx + x, ccw)
self.addTriFlip((z - 1)*nx + x - 1, z*nx + x - 1, z*nx + x, ccw) self.addTriFlip((z - 1)*nx + x - 1, z*nx + x - 1, z*nx + x, ccw)
def processGeometryExtrusion(self, node): def processGeometryExtrusion(self, node):
ccw = readBoolean(node, "ccw", True) ccw = readBoolean(node, "ccw", True)
begin_cap = readBoolean(node, "beginCap", True) begin_cap = readBoolean(node, "beginCap", True)
@ -384,23 +384,23 @@ class X3DReader(MeshReader):
# This converts X3D's axis/angle rotation to a 3x3 numpy matrix # This converts X3D's axis/angle rotation to a 3x3 numpy matrix
def toRotationMatrix(rot): def toRotationMatrix(rot):
(x, y, z) = rot[:3] (x, y, z) = rot[:3]
a = rot[3] a = rot[3]
s = sin(a) s = sin(a)
c = cos(a) c = cos(a)
t = 1-c t = 1-c
return numpy.array(( return numpy.array((
(x * x * t + c, x * y * t - z*s, x * z * t + y * s), (x * x * t + c, x * y * t - z*s, x * z * t + y * s),
(x * y * t + z*s, y * y * t + c, y * z * t - x * s), (x * y * t + z*s, y * y * t + c, y * z * t - x * s),
(x * z * t - y * s, y * z * t + x * s, z * z * t + c))) (x * z * t - y * s, y * z * t + x * s, z * z * t + c)))
orient = [toRotationMatrix(orient[i:i+4]) if orient[i+3] != 0 else None for i in range(0, len(orient), 4)] orient = [toRotationMatrix(orient[i:i+4]) if orient[i+3] != 0 else None for i in range(0, len(orient), 4)]
scale = readFloatArray(node, "scale", None) scale = readFloatArray(node, "scale", None)
if scale: if scale:
scale = [numpy.array(((scale[i], 0, 0), (0, 1, 0), (0, 0, scale[i+1]))) scale = [numpy.array(((scale[i], 0, 0), (0, 1, 0), (0, 0, scale[i+1])))
if scale[i] != 1 or scale[i+1] != 1 else None for i in range(0, len(scale), 2)] if scale[i] != 1 or scale[i+1] != 1 else None for i in range(0, len(scale), 2)]
# Special treatment for the closed spine and cross section. # Special treatment for the closed spine and cross section.
# Let's save some memory by not creating identical but distinct vertices; # Let's save some memory by not creating identical but distinct vertices;
# later we'll introduce conditional logic to link the last vertex with # later we'll introduce conditional logic to link the last vertex with
@ -413,14 +413,14 @@ class X3DReader(MeshReader):
ncf = nc if crossClosed else nc - 1 ncf = nc if crossClosed else nc - 1
# Face count along the cross; for closed cross, it's the same as the # Face count along the cross; for closed cross, it's the same as the
# respective vertex count # respective vertex count
spine_closed = spine[0] == spine[-1] spine_closed = spine[0] == spine[-1]
if spine_closed: if spine_closed:
spine = spine[:-1] spine = spine[:-1]
ns = len(spine) ns = len(spine)
spine = [Vector(*s) for s in spine] spine = [Vector(*s) for s in spine]
nsf = ns if spine_closed else ns - 1 nsf = ns if spine_closed else ns - 1
# This will be used for fallback, where the current spine point joins # This will be used for fallback, where the current spine point joins
# two collinear spine segments. No need to recheck the case of the # two collinear spine segments. No need to recheck the case of the
# closed spine/last-to-first point juncture; if there's an angle there, # closed spine/last-to-first point juncture; if there's an angle there,
@ -442,7 +442,7 @@ class X3DReader(MeshReader):
if v.cross(orig_y).length() > EPSILON: if v.cross(orig_y).length() > EPSILON:
# Spine at angle with global y - rotate the z accordingly # Spine at angle with global y - rotate the z accordingly
a = v.cross(orig_y) # Axis of rotation to get to the Z a = v.cross(orig_y) # Axis of rotation to get to the Z
(x, y, z) = a.normalized().getData() (x, y, z) = a.normalized().getData()
s = a.length()/v.length() s = a.length()/v.length()
c = sqrt(1-s*s) c = sqrt(1-s*s)
t = 1-c t = 1-c
@ -452,7 +452,7 @@ class X3DReader(MeshReader):
(x * z * t + y * s, y * z * t - x * s, z * z * t + c))) (x * z * t + y * s, y * z * t - x * s, z * z * t + c)))
orig_z = Vector(*m.dot(orig_z.getData())) orig_z = Vector(*m.dot(orig_z.getData()))
return orig_z return orig_z
self.reserveFaceAndVertexCount(2*nsf*ncf + (nc - 2 if begin_cap else 0) + (nc - 2 if end_cap else 0), ns*nc) self.reserveFaceAndVertexCount(2*nsf*ncf + (nc - 2 if begin_cap else 0) + (nc - 2 if end_cap else 0), ns*nc)
z = None z = None
@ -482,151 +482,151 @@ class X3DReader(MeshReader):
y = spt - sprev y = spt - sprev
# If there's more than one point in the spine, z is already set. # If there's more than one point in the spine, z is already set.
# One point in the spline is an error anyway. # One point in the spline is an error anyway.
z = z.normalized() z = z.normalized()
y = y.normalized() y = y.normalized()
x = y.cross(z) # Already normalized x = y.cross(z) # Already normalized
m = numpy.array(((x.x, y.x, z.x), (x.y, y.y, z.y), (x.z, y.z, z.z))) m = numpy.array(((x.x, y.x, z.x), (x.y, y.y, z.y), (x.z, y.z, z.z)))
# Columns are the unit vectors for the xz plane for the cross-section # Columns are the unit vectors for the xz plane for the cross-section
if orient: if orient:
mrot = orient[i] if len(orient) > 1 else orient[0] mrot = orient[i] if len(orient) > 1 else orient[0]
if not mrot is None: if not mrot is None:
m = m.dot(mrot) # Tested against X3DOM, the result matches, still not sure :( m = m.dot(mrot) # Tested against X3DOM, the result matches, still not sure :(
if scale: if scale:
mscale = scale[i] if len(scale) > 1 else scale[0] mscale = scale[i] if len(scale) > 1 else scale[0]
if not mscale is None: if not mscale is None:
m = m.dot(mscale) m = m.dot(mscale)
# First the cross-section 2-vector is scaled, # First the cross-section 2-vector is scaled,
# then rotated (which may make it a 3-vector), # then rotated (which may make it a 3-vector),
# then applied to the xz plane unit vectors # then applied to the xz plane unit vectors
sptv3 = numpy.array(spt.getData()[:3]) sptv3 = numpy.array(spt.getData()[:3])
for cpt in cross: for cpt in cross:
v = sptv3 + m.dot(cpt) v = sptv3 + m.dot(cpt)
self.addVertex(*v) self.addVertex(*v)
if begin_cap: if begin_cap:
self.addFace([x for x in range(nc - 1, -1, -1)], ccw) self.addFace([x for x in range(nc - 1, -1, -1)], ccw)
# Order of edges in the face: forward along cross, forward along spine, # Order of edges in the face: forward along cross, forward along spine,
# backward along cross, backward along spine, flipped if now ccw. # backward along cross, backward along spine, flipped if now ccw.
# This order is assumed later in the texture coordinate assignment; # This order is assumed later in the texture coordinate assignment;
# please don't change without syncing. # please don't change without syncing.
for s in range(ns - 1): for s in range(ns - 1):
for c in range(ncf): for c in range(ncf):
self.addQuadFlip(s * nc + c, s * nc + (c + 1) % nc, self.addQuadFlip(s * nc + c, s * nc + (c + 1) % nc,
(s + 1) * nc + (c + 1) % nc, (s + 1) * nc + c, ccw) (s + 1) * nc + (c + 1) % nc, (s + 1) * nc + c, ccw)
if spine_closed: if spine_closed:
# The faces between the last and the first spine points # The faces between the last and the first spine points
b = (ns - 1) * nc b = (ns - 1) * nc
for c in range(ncf): for c in range(ncf):
self.addQuadFlip(b + c, b + (c + 1) % nc, self.addQuadFlip(b + c, b + (c + 1) % nc,
(c + 1) % nc, c, ccw) (c + 1) % nc, c, ccw)
if end_cap: if end_cap:
self.addFace([(ns - 1) * nc + x for x in range(0, nc)], ccw) self.addFace([(ns - 1) * nc + x for x in range(0, nc)], ccw)
# Triangle meshes # Triangle meshes
# Helper for numerous nodes with a Coordinate subnode holding vertices # Helper for numerous nodes with a Coordinate subnode holding vertices
# That all triangle meshes and IndexedFaceSet # That all triangle meshes and IndexedFaceSet
# num_faces can be a function, in case the face count is a function of vertex count # num_faces can be a function, in case the face count is a function of vertex count
def startCoordMesh(self, node, num_faces): def startCoordMesh(self, node, num_faces):
ccw = readBoolean(node, "ccw", True) ccw = readBoolean(node, "ccw", True)
self.readVertices(node) # This will allocate and fill the vertex array self.readVertices(node) # This will allocate and fill the vertex array
if hasattr(num_faces, "__call__"): if hasattr(num_faces, "__call__"):
num_faces = num_faces(self.getVertexCount()) num_faces = num_faces(self.getVertexCount())
self.reserveFaceCount(num_faces) self.reserveFaceCount(num_faces)
return ccw return ccw
def processGeometryIndexedTriangleSet(self, node): def processGeometryIndexedTriangleSet(self, node):
index = readIntArray(node, "index", []) index = readIntArray(node, "index", [])
num_faces = len(index) // 3 num_faces = len(index) // 3
ccw = int(self.startCoordMesh(node, num_faces)) ccw = int(self.startCoordMesh(node, num_faces))
for i in range(0, num_faces*3, 3): for i in range(0, num_faces*3, 3):
self.addTri(index[i + 1 - ccw], index[i + ccw], index[i+2]) self.addTri(index[i + 1 - ccw], index[i + ccw], index[i+2])
def processGeometryIndexedTriangleStripSet(self, node): def processGeometryIndexedTriangleStripSet(self, node):
strips = readIndex(node, "index") strips = readIndex(node, "index")
ccw = int(self.startCoordMesh(node, sum([len(strip) - 2 for strip in strips]))) ccw = int(self.startCoordMesh(node, sum([len(strip) - 2 for strip in strips])))
for strip in strips: for strip in strips:
sccw = ccw # Running CCW value, reset for each strip sccw = ccw # Running CCW value, reset for each strip
for i in range(len(strip) - 2): for i in range(len(strip) - 2):
self.addTri(strip[i + 1 - sccw], strip[i + sccw], strip[i+2]) self.addTri(strip[i + 1 - sccw], strip[i + sccw], strip[i+2])
sccw = 1 - sccw sccw = 1 - sccw
def processGeometryIndexedTriangleFanSet(self, node): def processGeometryIndexedTriangleFanSet(self, node):
fans = readIndex(node, "index") fans = readIndex(node, "index")
ccw = int(self.startCoordMesh(node, sum([len(fan) - 2 for fan in fans]))) ccw = int(self.startCoordMesh(node, sum([len(fan) - 2 for fan in fans])))
for fan in fans: for fan in fans:
for i in range(1, len(fan) - 1): for i in range(1, len(fan) - 1):
self.addTri(fan[0], fan[i + 1 - ccw], fan[i + ccw]) self.addTri(fan[0], fan[i + 1 - ccw], fan[i + ccw])
def processGeometryTriangleSet(self, node): def processGeometryTriangleSet(self, node):
ccw = int(self.startCoordMesh(node, lambda num_vert: num_vert // 3)) ccw = int(self.startCoordMesh(node, lambda num_vert: num_vert // 3))
for i in range(0, self.getVertexCount(), 3): for i in range(0, self.getVertexCount(), 3):
self.addTri(i + 1 - ccw, i + ccw, i+2) self.addTri(i + 1 - ccw, i + ccw, i+2)
def processGeometryTriangleStripSet(self, node): def processGeometryTriangleStripSet(self, node):
strips = readIntArray(node, "stripCount", []) strips = readIntArray(node, "stripCount", [])
ccw = int(self.startCoordMesh(node, sum([n-2 for n in strips]))) ccw = int(self.startCoordMesh(node, sum([n-2 for n in strips])))
vb = 0 vb = 0
for n in strips: for n in strips:
sccw = ccw sccw = ccw
for i in range(n-2): for i in range(n-2):
self.addTri(vb + i + 1 - sccw, vb + i + sccw, vb + i + 2) self.addTri(vb + i + 1 - sccw, vb + i + sccw, vb + i + 2)
sccw = 1 - sccw sccw = 1 - sccw
vb += n vb += n
def processGeometryTriangleFanSet(self, node): def processGeometryTriangleFanSet(self, node):
fans = readIntArray(node, "fanCount", []) fans = readIntArray(node, "fanCount", [])
ccw = int(self.startCoordMesh(node, sum([n-2 for n in fans]))) ccw = int(self.startCoordMesh(node, sum([n-2 for n in fans])))
vb = 0 vb = 0
for n in fans: for n in fans:
for i in range(1, n-1): for i in range(1, n-1):
self.addTri(vb, vb + i + 1 - ccw, vb + i + ccw) self.addTri(vb, vb + i + 1 - ccw, vb + i + ccw)
vb += n vb += n
# Quad geometries from the CAD module, might be relevant for printing # Quad geometries from the CAD module, might be relevant for printing
def processGeometryQuadSet(self, node): def processGeometryQuadSet(self, node):
ccw = self.startCoordMesh(node, lambda num_vert: 2*(num_vert // 4)) ccw = self.startCoordMesh(node, lambda num_vert: 2*(num_vert // 4))
for i in range(0, self.getVertexCount(), 4): for i in range(0, self.getVertexCount(), 4):
self.addQuadFlip(i, i+1, i+2, i+3, ccw) self.addQuadFlip(i, i+1, i+2, i+3, ccw)
def processGeometryIndexedQuadSet(self, node): def processGeometryIndexedQuadSet(self, node):
index = readIntArray(node, "index", []) index = readIntArray(node, "index", [])
num_quads = len(index) // 4 num_quads = len(index) // 4
ccw = self.startCoordMesh(node, num_quads*2) ccw = self.startCoordMesh(node, num_quads*2)
for i in range(0, num_quads*4, 4): for i in range(0, num_quads*4, 4):
self.addQuadFlip(index[i], index[i+1], index[i+2], index[i+3], ccw) self.addQuadFlip(index[i], index[i+1], index[i+2], index[i+3], ccw)
# 2D polygon geometries # 2D polygon geometries
# Won't work for now, since Cura expects every mesh to have a nontrivial convex hull # Won't work for now, since Cura expects every mesh to have a nontrivial convex hull
# The only way around that is merging meshes. # The only way around that is merging meshes.
def processGeometryDisk2D(self, node): def processGeometryDisk2D(self, node):
innerRadius = readFloat(node, "innerRadius", 0) innerRadius = readFloat(node, "innerRadius", 0)
outerRadius = readFloat(node, "outerRadius", 1) outerRadius = readFloat(node, "outerRadius", 1)
n = readInt(node, "subdivision", DEFAULT_SUBDIV) n = readInt(node, "subdivision", DEFAULT_SUBDIV)
angle = 2 * pi / n angle = 2 * pi / n
self.reserveFaceAndVertexCount(n*4 if innerRadius else n-2, n*2 if innerRadius else n) self.reserveFaceAndVertexCount(n*4 if innerRadius else n-2, n*2 if innerRadius else n)
for i in range(n): for i in range(n):
s = sin(angle * i) s = sin(angle * i)
c = cos(angle * i) c = cos(angle * i)
@ -635,11 +635,11 @@ class X3DReader(MeshReader):
self.addVertex(innerRadius*c, innerRadius*s, 0) self.addVertex(innerRadius*c, innerRadius*s, 0)
ni = (i+1) % n ni = (i+1) % n
self.addQuad(2*i, 2*ni, 2*ni+1, 2*i+1) self.addQuad(2*i, 2*ni, 2*ni+1, 2*i+1)
if not innerRadius: if not innerRadius:
for i in range(2, n): for i in range(2, n):
self.addTri(0, i-1, i) self.addTri(0, i-1, i)
def processGeometryRectangle2D(self, node): def processGeometryRectangle2D(self, node):
(x, y) = readFloatArray(node, "size", (2, 2)) (x, y) = readFloatArray(node, "size", (2, 2))
self.reserveFaceAndVertexCount(2, 4) self.reserveFaceAndVertexCount(2, 4)
@ -648,7 +648,7 @@ class X3DReader(MeshReader):
self.addVertex(x/2, y/2, 0) self.addVertex(x/2, y/2, 0)
self.addVertex(-x/2, y/2, 0) self.addVertex(-x/2, y/2, 0)
self.addQuad(0, 1, 2, 3) self.addQuad(0, 1, 2, 3)
def processGeometryTriangleSet2D(self, node): def processGeometryTriangleSet2D(self, node):
verts = readFloatArray(node, "vertices", ()) verts = readFloatArray(node, "vertices", ())
num_faces = len(verts) // 6; num_faces = len(verts) // 6;
@ -656,25 +656,25 @@ class X3DReader(MeshReader):
self.reserveFaceAndVertexCount(num_faces, num_faces * 3) self.reserveFaceAndVertexCount(num_faces, num_faces * 3)
for vert in verts: for vert in verts:
self.addVertex(*vert) self.addVertex(*vert)
# The front face is on the +Z side, so CCW is a variable # The front face is on the +Z side, so CCW is a variable
for i in range(0, num_faces*3, 3): for i in range(0, num_faces*3, 3):
a = Vector(*verts[i+2]) - Vector(*verts[i]) a = Vector(*verts[i+2]) - Vector(*verts[i])
b = Vector(*verts[i+1]) - Vector(*verts[i]) b = Vector(*verts[i+1]) - Vector(*verts[i])
self.addTriFlip(i, i+1, i+2, a.x*b.y > a.y*b.x) self.addTriFlip(i, i+1, i+2, a.x*b.y > a.y*b.x)
# General purpose polygon mesh # General purpose polygon mesh
def processGeometryIndexedFaceSet(self, node): def processGeometryIndexedFaceSet(self, node):
faces = readIndex(node, "coordIndex") faces = readIndex(node, "coordIndex")
ccw = self.startCoordMesh(node, sum([len(face) - 2 for face in faces])) ccw = self.startCoordMesh(node, sum([len(face) - 2 for face in faces]))
for face in faces: for face in faces:
if len(face) == 3: if len(face) == 3:
self.addTriFlip(face[0], face[1], face[2], ccw) self.addTriFlip(face[0], face[1], face[2], ccw)
elif len(face) > 3: elif len(face) > 3:
self.addFace(face, ccw) self.addFace(face, ccw)
geometry_importers = { geometry_importers = {
"IndexedFaceSet": processGeometryIndexedFaceSet, "IndexedFaceSet": processGeometryIndexedFaceSet,
"IndexedTriangleSet": processGeometryIndexedTriangleSet, "IndexedTriangleSet": processGeometryIndexedTriangleSet,
@ -695,7 +695,7 @@ class X3DReader(MeshReader):
"Cylinder": processGeometryCylinder, "Cylinder": processGeometryCylinder,
"Cone": processGeometryCone "Cone": processGeometryCone
} }
# Parses the Coordinate.@point field, fills the verts array. # Parses the Coordinate.@point field, fills the verts array.
def readVertices(self, node): def readVertices(self, node):
for c in node: for c in node:
@ -704,9 +704,9 @@ class X3DReader(MeshReader):
if not c is None: if not c is None:
pt = c.attrib.get("point") pt = c.attrib.get("point")
if pt: if pt:
# allow the list of float values in 'point' attribute to # allow the list of float values in 'point' attribute to
# be separated by commas or whitespace as per spec of # be separated by commas or whitespace as per spec of
# XML encoding of X3D # XML encoding of X3D
# Ref ISO/IEC 19776-1:2015 : Section 5.1.2 # Ref ISO/IEC 19776-1:2015 : Section 5.1.2
co = [float(x) for vec in pt.split(',') for x in vec.split()] co = [float(x) for vec in pt.split(',') for x in vec.split()]
num_verts = len(co) // 3 num_verts = len(co) // 3
@ -715,57 +715,57 @@ class X3DReader(MeshReader):
# Group by three # Group by three
for i in range(num_verts): for i in range(num_verts):
self.verts[:3,i] = co[3*i:3*i+3] self.verts[:3,i] = co[3*i:3*i+3]
# Mesh builder helpers # Mesh builder helpers
def reserveFaceAndVertexCount(self, num_faces, num_verts): def reserveFaceAndVertexCount(self, num_faces, num_verts):
# Unlike the Cura MeshBuilder, we use 4-vectors stored as columns for easier transform # Unlike the Cura MeshBuilder, we use 4-vectors stored as columns for easier transform
self.verts = numpy.zeros((4, num_verts), dtype=numpy.float32) self.verts = numpy.zeros((4, num_verts), dtype=numpy.float32)
self.verts[3,:] = numpy.ones((num_verts), dtype=numpy.float32) self.verts[3,:] = numpy.ones((num_verts), dtype=numpy.float32)
self.num_verts = 0 self.num_verts = 0
self.reserveFaceCount(num_faces) self.reserveFaceCount(num_faces)
def reserveFaceCount(self, num_faces): def reserveFaceCount(self, num_faces):
self.faces = numpy.zeros((num_faces, 3), dtype=numpy.int32) self.faces = numpy.zeros((num_faces, 3), dtype=numpy.int32)
self.num_faces = 0 self.num_faces = 0
def getVertexCount(self): def getVertexCount(self):
return self.verts.shape[1] return self.verts.shape[1]
def addVertex(self, x, y, z): def addVertex(self, x, y, z):
self.verts[0, self.num_verts] = x self.verts[0, self.num_verts] = x
self.verts[1, self.num_verts] = y self.verts[1, self.num_verts] = y
self.verts[2, self.num_verts] = z self.verts[2, self.num_verts] = z
self.num_verts += 1 self.num_verts += 1
# Indices are 0-based for this shape, but they won't be zero-based in the merged mesh # Indices are 0-based for this shape, but they won't be zero-based in the merged mesh
def addTri(self, a, b, c): def addTri(self, a, b, c):
self.faces[self.num_faces, 0] = self.index_base + a self.faces[self.num_faces, 0] = self.index_base + a
self.faces[self.num_faces, 1] = self.index_base + b self.faces[self.num_faces, 1] = self.index_base + b
self.faces[self.num_faces, 2] = self.index_base + c self.faces[self.num_faces, 2] = self.index_base + c
self.num_faces += 1 self.num_faces += 1
def addTriFlip(self, a, b, c, ccw): def addTriFlip(self, a, b, c, ccw):
if ccw: if ccw:
self.addTri(a, b, c) self.addTri(a, b, c)
else: else:
self.addTri(b, a, c) self.addTri(b, a, c)
# Needs to be convex, but not necessaily planar # Needs to be convex, but not necessaily planar
# Assumed ccw, cut along the ac diagonal # Assumed ccw, cut along the ac diagonal
def addQuad(self, a, b, c, d): def addQuad(self, a, b, c, d):
self.addTri(a, b, c) self.addTri(a, b, c)
self.addTri(c, d, a) self.addTri(c, d, a)
def addQuadFlip(self, a, b, c, d, ccw): def addQuadFlip(self, a, b, c, d, ccw):
if ccw: if ccw:
self.addTri(a, b, c) self.addTri(a, b, c)
self.addTri(c, d, a) self.addTri(c, d, a)
else: else:
self.addTri(a, c, b) self.addTri(a, c, b)
self.addTri(c, a, d) self.addTri(c, a, d)
# Arbitrary polygon triangulation. # Arbitrary polygon triangulation.
# Doesn't assume convexity and doesn't check the "convex" flag in the file. # Doesn't assume convexity and doesn't check the "convex" flag in the file.
# Works by the "cutting of ears" algorithm: # Works by the "cutting of ears" algorithm:
@ -776,13 +776,13 @@ class X3DReader(MeshReader):
def addFace(self, indices, ccw): def addFace(self, indices, ccw):
# Resolve indices to coordinates for faster math # Resolve indices to coordinates for faster math
face = [Vector(data=self.verts[0:3, i]) for i in indices] face = [Vector(data=self.verts[0:3, i]) for i in indices]
# Need a normal to the plane so that we can know which vertices form inner angles # Need a normal to the plane so that we can know which vertices form inner angles
normal = findOuterNormal(face) normal = findOuterNormal(face)
if not normal: # Couldn't find an outer edge, non-planar polygon maybe? if not normal: # Couldn't find an outer edge, non-planar polygon maybe?
return return
# Find the vertex with the smallest inner angle and no points inside, cut off. Repeat until done # Find the vertex with the smallest inner angle and no points inside, cut off. Repeat until done
n = len(face) n = len(face)
vi = [i for i in range(n)] # We'll be using this to kick vertices from the face vi = [i for i in range(n)] # We'll be using this to kick vertices from the face
@ -807,17 +807,17 @@ class X3DReader(MeshReader):
if pointInsideTriangle(vx, next, prev, nextXprev): if pointInsideTriangle(vx, next, prev, nextXprev):
no_points_inside = False no_points_inside = False
break break
if no_points_inside: if no_points_inside:
max_cos = cos max_cos = cos
i_min = i i_min = i
self.addTriFlip(indices[vi[(i_min + n - 1) % n]], indices[vi[i_min]], indices[vi[(i_min + 1) % n]], ccw) self.addTriFlip(indices[vi[(i_min + n - 1) % n]], indices[vi[i_min]], indices[vi[(i_min + 1) % n]], ccw)
vi.pop(i_min) vi.pop(i_min)
n -= 1 n -= 1
self.addTriFlip(indices[vi[0]], indices[vi[1]], indices[vi[2]], ccw) self.addTriFlip(indices[vi[0]], indices[vi[1]], indices[vi[2]], ccw)
# ------------------------------------------------------------ # ------------------------------------------------------------
# X3D field parsers # X3D field parsers
# ------------------------------------------------------------ # ------------------------------------------------------------
@ -844,7 +844,7 @@ def readInt(node, attr, default):
if not s: if not s:
return default return default
return int(s, 0) return int(s, 0)
def readBoolean(node, attr, default): def readBoolean(node, attr, default):
s = node.attrib.get(attr) s = node.attrib.get(attr)
if not s: if not s:
@ -873,8 +873,8 @@ def readIndex(node, attr):
chunk.append(v[i]) chunk.append(v[i])
if chunk: if chunk:
chunks.append(chunk) chunks.append(chunk)
return chunks return chunks
# Given a face as a sequence of vectors, returns a normal to the polygon place that forms a right triple # Given a face as a sequence of vectors, returns a normal to the polygon place that forms a right triple
# with a vector along the polygon sequence and a vector backwards # with a vector along the polygon sequence and a vector backwards
def findOuterNormal(face): def findOuterNormal(face):
@ -894,25 +894,25 @@ def findOuterNormal(face):
if rejection.dot(prev_rejection) < -EPSILON: # points on both sides of the edge - not an outer one if rejection.dot(prev_rejection) < -EPSILON: # points on both sides of the edge - not an outer one
is_outer = False is_outer = False
break break
elif rejection.length() > prev_rejection.length(): # Pick a greater rejection for numeric stability elif rejection.length() > prev_rejection.length(): # Pick a greater rejection for numeric stability
prev_rejection = rejection prev_rejection = rejection
if is_outer: # Found an outer edge, prev_rejection is the rejection inside the face. Generate a normal. if is_outer: # Found an outer edge, prev_rejection is the rejection inside the face. Generate a normal.
return edge.cross(prev_rejection) return edge.cross(prev_rejection)
return False return False
# Given two *collinear* vectors a and b, returns the coefficient that takes b to a. # Given two *collinear* vectors a and b, returns the coefficient that takes b to a.
# No error handling. # No error handling.
# For stability, taking the ration between the biggest coordinates would be better... # For stability, taking the ration between the biggest coordinates would be better...
def ratio(a, b): def ratio(a, b):
if b.x > EPSILON or b.x < -EPSILON: if b.x > EPSILON or b.x < -EPSILON:
return a.x / b.x return a.x / b.x
elif b.y > EPSILON or b.y < -EPSILON: elif b.y > EPSILON or b.y < -EPSILON:
return a.y / b.y return a.y / b.y
else: else:
return a.z / b.z return a.z / b.z
def pointInsideTriangle(vx, next, prev, nextXprev): def pointInsideTriangle(vx, next, prev, nextXprev):
vxXprev = vx.cross(prev) vxXprev = vx.cross(prev)
r = ratio(vxXprev, nextXprev) r = ratio(vxXprev, nextXprev)
@ -921,4 +921,4 @@ def pointInsideTriangle(vx, next, prev, nextXprev):
vxXnext = vx.cross(next); vxXnext = vx.cross(next);
s = -ratio(vxXnext, nextXprev) s = -ratio(vxXnext, nextXprev)
return s > 0 and (s + r) < 1 return s > 0 and (s + r) < 1

View file

@ -0,0 +1,55 @@
{
"version":2,
"name":"Anycubic i3 Mega",
"inherits":"fdmprinter",
"metadata":{
"visible":true,
"author":"TheTobby",
"manufacturer":"Anycubic",
"file_formats":"text/x-gcode",
"icon":"icon_ultimaker2",
"platform":"anycubic_i3_mega_platform.stl",
"has_materials": false,
"has_machine_quality": true,
"preferred_quality": "*normal*"
},
"overrides":{
"machine_name":{
"default_value":"Anycubic i3 Mega"
},
"machine_heated_bed":{
"default_value":true
},
"machine_width":{
"default_value":210
},
"machine_height":{
"default_value":205
},
"machine_depth":{
"default_value":210
},
"machine_center_is_zero":{
"default_value":false
},
"machine_nozzle_size":{
"default_value":0.4
},
"material_diameter":{
"default_value":1.75
},
"gantry_height":{
"default_value":0
},
"machine_gcode_flavor":{
"default_value":"RepRap (Marlin/Sprinter)"
},
"machine_start_gcode":{
"default_value":"G21 ;metric values\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nM107 ;start with the fan off\nG28 X0 Y0 ;move X/Y to min endstops\nG28 Z0 ;move Z to min endstops\nG1 Z15.0 F{travel_speed} ;move the platform down 15mm\nG92 E0 ;zero the extruded length\nG1 F200 E3 ;extrude 3mm of feed stock\nG92 E0 ;zero the extruded length again\nG1 F{travel_speed}\nM117 Printing...\nG5"
},
"machine_end_gcode":{
"default_value":"M104 S0 ; turn off extruder\nM140 S0 ; turn off bed\nM84 ; disable motors\nM107\nG91 ;relative positioning\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle\nto release some of the pressure\nG1 Z+0.5 E-5 ;X-20 Y-20 F{travel_speed} ;move Z up a bit and retract filament even more\nG28 X0 ;Y0 ;move X/Y to min endstops\nso the head is out of the way\nG1 Y180 F2000\nM84 ;steppers off\nG90\nM300 P300 S4000"
}
}
}

View file

@ -1853,6 +1853,14 @@
"settable_per_mesh": true "settable_per_mesh": true
} }
} }
},
"infill_enable_travel_optimization":
{
"label": "Enable Travel Optimization",
"description": "When enabled, the order in which the infill lines are printed is optimized to reduce the distance travelled. The reduction in travel time achieved very much depends on the model being sliced, infill pattern, density, etc. Note that, for some models that have many small areas of infill, the time to slice the model may be greatly increased.",
"type": "bool",
"default_value": false,
"settable_per_mesh": true
} }
} }
}, },
@ -3475,13 +3483,22 @@
"settable_per_mesh": true, "settable_per_mesh": true,
"settable_per_extruder": false "settable_per_extruder": false
}, },
"support_tree_enable":
{
"label": "Tree Support",
"description": "Generate a tree-like support with branches that support your print. This may reduce material usage and print time.",
"type": "bool",
"default_value": false,
"settable_per_mesh": true,
"settable_per_extruder": false
},
"support_extruder_nr": "support_extruder_nr":
{ {
"label": "Support Extruder", "label": "Support Extruder",
"description": "The extruder train to use for printing the support. This is used in multi-extrusion.", "description": "The extruder train to use for printing the support. This is used in multi-extrusion.",
"type": "extruder", "type": "extruder",
"default_value": "0", "default_value": "0",
"enabled": "support_enable and machine_extruder_count > 1", "enabled": "(support_enable or support_tree_enable) and machine_extruder_count > 1",
"settable_per_mesh": false, "settable_per_mesh": false,
"settable_per_extruder": false, "settable_per_extruder": false,
"children": { "children": {
@ -3492,7 +3509,7 @@
"type": "extruder", "type": "extruder",
"default_value": "0", "default_value": "0",
"value": "support_extruder_nr", "value": "support_extruder_nr",
"enabled": "support_enable and machine_extruder_count > 1", "enabled": "(support_enable or support_tree_enable) and machine_extruder_count > 1",
"settable_per_mesh": false, "settable_per_mesh": false,
"settable_per_extruder": false "settable_per_extruder": false
}, },
@ -3503,7 +3520,7 @@
"type": "extruder", "type": "extruder",
"default_value": "0", "default_value": "0",
"value": "support_extruder_nr", "value": "support_extruder_nr",
"enabled": "support_enable and machine_extruder_count > 1", "enabled": "(support_enable or support_tree_enable) and machine_extruder_count > 1",
"settable_per_mesh": false, "settable_per_mesh": false,
"settable_per_extruder": false "settable_per_extruder": false
}, },
@ -3514,7 +3531,7 @@
"type": "extruder", "type": "extruder",
"default_value": "0", "default_value": "0",
"value": "support_extruder_nr", "value": "support_extruder_nr",
"enabled": "support_enable and machine_extruder_count > 1", "enabled": "(support_enable or support_tree_enable) and machine_extruder_count > 1",
"settable_per_mesh": false, "settable_per_mesh": false,
"settable_per_extruder": false, "settable_per_extruder": false,
"children": "children":
@ -3526,7 +3543,7 @@
"type": "extruder", "type": "extruder",
"default_value": "0", "default_value": "0",
"value": "support_interface_extruder_nr", "value": "support_interface_extruder_nr",
"enabled": "support_enable and machine_extruder_count > 1", "enabled": "(support_enable or support_tree_enable) and machine_extruder_count > 1",
"settable_per_mesh": false, "settable_per_mesh": false,
"settable_per_extruder": false "settable_per_extruder": false
}, },
@ -3537,7 +3554,7 @@
"type": "extruder", "type": "extruder",
"default_value": "0", "default_value": "0",
"value": "support_interface_extruder_nr", "value": "support_interface_extruder_nr",
"enabled": "support_enable and machine_extruder_count > 1", "enabled": "(support_enable or support_tree_enable) and machine_extruder_count > 1",
"settable_per_mesh": false, "settable_per_mesh": false,
"settable_per_extruder": false "settable_per_extruder": false
} }
@ -3557,7 +3574,7 @@
}, },
"default_value": "everywhere", "default_value": "everywhere",
"resolve": "'everywhere' if 'everywhere' in extruderValues('support_type') else 'buildplate'", "resolve": "'everywhere' if 'everywhere' in extruderValues('support_type') else 'buildplate'",
"enabled": "support_enable", "enabled": "support_enable or support_tree_enable",
"settable_per_mesh": false, "settable_per_mesh": false,
"settable_per_extruder": false "settable_per_extruder": false
}, },
@ -3572,7 +3589,7 @@
"maximum_value_warning": "80", "maximum_value_warning": "80",
"default_value": 50, "default_value": 50,
"limit_to_extruder": "support_roof_extruder_nr if support_roof_enable else support_infill_extruder_nr", "limit_to_extruder": "support_roof_extruder_nr if support_roof_enable else support_infill_extruder_nr",
"enabled": "support_enable", "enabled": "support_enable or support_tree_enable",
"settable_per_mesh": true "settable_per_mesh": true
}, },
"support_pattern": "support_pattern":
@ -3591,7 +3608,7 @@
"cross": "Cross" "cross": "Cross"
}, },
"default_value": "zigzag", "default_value": "zigzag",
"enabled": "support_enable", "enabled": "support_enable or support_tree_enable",
"limit_to_extruder": "support_infill_extruder_nr", "limit_to_extruder": "support_infill_extruder_nr",
"settable_per_mesh": false, "settable_per_mesh": false,
"settable_per_extruder": true "settable_per_extruder": true
@ -3602,7 +3619,7 @@
"description": "Connect the ZigZags. This will increase the strength of the zig zag support structure.", "description": "Connect the ZigZags. This will increase the strength of the zig zag support structure.",
"type": "bool", "type": "bool",
"default_value": true, "default_value": true,
"enabled": "support_enable and (support_pattern == 'zigzag')", "enabled": "(support_enable or support_tree_enable) and support_pattern == 'zigzag'",
"limit_to_extruder": "support_infill_extruder_nr", "limit_to_extruder": "support_infill_extruder_nr",
"settable_per_mesh": false, "settable_per_mesh": false,
"settable_per_extruder": true "settable_per_extruder": true
@ -3616,7 +3633,8 @@
"minimum_value": "0", "minimum_value": "0",
"maximum_value_warning": "100", "maximum_value_warning": "100",
"default_value": 15, "default_value": 15,
"enabled": "support_enable", "value": "15 if support_enable else 0",
"enabled": "support_enable or support_tree_enable",
"limit_to_extruder": "support_infill_extruder_nr", "limit_to_extruder": "support_infill_extruder_nr",
"settable_per_mesh": false, "settable_per_mesh": false,
"settable_per_extruder": true, "settable_per_extruder": true,
@ -3631,7 +3649,7 @@
"minimum_value": "0", "minimum_value": "0",
"minimum_value_warning": "support_line_width", "minimum_value_warning": "support_line_width",
"default_value": 2.66, "default_value": 2.66,
"enabled": "support_enable", "enabled": "support_enable or support_tree_enable",
"value": "0 if support_infill_rate == 0 else (support_line_width * 100) / support_infill_rate * (2 if support_pattern == 'grid' else (3 if support_pattern == 'triangles' else 1))", "value": "0 if support_infill_rate == 0 else (support_line_width * 100) / support_infill_rate * (2 if support_pattern == 'grid' else (3 if support_pattern == 'triangles' else 1))",
"limit_to_extruder": "support_infill_extruder_nr", "limit_to_extruder": "support_infill_extruder_nr",
"settable_per_mesh": false, "settable_per_mesh": false,
@ -3649,7 +3667,7 @@
"maximum_value_warning": "machine_nozzle_size", "maximum_value_warning": "machine_nozzle_size",
"default_value": 0.1, "default_value": 0.1,
"limit_to_extruder": "support_interface_extruder_nr if support_interface_enable else support_infill_extruder_nr", "limit_to_extruder": "support_interface_extruder_nr if support_interface_enable else support_infill_extruder_nr",
"enabled": "support_enable", "enabled": "support_enable or support_tree_enable",
"settable_per_mesh": true, "settable_per_mesh": true,
"children": "children":
{ {
@ -3662,7 +3680,7 @@
"maximum_value_warning": "machine_nozzle_size", "maximum_value_warning": "machine_nozzle_size",
"default_value": 0.1, "default_value": 0.1,
"type": "float", "type": "float",
"enabled": "support_enable", "enabled": "support_enable or support_tree_enable",
"value": "extruderValue(support_roof_extruder_nr if support_roof_enable else support_infill_extruder_nr, 'support_z_distance')", "value": "extruderValue(support_roof_extruder_nr if support_roof_enable else support_infill_extruder_nr, 'support_z_distance')",
"limit_to_extruder": "support_roof_extruder_nr if support_roof_enable else support_infill_extruder_nr", "limit_to_extruder": "support_roof_extruder_nr if support_roof_enable else support_infill_extruder_nr",
"settable_per_mesh": true "settable_per_mesh": true
@ -3678,7 +3696,7 @@
"value": "extruderValue(support_bottom_extruder_nr if support_bottom_enable else support_infill_extruder_nr, 'support_z_distance') if support_type == 'everywhere' else 0", "value": "extruderValue(support_bottom_extruder_nr if support_bottom_enable else support_infill_extruder_nr, 'support_z_distance') if support_type == 'everywhere' else 0",
"limit_to_extruder": "support_bottom_extruder_nr if support_bottom_enable else support_infill_extruder_nr", "limit_to_extruder": "support_bottom_extruder_nr if support_bottom_enable else support_infill_extruder_nr",
"type": "float", "type": "float",
"enabled": "support_enable and resolveOrValue('support_type') == 'everywhere'", "enabled": "(support_enable or support_tree_enable) and resolveOrValue('support_type') == 'everywhere'",
"settable_per_mesh": true "settable_per_mesh": true
} }
} }
@ -3693,7 +3711,7 @@
"maximum_value_warning": "1.5 * machine_nozzle_tip_outer_diameter", "maximum_value_warning": "1.5 * machine_nozzle_tip_outer_diameter",
"default_value": 0.7, "default_value": 0.7,
"limit_to_extruder": "support_infill_extruder_nr", "limit_to_extruder": "support_infill_extruder_nr",
"enabled": "support_enable", "enabled": "support_enable or support_tree_enable",
"settable_per_mesh": true "settable_per_mesh": true
}, },
"support_xy_overrides_z": "support_xy_overrides_z":
@ -3788,10 +3806,114 @@
"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') * 8",
"value": "resolveOrValue('layer_height')", "value": "resolveOrValue('layer_height')",
"enabled": "support_enable and support_infill_rate > 0", "enabled": "(support_enable or support_tree_enable) and support_infill_rate > 0",
"limit_to_extruder": "support_infill_extruder_nr", "limit_to_extruder": "support_infill_extruder_nr",
"settable_per_mesh": false "settable_per_mesh": false
}, },
"support_tree_angle":
{
"label": "Tree Support Branch Angle",
"description": "The angle of the branches. Use a lower angle to make them more vertical and more stable. Use a higher angle to be able to have more reach.",
"unit": "°",
"type": "float",
"minimum_value": "0",
"maximum_value": "90",
"maximum_value_warning": "60",
"default_value": 40,
"limit_to_extruder": "support_infill_extruder_nr",
"enabled": "support_tree_enable",
"settable_per_mesh": false,
"settable_per_extruder": true
},
"support_tree_branch_distance":
{
"label": "Tree Support Branch Distance",
"description": "How far apart the branches need to be when they touch the model. Making this distance small will cause the tree support to touch the model at more points, causing better overhang but making support harder to remove.",
"unit": "mm",
"type": "float",
"minimum_value": "0.001",
"default_value": 4,
"limit_to_extruder": "support_infill_extruder_nr",
"enabled": "support_tree_enable",
"settable_per_mesh": true
},
"support_tree_branch_diameter":
{
"label": "Tree Support Branch Diameter",
"description": "The diameter of the thinnest branches of tree support. Thicker branches are more sturdy. Branches towards the base will be thicker than this.",
"unit": "mm",
"type": "float",
"minimum_value": "0.001",
"minimum_value_warning": "support_line_width * 2",
"default_value": 2,
"limit_to_extruder": "support_infill_extruder_nr",
"enabled": "support_tree_enable",
"settable_per_mesh": false,
"settable_per_extruder": true
},
"support_tree_branch_diameter_angle":
{
"label": "Tree Support Branch Diameter Angle",
"description": "The angle of the branches' diameter as they gradually become thicker towards the bottom. An angle of 0 will cause the branches to have uniform thickness over their length. A bit of an angle can increase stability of the tree support.",
"unit": "°",
"type": "float",
"minimum_value": "0",
"maximum_value": "89.9999",
"maximum_value_warning": "15",
"default_value": 5,
"limit_to_extruder": "support_infill_extruder_nr",
"enabled": "support_tree_enable",
"settable_per_mesh": false,
"settable_per_extruder": true
},
"support_tree_collision_resolution":
{
"label": "Tree Support Collision Resolution",
"description": "Resolution to compute collisions with to avoid hitting the model. Setting this lower will produce more accurate trees that fail less often, but increases slicing time dramatically.",
"unit": "mm",
"type": "float",
"minimum_value": "0.001",
"minimum_value_warning": "support_line_width / 4",
"maximum_value_warning": "support_line_width * 2",
"default_value": 0.4,
"value": "support_line_width / 2",
"limit_to_extruder": "support_infill_extruder_nr",
"enabled": "support_tree_enable and support_tree_branch_diameter_angle > 0",
"settable_per_mesh": false,
"settable_per_extruder": true
},
"support_tree_wall_thickness":
{
"label": "Tree Support Wall Thickness",
"description": "The thickness of the walls of the branches of tree support. Thicker walls take longer to print but don't fall over as easily.",
"unit": "mm",
"type": "float",
"minimum_value": "0",
"minimum_value_warning": "wall_line_width",
"default_value": 0.8,
"value": "support_line_width",
"limit_to_extruder": "support_infill_extruder_nr",
"enabled": "support_tree_enable",
"settable_per_mesh": false,
"settable_per_extruder": true,
"children":
{
"support_tree_wall_count":
{
"label": "Tree Support Wall Line Count",
"description": "The number of walls of the branches of tree support. Thicker walls take longer to print but don't fall over as easily.",
"type": "int",
"minimum_value": "0",
"minimum_value_warning": "1",
"default_value": 1,
"value": "round(support_tree_wall_thickness / support_line_width)",
"limit_to_extruder": "support_infill_extruder_nr",
"enabled": "support_tree_enable",
"settable_per_mesh": false,
"settable_per_extruder": true
}
}
},
"gradual_support_infill_steps": "gradual_support_infill_steps":
{ {
"label": "Gradual Support Infill Steps", "label": "Gradual Support Infill Steps",
@ -3801,7 +3923,7 @@
"minimum_value": "0", "minimum_value": "0",
"maximum_value_warning": "1 if (support_pattern == 'cross' or support_pattern == 'lines' or support_pattern == 'zigzag' or support_pattern == 'concentric' or support_pattern == 'concentric_3d') else 5", "maximum_value_warning": "1 if (support_pattern == 'cross' or support_pattern == 'lines' or support_pattern == 'zigzag' or support_pattern == 'concentric' or support_pattern == 'concentric_3d') else 5",
"maximum_value": "999999 if support_line_distance == 0 else (20 - math.log(support_line_distance) / math.log(2))", "maximum_value": "999999 if support_line_distance == 0 else (20 - math.log(support_line_distance) / math.log(2))",
"enabled": "support_enable and support_infill_rate > 0", "enabled": "(support_enable or support_tree_enable) and support_infill_rate > 0",
"limit_to_extruder": "support_infill_extruder_nr", "limit_to_extruder": "support_infill_extruder_nr",
"settable_per_mesh": false "settable_per_mesh": false
}, },
@ -3814,7 +3936,7 @@
"default_value": 1, "default_value": 1,
"minimum_value": "0.0001", "minimum_value": "0.0001",
"minimum_value_warning": "3 * resolveOrValue('layer_height')", "minimum_value_warning": "3 * resolveOrValue('layer_height')",
"enabled": "support_enable and support_infill_rate > 0 and gradual_support_infill_steps > 0", "enabled": "(support_enable or support_tree_enable) and support_infill_rate > 0 and gradual_support_infill_steps > 0",
"limit_to_extruder": "support_infill_extruder_nr", "limit_to_extruder": "support_infill_extruder_nr",
"settable_per_mesh": false "settable_per_mesh": false
}, },
@ -3825,7 +3947,7 @@
"type": "bool", "type": "bool",
"default_value": false, "default_value": false,
"limit_to_extruder": "support_interface_extruder_nr", "limit_to_extruder": "support_interface_extruder_nr",
"enabled": "support_enable", "enabled": "support_enable or support_tree_enable",
"settable_per_mesh": true, "settable_per_mesh": true,
"children": "children":
{ {
@ -3837,7 +3959,7 @@
"default_value": false, "default_value": false,
"value": "extruderValue(support_roof_extruder_nr, 'support_interface_enable')", "value": "extruderValue(support_roof_extruder_nr, 'support_interface_enable')",
"limit_to_extruder": "support_roof_extruder_nr", "limit_to_extruder": "support_roof_extruder_nr",
"enabled": "support_enable", "enabled": "support_enable or support_tree_enable",
"settable_per_mesh": true "settable_per_mesh": true
}, },
"support_bottom_enable": "support_bottom_enable":
@ -3848,7 +3970,7 @@
"default_value": false, "default_value": false,
"value": "extruderValue(support_bottom_extruder_nr, 'support_interface_enable')", "value": "extruderValue(support_bottom_extruder_nr, 'support_interface_enable')",
"limit_to_extruder": "support_bottom_extruder_nr", "limit_to_extruder": "support_bottom_extruder_nr",
"enabled": "support_enable", "enabled": "support_enable or support_tree_enable",
"settable_per_mesh": true "settable_per_mesh": true
} }
} }
@ -3864,7 +3986,7 @@
"minimum_value_warning": "0.2 + layer_height", "minimum_value_warning": "0.2 + layer_height",
"maximum_value_warning": "10", "maximum_value_warning": "10",
"limit_to_extruder": "support_interface_extruder_nr", "limit_to_extruder": "support_interface_extruder_nr",
"enabled": "support_interface_enable and support_enable", "enabled": "support_interface_enable and (support_enable or support_tree_enable)",
"settable_per_mesh": true, "settable_per_mesh": true,
"children": "children":
{ {
@ -3880,7 +4002,7 @@
"maximum_value_warning": "10", "maximum_value_warning": "10",
"value": "extruderValue(support_roof_extruder_nr, 'support_interface_height')", "value": "extruderValue(support_roof_extruder_nr, 'support_interface_height')",
"limit_to_extruder": "support_roof_extruder_nr", "limit_to_extruder": "support_roof_extruder_nr",
"enabled": "support_roof_enable and support_enable", "enabled": "support_roof_enable and (support_enable or support_tree_enable)",
"settable_per_mesh": true "settable_per_mesh": true
}, },
"support_bottom_height": "support_bottom_height":
@ -3895,7 +4017,7 @@
"minimum_value_warning": "min(0.2 + layer_height, support_bottom_stair_step_height)", "minimum_value_warning": "min(0.2 + layer_height, support_bottom_stair_step_height)",
"maximum_value_warning": "10", "maximum_value_warning": "10",
"limit_to_extruder": "support_bottom_extruder_nr", "limit_to_extruder": "support_bottom_extruder_nr",
"enabled": "support_bottom_enable and support_enable", "enabled": "support_bottom_enable and (support_enable or support_tree_enable)",
"settable_per_mesh": true "settable_per_mesh": true
} }
} }
@ -3922,7 +4044,7 @@
"minimum_value": "0", "minimum_value": "0",
"maximum_value_warning": "100", "maximum_value_warning": "100",
"limit_to_extruder": "support_interface_extruder_nr", "limit_to_extruder": "support_interface_extruder_nr",
"enabled": "support_interface_enable and support_enable", "enabled": "support_interface_enable and (support_enable or support_tree_enable)",
"settable_per_mesh": false, "settable_per_mesh": false,
"settable_per_extruder": true, "settable_per_extruder": true,
"children": "children":
@ -3937,7 +4059,7 @@
"minimum_value": "0", "minimum_value": "0",
"maximum_value": "100", "maximum_value": "100",
"limit_to_extruder": "support_roof_extruder_nr", "limit_to_extruder": "support_roof_extruder_nr",
"enabled": "support_roof_enable and support_enable", "enabled": "support_roof_enable and (support_enable or support_tree_enable)",
"value": "extruderValue(support_roof_extruder_nr, 'support_interface_density')", "value": "extruderValue(support_roof_extruder_nr, 'support_interface_density')",
"settable_per_mesh": false, "settable_per_mesh": false,
"settable_per_extruder": true, "settable_per_extruder": true,
@ -3954,7 +4076,7 @@
"minimum_value_warning": "support_roof_line_width - 0.0001", "minimum_value_warning": "support_roof_line_width - 0.0001",
"value": "0 if support_roof_density == 0 else (support_roof_line_width * 100) / support_roof_density * (2 if support_roof_pattern == 'grid' else (3 if support_roof_pattern == 'triangles' else 1))", "value": "0 if support_roof_density == 0 else (support_roof_line_width * 100) / support_roof_density * (2 if support_roof_pattern == 'grid' else (3 if support_roof_pattern == 'triangles' else 1))",
"limit_to_extruder": "support_roof_extruder_nr", "limit_to_extruder": "support_roof_extruder_nr",
"enabled": "support_roof_enable and support_enable", "enabled": "support_roof_enable and (support_enable or support_tree_enable)",
"settable_per_mesh": false, "settable_per_mesh": false,
"settable_per_extruder": true "settable_per_extruder": true
} }
@ -3970,7 +4092,7 @@
"minimum_value": "0", "minimum_value": "0",
"maximum_value": "100", "maximum_value": "100",
"limit_to_extruder": "support_bottom_extruder_nr", "limit_to_extruder": "support_bottom_extruder_nr",
"enabled": "support_bottom_enable and support_enable", "enabled": "support_bottom_enable and (support_enable or support_tree_enable)",
"value": "extruderValue(support_bottom_extruder_nr, 'support_interface_density')", "value": "extruderValue(support_bottom_extruder_nr, 'support_interface_density')",
"settable_per_mesh": false, "settable_per_mesh": false,
"settable_per_extruder": true, "settable_per_extruder": true,
@ -3987,7 +4109,7 @@
"minimum_value_warning": "support_bottom_line_width - 0.0001", "minimum_value_warning": "support_bottom_line_width - 0.0001",
"value": "0 if support_bottom_density == 0 else (support_bottom_line_width * 100) / support_bottom_density * (2 if support_bottom_pattern == 'grid' else (3 if support_bottom_pattern == 'triangles' else 1))", "value": "0 if support_bottom_density == 0 else (support_bottom_line_width * 100) / support_bottom_density * (2 if support_bottom_pattern == 'grid' else (3 if support_bottom_pattern == 'triangles' else 1))",
"limit_to_extruder": "support_bottom_extruder_nr", "limit_to_extruder": "support_bottom_extruder_nr",
"enabled": "support_bottom_enable and support_enable", "enabled": "support_bottom_enable and (support_enable or support_tree_enable)",
"settable_per_mesh": false, "settable_per_mesh": false,
"settable_per_extruder": true "settable_per_extruder": true
} }
@ -4011,7 +4133,7 @@
}, },
"default_value": "concentric", "default_value": "concentric",
"limit_to_extruder": "support_interface_extruder_nr", "limit_to_extruder": "support_interface_extruder_nr",
"enabled": "support_interface_enable and support_enable", "enabled": "support_interface_enable and (support_enable or support_tree_enable)",
"settable_per_mesh": false, "settable_per_mesh": false,
"settable_per_extruder": true, "settable_per_extruder": true,
"children": "children":
@ -4033,7 +4155,7 @@
"default_value": "concentric", "default_value": "concentric",
"value": "extruderValue(support_roof_extruder_nr, 'support_interface_pattern')", "value": "extruderValue(support_roof_extruder_nr, 'support_interface_pattern')",
"limit_to_extruder": "support_roof_extruder_nr", "limit_to_extruder": "support_roof_extruder_nr",
"enabled": "support_roof_enable and support_enable", "enabled": "support_roof_enable and (support_enable or support_tree_enable)",
"settable_per_mesh": false, "settable_per_mesh": false,
"settable_per_extruder": true "settable_per_extruder": true
}, },
@ -4054,7 +4176,7 @@
"default_value": "concentric", "default_value": "concentric",
"value": "extruderValue(support_bottom_extruder_nr, 'support_interface_pattern')", "value": "extruderValue(support_bottom_extruder_nr, 'support_interface_pattern')",
"limit_to_extruder": "support_bottom_extruder_nr", "limit_to_extruder": "support_bottom_extruder_nr",
"enabled": "support_bottom_enable and support_enable", "enabled": "support_bottom_enable and (support_enable or support_tree_enable)",
"settable_per_mesh": false, "settable_per_mesh": false,
"settable_per_extruder": true "settable_per_extruder": true
} }

View file

@ -0,0 +1,54 @@
{
"version": 2,
"name": "Tevo Black Widow",
"inherits": "fdmprinter",
"metadata": {
"visible": true,
"author": "TheTobby",
"manufacturer": "Tevo",
"file_formats": "text/x-gcode",
"icon": "icon_ultimaker2",
"has_materials": false,
"has_machine_quality": true,
"platform": "prusai3_platform.stl",
"preferred_quality": "*normal*"
},
"overrides": {
"machine_name": {
"default_value": "Tevo Black Widow"
},
"machine_heated_bed": {
"default_value": true
},
"machine_width": {
"default_value": 350
},
"machine_height": {
"default_value": 250
},
"machine_depth": {
"default_value": 250
},
"machine_center_is_zero": {
"default_value": false
},
"machine_nozzle_size": {
"default_value": 0.4
},
"material_diameter": {
"default_value": 1.75
},
"gantry_height": {
"default_value": 0
},
"machine_gcode_flavor": {
"default_value": "RepRap (Marlin/Sprinter)"
},
"machine_start_gcode": {
"default_value": "M280 P0 S160 ; release BLTouch alarm (OK to send for Non BLTouch)\nM420 Z2 ; set fade leveling at 2mm for BLTouch (OK to send for Non BLTouch)\nG28 ; home all\nG29 ; probe bed\nG92 E0 ;zero the extruded length\nG1 X0.0 Y50.0 Z10.0 F3600\n; perform wipe and prime\nG1 Z0.0 F1000\nG1 Z0.2 Y70.0 E9.0 F1000.0 ; prime\nG1 Y100.0 E12.5 F1000.0 ; prime\nG92 E0 ; zero extruder again\nM117 Printing..."
},
"machine_end_gcode": {
"default_value": "G92 E0 ; zero the extruded length again\nG1 E-1.5 F500 ; retract the filament to release some of the pressure\nM104 S0 ; turn off extruder\nM140 S0 ; turn off bed\nG28 X0 ; home X axis\nG1 Y245 ; move Y axis to end position\nM84 ; disable motors\nM107 ; turn off fan"
}
}
}

View file

@ -66,7 +66,7 @@
"default_value": "G21 ;metric values\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nM107 ;start with the fan off\nG28 X0 Y0 ;move X/Y to min endstops\nG28 Z0 ;move Z to min endstops\nG1 Z15.0 F9000 ;move the platform down 15mm\nG92 E0 ;zero the extruded length\nG1 F200 E3 ;extrude 3mm of feed stock\nG92 E0 ;zero the extruded length again\nG1 F9000\n;Put printing message on LCD screen\nM117 Printing..." "default_value": "G21 ;metric values\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nM107 ;start with the fan off\nG28 X0 Y0 ;move X/Y to min endstops\nG28 Z0 ;move Z to min endstops\nG1 Z15.0 F9000 ;move the platform down 15mm\nG92 E0 ;zero the extruded length\nG1 F200 E3 ;extrude 3mm of feed stock\nG92 E0 ;zero the extruded length again\nG1 F9000\n;Put printing message on LCD screen\nM117 Printing..."
}, },
"machine_end_gcode": { "machine_end_gcode": {
"default_value": "M104 S0 ;extruder heater off\nM140 S0 ;heated bed heater off (if you have it)\nG91 ;relative positioning\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\nG1 Z+0.5 E-5 X-20 Y-20 F9000 ;move Z up a bit and retract filament even more\nG28 X0 Y0 ;move X/Y to min endstops, so the head is out of the way\nM84 ;steppers off\nG90 ;absolute positioning\nG1 Y200 F3600 ;move baseplate to front for easier access to printed object" "default_value": "M104 S0 ;extruder heater off\nM140 S0 ;heated bed heater off (if you have it)\nG91 ;relative positioning\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\nG1 Z+0.5 E-5 X-20 Y-20 F9000 ;move Z up a bit and retract filament even more\nG90 ;absolute positioning\nG1 X0 Y200 F3600 ;move extruder out of the way by moving the baseplate to the front for easier access to printed object\nM84 ;steppers off"
} }
} }
} }

Binary file not shown.

Binary file not shown.

View file

@ -17,7 +17,12 @@ Item
property alias undo: undoAction; property alias undo: undoAction;
property alias redo: redoAction; property alias redo: redoAction;
property alias homeCamera: homeCameraAction; property alias view3DCamera: view3DCameraAction;
property alias viewFrontCamera: viewFrontCameraAction;
property alias viewTopCamera: viewTopCameraAction;
property alias viewLeftSideCamera: viewLeftSideCameraAction;
property alias viewRightSideCamera: viewRightSideCameraAction;
property alias expandSidebar: expandSidebarAction; property alias expandSidebar: expandSidebarAction;
property alias deleteSelection: deleteSelectionAction; property alias deleteSelection: deleteSelectionAction;
@ -36,6 +41,7 @@ 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 arrangeAllBuildPlates: arrangeAllBuildPlatesAction;
property alias arrangeAll: arrangeAllAction; property alias arrangeAll: arrangeAllAction;
property alias arrangeSelection: arrangeSelectionAction; property alias arrangeSelection: arrangeSelectionAction;
property alias resetAllTranslation: resetAllTranslationAction; property alias resetAllTranslation: resetAllTranslationAction;
@ -104,9 +110,37 @@ Item
Action Action
{ {
id: homeCameraAction; id: view3DCameraAction;
text: catalog.i18nc("@action:inmenu menubar:view","&Reset camera position"); text: catalog.i18nc("@action:inmenu menubar:view","&3D View");
onTriggered: CuraActions.homeCamera(); onTriggered: UM.Controller.rotateView("3d", 0);
}
Action
{
id: viewFrontCameraAction;
text: catalog.i18nc("@action:inmenu menubar:view","&Front View");
onTriggered: UM.Controller.rotateView("home", 0);
}
Action
{
id: viewTopCameraAction;
text: catalog.i18nc("@action:inmenu menubar:view","&Top View");
onTriggered: UM.Controller.rotateView("y", 90);
}
Action
{
id: viewLeftSideCameraAction;
text: catalog.i18nc("@action:inmenu menubar:view","&Left Side View");
onTriggered: UM.Controller.rotateView("x", 90);
}
Action
{
id: viewRightSideCameraAction;
text: catalog.i18nc("@action:inmenu menubar:view","&Right Side View");
onTriggered: UM.Controller.rotateView("x", -90);
} }
Action Action
@ -311,6 +345,13 @@ Item
onTriggered: CuraApplication.reloadAll(); onTriggered: CuraApplication.reloadAll();
} }
Action
{
id: arrangeAllBuildPlatesAction;
text: catalog.i18nc("@action:inmenu menubar:edit","Arrange All Models To All Build Plates");
onTriggered: Printer.arrangeObjectsToAllBuildPlates();
}
Action Action
{ {
id: arrangeAllAction; id: arrangeAllAction;

View file

@ -375,6 +375,18 @@ UM.MainWindow
} }
} }
ObjectsList
{
id: objectsList;
visible: UM.Preferences.getValue("cura/use_multi_build_plate");
anchors
{
bottom: parent.bottom;
left: parent.left;
}
}
Topbar Topbar
{ {
id: topbar id: topbar

View file

@ -7,7 +7,7 @@ import QtQuick.Dialogs 1.2
import QtQuick.Window 2.1 import QtQuick.Window 2.1
import UM 1.2 as UM import UM 1.2 as UM
import Cura 1.0 as Cura import Cura 1.2 as Cura
Menu Menu
{ {
@ -39,6 +39,35 @@ Menu
onObjectRemoved: base.removeItem(object) onObjectRemoved: base.removeItem(object)
} }
MenuSeparator {
visible: UM.Preferences.getValue("cura/use_multi_build_plate")
}
Instantiator
{
model: Cura.BuildPlateModel
MenuItem {
text: Cura.BuildPlateModel.getItem(index).name;
onTriggered: CuraActions.setBuildPlateForSelection(Cura.BuildPlateModel.getItem(index).buildPlateNumber);
checkable: true
checked: Cura.BuildPlateModel.selectionBuildPlates.indexOf(Cura.BuildPlateModel.getItem(index).buildPlateNumber) != -1;
visible: UM.Preferences.getValue("cura/use_multi_build_plate")
}
onObjectAdded: base.insertItem(index, object);
onObjectRemoved: base.removeItem(object);
}
MenuItem {
text: "New build plate";
onTriggered: {
CuraActions.setBuildPlateForSelection(Cura.BuildPlateModel.maxBuildPlate + 1);
checked = false;
}
checkable: true
checked: false
visible: UM.Preferences.getValue("cura/use_multi_build_plate")
}
// Global actions // Global actions
MenuSeparator {} MenuSeparator {}
MenuItem { action: Cura.Actions.selectAll; } MenuItem { action: Cura.Actions.selectAll; }

View file

@ -5,12 +5,12 @@ import QtQuick 2.2
import QtQuick.Controls 1.1 import QtQuick.Controls 1.1
import UM 1.2 as UM import UM 1.2 as UM
import Cura 1.0 as Cura import Cura 1.2 as Cura
Menu Menu
{ {
title: catalog.i18nc("@title:menu menubar:toplevel", "&View"); title: catalog.i18nc("@title:menu menubar:toplevel", "&View");
id: menu id: base
enabled: !PrintInformation.preSliced enabled: !PrintInformation.preSliced
// main views // main views
@ -25,12 +25,50 @@ Menu
exclusiveGroup: group exclusiveGroup: group
onTriggered: UM.Controller.setActiveView(model.id) onTriggered: UM.Controller.setActiveView(model.id)
} }
onObjectAdded: menu.insertItem(index, object) onObjectAdded: base.insertItem(index, object)
onObjectRemoved: menu.removeItem(object) onObjectRemoved: base.removeItem(object)
} }
ExclusiveGroup { id: group } ExclusiveGroup { id: group }
MenuSeparator {} MenuSeparator {}
MenuItem { action: Cura.Actions.homeCamera; }
Menu
{
title: catalog.i18nc("@action:inmenu menubar:view","&Camera position");
MenuItem { action: Cura.Actions.view3DCamera; }
MenuItem { action: Cura.Actions.viewFrontCamera; }
MenuItem { action: Cura.Actions.viewTopCamera; }
MenuItem { action: Cura.Actions.viewLeftSideCamera; }
MenuItem { action: Cura.Actions.viewRightSideCamera; }
}
MenuSeparator {
visible: UM.Preferences.getValue("cura/use_multi_build_plate")
}
Menu
{
id: buildPlateMenu;
title: catalog.i18nc("@action:inmenu menubar:view","&Build plate");
visible: UM.Preferences.getValue("cura/use_multi_build_plate")
Instantiator
{
model: Cura.BuildPlateModel
MenuItem {
text: Cura.BuildPlateModel.getItem(index).name;
onTriggered: Cura.SceneController.setActiveBuildPlate(Cura.BuildPlateModel.getItem(index).buildPlateNumber);
checkable: true;
checked: Cura.BuildPlateModel.getItem(index).buildPlateNumber == Cura.BuildPlateModel.activeBuildPlate;
exclusiveGroup: buildPlateGroup;
visible: UM.Preferences.getValue("cura/use_multi_build_plate")
}
onObjectAdded: buildPlateMenu.insertItem(index, object);
onObjectRemoved: buildPlateMenu.removeItem(object)
}
ExclusiveGroup { id: buildPlateGroup; }
}
MenuSeparator {}
MenuItem { action: Cura.Actions.expandSidebar; } MenuItem { action: Cura.Actions.expandSidebar; }
} }

View file

@ -0,0 +1,266 @@
// Copyright (c) 2017 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 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.2 as Cura
import "Menus"
Rectangle
{
id: base;
color: UM.Theme.getColor("tool_panel_background")
width: UM.Theme.getSize("objects_menu_size").width
height: {
if (collapsed) {
return UM.Theme.getSize("objects_menu_size_collapsed").height;
} else {
return UM.Theme.getSize("objects_menu_size").height;
}
}
Behavior on height { NumberAnimation { duration: 100 } }
border.width: UM.Theme.getSize("default_lining").width
border.color: UM.Theme.getColor("lining")
property bool collapsed: false;
SystemPalette { id: palette }
Button {
id: collapseButton
anchors.top: parent.top
anchors.topMargin: Math.floor(UM.Theme.getSize("default_margin").height + (UM.Theme.getSize("layerview_row").height - UM.Theme.getSize("default_margin").height) / 2)
anchors.right: parent.right
anchors.rightMargin: UM.Theme.getSize("default_margin").width
width: UM.Theme.getSize("standard_arrow").width
height: UM.Theme.getSize("standard_arrow").height
onClicked: collapsed = !collapsed
style: ButtonStyle
{
background: UM.RecolorImage
{
width: control.width
height: control.height
sourceSize.width: width
sourceSize.height: width
color: UM.Theme.getColor("setting_control_text")
source: collapsed ? UM.Theme.getIcon("arrow_left") : UM.Theme.getIcon("arrow_bottom")
}
label: Label{ }
}
}
Component {
id: buildPlateDelegate
Rectangle
{
height: childrenRect.height
color: Cura.BuildPlateModel.getItem(index).buildPlateNumber == Cura.BuildPlateModel.activeBuildPlate ? palette.highlight : index % 2 ? palette.base : palette.alternateBase
width: parent.width
Label
{
id: buildPlateNameLabel
anchors.left: parent.left
anchors.leftMargin: UM.Theme.getSize("default_margin").width
width: parent.width - 2 * UM.Theme.getSize("default_margin").width - 30
text: Cura.BuildPlateModel.getItem(index) ? Cura.BuildPlateModel.getItem(index).name : "";
color: Cura.BuildPlateModel.activeBuildPlate == index ? palette.highlightedText : palette.text
elide: Text.ElideRight
}
MouseArea
{
anchors.fill: parent;
onClicked:
{
Cura.SceneController.setActiveBuildPlate(index);
}
}
}
}
ScrollView
{
id: buildPlateSelection
frameVisible: true
height: UM.Theme.getSize("build_plate_selection_size").height
width: parent.width - 2 * UM.Theme.getSize("default_margin").height
style: UM.Theme.styles.scrollview
anchors
{
top: collapseButton.bottom;
topMargin: UM.Theme.getSize("default_margin").height;
left: parent.left;
leftMargin: UM.Theme.getSize("default_margin").height;
//bottom: objectsList.top;
bottomMargin: UM.Theme.getSize("default_margin").height;
}
Rectangle
{
parent: viewport
anchors.fill: parent
color: palette.light
}
ListView
{
id: buildPlateListView
model: Cura.BuildPlateModel
width: parent.width
delegate: buildPlateDelegate
}
}
Component {
id: objectDelegate
Rectangle
{
height: childrenRect.height
color: Cura.ObjectsModel.getItem(index).isSelected ? palette.highlight : index % 2 ? palette.base : palette.alternateBase
width: parent.width
Label
{
id: nodeNameLabel
anchors.left: parent.left
anchors.leftMargin: UM.Theme.getSize("default_margin").width
width: parent.width - 2 * UM.Theme.getSize("default_margin").width - 30
text: Cura.ObjectsModel.getItem(index) ? Cura.ObjectsModel.getItem(index).name : "";
color: Cura.ObjectsModel.getItem(index).isSelected ? palette.highlightedText : (Cura.ObjectsModel.getItem(index).isOutsideBuildArea ? palette.mid : palette.text)
elide: Text.ElideRight
}
Label
{
id: buildPlateNumberLabel
width: 20
anchors.left: nodeNameLabel.right
anchors.leftMargin: UM.Theme.getSize("default_margin").width
anchors.right: parent.right
text: Cura.ObjectsModel.getItem(index).buildPlateNumber != -1 ? Cura.ObjectsModel.getItem(index).buildPlateNumber + 1 : "";
color: Cura.ObjectsModel.getItem(index).isSelected ? palette.highlightedText : palette.text
elide: Text.ElideRight
}
MouseArea
{
anchors.fill: parent;
onClicked:
{
Cura.SceneController.changeSelection(index);
}
}
}
}
// list all the scene nodes
ScrollView
{
id: objectsList
frameVisible: true
visible: !collapsed
width: parent.width - 2 * UM.Theme.getSize("default_margin").height
anchors
{
top: buildPlateSelection.bottom;
topMargin: UM.Theme.getSize("default_margin").height;
left: parent.left;
leftMargin: UM.Theme.getSize("default_margin").height;
bottom: filterBuildPlateCheckbox.top;
bottomMargin: UM.Theme.getSize("default_margin").height;
}
Rectangle
{
parent: viewport
anchors.fill: parent
color: palette.light
}
ListView
{
id: listview
model: Cura.ObjectsModel
width: parent.width
delegate: objectDelegate
}
}
CheckBox
{
id: filterBuildPlateCheckbox
visible: !collapsed
checked: UM.Preferences.getValue("view/filter_current_build_plate")
onClicked: UM.Preferences.setValue("view/filter_current_build_plate", checked)
text: catalog.i18nc("@option:check","See only current build plate");
style: UM.Theme.styles.checkbox;
anchors
{
left: parent.left;
topMargin: UM.Theme.getSize("default_margin").height;
bottomMargin: UM.Theme.getSize("default_margin").height;
leftMargin: UM.Theme.getSize("default_margin").height;
bottom: arrangeAllBuildPlatesButton.top;
}
}
Button
{
id: arrangeAllBuildPlatesButton;
text: catalog.i18nc("@action:button","Arrange to all build plates");
style: UM.Theme.styles.sidebar_action_button
height: UM.Theme.getSize("objects_menu_button").height;
tooltip: '';
anchors
{
topMargin: UM.Theme.getSize("default_margin").height;
left: parent.left;
leftMargin: UM.Theme.getSize("default_margin").height;
right: parent.right;
rightMargin: UM.Theme.getSize("default_margin").height;
bottom: arrangeBuildPlateButton.top;
bottomMargin: UM.Theme.getSize("default_margin").height;
}
action: Cura.Actions.arrangeAllBuildPlates;
}
Button
{
id: arrangeBuildPlateButton;
text: catalog.i18nc("@action:button","Arrange current build plate");
style: UM.Theme.styles.sidebar_action_button
height: UM.Theme.getSize("objects_menu_button").height;
tooltip: '';
anchors
{
topMargin: UM.Theme.getSize("default_margin").height;
left: parent.left;
leftMargin: UM.Theme.getSize("default_margin").height;
right: parent.right;
rightMargin: UM.Theme.getSize("default_margin").height;
bottom: parent.bottom;
bottomMargin: UM.Theme.getSize("default_margin").height;
}
action: Cura.Actions.arrangeAll;
}
}

View file

@ -306,7 +306,7 @@ UM.PreferencesPage
text: catalog.i18nc("@option:check","Slice automatically"); text: catalog.i18nc("@option:check","Slice automatically");
} }
} }
Item Item
{ {
//: Spacer //: Spacer
@ -453,6 +453,34 @@ UM.PreferencesPage
text: catalog.i18nc("@label","Opening and saving files") text: catalog.i18nc("@label","Opening and saving files")
} }
UM.TooltipArea {
width: childrenRect.width
height: childrenRect.height
text: catalog.i18nc("@info:tooltip","Use multi build plate functionality (EXPERIMENTAL)")
CheckBox
{
id: useMultiBuildPlateCheckbox
text: catalog.i18nc("@option:check","Use multi build plate functionality (EXPERIMENTAL, restart)")
checked: boolCheck(UM.Preferences.getValue("cura/use_multi_build_plate"))
onCheckedChanged: UM.Preferences.setValue("cura/use_multi_build_plate", checked)
}
}
UM.TooltipArea {
width: childrenRect.width
height: childrenRect.height
text: catalog.i18nc("@info:tooltip","Should newly loaded models be arranged on the build plate? Used in conjunction with multi build plate (EXPERIMENTAL)")
CheckBox
{
id: arrangeOnLoadCheckbox
text: catalog.i18nc("@option:check","Arrange objects on load (EXPERIMENTAL)")
checked: boolCheck(UM.Preferences.getValue("cura/arrange_objects_on_load"))
onCheckedChanged: UM.Preferences.setValue("cura/arrange_objects_on_load", checked)
}
}
UM.TooltipArea { UM.TooltipArea {
width: childrenRect.width width: childrenRect.width
height: childrenRect.height height: childrenRect.height

View file

@ -1,18 +1,58 @@
// Copyright (c) 2017 Ultimaker B.V. // Copyright (c) 2017 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher. // Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2 import QtQuick 2.7
import QtQuick.Controls 1.1 import QtQuick.Controls 2.0
import QtQuick.Controls.Styles 1.1
import QtQuick.Layouts 1.1
import UM 1.1 as UM import UM 1.1 as UM
import Cura 1.0 as Cura import Cura 1.0 as Cura
Button { Button
id: base; {
id: base
style: UM.Theme.styles.sidebar_category; anchors.left: parent.left
anchors.right: parent.right
anchors.leftMargin: UM.Theme.getSize("sidebar_margin").width
anchors.rightMargin: UM.Theme.getSize("sidebar_margin").width
background: Rectangle
{
implicitHeight: UM.Theme.getSize("section").height
color: {
if (base.color) {
return base.color;
} else if (!base.enabled) {
return UM.Theme.getColor("setting_category_disabled");
} else if (base.hovered && base.checkable && base.checked) {
return UM.Theme.getColor("setting_category_active_hover");
} else if (base.pressed || (base.checkable && base.checked)) {
return UM.Theme.getColor("setting_category_active");
} else if (base.hovered) {
return UM.Theme.getColor("setting_category_hover");
} else {
return UM.Theme.getColor("setting_category");
}
}
Behavior on color { ColorAnimation { duration: 50; } }
Rectangle
{
height: UM.Theme.getSize("default_lining").height
width: parent.width
anchors.bottom: parent.bottom
color: {
if (!base.enabled) {
return UM.Theme.getColor("setting_category_disabled_border");
} else if ((base.hovered || base.activeFocus) && base.checkable && base.checked) {
return UM.Theme.getColor("setting_category_active_hover_border");
} else if (base.pressed || (base.checkable && base.checked)) {
return UM.Theme.getColor("setting_category_active_border");
} else if (base.hovered || base.activeFocus) {
return UM.Theme.getColor("setting_category_hover_border");
} else {
return UM.Theme.getColor("setting_category_border");
}
}
}
}
signal showTooltip(string text) signal showTooltip(string text)
signal hideTooltip() signal hideTooltip()
@ -23,20 +63,102 @@ Button {
property var focusItem: base property var focusItem: base
text: definition.label //text: definition.label
iconSource: UM.Theme.getIcon(definition.icon)
contentItem: Item {
anchors.fill: parent
anchors.left: parent.left
Label {
anchors
{
left: parent.left
leftMargin: 2 * UM.Theme.getSize("default_margin").width + UM.Theme.getSize("section_icon").width
right: parent.right;
verticalCenter: parent.verticalCenter;
}
text: definition.label
font: UM.Theme.getFont("setting_category")
color:
{
if (!base.enabled) {
return UM.Theme.getColor("setting_category_disabled_text");
} else if ((base.hovered || base.activeFocus) && base.checkable && base.checked) {
return UM.Theme.getColor("setting_category_active_hover_text");
} else if (base.pressed || (base.checkable && base.checked)) {
return UM.Theme.getColor("setting_category_active_text");
} else if (base.hovered || base.activeFocus) {
return UM.Theme.getColor("setting_category_hover_text");
} else {
return UM.Theme.getColor("setting_category_text");
}
}
fontSizeMode: Text.HorizontalFit
minimumPointSize: 8
}
UM.RecolorImage
{
id: category_arrow
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
anchors.rightMargin: UM.Theme.getSize("default_margin").width
width: UM.Theme.getSize("standard_arrow").width
height: UM.Theme.getSize("standard_arrow").height
sourceSize.width: width
sourceSize.height: width
color:
{
if (!base.enabled) {
return UM.Theme.getColor("setting_category_disabled_text");
} else if ((base.hovered || base.activeFocus) && base.checkable && base.checked) {
return UM.Theme.getColor("setting_category_active_hover_text");
} else if (base.pressed || (base.checkable && base.checked)) {
return UM.Theme.getColor("setting_category_active_text");
} else if (base.hovered || base.activeFocus) {
return UM.Theme.getColor("setting_category_hover_text");
} else {
return UM.Theme.getColor("setting_category_text");
}
}
source: base.checked ? UM.Theme.getIcon("arrow_bottom") : UM.Theme.getIcon("arrow_left")
}
}
UM.RecolorImage
{
id: icon
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: UM.Theme.getSize("default_margin").width
color:
{
if (!base.enabled) {
return UM.Theme.getColor("setting_category_disabled_text");
} else if((base.hovered || base.activeFocus) && base.checkable && base.checked) {
return UM.Theme.getColor("setting_category_active_hover_text");
} else if(base.pressed || (base.checkable && base.checked)) {
return UM.Theme.getColor("setting_category_active_text");
} else if(base.hovered || base.activeFocus) {
return UM.Theme.getColor("setting_category_hover_text");
} else {
return UM.Theme.getColor("setting_category_text");
}
}
source: UM.Theme.getIcon(definition.icon)
width: UM.Theme.getSize("section_icon").width;
height: UM.Theme.getSize("section_icon").height;
sourceSize.width: width + 15 * screenScaleFactor
sourceSize.height: width + 15 * screenScaleFactor
}
checkable: true checkable: true
checked: definition.expanded checked: definition.expanded
onClicked: onClicked:
{ {
if(definition.expanded) if (definition.expanded) {
{
settingDefinitionsModel.collapse(definition.key); settingDefinitionsModel.collapse(definition.key);
} } else {
else
{
settingDefinitionsModel.expandAll(definition.key); settingDefinitionsModel.expandAll(definition.key);
} }
//Set focus so that tab navigation continues from this point on. //Set focus so that tab navigation continues from this point on.
@ -70,13 +192,14 @@ Button {
anchors { anchors {
right: inheritButton.visible ? inheritButton.left : parent.right right: inheritButton.visible ? inheritButton.left : parent.right
rightMargin: inheritButton.visible? UM.Theme.getSize("default_margin").width / 2 : UM.Theme.getSize("setting_preferences_button_margin").width // use 1.9 as the factor because there is a 0.1 difference between the settings and inheritance warning icons
verticalCenter: parent.verticalCenter; rightMargin: inheritButton.visible ? UM.Theme.getSize("default_margin").width / 2 : category_arrow.width + UM.Theme.getSize("default_margin").width * 1.9
verticalCenter: parent.verticalCenter
} }
color: UM.Theme.getColor("setting_control_button"); color: UM.Theme.getColor("setting_control_button")
hoverColor: UM.Theme.getColor("setting_control_button_hover") hoverColor: UM.Theme.getColor("setting_control_button_hover")
iconSource: UM.Theme.getIcon("settings"); iconSource: UM.Theme.getIcon("settings")
onClicked: { onClicked: {
Cura.Actions.configureSettingVisibility.trigger(definition) Cura.Actions.configureSettingVisibility.trigger(definition)
@ -85,20 +208,20 @@ Button {
UM.SimpleButton UM.SimpleButton
{ {
id: inheritButton; id: inheritButton
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right anchors.right: parent.right
anchors.rightMargin: UM.Theme.getSize("setting_preferences_button_margin").width anchors.rightMargin: category_arrow.width + UM.Theme.getSize("default_margin").width * 2
visible: visible:
{ {
if(Cura.SettingInheritanceManager.settingsWithInheritanceWarning.indexOf(definition.key) >= 0) if (Cura.SettingInheritanceManager.settingsWithInheritanceWarning.indexOf(definition.key) >= 0)
{ {
var children_with_override = Cura.SettingInheritanceManager.getChildrenKeysWithOverride(definition.key) var children_with_override = Cura.SettingInheritanceManager.getChildrenKeysWithOverride(definition.key)
for(var i = 0; i < children_with_override.length; i++) for (var i = 0; i < children_with_override.length; i++)
{ {
if(!settingDefinitionsModel.getVisible(children_with_override[i])) if (!settingDefinitionsModel.getVisible(children_with_override[i]))
{ {
return true return true
} }

View file

@ -1,10 +1,9 @@
// Copyright (c) 2015 Ultimaker B.V. // Copyright (c) 2015 Ultimaker B.V.
// Uranium is released under the terms of the LGPLv3 or higher. // Uranium is released under the terms of the LGPLv3 or higher.
import QtQuick 2.1 import QtQuick 2.7
import QtQuick.Layouts 1.1 import QtQuick.Layouts 1.1
import QtQuick.Controls 1.1 import QtQuick.Controls 2.0
import QtQuick.Controls.Styles 1.1
import UM 1.2 as UM import UM 1.2 as UM

View file

@ -1,9 +1,8 @@
// Copyright (c) 2015 Ultimaker B.V. // Copyright (c) 2015 Ultimaker B.V.
// Uranium is released under the terms of the LGPLv3 or higher. // Uranium is released under the terms of the LGPLv3 or higher.
import QtQuick 2.1 import QtQuick 2.7
import QtQuick.Controls 1.1 import QtQuick.Controls 2.0
import QtQuick.Controls.Styles 1.1
import UM 1.1 as UM import UM 1.1 as UM
@ -17,95 +16,103 @@ SettingItem
id: control id: control
model: definition.options model: definition.options
textRole: "value"; textRole: "value"
anchors.fill: parent anchors.fill: parent
MouseArea MouseArea
{ {
anchors.fill: parent; anchors.fill: parent
acceptedButtons: Qt.NoButton; acceptedButtons: Qt.NoButton
onWheel: wheel.accepted = true; onWheel: wheel.accepted = true
} }
style: ComboBoxStyle background: Rectangle
{ {
background: Rectangle color:
{ {
color: if (!enabled) {
{ return UM.Theme.getColor("setting_control_disabled")
if(!enabled)
{
return UM.Theme.getColor("setting_control_disabled")
}
if(control.hovered || control.activeFocus)
{
return UM.Theme.getColor("setting_control_highlight")
}
return UM.Theme.getColor("setting_control")
} }
border.width: UM.Theme.getSize("default_lining").width
border.color: if (control.hovered || control.activeFocus) {
{ return UM.Theme.getColor("setting_control_highlight")
if(!enabled)
{
return UM.Theme.getColor("setting_control_disabled_border")
}
if(control.hovered || control.activeFocus)
{
return UM.Theme.getColor("setting_control_border_highlight")
}
return UM.Theme.getColor("setting_control_border")
} }
return UM.Theme.getColor("setting_control")
} }
label: Item
border.width: UM.Theme.getSize("default_lining").width
border.color:
{ {
Label if (!enabled) {
{ return UM.Theme.getColor("setting_control_disabled_border")
anchors.left: parent.left;
anchors.leftMargin: UM.Theme.getSize("default_lining").width
anchors.right: downArrow.left;
anchors.rightMargin: UM.Theme.getSize("default_lining").width;
anchors.verticalCenter: parent.verticalCenter;
text: control.currentText;
font: UM.Theme.getFont("default");
color: !enabled ? UM.Theme.getColor("setting_control_disabled_text") : UM.Theme.getColor("setting_control_text");
elide: Text.ElideRight;
verticalAlignment: Text.AlignVCenter;
} }
UM.RecolorImage if (control.hovered || control.activeFocus) {
{ return UM.Theme.getColor("setting_control_border_highlight")
id: downArrow
anchors.right: parent.right;
anchors.rightMargin: UM.Theme.getSize("default_lining").width * 2;
anchors.verticalCenter: parent.verticalCenter;
source: UM.Theme.getIcon("arrow_bottom")
width: UM.Theme.getSize("standard_arrow").width
height: UM.Theme.getSize("standard_arrow").height
sourceSize.width: width + 5 * screenScaleFactor
sourceSize.height: width + 5 * screenScaleFactor
color: UM.Theme.getColor("setting_control_text");
} }
return UM.Theme.getColor("setting_control_border")
}
}
indicator: UM.RecolorImage
{
id: downArrow
x: control.width - width - control.rightPadding
y: control.topPadding + (control.availableHeight - height) / 2
source: UM.Theme.getIcon("arrow_bottom")
width: UM.Theme.getSize("standard_arrow").width
height: UM.Theme.getSize("standard_arrow").height
sourceSize.width: width + 5 * screenScaleFactor
sourceSize.height: width + 5 * screenScaleFactor
color: UM.Theme.getColor("setting_control_text")
}
contentItem: Label
{
anchors.left: parent.left
anchors.leftMargin: UM.Theme.getSize("setting_unit_margin").width
anchors.verticalCenter: parent.verticalCenter
anchors.right: downArrow.left
text: control.currentText
font: UM.Theme.getFont("default")
color: !enabled ? UM.Theme.getColor("setting_control_disabled_text") : UM.Theme.getColor("setting_control_text")
elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter
}
delegate: ItemDelegate
{
width: control.width
height: control.height
highlighted: control.highlightedIndex == index
contentItem: Text
{
text: modelData.value
color: control.contentItem.color
font: UM.Theme.getFont("default")
elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter
} }
} }
onActivated: onActivated:
{ {
forceActiveFocus(); forceActiveFocus()
propertyProvider.setPropertyValue("value", definition.options[index].key); propertyProvider.setPropertyValue("value", definition.options[index].key)
} }
onActiveFocusChanged: onActiveFocusChanged:
{ {
if(activeFocus) if(activeFocus)
{ {
base.focusReceived(); base.focusReceived()
} }
} }
@ -113,6 +120,7 @@ SettingItem
{ {
base.setActiveFocusToNextSetting(true) base.setActiveFocusToNextSetting(true)
} }
Keys.onBacktabPressed: Keys.onBacktabPressed:
{ {
base.setActiveFocusToNextSetting(false) base.setActiveFocusToNextSetting(false)
@ -126,16 +134,14 @@ SettingItem
{ {
// FIXME this needs to go away once 'resolve' is combined with 'value' in our data model. // FIXME this needs to go away once 'resolve' is combined with 'value' in our data model.
var value = undefined; var value = undefined;
if ((base.resolve != "None") && (base.stackLevel != 0) && (base.stackLevel != 1)) if ((base.resolve != "None") && (base.stackLevel != 0) && (base.stackLevel != 1)) {
{
// We have a resolve function. Indicates that the setting is not settable per extruder and that // We have a resolve function. Indicates that the setting is not settable per extruder and that
// we have to choose between the resolved value (default) and the global value // we have to choose between the resolved value (default) and the global value
// (if user has explicitly set this). // (if user has explicitly set this).
value = base.resolve; value = base.resolve;
} }
if (value == undefined) if (value == undefined) {
{
value = propertyProvider.properties.value; value = propertyProvider.properties.value;
} }

View file

@ -1,9 +1,8 @@
// Copyright (c) 2016 Ultimaker B.V. // Copyright (c) 2016 Ultimaker B.V.
// Uranium is released under the terms of the LGPLv3 or higher. // Uranium is released under the terms of the LGPLv3 or higher.
import QtQuick 2.1 import QtQuick 2.7
import QtQuick.Controls 1.1 import QtQuick.Controls 2.0
import QtQuick.Controls.Styles 1.1
import UM 1.1 as UM import UM 1.1 as UM
import Cura 1.0 as Cura import Cura 1.0 as Cura
@ -65,83 +64,101 @@ SettingItem
value: control.currentText != "" ? control.model.getItem(control.currentIndex).color : "" value: control.currentText != "" ? control.model.getItem(control.currentIndex).color : ""
} }
style: ComboBoxStyle indicator: UM.RecolorImage
{ {
background: Rectangle id: downArrow
x: control.width - width - control.rightPadding
y: control.topPadding + (control.availableHeight - height) / 2
source: UM.Theme.getIcon("arrow_bottom")
width: UM.Theme.getSize("standard_arrow").width
height: UM.Theme.getSize("standard_arrow").height
sourceSize.width: width + 5 * screenScaleFactor
sourceSize.height: width + 5 * screenScaleFactor
color: UM.Theme.getColor("setting_control_text");
}
background: Rectangle
{
color:
{ {
color: if (!enabled)
{ {
if(!enabled) return UM.Theme.getColor("setting_control_disabled");
{
return UM.Theme.getColor("setting_control_disabled");
}
if(control.hovered || base.activeFocus)
{
return UM.Theme.getColor("setting_control_highlight");
}
return UM.Theme.getColor("setting_control");
} }
border.width: UM.Theme.getSize("default_lining").width if (control.hovered || base.activeFocus)
border.color:
{ {
if(!enabled) return UM.Theme.getColor("setting_control_highlight");
{
return UM.Theme.getColor("setting_control_disabled_border")
}
if(control.hovered || control.activeFocus)
{
return UM.Theme.getColor("setting_control_border_highlight")
}
return UM.Theme.getColor("setting_control_border")
} }
return UM.Theme.getColor("setting_control");
} }
label: Item border.width: UM.Theme.getSize("default_lining").width
border.color:
{ {
Label if (!enabled)
{ {
id: extruderText return UM.Theme.getColor("setting_control_disabled_border")
anchors.verticalCenter: parent.verticalCenter
text: control.currentText
font: UM.Theme.getFont("default")
color: enabled ? UM.Theme.getColor("setting_control_text") : UM.Theme.getColor("setting_control_disabled_text")
elide: Text.ElideLeft
verticalAlignment: Text.AlignVCenter
} }
Rectangle if (control.hovered || control.activeFocus)
{ {
id: swatch return UM.Theme.getColor("setting_control_border_highlight")
height: UM.Theme.getSize("setting_control").height / 2
width: height
anchors
{
right: arrow.left
verticalCenter: parent.verticalCenter
margins: UM.Theme.getSize("default_margin").width / 4
}
border.width: UM.Theme.getSize("default_lining").width * 2
border.color: enabled ? UM.Theme.getColor("setting_control_border") : UM.Theme.getColor("setting_control_disabled_border")
radius: width / 2
color: control.color
} }
UM.RecolorImage return UM.Theme.getColor("setting_control_border")
{ }
id: arrow }
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
source: UM.Theme.getIcon("arrow_bottom") contentItem: Item
width: UM.Theme.getSize("standard_arrow").width {
height: UM.Theme.getSize("standard_arrow").height Label
sourceSize.width: width + 5 * screenScaleFactor {
sourceSize.height: width + 5 * screenScaleFactor id: extruderText
color: UM.Theme.getColor("setting_control_text") anchors.verticalCenter: parent.verticalCenter
} anchors.left: parent.left
anchors.leftMargin: UM.Theme.getSize("setting_unit_margin").width
anchors.right: swatch.left
text: control.currentText
font: UM.Theme.getFont("default")
color: enabled ? UM.Theme.getColor("setting_control_text") : UM.Theme.getColor("setting_control_disabled_text")
elide: Text.ElideLeft
verticalAlignment: Text.AlignVCenter
}
Rectangle
{
id: swatch
height: UM.Theme.getSize("setting_control").height / 2
width: height
anchors.right: parent.right
anchors.rightMargin: downArrow.width + UM.Theme.getSize("setting_unit_margin").width
anchors.verticalCenter: parent.verticalCenter
anchors.margins: UM.Theme.getSize("default_margin").width / 4
border.width: UM.Theme.getSize("default_lining").width
border.color: enabled ? UM.Theme.getColor("setting_control_border") : UM.Theme.getColor("setting_control_disabled_border")
radius: width / 2
color: control.color
}
}
delegate: ItemDelegate
{
width: control.width
height: control.height
highlighted: control.highlightedIndex == index
contentItem: Text
{
text: model.name
color: UM.Theme.getColor("setting_control_text")
font: UM.Theme.getFont("default")
elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter
} }
} }
} }

View file

@ -1,10 +1,9 @@
// Copyright (c) 2017 Ultimaker B.V. // Copyright (c) 2017 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher. // Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.1 import QtQuick 2.7
import QtQuick.Layouts 1.1 import QtQuick.Layouts 1.1
import QtQuick.Controls 1.1 import QtQuick.Controls 2.0
import QtQuick.Controls.Styles 1.1
import UM 1.1 as UM import UM 1.1 as UM
import Cura 1.0 as Cura import Cura 1.0 as Cura

View file

@ -1,9 +1,8 @@
// Copyright (c) 2016 Ultimaker B.V. // Copyright (c) 2016 Ultimaker B.V.
// Uranium is released under the terms of the LGPLv3 or higher. // Uranium is released under the terms of the LGPLv3 or higher.
import QtQuick 2.1 import QtQuick 2.7
import QtQuick.Controls 1.1 import QtQuick.Controls 2.0
import QtQuick.Controls.Styles 1.1
import UM 1.1 as UM import UM 1.1 as UM
import Cura 1.0 as Cura import Cura 1.0 as Cura
@ -84,83 +83,101 @@ SettingItem
value: control.currentText != "" ? control.model.getItem(control.currentIndex).color : "" value: control.currentText != "" ? control.model.getItem(control.currentIndex).color : ""
} }
style: ComboBoxStyle indicator: UM.RecolorImage
{ {
background: Rectangle id: downArrow
x: control.width - width - control.rightPadding
y: control.topPadding + (control.availableHeight - height) / 2
source: UM.Theme.getIcon("arrow_bottom")
width: UM.Theme.getSize("standard_arrow").width
height: UM.Theme.getSize("standard_arrow").height
sourceSize.width: width + 5 * screenScaleFactor
sourceSize.height: width + 5 * screenScaleFactor
color: UM.Theme.getColor("setting_control_text");
}
background: Rectangle
{
color:
{ {
color: if (!enabled)
{ {
if(!enabled) return UM.Theme.getColor("setting_control_disabled");
{
return UM.Theme.getColor("setting_control_disabled");
}
if(control.hovered || control.activeFocus)
{
return UM.Theme.getColor("setting_control_highlight");
}
return UM.Theme.getColor("setting_control");
} }
border.width: UM.Theme.getSize("default_lining").width if (control.hovered || control.activeFocus)
border.color:
{ {
if(!enabled) return UM.Theme.getColor("setting_control_highlight");
{
return UM.Theme.getColor("setting_control_disabled_border")
}
if(control.hovered || control.activeFocus)
{
return UM.Theme.getColor("setting_control_border_highlight")
}
return UM.Theme.getColor("setting_control_border")
} }
return UM.Theme.getColor("setting_control");
} }
label: Item border.width: UM.Theme.getSize("default_lining").width
border.color:
{ {
Label if (!enabled)
{ {
anchors.verticalCenter: parent.verticalCenter return UM.Theme.getColor("setting_control_disabled_border")
width: parent.width - swatch.width - arrow.width;
text: control.currentText
font: UM.Theme.getFont("default")
color: enabled ? UM.Theme.getColor("setting_control_text") : UM.Theme.getColor("setting_control_disabled_text")
elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter
} }
Rectangle if (control.hovered || control.activeFocus)
{ {
id: swatch return UM.Theme.getColor("setting_control_border_highlight")
height: UM.Theme.getSize("setting_control").height / 2
width: height
anchors
{
right: arrow.left;
verticalCenter: parent.verticalCenter
margins: UM.Theme.getSize("default_margin").width / 4
}
border.width: UM.Theme.getSize("default_lining").width * 2
border.color: enabled ? UM.Theme.getColor("setting_control_border") : UM.Theme.getColor("setting_control_disabled_border")
radius: width / 2
color: control.color
} }
UM.RecolorImage return UM.Theme.getColor("setting_control_border")
{ }
id: arrow }
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
source: UM.Theme.getIcon("arrow_bottom") contentItem: Item
width: UM.Theme.getSize("standard_arrow").width {
height: UM.Theme.getSize("standard_arrow").height Label
sourceSize.width: width + 5 * screenScaleFactor {
sourceSize.height: width + 5 * screenScaleFactor id: extruderText
color: UM.Theme.getColor("setting_control_text") anchors.verticalCenter: parent.verticalCenter
} anchors.left: parent.left
anchors.leftMargin: UM.Theme.getSize("setting_unit_margin").width
anchors.right: swatch.left
text: control.currentText
font: UM.Theme.getFont("default")
color: enabled ? UM.Theme.getColor("setting_control_text") : UM.Theme.getColor("setting_control_disabled_text")
elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter
}
Rectangle
{
id: swatch
height: UM.Theme.getSize("setting_control").height / 2
width: height
anchors.right: parent.right
anchors.rightMargin: downArrow.width + UM.Theme.getSize("setting_unit_margin").width
anchors.verticalCenter: parent.verticalCenter
anchors.margins: UM.Theme.getSize("default_margin").width / 4
border.width: UM.Theme.getSize("default_lining").width
border.color: enabled ? UM.Theme.getColor("setting_control_border") : UM.Theme.getColor("setting_control_disabled_border")
radius: width / 2
color: control.color
}
}
delegate: ItemDelegate
{
width: control.width
height: control.height
highlighted: control.highlightedIndex == index
contentItem: Text
{
text: model.name
color: UM.Theme.getColor("setting_control_text")
font: UM.Theme.getFont("default")
elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter
} }
} }
} }

View file

@ -1,8 +1,8 @@
// Copyright (c) 2017 Ultimaker B.V. // Copyright (c) 2017 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher. // Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2 import QtQuick 2.7
import QtQuick.Controls 1.2 import QtQuick.Controls 2.0
import UM 1.1 as UM import UM 1.1 as UM
@ -17,6 +17,7 @@ SettingItem
{ {
textHasChanged = false; textHasChanged = false;
textBeforeEdit = focusItem.text; textBeforeEdit = focusItem.text;
focusItem.selectAll();
} }
contents: Rectangle contents: Rectangle

View file

@ -1,8 +1,8 @@
// Copyright (c) 2015 Ultimaker B.V. // Copyright (c) 2015 Ultimaker B.V.
// Uranium is released under the terms of the LGPLv3 or higher. // Uranium is released under the terms of the LGPLv3 or higher.
import QtQuick 2.1 import QtQuick 2.7
import QtQuick.Controls 1.1 import QtQuick.Controls 2.0
import UM 1.2 as UM import UM 1.2 as UM

View file

@ -1,7 +1,7 @@
// Copyright (c) 2017 Ultimaker B.V. // Copyright (c) 2017 Ultimaker B.V.
// Uranium is released under the terms of the LGPLv3 or higher. // Uranium is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2 import QtQuick 2.7
import QtQuick.Controls 1.1 import QtQuick.Controls 1.1
import QtQuick.Controls.Styles 1.1 import QtQuick.Controls.Styles 1.1
import QtQuick.Layouts 1.1 import QtQuick.Layouts 1.1
@ -372,7 +372,7 @@ Item
{ {
id: provider id: provider
containerStackId: Cura.ExtruderManager.activeExtruderStackId containerStackId: Cura.MachineManager.activeMachineId
key: model.key ? model.key : "" key: model.key ? model.key : ""
watchedProperties: [ "value", "enabled", "state", "validationState", "settable_per_extruder", "resolve" ] watchedProperties: [ "value", "enabled", "state", "validationState", "settable_per_extruder", "resolve" ]
storeIndex: 0 storeIndex: 0

View file

@ -1,10 +1,9 @@
// Copyright (c) 2017 Ultimaker B.V. // Copyright (c) 2017 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher. // Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2 import QtQuick 2.7
import QtQuick.Controls 1.1 import QtQuick.Controls 2.0
import QtQuick.Controls.Styles 1.1 import QtQuick.Layouts 1.3
import QtQuick.Layouts 1.1
import UM 1.2 as UM import UM 1.2 as UM
import Cura 1.0 as Cura import Cura 1.0 as Cura
@ -12,9 +11,9 @@ import "Menus"
Rectangle Rectangle
{ {
id: base; id: base
property int currentModeIndex; property int currentModeIndex
property bool hideSettings: PrintInformation.preSliced property bool hideSettings: PrintInformation.preSliced
property bool hideView: Cura.MachineManager.activeMachineName == "" property bool hideView: Cura.MachineManager.activeMachineName == ""
@ -78,7 +77,7 @@ Rectangle
MouseArea MouseArea
{ {
anchors.fill: parent anchors.fill: parent
acceptedButtons: Qt.AllButtons; acceptedButtons: Qt.AllButtons
onWheel: onWheel:
{ {
@ -119,13 +118,14 @@ Rectangle
UM.Preferences.setValue("cura/active_mode", currentModeIndex); UM.Preferences.setValue("cura/active_mode", currentModeIndex);
if(modesListModel.count > base.currentModeIndex) if(modesListModel.count > base.currentModeIndex)
{ {
sidebarContents.push({ "item": modesListModel.get(base.currentModeIndex).item, "replace": true }); sidebarContents.replace(modesListModel.get(base.currentModeIndex).item, { "replace": true })
} }
} }
Label { Label
{
id: settingsModeLabel id: settingsModeLabel
text: !hideSettings ? catalog.i18nc("@label:listbox", "Print Setup") : catalog.i18nc("@label:listbox","Print Setup disabled\nG-code files cannot be modified"); text: !hideSettings ? catalog.i18nc("@label:listbox", "Print Setup") : catalog.i18nc("@label:listbox", "Print Setup disabled\nG-code files cannot be modified")
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: UM.Theme.getSize("sidebar_margin").width anchors.leftMargin: UM.Theme.getSize("sidebar_margin").width
anchors.top: headerSeparator.bottom anchors.top: headerSeparator.bottom
@ -136,13 +136,18 @@ Rectangle
visible: !monitoringPrint && !hideView visible: !monitoringPrint && !hideView
} }
Rectangle { // Settings mode selection toggle
Rectangle
{
id: settingsModeSelection id: settingsModeSelection
color: "transparent" color: "transparent"
width: Math.floor(parent.width * 0.55) width: Math.floor(parent.width * 0.55)
height: UM.Theme.getSize("sidebar_header_mode_toggle").height height: UM.Theme.getSize("sidebar_header_mode_toggle").height
anchors.right: parent.right anchors.right: parent.right
anchors.rightMargin: UM.Theme.getSize("sidebar_margin").width anchors.rightMargin: UM.Theme.getSize("sidebar_margin").width
anchors.topMargin: UM.Theme.getSize("sidebar_margin").height
anchors.top: anchors.top:
{ {
if (settingsModeLabel.contentWidth >= parent.width - width - UM.Theme.getSize("sidebar_margin").width * 2) if (settingsModeLabel.contentWidth >= parent.width - width - UM.Theme.getSize("sidebar_margin").width * 2)
@ -154,66 +159,69 @@ Rectangle
return headerSeparator.bottom; return headerSeparator.bottom;
} }
} }
anchors.topMargin: UM.Theme.getSize("sidebar_margin").height
visible: !monitoringPrint && !hideSettings && !hideView visible: !monitoringPrint && !hideSettings && !hideView
Component{
Component
{
id: wizardDelegate id: wizardDelegate
Button {
Button
{
id: control
height: settingsModeSelection.height height: settingsModeSelection.height
width: Math.floor(0.5 * parent.width)
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: model.index * Math.floor(settingsModeSelection.width / 2) anchors.leftMargin: model.index * Math.floor(settingsModeSelection.width / 2)
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
width: Math.floor(0.5 * parent.width)
text: model.text ButtonGroup.group: modeMenuGroup
exclusiveGroup: modeMenuGroup;
checkable: true; checkable: true
checked: base.currentModeIndex == index checked: base.currentModeIndex == index
onClicked: base.currentModeIndex = index onClicked: base.currentModeIndex = index
onHoveredChanged: { onHoveredChanged:
{
if (hovered) if (hovered)
{ {
tooltipDelayTimer.item = settingsModeSelection tooltipDelayTimer.item = settingsModeSelection
tooltipDelayTimer.text = model.tooltipText tooltipDelayTimer.text = model.tooltipText
tooltipDelayTimer.start(); tooltipDelayTimer.start()
} }
else else
{ {
tooltipDelayTimer.stop(); tooltipDelayTimer.stop()
base.hideTooltip(); base.hideTooltip()
} }
} }
style: ButtonStyle { background: Rectangle
background: Rectangle { {
border.width: control.checked ? UM.Theme.getSize("default_lining").width * 2 : UM.Theme.getSize("default_lining").width border.width: control.checked ? UM.Theme.getSize("default_lining").width * 2 : UM.Theme.getSize("default_lining").width
border.color: (control.checked || control.pressed) ? UM.Theme.getColor("action_button_active_border") : border.color: (control.checked || control.pressed) ? UM.Theme.getColor("action_button_active_border") : control.hovered ? UM.Theme.getColor("action_button_hovered_border"): UM.Theme.getColor("action_button_border")
control.hovered ? UM.Theme.getColor("action_button_hovered_border") :
UM.Theme.getColor("action_button_border") // for some reason, QtQuick decided to use the color of the background property as text color for the contentItem, so here it is
color: (control.checked || control.pressed) ? UM.Theme.getColor("action_button_active") : color: (control.checked || control.pressed) ? UM.Theme.getColor("action_button_active") : control.hovered ? UM.Theme.getColor("action_button_hovered") : UM.Theme.getColor("action_button")
control.hovered ? UM.Theme.getColor("action_button_hovered") : }
UM.Theme.getColor("action_button")
Behavior on color { ColorAnimation { duration: 50; } } contentItem: Text
Label { {
anchors.left: parent.left text: model.text
anchors.right: parent.right font: UM.Theme.getFont("default")
anchors.verticalCenter: parent.verticalCenter horizontalAlignment: Text.AlignHCenter
anchors.leftMargin: UM.Theme.getSize("default_lining").width * 2 verticalAlignment: Text.AlignVCenter
anchors.rightMargin: UM.Theme.getSize("default_lining").width * 2 elide: Text.ElideRight
color: (control.checked || control.pressed) ? UM.Theme.getColor("action_button_active_text") :
control.hovered ? UM.Theme.getColor("action_button_hovered_text") :
UM.Theme.getColor("action_button_text")
font: UM.Theme.getFont("default")
text: control.text
horizontalAlignment: Text.AlignHCenter
elide: Text.ElideMiddle
}
}
label: Item { }
} }
} }
} }
ExclusiveGroup { id: modeMenuGroup; }
ButtonGroup
{
id: modeMenuGroup
}
ListView ListView
{ {
@ -238,31 +246,21 @@ Rectangle
anchors.right: base.right anchors.right: base.right
visible: !monitoringPrint && !hideSettings visible: !monitoringPrint && !hideSettings
delegate: StackViewDelegate replaceEnter: Transition {
{ PropertyAnimation {
function transitionFinished(properties) property: "opacity"
{ from: 0
properties.exitItem.opacity = 1 to:1
duration: 100
} }
}
pushTransition: StackViewTransition replaceExit: Transition {
{ PropertyAnimation {
PropertyAnimation property: "opacity"
{ from: 1
target: enterItem to:0
property: "opacity" duration: 100
from: 0
to: 1
duration: 100
}
PropertyAnimation
{
target: exitItem
property: "opacity"
from: 1
to: 0
duration: 100
}
} }
} }
} }
@ -559,19 +557,19 @@ Rectangle
SidebarTooltip SidebarTooltip
{ {
id: tooltip; id: tooltip
} }
// Setting mode: Recommended or Custom // Setting mode: Recommended or Custom
ListModel ListModel
{ {
id: modesListModel; id: modesListModel
} }
SidebarSimple SidebarSimple
{ {
id: sidebarSimple; id: sidebarSimple
visible: false; visible: false
onShowTooltip: base.showTooltip(item, location, text) onShowTooltip: base.showTooltip(item, location, text)
onHideTooltip: base.hideTooltip() onHideTooltip: base.hideTooltip()
@ -579,8 +577,8 @@ Rectangle
SidebarAdvanced SidebarAdvanced
{ {
id: sidebarAdvanced; id: sidebarAdvanced
visible: false; visible: false
onShowTooltip: base.showTooltip(item, location, text) onShowTooltip: base.showTooltip(item, location, text)
onHideTooltip: base.hideTooltip() onHideTooltip: base.hideTooltip()
@ -598,7 +596,7 @@ Rectangle
tooltipText: catalog.i18nc("@tooltip", "<b>Custom Print Setup</b><br/><br/>Print with finegrained control over every last bit of the slicing process."), tooltipText: catalog.i18nc("@tooltip", "<b>Custom Print Setup</b><br/><br/>Print with finegrained control over every last bit of the slicing process."),
item: sidebarAdvanced item: sidebarAdvanced
}) })
sidebarContents.push({ "item": modesListModel.get(base.currentModeIndex).item, "immediate": true }); sidebarContents.replace(modesListModel.get(base.currentModeIndex).item, { "immediate": true })
var index = Math.floor(UM.Preferences.getValue("cura/active_mode")) var index = Math.floor(UM.Preferences.getValue("cura/active_mode"))
if(index) if(index)

View file

@ -1,9 +1,8 @@
// Copyright (c) 2015 Ultimaker B.V. // Copyright (c) 2015 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher. // Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.0 import QtQuick 2.7
import QtQuick.Controls 2.0
import QtQuick.Controls 1.2
import "Settings" import "Settings"

View file

@ -1,17 +1,17 @@
// Copyright (c) 2017 Ultimaker B.V. // Copyright (c) 2017 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher. // Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2 import QtQuick 2.7
import QtQuick.Controls 1.1 import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.1 import QtQuick.Controls.Styles 1.4
import QtQuick.Layouts 1.1 import QtQuick.Layouts 1.3
import UM 1.2 as UM import UM 1.2 as UM
import Cura 1.2 as Cura import Cura 1.2 as Cura
Item Item
{ {
id: base; id: base
signal showTooltip(Item item, point location, string text); signal showTooltip(Item item, point location, string text);
signal hideTooltip(); signal hideTooltip();
@ -70,6 +70,18 @@ Item
onActiveVariantChanged: qualityModel.update() onActiveVariantChanged: qualityModel.update()
} }
Connections {
target: base
onVisibleChanged:
{
// update needs to be called when the widgets are visible, otherwise the step width calculation
// will fail because the width of an invisible item is 0.
if (visible) {
qualityModel.update();
}
}
}
ListModel ListModel
{ {
id: qualityModel id: qualityModel
@ -103,7 +115,7 @@ Item
if (Cura.SimpleModeSettingsManager.isProfileUserCreated) { if (Cura.SimpleModeSettingsManager.isProfileUserCreated) {
qualityModel.qualitySliderActiveIndex = -1 qualityModel.qualitySliderActiveIndex = -1
} else { } else {
qualityModel.qualitySliderActiveIndex = i qualityModel.qualitySliderActiveIndex = i
} }
qualityModel.existingQualityProfile = 1 qualityModel.existingQualityProfile = 1
@ -183,11 +195,10 @@ Item
text: text:
{ {
var result = "" var result = ""
if(Cura.MachineManager.activeMachine != null){ if (Cura.MachineManager.activeMachine != null) {
result = Cura.ProfilesModel.getItem(index).layer_height_without_unit
var result = Cura.ProfilesModel.getItem(index).layer_height_without_unit if (result == undefined)
if(result == undefined)
result = "" result = ""
} }
return result return result

View file

@ -116,10 +116,8 @@ Rectangle
iconSource: UM.Theme.getIcon("view_3d") iconSource: UM.Theme.getIcon("view_3d")
style: UM.Theme.styles.small_tool_button style: UM.Theme.styles.small_tool_button
anchors.verticalCenter: viewOrientationControl.verticalCenter anchors.verticalCenter: viewOrientationControl.verticalCenter
onClicked:{ onClicked:UM.Controller.rotateView("3d", 0)
UM.Controller.rotateView("3d", 0); visible: base.width - allItemsWidth - 4 * this.width > 0
}
visible: base.width - allItemsWidth - 4 * this.width > 0;
} }
// #2 Front view // #2 Front view
@ -128,10 +126,8 @@ Rectangle
iconSource: UM.Theme.getIcon("view_front") iconSource: UM.Theme.getIcon("view_front")
style: UM.Theme.styles.small_tool_button style: UM.Theme.styles.small_tool_button
anchors.verticalCenter: viewOrientationControl.verticalCenter anchors.verticalCenter: viewOrientationControl.verticalCenter
onClicked:{ onClicked: UM.Controller.rotateView("home", 0);
UM.Controller.rotateView("home", 0); visible: base.width - allItemsWidth - 3 * this.width > 0
}
visible: base.width - allItemsWidth - 3 * this.width > 0;
} }
// #3 Top view // #3 Top view
@ -140,10 +136,8 @@ Rectangle
iconSource: UM.Theme.getIcon("view_top") iconSource: UM.Theme.getIcon("view_top")
style: UM.Theme.styles.small_tool_button style: UM.Theme.styles.small_tool_button
anchors.verticalCenter: viewOrientationControl.verticalCenter anchors.verticalCenter: viewOrientationControl.verticalCenter
onClicked:{ onClicked: UM.Controller.rotateView("y", 90)
UM.Controller.rotateView("y", 90); visible: base.width - allItemsWidth - 2 * this.width > 0
}
visible: base.width - allItemsWidth - 2 * this.width > 0;
} }
// #4 Left view // #4 Left view
@ -152,10 +146,8 @@ Rectangle
iconSource: UM.Theme.getIcon("view_left") iconSource: UM.Theme.getIcon("view_left")
style: UM.Theme.styles.small_tool_button style: UM.Theme.styles.small_tool_button
anchors.verticalCenter: viewOrientationControl.verticalCenter anchors.verticalCenter: viewOrientationControl.verticalCenter
onClicked:{ onClicked: UM.Controller.rotateView("x", 90)
UM.Controller.rotateView("x", 90); visible: base.width - allItemsWidth - 1 * this.width > 0
}
visible: base.width - allItemsWidth - 1 * this.width > 0;
} }
// #5 Left view // #5 Left view
@ -164,10 +156,8 @@ Rectangle
iconSource: UM.Theme.getIcon("view_right") iconSource: UM.Theme.getIcon("view_right")
style: UM.Theme.styles.small_tool_button style: UM.Theme.styles.small_tool_button
anchors.verticalCenter: viewOrientationControl.verticalCenter anchors.verticalCenter: viewOrientationControl.verticalCenter
onClicked:{ onClicked: UM.Controller.rotateView("x", -90)
UM.Controller.rotateView("x", -90); visible: base.width - allItemsWidth > 0
}
visible: base.width - allItemsWidth > 0;
} }
} }

View file

@ -0,0 +1,60 @@
[general]
version = 2
name = Draft
definition = anycubic_i3_mega
[metadata]
type = quality
quality_type = draft
weight = 0
setting_version = 4
[values]
acceleration_enabled = True
acceleration_print = 2000
acceleration_travel = 3500
adhesion_type = skirt
brim_width = 4.0
cool_fan_full_at_height = 0.5
cool_fan_speed = 100
cool_fan_speed_0 = 100
infill_overlap = 15
infill_pattern = zigzag
infill_sparse_density = 25
initial_layer_line_width_factor = 140
jerk_enabled = True
jerk_print = 13
jerk_travel = 13
layer_height = 0.4
layer_height_0 = 0.4
material_bed_temperature = 60
material_diameter = 1.75
material_print_temperature = 200
material_print_temperature_layer_0 = 0
retract_at_layer_change = False
retraction_amount = 7
retraction_hop = 0.075
retraction_hop_enabled = True
retraction_hop_only_when_collides = True
retraction_min_travel = 1.5
retraction_speed = 40
skirt_brim_speed = 40
skirt_gap = 5
skirt_line_count = 3
speed_infill = 60
speed_print = 60
speed_support = 60
speed_topbottom = 30
speed_travel = 100
speed_wall = 60
speed_wall_x = 60
support_angle = 60
support_enable = True
support_interface_enable = True
support_pattern = triangles
support_roof_enable = True
support_type = everywhere
support_use_towers = False
support_xy_distance = 0.7
top_bottom_thickness = 1.2
wall_thickness = 1.2

View file

@ -0,0 +1,60 @@
[general]
version = 2
name = High
definition = anycubic_i3_mega
[metadata]
type = quality
quality_type = high
weight = 2
setting_version = 4
[values]
acceleration_enabled = True
acceleration_print = 2000
acceleration_travel = 3500
adhesion_type = skirt
brim_width = 4.0
cool_fan_full_at_height = 0.5
cool_fan_speed = 100
cool_fan_speed_0 = 100
infill_overlap = 15
infill_pattern = zigzag
infill_sparse_density = 25
initial_layer_line_width_factor = 140
jerk_enabled = True
jerk_print = 13
jerk_travel = 13
layer_height = 0.1
layer_height_0 = 0.1
material_bed_temperature = 60
material_diameter = 1.75
material_print_temperature = 200
material_print_temperature_layer_0 = 0
retract_at_layer_change = False
retraction_amount = 7
retraction_hop = 0.075
retraction_hop_enabled = True
retraction_hop_only_when_collides = True
retraction_min_travel = 1.5
retraction_speed = 40
skirt_brim_speed = 40
skirt_gap = 5
skirt_line_count = 3
speed_infill = 50
speed_print = 50
speed_support = 30
speed_topbottom = 20
speed_travel = 50
speed_wall = 50
speed_wall_x = 50
support_angle = 60
support_enable = True
support_interface_enable = True
support_pattern = triangles
support_roof_enable = True
support_type = everywhere
support_use_towers = False
support_xy_distance = 0.7
top_bottom_thickness = 1.2
wall_thickness = 1.2

View file

@ -0,0 +1,60 @@
[general]
version = 2
name = Normal
definition = anycubic_i3_mega
[metadata]
type = quality
quality_type = normal
weight = 1
setting_version = 4
[values]
acceleration_enabled = True
acceleration_print = 2000
acceleration_travel = 3500
adhesion_type = skirt
brim_width = 4.0
cool_fan_full_at_height = 0.5
cool_fan_speed = 100
cool_fan_speed_0 = 100
infill_overlap = 15
infill_pattern = zigzag
infill_sparse_density = 25
initial_layer_line_width_factor = 140
jerk_enabled = True
jerk_print = 13
jerk_travel = 13
layer_height = 0.2
layer_height_0 = 0.2
material_bed_temperature = 60
material_diameter = 1.75
material_print_temperature = 200
material_print_temperature_layer_0 = 0
retract_at_layer_change = False
retraction_amount = 7
retraction_hop = 0.075
retraction_hop_enabled = True
retraction_hop_only_when_collides = True
retraction_min_travel = 1.5
retraction_speed = 40
skirt_brim_speed = 40
skirt_gap = 5
skirt_line_count = 3
speed_infill = 50
speed_print = 50
speed_support = 30
speed_topbottom = 20
speed_travel = 100
speed_wall = 50
speed_wall_x = 50
support_angle = 60
support_enable = True
support_interface_enable = True
support_pattern = triangles
support_roof_enable = True
support_type = everywhere
support_use_towers = False
support_xy_distance = 0.7
top_bottom_thickness = 1.2
wall_thickness = 1.2

View file

@ -0,0 +1,33 @@
[general]
version = 2
name = Draft
definition = tevo_blackwidow
[metadata]
type = quality
quality_type = draft
weight = -2
setting_version = 4
[values]
brim_width = 4.0
infill_pattern = zigzag
layer_height = 0.4
material_diameter = 1.75
speed_infill = 50
speed_print = 50
speed_support = 30
speed_topbottom = 20
speed_travel = 100
speed_wall = 50
speed_wall_x = 50
support_angle = 60
support_enable = True
support_interface_enable = True
support_pattern = triangles
support_roof_enable = True
support_type = everywhere
support_use_towers = False
support_xy_distance = 0.7
top_bottom_thickness = 1.2
wall_thickness = 1.2

View file

@ -0,0 +1,33 @@
[general]
version = 2
name = High
definition = tevo_blackwidow
[metadata]
type = quality
quality_type = high
weight = 1
setting_version = 4
[values]
brim_width = 4.0
infill_pattern = zigzag
layer_height = 0.1
material_diameter = 1.75
speed_infill = 50
speed_print = 50
speed_support = 30
speed_topbottom = 15
speed_travel = 100
speed_wall = 50
speed_wall_x = 50
support_angle = 60
support_enable = True
support_interface_enable = True
support_pattern = triangles
support_roof_enable = True
support_type = everywhere
support_use_towers = False
support_xy_distance = 0.7
top_bottom_thickness = 1.2
wall_thickness = 1.2

View file

@ -0,0 +1,33 @@
[general]
version = 2
name = Normal
definition = tevo_blackwidow
[metadata]
type = quality
quality_type = normal
weight = 0
setting_version = 4
[values]
brim_width = 4.0
infill_pattern = zigzag
layer_height = 0.2
material_diameter = 1.75
speed_infill = 60
speed_print = 50
speed_support = 30
speed_topbottom = 20
speed_travel = 100
speed_wall = 50
speed_wall_x = 50
support_angle = 60
support_enable = True
support_interface_enable = True
support_pattern = triangles
support_roof_enable = True
support_type = everywhere
support_use_towers = False
support_xy_distance = 0.7
top_bottom_thickness = 1.2
wall_thickness = 1.2

View file

@ -391,6 +391,11 @@
"infill_button_margin": [0.5, 0.5], "infill_button_margin": [0.5, 0.5],
"jobspecs_line": [2.0, 2.0] "jobspecs_line": [2.0, 2.0],
"objects_menu_size": [20, 40],
"objects_menu_size_collapsed": [20, 17],
"build_plate_selection_size": [15, 5],
"objects_menu_button": [0.3, 2.7]
} }
} }

View file

@ -1,9 +1,7 @@
import pytest
import numpy import numpy
import time
from cura.Arrange import Arrange from cura.Arranging.Arrange import Arrange
from cura.ShapeArray import ShapeArray from cura.Arranging.ShapeArray import ShapeArray
def gimmeShapeArray(): def gimmeShapeArray():
@ -120,6 +118,13 @@ def test_arrayFromPolygon2():
assert numpy.any(array) assert numpy.any(array)
## Polygon -> array
def test_fromPolygon():
vertices = numpy.array([[0, 0.5], [0, 0], [0.5, 0]])
array = ShapeArray.fromPolygon(vertices, scale=0.5)
assert numpy.any(array.arr)
## Line definition -> array with true/false ## Line definition -> array with true/false
def test_check(): def test_check():
base_array = numpy.zeros([5, 5], dtype=float) base_array = numpy.zeros([5, 5], dtype=float)