Automatically save the project when computing or submitting to renderfarm

If the project is not saved at all, it will suggest to save it manually
or to define a project in a temporary folder using date/time for the
project name.
This commit is contained in:
Fabien Castan 2025-04-12 19:49:11 +02:00
parent db8fd02aeb
commit 008d6c75ee
6 changed files with 53 additions and 44 deletions

View file

@ -31,7 +31,6 @@ logging.basicConfig(format='[%(asctime)s][%(levelname)s] %(message)s', level=log
sessionUid = str(uuid.uuid1())
cacheFolderName = 'MeshroomCache'
defaultCacheFolder = os.environ.get('MESHROOM_CACHE', os.path.join(tempfile.gettempdir(), cacheFolderName))
nodesDesc = {}
submitters = {}
pipelineTemplates = {}

View file

@ -25,18 +25,6 @@ class MrNodeType(enum.Enum):
COMMANDLINE = enum.auto()
INPUT = enum.auto()
def isNodeSaved(node):
"""Returns whether a node is identical to its serialized counterpart in the current graph file."""
filepath = node.graph.filepath
if not filepath:
return False
from meshroom.core.graph import loadGraph
graphSaved = loadGraph(filepath)
nodeSaved = graphSaved.node(node.name)
if nodeSaved is None:
return False
return nodeSaved._uid == node._uid
class BaseNode(object):
"""
@ -259,9 +247,6 @@ class Node(BaseNode):
return MrNodeType.NODE
def processChunkInEnvironment(self, chunk):
if not isNodeSaved(chunk.node):
raise RuntimeError("File must be saved before computing in isolated environment.")
meshroomComputeCmd = f"python {_MESHROOM_COMPUTE} {chunk.node.graph.filepath} --node {chunk.node.name} --extern --inCurrentEnv"
if len(chunk.node.getChunks()) > 1:
meshroomComputeCmd += f" --iteration {chunk.range.iteration}"

View file

@ -182,7 +182,6 @@ class Graph(BaseObject):
edges = {B.input: A.output, C.input: B.output,}
"""
_cacheDir = ""
def __init__(self, name, parent=None):
super(Graph, self).__init__(parent)
@ -199,7 +198,7 @@ class Graph(BaseObject):
# Edges: use dst attribute as unique key since it can only have one input connection
self._edges = DictModel(keyAttrName='dst', parent=self)
self._compatibilityNodes = DictModel(keyAttrName='name', parent=self)
self.cacheDir = meshroom.core.defaultCacheFolder
self._cacheDir = ''
self._filepath = ''
self._fileDateVersion = 0
self.header = {}
@ -1354,7 +1353,7 @@ class Graph(BaseObject):
def _unsetFilepath(self):
self._filepath = ""
self.name = ""
self.cacheDir = meshroom.core.defaultCacheFolder
self.cacheDir = ""
self.filepathChanged.emit()
def updateInternals(self, startNodes=None, force=False):

View file

@ -12,9 +12,9 @@ import os
from dataclasses import dataclass
from enum import Enum
import sys
import tempfile
from typing import Any, Type
meshroomFolder = os.path.dirname(__file__)
@dataclass
@ -46,6 +46,8 @@ class EnvVar(Enum):
MESHROOM_NODES_PATH = VarDefinition(str, "", "Paths to set of nodes folders")
MESHROOM_SUBMITTERS_PATH = VarDefinition(str, "", "Paths to set of submitters folders")
MESHROOM_PIPELINE_TEMPLATES_PATH = VarDefinition(str, "", "Paths to et of pipeline templates folders")
MESHROOM_TEMP_PATH = VarDefinition(str, tempfile.gettempdir(), "Path to the temporary folder")
@staticmethod
def get(envVar: "EnvVar") -> Any:

View file

@ -514,6 +514,14 @@ class UIGraph(QObject):
# => force re-evaluation of monitored status files paths
self.updateChunkMonitor(self._sortedDFSChunks)
@Slot()
def saveAsTemp(self):
from meshroom.env import EnvVar
from datetime import datetime
tempFolder = EnvVar.get(EnvVar.MESHROOM_TEMP_PATH)
timestamp = datetime.now().strftime("%Y-%m-%d_%H:%M")
self._saveAs(os.path.join(tempFolder, f"meshroom_{timestamp}.mg"))
@Slot()
def save(self):
self._graph.save()
@ -531,6 +539,7 @@ class UIGraph(QObject):
@Slot(list)
def execute(self, nodes: Optional[Union[list[Node], Node]] = None):
nodes = [nodes] if not isinstance(nodes, Iterable) and nodes else nodes
self.save() # always save the graph before computing
self._taskManager.compute(self._graph, nodes)
self.updateLockedUndoStack() # explicitly call the update while it is already computing

View file

@ -134,6 +134,8 @@ Page {
id: saveFileDialog
options: Platform.FileDialog.DontUseNativeDialog
property var _callback: undefined
signal closed(var result)
title: "Save File"
@ -150,8 +152,28 @@ Page {
_reconstruction.saveAs(currentFile)
MeshroomApp.addRecentProjectFile(currentFile.toString())
closed(Platform.Dialog.Accepted)
fireCallback(Platform.Dialog.Accepted)
}
onRejected: {
closed(Platform.Dialog.Rejected)
fireCallback(Platform.Dialog.Rejected)
}
function fireCallback(rc)
{
// Call the callback and reset it
if (_callback)
_callback(rc)
_callback = undefined
}
// Open the unsaved dialog warning with an optional
// callback to fire when the dialog is accepted/discarded
function prompt(callback)
{
_callback = callback
open()
}
onRejected: closed(Platform.Dialog.Rejected)
}
Platform.FileDialog {
@ -218,8 +240,6 @@ Page {
Item {
id: computeManager
property bool warnIfUnsaved: true
// Evaluate if graph computation can be submitted externally
property bool canSubmit: _reconstruction ?
_reconstruction.canSubmit // current setup allows to compute externally
@ -227,7 +247,7 @@ Page {
false
function compute(nodes, force) {
if (!force && warnIfUnsaved && !_reconstruction.graph.filepath) {
if (!force && !_reconstruction.graph.filepath) {
unsavedComputeDialog.selectedNodes = nodes;
unsavedComputeDialog.open();
}
@ -339,29 +359,28 @@ Page {
parent: Overlay.overlay
preset: "Warning"
title: "Unsaved Project"
text: "Data will be computed in the default cache folder if project remains unsaved."
detailedText: "Default cache folder: " + (_reconstruction ? _reconstruction.graph.cacheDir : "unknown")
helperText: "Save project first?"
text: "Saving the project is required."
helperText: "Choose a location to save the project, or use the default temporary path."
standardButtons: Dialog.Discard | Dialog.Cancel | Dialog.Save
CheckBox {
Layout.alignment: Qt.AlignRight
text: "Don't ask again for this session"
padding: 0
onToggled: computeManager.warnIfUnsaved = !checked
}
Component.onCompleted: {
// Set up discard button text
standardButton(Dialog.Discard).text = "Continue without Saving"
standardButton(Dialog.Discard).text = "Continue in Temp Folder"
standardButton(Dialog.Save).text = "Save As"
}
onDiscarded: {
_reconstruction.saveAsTemp()
close()
computeManager.compute(selectedNodes, true)
}
onAccepted: saveAsAction.trigger()
onAccepted: {
initFileDialogFolder(saveFileDialog)
saveFileDialog.prompt(function(rc) {
computeManager.compute(selectedNodes, true)
})
}
}
MessageDialog {
@ -444,14 +463,10 @@ Page {
}
// Open "Save As" dialog
else {
saveFileDialog.open()
function _callbackWrapper(rc) {
saveFileDialog.prompt(function(rc) {
if (rc === Platform.Dialog.Accepted)
fireCallback()
saveFileDialog.closed.disconnect(_callbackWrapper)
}
saveFileDialog.closed.connect(_callbackWrapper)
})
}
}
@ -463,8 +478,8 @@ Page {
_callback = undefined
}
/// Open the unsaved dialog warning with an optional
/// callback to fire when the dialog is accepted/discarded
// Open the unsaved dialog warning with an optional
// callback to fire when the dialog is accepted/discarded
function prompt(callback)
{
_callback = callback