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

This commit is contained in:
Ghostkeeper 2020-04-14 14:20:17 +02:00
commit 0e30f0bb6b
No known key found for this signature in database
GPG Key ID: D2A8871EE34EC59A
29 changed files with 81 additions and 80 deletions

View File

@ -1,6 +1,6 @@
# Copyright (c) 2020 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from typing import List, Optional
from typing import Optional
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Logger import Logger
@ -16,18 +16,17 @@ from collections import namedtuple
import numpy
import copy
## Return object for bestSpot
LocationSuggestion = namedtuple("LocationSuggestion", ["x", "y", "penalty_points", "priority"])
## The Arrange classed is used together with ShapeArray. Use it to find
# good locations for objects that you try to put on a build place.
# Different priority schemes can be defined so it alters the behavior while using
# the same logic.
#
# Note: Make sure the scale is the same between ShapeArray objects and the Arrange instance.
class Arrange:
"""
The Arrange classed is used together with ShapeArray. Use it to find good locations for objects that you try to put
on a build place. Different priority schemes can be defined so it alters the behavior while using the same logic.
Note: Make sure the scale is the same between ShapeArray objects and the Arrange instance.
"""
build_volume = None # type: Optional[BuildVolume]
def __init__(self, x, y, offset_x, offset_y, scale = 0.5):
@ -42,14 +41,21 @@ class Arrange:
self._last_priority = 0
self._is_empty = True
## Helper to create an Arranger instance
#
# Either fill in scene_root and create will find all sliceable nodes by itself,
# or use fixed_nodes to provide the nodes yourself.
# \param scene_root Root for finding all scene nodes
# \param fixed_nodes Scene nodes to be placed
@classmethod
def create(cls, scene_root = None, fixed_nodes = None, scale = 0.5, x = 350, y = 250, min_offset = 8):
"""
Helper to create an Arranger instance
Either fill in scene_root and create will find all sliceable nodes by itself, or use fixed_nodes to provide the
nodes yourself.
:param scene_root: Root for finding all scene nodes
:param fixed_nodes: Scene nodes to be placed
:param scale:
:param x:
:param y:
:param min_offset:
:return:
"""
arranger = Arrange(x, y, x // 2, y // 2, scale = scale)
arranger.centerFirst()
@ -88,12 +94,15 @@ class Arrange:
def resetLastPriority(self):
self._last_priority = 0
## Find placement for a node (using offset shape) and place it (using hull shape)
# return the nodes that should be placed
# \param node
# \param offset_shape_arr ShapeArray with offset, for placing the shape
# \param hull_shape_arr ShapeArray without offset, used to find location
def findNodePlacement(self, node: SceneNode, offset_shape_arr: ShapeArray, hull_shape_arr: ShapeArray, step = 1):
"""
Find placement for a node (using offset shape) and place it (using hull shape)
:param node:
:param offset_shape_arr: hapeArray with offset, for placing the shape
:param hull_shape_arr: ShapeArray without offset, used to find location
:param step:
:return: the nodes that should be placed
"""
best_spot = self.bestSpot(
hull_shape_arr, start_prio = self._last_priority, step = step)
x, y = best_spot.x, best_spot.y
@ -119,29 +128,35 @@ class Arrange:
node.setPosition(Vector(200, center_y, 100))
return found_spot
## Fill priority, center is best. Lower value is better
# This is a strategy for the arranger.
def centerFirst(self):
"""
Fill priority, center is best. Lower value is better.
:return:
"""
# Square distance: creates a more round shape
self._priority = numpy.fromfunction(
lambda j, i: (self._offset_x - i) ** 2 + (self._offset_y - j) ** 2, self._shape, dtype=numpy.int32)
self._priority_unique_values = numpy.unique(self._priority)
self._priority_unique_values.sort()
## Fill priority, back is best. Lower value is better
# This is a strategy for the arranger.
def backFirst(self):
"""
Fill priority, back is best. Lower value is better
:return:
"""
self._priority = numpy.fromfunction(
lambda j, i: 10 * j + abs(self._offset_x - i), self._shape, dtype=numpy.int32)
self._priority_unique_values = numpy.unique(self._priority)
self._priority_unique_values.sort()
## Return the amount of "penalty points" for polygon, which is the sum of priority
# None if occupied
# \param x x-coordinate to check shape
# \param y y-coordinate
# \param shape_arr the ShapeArray object to place
def checkShape(self, x, y, shape_arr):
"""
Return the amount of "penalty points" for polygon, which is the sum of priority
:param x: x-coordinate to check shape
:param y:
:param shape_arr: the ShapeArray object to place
:return: None if occupied
"""
x = int(self._scale * x)
y = int(self._scale * y)
offset_x = x + self._offset_x + shape_arr.offset_x
@ -165,12 +180,14 @@ class Arrange:
offset_x:offset_x + shape_arr.arr.shape[1]]
return numpy.sum(prio_slice[numpy.where(shape_arr.arr == 1)])
## Find "best" spot for ShapeArray
# Return namedtuple with properties x, y, penalty_points, priority.
# \param shape_arr ShapeArray
# \param start_prio Start with this priority value (and skip the ones before)
# \param step Slicing value, higher = more skips = faster but less accurate
def bestSpot(self, shape_arr, start_prio = 0, step = 1):
"""
Find "best" spot for ShapeArray
:param shape_arr:
:param start_prio: Start with this priority value (and skip the ones before)
:param step: Slicing value, higher = more skips = faster but less accurate
:return: namedtuple with properties x, y, penalty_points, priority.
"""
start_idx_list = numpy.where(self._priority_unique_values == start_prio)
if start_idx_list:
try:
@ -192,13 +209,16 @@ class Arrange:
return LocationSuggestion(x = projected_x, y = projected_y, penalty_points = penalty_points, priority = priority)
return LocationSuggestion(x = None, y = None, penalty_points = None, priority = priority) # No suitable location found :-(
## Place the object.
# Marks the locations in self._occupied and self._priority
# \param x x-coordinate
# \param y y-coordinate
# \param shape_arr ShapeArray object
# \param update_empty updates the _is_empty, used when adding disallowed areas
def place(self, x, y, shape_arr, update_empty = True):
"""
Place the object.
Marks the locations in self._occupied and self._priority
:param x:
:param y:
:param shape_arr:
:param update_empty: updates the _is_empty, used when adding disallowed areas
:return:
"""
x = int(self._scale * x)
y = int(self._scale * y)
offset_x = x + self._offset_x + shape_arr.offset_x

