mirror of
https://github.com/alicevision/Meshroom.git
synced 2025-05-01 19:27:10 +02:00
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:
parent
db8fd02aeb
commit
008d6c75ee
6 changed files with 53 additions and 44 deletions
|
@ -31,7 +31,6 @@ logging.basicConfig(format='[%(asctime)s][%(levelname)s] %(message)s', level=log
|
||||||
sessionUid = str(uuid.uuid1())
|
sessionUid = str(uuid.uuid1())
|
||||||
|
|
||||||
cacheFolderName = 'MeshroomCache'
|
cacheFolderName = 'MeshroomCache'
|
||||||
defaultCacheFolder = os.environ.get('MESHROOM_CACHE', os.path.join(tempfile.gettempdir(), cacheFolderName))
|
|
||||||
nodesDesc = {}
|
nodesDesc = {}
|
||||||
submitters = {}
|
submitters = {}
|
||||||
pipelineTemplates = {}
|
pipelineTemplates = {}
|
||||||
|
|
|
@ -25,18 +25,6 @@ class MrNodeType(enum.Enum):
|
||||||
COMMANDLINE = enum.auto()
|
COMMANDLINE = enum.auto()
|
||||||
INPUT = 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):
|
class BaseNode(object):
|
||||||
"""
|
"""
|
||||||
|
@ -259,9 +247,6 @@ class Node(BaseNode):
|
||||||
return MrNodeType.NODE
|
return MrNodeType.NODE
|
||||||
|
|
||||||
def processChunkInEnvironment(self, chunk):
|
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"
|
meshroomComputeCmd = f"python {_MESHROOM_COMPUTE} {chunk.node.graph.filepath} --node {chunk.node.name} --extern --inCurrentEnv"
|
||||||
if len(chunk.node.getChunks()) > 1:
|
if len(chunk.node.getChunks()) > 1:
|
||||||
meshroomComputeCmd += f" --iteration {chunk.range.iteration}"
|
meshroomComputeCmd += f" --iteration {chunk.range.iteration}"
|
||||||
|
|
|
@ -182,7 +182,6 @@ class Graph(BaseObject):
|
||||||
edges = {B.input: A.output, C.input: B.output,}
|
edges = {B.input: A.output, C.input: B.output,}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
_cacheDir = ""
|
|
||||||
|
|
||||||
def __init__(self, name, parent=None):
|
def __init__(self, name, parent=None):
|
||||||
super(Graph, self).__init__(parent)
|
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
|
# Edges: use dst attribute as unique key since it can only have one input connection
|
||||||
self._edges = DictModel(keyAttrName='dst', parent=self)
|
self._edges = DictModel(keyAttrName='dst', parent=self)
|
||||||
self._compatibilityNodes = DictModel(keyAttrName='name', parent=self)
|
self._compatibilityNodes = DictModel(keyAttrName='name', parent=self)
|
||||||
self.cacheDir = meshroom.core.defaultCacheFolder
|
self._cacheDir = ''
|
||||||
self._filepath = ''
|
self._filepath = ''
|
||||||
self._fileDateVersion = 0
|
self._fileDateVersion = 0
|
||||||
self.header = {}
|
self.header = {}
|
||||||
|
@ -1354,7 +1353,7 @@ class Graph(BaseObject):
|
||||||
def _unsetFilepath(self):
|
def _unsetFilepath(self):
|
||||||
self._filepath = ""
|
self._filepath = ""
|
||||||
self.name = ""
|
self.name = ""
|
||||||
self.cacheDir = meshroom.core.defaultCacheFolder
|
self.cacheDir = ""
|
||||||
self.filepathChanged.emit()
|
self.filepathChanged.emit()
|
||||||
|
|
||||||
def updateInternals(self, startNodes=None, force=False):
|
def updateInternals(self, startNodes=None, force=False):
|
||||||
|
|
|
@ -12,9 +12,9 @@ import os
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
import sys
|
import sys
|
||||||
|
import tempfile
|
||||||
from typing import Any, Type
|
from typing import Any, Type
|
||||||
|
|
||||||
|
|
||||||
meshroomFolder = os.path.dirname(__file__)
|
meshroomFolder = os.path.dirname(__file__)
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
@ -46,6 +46,8 @@ class EnvVar(Enum):
|
||||||
MESHROOM_NODES_PATH = VarDefinition(str, "", "Paths to set of nodes folders")
|
MESHROOM_NODES_PATH = VarDefinition(str, "", "Paths to set of nodes folders")
|
||||||
MESHROOM_SUBMITTERS_PATH = VarDefinition(str, "", "Paths to set of submitters 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_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
|
@staticmethod
|
||||||
def get(envVar: "EnvVar") -> Any:
|
def get(envVar: "EnvVar") -> Any:
|
||||||
|
|
|
@ -514,6 +514,14 @@ class UIGraph(QObject):
|
||||||
# => force re-evaluation of monitored status files paths
|
# => force re-evaluation of monitored status files paths
|
||||||
self.updateChunkMonitor(self._sortedDFSChunks)
|
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()
|
@Slot()
|
||||||
def save(self):
|
def save(self):
|
||||||
self._graph.save()
|
self._graph.save()
|
||||||
|
@ -531,6 +539,7 @@ class UIGraph(QObject):
|
||||||
@Slot(list)
|
@Slot(list)
|
||||||
def execute(self, nodes: Optional[Union[list[Node], Node]] = None):
|
def execute(self, nodes: Optional[Union[list[Node], Node]] = None):
|
||||||
nodes = [nodes] if not isinstance(nodes, Iterable) and nodes else nodes
|
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._taskManager.compute(self._graph, nodes)
|
||||||
self.updateLockedUndoStack() # explicitly call the update while it is already computing
|
self.updateLockedUndoStack() # explicitly call the update while it is already computing
|
||||||
|
|
||||||
|
|
|
@ -134,6 +134,8 @@ Page {
|
||||||
id: saveFileDialog
|
id: saveFileDialog
|
||||||
options: Platform.FileDialog.DontUseNativeDialog
|
options: Platform.FileDialog.DontUseNativeDialog
|
||||||
|
|
||||||
|
property var _callback: undefined
|
||||||
|
|
||||||
signal closed(var result)
|
signal closed(var result)
|
||||||
|
|
||||||
title: "Save File"
|
title: "Save File"
|
||||||
|
@ -150,8 +152,28 @@ Page {
|
||||||
_reconstruction.saveAs(currentFile)
|
_reconstruction.saveAs(currentFile)
|
||||||
MeshroomApp.addRecentProjectFile(currentFile.toString())
|
MeshroomApp.addRecentProjectFile(currentFile.toString())
|
||||||
closed(Platform.Dialog.Accepted)
|
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 {
|
Platform.FileDialog {
|
||||||
|
@ -218,8 +240,6 @@ Page {
|
||||||
Item {
|
Item {
|
||||||
id: computeManager
|
id: computeManager
|
||||||
|
|
||||||
property bool warnIfUnsaved: true
|
|
||||||
|
|
||||||
// Evaluate if graph computation can be submitted externally
|
// Evaluate if graph computation can be submitted externally
|
||||||
property bool canSubmit: _reconstruction ?
|
property bool canSubmit: _reconstruction ?
|
||||||
_reconstruction.canSubmit // current setup allows to compute externally
|
_reconstruction.canSubmit // current setup allows to compute externally
|
||||||
|
@ -227,7 +247,7 @@ Page {
|
||||||
false
|
false
|
||||||
|
|
||||||
function compute(nodes, force) {
|
function compute(nodes, force) {
|
||||||
if (!force && warnIfUnsaved && !_reconstruction.graph.filepath) {
|
if (!force && !_reconstruction.graph.filepath) {
|
||||||
unsavedComputeDialog.selectedNodes = nodes;
|
unsavedComputeDialog.selectedNodes = nodes;
|
||||||
unsavedComputeDialog.open();
|
unsavedComputeDialog.open();
|
||||||
}
|
}
|
||||||
|
@ -339,29 +359,28 @@ Page {
|
||||||
parent: Overlay.overlay
|
parent: Overlay.overlay
|
||||||
preset: "Warning"
|
preset: "Warning"
|
||||||
title: "Unsaved Project"
|
title: "Unsaved Project"
|
||||||
text: "Data will be computed in the default cache folder if project remains unsaved."
|
text: "Saving the project is required."
|
||||||
detailedText: "Default cache folder: " + (_reconstruction ? _reconstruction.graph.cacheDir : "unknown")
|
helperText: "Choose a location to save the project, or use the default temporary path."
|
||||||
helperText: "Save project first?"
|
|
||||||
standardButtons: Dialog.Discard | Dialog.Cancel | Dialog.Save
|
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: {
|
Component.onCompleted: {
|
||||||
// Set up discard button text
|
// 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: {
|
onDiscarded: {
|
||||||
|
_reconstruction.saveAsTemp()
|
||||||
close()
|
close()
|
||||||
computeManager.compute(selectedNodes, true)
|
computeManager.compute(selectedNodes, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
onAccepted: saveAsAction.trigger()
|
onAccepted: {
|
||||||
|
initFileDialogFolder(saveFileDialog)
|
||||||
|
saveFileDialog.prompt(function(rc) {
|
||||||
|
computeManager.compute(selectedNodes, true)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MessageDialog {
|
MessageDialog {
|
||||||
|
@ -444,14 +463,10 @@ Page {
|
||||||
}
|
}
|
||||||
// Open "Save As" dialog
|
// Open "Save As" dialog
|
||||||
else {
|
else {
|
||||||
saveFileDialog.open()
|
saveFileDialog.prompt(function(rc) {
|
||||||
function _callbackWrapper(rc) {
|
|
||||||
if (rc === Platform.Dialog.Accepted)
|
if (rc === Platform.Dialog.Accepted)
|
||||||
fireCallback()
|
fireCallback()
|
||||||
|
})
|
||||||
saveFileDialog.closed.disconnect(_callbackWrapper)
|
|
||||||
}
|
|
||||||
saveFileDialog.closed.connect(_callbackWrapper)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -463,8 +478,8 @@ Page {
|
||||||
_callback = undefined
|
_callback = undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Open the unsaved dialog warning with an optional
|
// Open the unsaved dialog warning with an optional
|
||||||
/// callback to fire when the dialog is accepted/discarded
|
// callback to fire when the dialog is accepted/discarded
|
||||||
function prompt(callback)
|
function prompt(callback)
|
||||||
{
|
{
|
||||||
_callback = callback
|
_callback = callback
|
||||||
|
|
Loading…
Add table
Reference in a new issue