Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix removing watching for file modifications #965

Open
wants to merge 3 commits into
base: 5.8
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 0 additions & 11 deletions UM/Mesh/MeshData.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ class MeshData:

def __init__(self, vertices=None, normals=None, indices=None, colors=None, uvs=None, file_name=None,
center_position=None, zero_position=None, type = MeshType.faces, attributes=None) -> None:
self._application = None # Initialize this later otherwise unit tests break

self._vertices = NumPyUtil.immutableNDArray(vertices)
self._normals = NumPyUtil.immutableNDArray(normals)
Expand Down Expand Up @@ -82,16 +81,6 @@ def __init__(self, vertices=None, normals=None, indices=None, colors=None, uvs=N
new_value[attribute_key] = attribute_value
self._attributes[key] = new_value

def __del__(self):
"""Triggered when this file is deleted.

The file will then no longer be watched for changes.
"""

if self._file_name:
if self._application:
self._application.getController().getScene().removeWatchedFile(self._file_name)

def set(self, vertices=Reuse, normals=Reuse, indices=Reuse, colors=Reuse, uvs=Reuse, file_name=Reuse,
center_position=Reuse, zero_position=Reuse, attributes=Reuse) -> "MeshData":
"""Create a new MeshData with specified changes
Expand Down
2 changes: 0 additions & 2 deletions UM/Mesh/MeshReader.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

from typing import Union, List

import UM.Application
from UM.FileHandler.FileReader import FileReader
from UM.FileHandler.FileHandler import resolveAnySymlink
from UM.Logger import Logger
Expand All @@ -24,7 +23,6 @@ def read(self, file_name: str) -> Union[SceneNode, List[SceneNode]]:

file_name = resolveAnySymlink(file_name)
result = self._read(file_name)
UM.Application.Application.getInstance().getController().getScene().addWatchedFile(file_name)

# The mesh reader may set a MIME type itself if it knows a more specific MIME type than just going by extension.
# If not, automatically generate one from our MIME type database, going by the file extension.
Expand Down
64 changes: 46 additions & 18 deletions UM/Scene/Scene.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,19 @@ def __init__(self) -> None:
self._file_watcher.fileChanged.connect(self._onFileChanged)

self._reload_message: Optional[Message] = None
self._reload_callback: Optional[functools.partial] = None

# Need to keep these in memory. This is a memory leak every time you refresh, but a tiny one.
self._callbacks: Set[Callable] = set()

self._metadata: Dict[str, Any] = {}

# If available connect reloadAll to hide the file change reload message.
import UM.Application
app = UM.Application.Application.getInstance()
if getattr(app, "startReloadAll", None):
app.startReloadAll.connect(self._hideReloadMessage)

def setMetaDataEntry(self, key: str, entry: Any) -> None:
self._metadata[key] = entry

Expand All @@ -65,11 +72,13 @@ def _connectSignalsRoot(self) -> None:
self._root.transformationChanged.connect(self.sceneChanged)
self._root.childrenChanged.connect(self.sceneChanged)
self._root.meshDataChanged.connect(self.sceneChanged)
self._root.meshDataChanged.connect(self._updateWatchedFiles)

def _disconnectSignalsRoot(self) -> None:
self._root.transformationChanged.disconnect(self.sceneChanged)
self._root.childrenChanged.disconnect(self.sceneChanged)
self._root.meshDataChanged.disconnect(self.sceneChanged)
self._root.meshDataChanged.disconnect(self._updateWatchedFiles)

def setIgnoreSceneChanges(self, ignore_scene_changes: bool) -> None:
if self._ignore_scene_changes != ignore_scene_changes:
Expand Down Expand Up @@ -149,25 +158,39 @@ def findCamera(self, name: str) -> Optional[Camera]:
return node
return None

def addWatchedFile(self, file_path: str) -> None:
"""Add a file to be watched for changes.
def _updateWatchedFiles(self, node_changed: SceneNode) -> None:
"""Update the list of watched files based on the currently loaded nodes.
This handles both add and remove for _file_watcher."""

:param file_path: The path to the file that must be watched.
"""
watching = set(self._file_watcher.files())
active = set()

# Get the list of files that are currently loaded in the scene graph.
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
for node in DepthFirstIterator(self.getRoot()):
md = node.getMeshData()
if md and md.getFileName() is not None:
active.add(md.getFileName())

# File watcher causes cura to crash on windows if threaded from removable device (usb, ...).
# Create QEventLoop earlier to fix this.
if Platform.isWindows():
QEventLoop()
self._file_watcher.addPath(file_path)
if watching == active:
return

def removeWatchedFile(self, file_path: str) -> None:
"""Remove a file so that it will no longer be watched for changes.
stop_watching = watching - active
start_watching = active - watching
for file_path in stop_watching:
self._file_watcher.removePath(file_path)
if start_watching:
# File watcher causes cura to crash on windows if threaded from removable device (usb, ...).
# Create QEventLoop earlier to fix this.
if Platform.isWindows():
QEventLoop()

:param file_path: The path to the file that must no longer be watched.
"""
for file_path in start_watching:
self._file_watcher.addPath(file_path)

self._file_watcher.removePath(file_path)
# Remove modified reload prompt if the file is no longer watched.
if self._reload_callback is not None and self._reload_callback.args[1] in stop_watching:
self._hideReloadMessage()

def _onFileChanged(self, file_path: str) -> None:
"""Triggered whenever a file is changed that we currently have loaded."""
Expand All @@ -189,8 +212,7 @@ def _onFileChanged(self, file_path: str) -> None:
if modified_nodes:
# Hide the message if it was already visible
# Todo: keep one message for each modified file, when multiple had been updated at same time
if self._reload_message is not None:
self._reload_message.hide()
self._hideReloadMessage()

self._reload_message = Message(i18n_catalog.i18nc("@info", "Would you like to reload {filename}?").format(
filename = os.path.basename(file_path)),
Expand All @@ -212,8 +234,7 @@ def _reloadNodes(self, nodes: List["SceneNode"], file_path: str, message: str, a

if action != "reload":
return
if self._reload_message is not None:
self._reload_message.hide()
self._hideReloadMessage()

if not file_path or not os.path.isfile(file_path): # File doesn't exist anymore.
return
Expand Down Expand Up @@ -265,3 +286,10 @@ def _reloadJobFinished(self, replaced_nodes: [SceneNode], job: ReadMeshJob) -> N
# Current node is a new one in the file, or it's name has changed
# TODO: Load this mesh into the scene. Also alter the "ReloadAll" action in CuraApplication.
Logger.log("w", "Could not find matching node for object '{0}' in the scene.", node_name)

def _hideReloadMessage(self) -> None:
"""Hide file modification reload dialog if showing"""
if self._reload_message is not None:
self._reload_message.hide()
if self._reload_callback is not None:
self._reload_callback = None
12 changes: 8 additions & 4 deletions UM/Scene/SceneNode.py
Original file line number Diff line number Diff line change
Expand Up @@ -429,7 +429,10 @@ def setMeshData(self, mesh_data: Optional[MeshData]) -> None:
self.meshDataChanged.emit(self)

meshDataChanged = Signal()
"""Emitted whenever the attached mesh data object changes."""
"""Emitted whenever the attached mesh data object changes.

:param object: The SceneNode that had the changed mesh data object.
"""

def _onMeshDataChanged(self) -> None:
self.meshDataChanged.emit(self)
Expand Down Expand Up @@ -517,7 +520,7 @@ def getAllChildren(self) -> List["SceneNode"]:
childrenChanged = Signal()
"""Emitted whenever the list of children of this object or any child object changes.

:param object: The object that triggered the change.
:param object: The SceneNode that triggered the change.
"""

def _updateCachedNormalMatrix(self) -> None:
Expand Down Expand Up @@ -716,7 +719,8 @@ def setPosition(self, position: Vector, transform_space: int = TransformSpace.Lo

transformationChanged = Signal()
"""Signal. Emitted whenever the transformation of this object or any child object changes.
:param object: The object that caused the change.

:param object: The SceneNode that triggered the change.
"""

def lookAt(self, target: Vector, up: Vector = Vector.Unit_Y) -> None:
Expand Down Expand Up @@ -885,4 +889,4 @@ def __str__(self) -> str:
"""String output for debugging."""

name = self._name if self._name != "" else hex(id(self))
return "<" + self.__class__.__qualname__ + " object: '" + name + "'>"
return "<" + self.__class__.__qualname__ + " object: '" + name + "'>"
Loading