View File

@ -72,8 +72,6 @@ class DiscoveredPrinter(QObject):
# Human readable machine type string
@pyqtProperty(str, notify = machineTypeChanged)
def readableMachineType(self) -> str:
from cura.CuraApplication import CuraApplication
machine_manager = CuraApplication.getInstance().getMachineManager()
# In NetworkOutputDevice, when it updates a printer information, it updates the machine type using the field
# "machine_variant", and for some reason, it's not the machine type ID/codename/... but a human-readable string
# like "Ultimaker 3". The code below handles this case.

View File

@ -4,13 +4,12 @@
import collections
from PyQt5.QtCore import Qt, QTimer
from typing import TYPE_CHECKING, Optional, Dict
from cura.Machines.Models.IntentTranslations import intent_translations
from cura.Machines.Models.IntentModel import IntentModel
from cura.Settings.IntentManager import IntentManager
from UM.Qt.ListModel import ListModel
from UM.Settings.ContainerRegistry import ContainerRegistry #To update the list if anything changes.
from PyQt5.QtCore import pyqtProperty, pyqtSignal
from PyQt5.QtCore import pyqtSignal
import cura.CuraApplication
if TYPE_CHECKING:
from UM.Settings.ContainerRegistry import ContainerInterface

View File

@ -3,8 +3,6 @@
from typing import Dict, Optional, List, Set
from PyQt5.QtCore import QObject, pyqtSlot
from UM.Logger import Logger
from UM.Util import parseBool

View File

@ -5,7 +5,6 @@ from typing import Optional
from UM.Scene.SceneNode import SceneNode
from UM.Operations import Operation
from UM.Math.Vector import Vector
## An operation that parents a scene node to another scene node.

View File

@ -111,7 +111,7 @@ class NetworkMJPGImage(QQuickPaintedItem):
if not self._image_reply.isFinished():
self._image_reply.close()
except Exception as e: # RuntimeError
except Exception: # RuntimeError
pass # It can happen that the wrapped c++ object is already deleted.
self._image_reply = None

View File

@ -9,7 +9,6 @@ from UM.Settings.Interfaces import DefinitionContainerInterface
from UM.Settings.InstanceContainer import InstanceContainer
from cura.Machines.ContainerTree import ContainerTree
from cura.Machines.MachineNode import MachineNode
from .GlobalStack import GlobalStack
from .ExtruderStack import ExtruderStack

View File

@ -2,7 +2,7 @@
# Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal, pyqtSlot
from typing import Any, Dict, List, Optional, Set, Tuple, TYPE_CHECKING
from typing import Any, Dict, List, Set, Tuple, TYPE_CHECKING
from UM.Logger import Logger
from UM.Settings.InstanceContainer import InstanceContainer

View File

