Cura/cura/UI/ObjectsModel.py
Jaime van Kessel 502eea4d26 Simplify the objects model further
CUA-6283
2019-05-23 16:09:22 +02:00

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)