mirror of
https://git.mirrors.martin98.com/https://github.com/Ultimaker/Cura
synced 2025-04-21 05:09:37 +08:00
185 lines
7.5 KiB
Python
185 lines
7.5 KiB
Python
# Copyright (c) 2018 Ultimaker B.V.
|
|
# Cura is released under the terms of the LGPLv3 or higher.
|
|
|
|
from collections import namedtuple
|
|
import re
|
|
from typing import Any, Dict, List, Optional, Union
|
|
|
|
from PyQt5.QtCore import QTimer, Qt
|
|
|
|
from UM.Application import Application
|
|
from UM.Qt.ListModel import ListModel
|
|
from UM.Scene.Camera import Camera
|
|
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
|
from UM.Scene.SceneNode import SceneNode
|
|
from UM.Scene.Selection import Selection
|
|
from UM.i18n import i18nCatalog
|
|
|
|
catalog = i18nCatalog("cura")
|
|
|
|
|
|
# Simple convenience class to keep stuff together. Since we're still stuck on python 3.5, we can't use the full
|
|
# typed named tuple, so we have to do it like this.
|
|
# Once we are at python 3.6, feel free to change this to a named tuple.
|
|
class _NodeInfo:
|
|
def __init__(self, index_to_node: Optional[Dict[int, SceneNode]] = None, nodes_to_rename: Optional[List[SceneNode]] = None, is_group: bool = False) -> None:
|
|
if index_to_node is None:
|
|
index_to_node = {}
|
|
if nodes_to_rename is None:
|
|
nodes_to_rename = []
|
|
self.index_to_node = index_to_node # type: Dict[int, SceneNode]
|
|
self.nodes_to_rename = nodes_to_rename # type: List[SceneNode]
|
|
self.is_group = is_group # type: bool
|
|
|
|
|
|
## Keep track of all objects in the project
|
|
class ObjectsModel(ListModel):
|
|
NameRole = Qt.UserRole + 1
|
|
SelectedRole = Qt.UserRole + 2
|
|
OutsideAreaRole = Qt.UserRole + 3
|
|
BuilplateNumberRole = Qt.UserRole + 4
|
|
NodeRole = Qt.UserRole + 5
|
|
|
|
def __init__(self, parent = None) -> None:
|
|
super().__init__(parent)
|
|
|
|
self.addRoleName(self.NameRole, "name")
|
|
self.addRoleName(self.SelectedRole, "selected")
|
|
self.addRoleName(self.OutsideAreaRole, "outside_build_area")
|
|
self.addRoleName(self.BuilplateNumberRole, "buildplate_number")
|
|
self.addRoleName(self.NodeRole, "node")
|
|
|
|
Application.getInstance().getController().getScene().sceneChanged.connect(self._updateSceneDelayed)
|
|
Application.getInstance().getPreferences().preferenceChanged.connect(self._updateDelayed)
|
|
|
|
self._update_timer = QTimer()
|
|
self._update_timer.setInterval(200)
|
|
self._update_timer.setSingleShot(True)
|
|
self._update_timer.timeout.connect(self._update)
|
|
|
|
self._build_plate_number = -1
|
|
|
|
def setActiveBuildPlate(self, nr: int) -> None:
|
|
if self._build_plate_number != nr:
|
|
self._build_plate_number = nr
|
|
self._update()
|
|
|
|
def _updateSceneDelayed(self, source) -> None:
|
|
if not isinstance(source, Camera):
|
|
self._update_timer.start()
|
|
|
|
def _updateDelayed(self, *args) -> None:
|
|
self._update_timer.start()
|
|
|
|
def _update(self, *args) -> None:
|
|
nodes = []
|
|
filter_current_build_plate = Application.getInstance().getPreferences().getValue("view/filter_current_build_plate")
|
|
active_build_plate_number = self._build_plate_number
|
|
|
|
naming_regex = re.compile("^(.+)\(([0-9]+)\)$")
|
|
|
|
name_to_node_info_dict = {} # type: Dict[str, _NodeInfo]
|
|
|
|
group_name_template = catalog.i18nc("@label", "Group #{group_nr}")
|
|
group_name_prefix = group_name_template.split("#")[0]
|
|
|
|
for node in DepthFirstIterator(Application.getInstance().getController().getScene().getRoot()): # type: ignore
|
|
is_group = bool(node.callDecoration("isGroup"))
|
|
if not node.callDecoration("isSliceable") and not is_group:
|
|
continue
|
|
|
|
parent = node.getParent()
|
|
if parent and parent.callDecoration("isGroup"):
|
|
continue # Grouped nodes don't need resetting as their parent (the group) is resetted)
|
|
|
|
node_build_plate_number = node.callDecoration("getBuildPlateNumber")
|
|
if filter_current_build_plate and node_build_plate_number != active_build_plate_number:
|
|
continue
|
|
|
|
force_rename = False
|
|
if not is_group:
|
|
# Handle names for individual nodes
|
|
name = node.getName()
|
|
|
|
name_match = naming_regex.fullmatch(name)
|
|
if name_match is None:
|
|
original_name = name
|
|
name_index = 0
|
|
else:
|
|
original_name = name_match.groups()[0]
|
|
name_index = int(name_match.groups()[1])
|
|
|
|
else:
|
|
# Handle names for grouped nodes
|
|
original_name = group_name_prefix
|
|
|
|
current_name = node.getName()
|
|
if current_name.startswith(group_name_prefix):
|
|
name_index = int(current_name.split("#")[-1])
|
|
else:
|
|
# Force rename this group because this node has not been named as a group yet, probably because
|
|
# it's a newly created group.
|
|
name_index = 0
|
|
force_rename = True
|
|
|
|
if original_name not in name_to_node_info_dict:
|
|
# Keep track of 2 things:
|
|
# - known indices for nodes which doesn't need to be renamed
|
|
# - a list of nodes that need to be renamed. When renaming then, we should avoid using the known indices.
|
|
name_to_node_info_dict[original_name] = _NodeInfo(is_group = is_group)
|
|
node_info = name_to_node_info_dict[original_name]
|
|
if not force_rename and name_index not in node_info.index_to_node:
|
|
node_info.index_to_node[name_index] = node
|
|
else:
|
|
node_info.nodes_to_rename.append(node)
|
|
|
|
# Go through all names and rename the nodes that need to be renamed.
|
|
node_rename_list = [] # type: List[Dict[str, Union[str, SceneNode]]]
|
|
for name, node_info in name_to_node_info_dict.items():
|
|
# First add the ones that do not need to be renamed.
|
|
for node in node_info.index_to_node.values():
|
|
node_rename_list.append({"node": node})
|
|
|
|
# Generate new names for the nodes that need to be renamed
|
|
current_index = 0
|
|
for node in node_info.nodes_to_rename:
|
|
current_index += 1
|
|
while current_index in node_info.index_to_node:
|
|
current_index += 1
|
|
|
|
if not node_info.is_group:
|
|
new_group_name = "{0}({1})".format(name, current_index)
|
|
else:
|
|
new_group_name = "{0}#{1}".format(name, current_index)
|
|
node_rename_list.append({"node": node,
|
|
"new_name": new_group_name})
|
|
|
|
for rename_dict in node_rename_list:
|
|
node = rename_dict["node"]
|
|
new_name = rename_dict.get("new_name")
|
|
|
|
if hasattr(node, "isOutsideBuildArea"):
|
|
is_outside_build_area = node.isOutsideBuildArea() # type: ignore
|
|
else:
|
|
is_outside_build_area = False
|
|
|
|
node_build_plate_number = node.callDecoration("getBuildPlateNumber")
|
|
|
|
from UM.Logger import Logger
|
|
|
|
if new_name is not None:
|
|
old_name = node.getName()
|
|
node.setName(new_name)
|
|
Logger.log("d", "Node [%s] renamed to [%s]", old_name, new_name)
|
|
|
|
nodes.append({
|
|
"name": node.getName(),
|
|
"selected": Selection.isSelected(node),
|
|
"outside_build_area": is_outside_build_area,
|
|
"buildplate_number": node_build_plate_number,
|
|
"node": node
|
|
})
|
|
|
|
nodes = sorted(nodes, key=lambda n: n["name"])
|
|
self.setItems(nodes)
|