@ -333,7 +333,7 @@ class MachineManager(QObject):
self.setVariantByName(extruder.getMetaDataEntry("position"), machine_node.preferred_variant_name)
variant_node = machine_node.variants.get(machine_node.preferred_variant_name)
material_node = variant_node.materials.get(extruder.material.getId())
material_node = variant_node.materials.get(extruder.material.getMetaDataEntry("base_file"))
if material_node is None:
Logger.log("w", "An extruder has an unknown material, switching it to the preferred material")
self.setMaterialById(extruder.getMetaDataEntry("position"), machine_node.preferred_material)

View File

@ -1,8 +1,7 @@
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from typing import Set
from PyQt5.QtCore import QObject, pyqtSignal, pyqtProperty, pyqtSlot
from PyQt5.QtCore import QObject, pyqtSignal, pyqtProperty
from UM.Application import Application

View File

@ -2,7 +2,7 @@
# Cura is released under the terms of the LGPLv3 or higher.
from UM.Logger import Logger
import re
from typing import Any, Dict, List, Optional, Union
from typing import Dict, List, Optional, Union
from PyQt5.QtCore import QTimer, Qt

View File

@ -9,7 +9,7 @@ from UM.Version import Version
import urllib.request
from urllib.error import URLError
from typing import Dict, Optional
from typing import Dict
import ssl
import certifi

View File

@ -79,7 +79,7 @@ class ModelChecker(QObject, Extension):
# This function can be triggered in the middle of a machine change, so do not proceed if the machine change
# has not done yet.
try:
extruder = global_container_stack.extruderList[int(node_extruder_position)]
global_container_stack.extruderList[int(node_extruder_position)]
except IndexError:
Application.getInstance().callLater(lambda: self.onChanged.emit())
return False

View File

