mirror of
https://git.mirrors.martin98.com/https://github.com/Ultimaker/Cura
synced 2025-08-11 20:58:58 +08:00
Merge branch 'master' of github.com:Ultimaker/Cura
This commit is contained in:
commit
0e30f0bb6b
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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.
|
||||
|
@ -1,5 +1,4 @@
|
||||
from unittest.mock import MagicMock, patch
|
||||
from UM.Math.AxisAlignedBox import AxisAlignedBox
|
||||
import pytest
|
||||
|
||||
from UM.Math.Polygon import Polygon
|
||||
|
@ -1,5 +1,5 @@
|
||||
|
||||
from unittest.mock import MagicMock, patch
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
|
||||
def createMockedExtruder(extruder_id):
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user