@ -23,16 +23,13 @@ class BQ_PauseAtHeight(Script):
}"""
def execute(self, data):
x = 0.
y = 0.
current_z = 0.
pause_z = self.getSettingValueByKey("pause_height")
for layer in data:
lines = layer.split("\n")
for line in lines:
if self.getValue(line, 'G') == 1 or self.getValue(line, 'G') == 0:
current_z = self.getValue(line, 'Z')
if current_z != None:
if current_z is not None:
if current_z >= pause_z:
prepend_gcode = ";TYPE:CUSTOM\n"
prepend_gcode += "; -- Pause at height (%.2f mm) --\n" % pause_z

View File

@ -170,7 +170,7 @@ class ColorMix(Script):
modelNumber = 0
for active_layer in data:
modified_gcode = ""
lineIndex = 0;
lineIndex = 0
lines = active_layer.split("\n")
for line in lines:
#dont leave blanks

View File

@ -10,10 +10,10 @@ WARNING This script has never been tested with several extruders
from ..Script import Script
import numpy as np
from UM.Logger import Logger
from UM.Application import Application
import re
from cura.Settings.ExtruderManager import ExtruderManager
def _getValue(line, key, default=None):
"""
Convenience function that finds the value in a line of g-code.
@ -30,6 +30,7 @@ def _getValue(line, key, default=None):
return default
return float(number.group(0))
class GCodeStep():
"""
Class to store the current value of each G_Code parameter
@ -85,7 +86,7 @@ class GCodeStep():
# Execution part of the stretch plugin
class Stretcher():
class Stretcher:
"""
Execution part of the stretch algorithm
"""
@ -207,7 +208,6 @@ class Stretcher():
return False
return True # New sequence
def processLayer(self, layer_steps):
"""
Computes the new coordinates of g-code steps
@ -291,7 +291,6 @@ class Stretcher():
else:
self.layergcode = self.layergcode + layer_steps[i].comment + "\n"
def workOnSequence(self, orig_seq, modif_seq):
"""
Computes new coordinates for a sequence

View File

@ -49,7 +49,7 @@ class OSXRemovableDrivePlugin(RemovableDrivePlugin.RemovableDrivePlugin):
def performEjectDevice(self, device):
p = subprocess.Popen(["diskutil", "eject", device.getId()], stdin = subprocess.PIPE, stdout = subprocess.PIPE, stderr = subprocess.PIPE)
output = p.communicate()
p.communicate()
return_code = p.wait()
if return_code != 0:

View File

@ -6,7 +6,6 @@ from PyQt5.QtNetwork import QNetworkReply, QNetworkRequest
from UM.Job import Job
from UM.Logger import Logger
from UM.Settings import ContainerRegistry
from cura.CuraApplication import CuraApplication
from ..Models.Http.ClusterMaterial import ClusterMaterial

View File

@ -299,7 +299,7 @@ class UltimakerNetworkedPrinterOutputDevice(NetworkedPrinterOutputDevice):
new_print_jobs = []
# Check which print jobs need to be created or updated.
for index, print_job_data in enumerate(remote_jobs):
for print_job_data in remote_jobs:
print_job = next(
iter(print_job for print_job in self._print_jobs if print_job.key == print_job_data.uuid), None)
if not print_job:

View File

@ -50,7 +50,7 @@ class USBPrinterOutputDeviceManager(QObject, OutputDevicePlugin):
# The method updates/reset the USB settings for all connected USB devices
def updateUSBPrinterOutputDevices(self):
for key, device in self._usb_output_devices.items():
for device in self._usb_output_devices.values():
if isinstance(device, USBPrinterOutputDevice.USBPrinterOutputDevice):
device.resetDeviceSettings()

View File

@ -653,7 +653,7 @@ class X3DReader(MeshReader):
def processGeometryTriangleSet2D(self, node):
verts = readFloatArray(node, "vertices", ())
num_faces = len(verts) // 6;
num_faces = len(verts) // 6
verts = [(verts[i], verts[i+1], 0) for i in range(0, 6 * num_faces, 2)]
self.reserveFaceAndVertexCount(num_faces, num_faces * 3)
for vert in verts:
@ -904,6 +904,7 @@ def findOuterNormal(face):
return False
# Given two *collinear* vectors a and b, returns the coefficient that takes b to a.
# No error handling.
# For stability, taking the ration between the biggest coordinates would be better...
@ -915,12 +916,13 @@ def ratio(a, b):
else:
return a.z / b.z
def pointInsideTriangle(vx, next, prev, nextXprev):
vxXprev = vx.cross(prev)
r = ratio(vxXprev, nextXprev)
if r < 0:
return False
vxXnext = vx.cross(next);
vxXnext = vx.cross(next)
s = -ratio(vxXnext, nextXprev)
return s > 0 and (s + r) < 1

View File

@ -5,7 +5,6 @@ import copy
import io
import json #To parse the product-to-id mapping file.
import os.path #To find the product-to-id mapping.
import sys
from typing import Any, Dict, List, Optional, Tuple, cast, Set, Union
import xml.etree.ElementTree as ET
@ -18,7 +17,6 @@ from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.ConfigurationErrorMessage import ConfigurationErrorMessage
from cura.CuraApplication import CuraApplication
from cura.Machines.ContainerTree import ContainerTree
from cura.Machines.VariantType import VariantType
try:

View File

@ -1,6 +1,4 @@
from unittest.mock import patch, MagicMock
import sys
import os
# Prevents error: "PyCapsule_GetPointer called with incorrect name" with conflicting SIP configurations between Arcus and PyQt: Import Arcus and Savitar first!
import Savitar # Dont remove this line

View File

@ -3,7 +3,6 @@
from unittest.mock import patch, MagicMock
import pytest
from UM.Settings.DefinitionContainer import DefinitionContainer
from cura.Machines.ContainerTree import ContainerTree
from cura.Settings.GlobalStack import GlobalStack

View File

@ -5,7 +5,6 @@ import os #To find the directory with test files and find the test files.
import pytest #To parameterize tests.
import unittest.mock #To mock and monkeypatch stuff.
from UM.Settings.DefinitionContainer import DefinitionContainer
from cura.ReaderWriters.ProfileReader import NoProfileException
from cura.Settings.ExtruderStack import ExtruderStack #Testing for returning the correct types of stacks.
from cura.Settings.GlobalStack import GlobalStack #Testing for returning the correct types of stacks.

View File

@ -1,5 +1,4 @@
from unittest.mock import MagicMock, patch
from UM.Math.AxisAlignedBox import AxisAlignedBox
import pytest
from UM.Math.Polygon import Polygon

View File

@ -1,5 +1,5 @@
from unittest.mock import MagicMock, patch
from unittest.mock import MagicMock
def createMockedExtruder(extruder_id):

View File

@ -108,9 +108,9 @@ def test_addMachineAction(machine_action_manager):
# Adding unknown action should not crash.
machine_action_manager.addFirstStartAction(test_machine, "key_that_doesnt_exists")
def test_removeMachineAction(machine_action_manager):
test_action = MachineAction(key="test_action")
test_machine = Machine("test_machine")
machine_action_manager.addMachineAction(test_action)
# Remove the machine action

View File

@ -5,7 +5,6 @@ from cura.UI import PrintInformation
from cura.Settings.MachineManager import MachineManager
from unittest.mock import MagicMock, patch
from UM.Application import Application
from UM.MimeTypeDatabase import MimeTypeDatabase, MimeType