moved plugin logic to nodeDesc instead of class

added build badge

changes for uid renaming

added back menu after update

fix for existing symlink
This commit is contained in:
Matthieu Hog 2024-09-02 16:49:58 +02:00
parent c2aac17c88
commit 370d1346b4
13 changed files with 429 additions and 244 deletions

View file

@ -19,7 +19,7 @@ except Exception:
pass
from meshroom.core.submitter import BaseSubmitter
from . import desc
from meshroom.core import desc
# Setup logging
logging.basicConfig(format='[%(asctime)s][%(levelname)s] %(message)s', level=logging.INFO)

View file

@ -717,9 +717,53 @@ class Node(object):
documentation = ''
category = 'Other'
_isPlugin = True
def __init__(self):
super(Node, self).__init__()
self.hasDynamicOutputAttribute = any(output.isDynamicValue for output in self.outputs)
try:
self.envFile
self.envType
except:
self._isPlugin=False
@property
def envType(cls):
from core.plugin import EnvType #lazy import for plugin to avoid circular dependency
return EnvType.NONE
@property
def envFile(cls):
"""
Env file used to build the environement, you may overwrite this to custom the behaviour
"""
raise NotImplementedError("You must specify an env file")
@property
def _envName(cls):
"""
Get the env name by hashing the env files, overwrite this to use a custom pre-build env
"""
with open(cls.envFile, 'r') as file:
envContent = file.read()
from meshroom.core.plugin import getEnvName #lazy import as to avoid circular dep
return getEnvName(envContent)
@property
def isPlugin(self):
"""
Tests if the node is a valid plugin node
"""
return self._isPlugin
@property
def isBuilt(self):
"""
Tests if the environnement is built
"""
from meshroom.core.plugin import isBuilt
return self._isPlugin and isBuilt(self)
def upgradeAttributeValues(self, attrValues, fromVersion):
return attrValues
@ -806,7 +850,17 @@ class CommandLineNode(Node):
if chunk.node.isParallelized and chunk.node.size > 1:
cmdSuffix = ' ' + self.commandLineRange.format(**chunk.range.toDict())
return cmdPrefix + chunk.node.nodeDesc.commandLine.format(**chunk.node._cmdVars) + cmdSuffix
cmd=cmdPrefix + chunk.node.nodeDesc.commandLine.format(**chunk.node._cmdVars) + cmdSuffix
#the process in Popen does not seem to use the right python, even if meshroom_compute is called within the env
#so in the case of command line using python, we have to make sure it is using the correct python
from meshroom.core.plugin import EnvType, getVenvPath, getVenvExe #lazy import to prevent circular dep
if self.isPlugin and self.envType == EnvType.VENV:
envPath = getVenvPath(self._envName)
envExe = getVenvExe(envPath)
cmd=cmd.replace("python", envExe)
return cmd
def stopProcess(self, chunk):
# The same node could exists several times in the graph and

View file

@ -21,7 +21,6 @@ from meshroom.core import desc, stats, hashValue, nodeVersion, Version
from meshroom.core.attribute import attributeFactory, ListAttribute, GroupAttribute, Attribute
from meshroom.core.exception import NodeUpgradeError, UnknownNodeTypeError
def getWritingFilepath(filepath):
return filepath + '.writing.' + str(uuid.uuid4())
@ -406,13 +405,14 @@ class NodeChunk(BaseObject):
#if plugin node and if first call call meshroom_compute inside the env on 'host' so that the processchunk
# of the node will be ran into the env
if hasattr(self.node.nodeDesc, 'envFile') and self._status.status!=Status.FIRST_RUN:
if self.node.nodeDesc.isPlugin and self._status.status!=Status.FIRST_RUN:
try:
if not self.node.nodeDesc.isBuild():
from meshroom.core.plugin import isBuilt, build, getCommandLine #lazy import to avoid circular dep
if not isBuilt(self.node.nodeDesc):
self.upgradeStatusTo(Status.BUILD)
self.node.nodeDesc.build()
build(self.node.nodeDesc)
self.upgradeStatusTo(Status.FIRST_RUN)
command = self.node.nodeDesc.getCommandLine(self)
command = getCommandLine(self)
#NOTE: docker returns 0 even if mount fail (it fails on the deamon side)
logging.info("Running plugin node with "+command)
status = os.system(command)
@ -484,6 +484,7 @@ class NodeChunk(BaseObject):
elapsedTime = Property(float, lambda self: self._status.elapsedTime, notify=statusChanged)
# Simple structure for storing node position
Position = namedtuple("Position", ["x", "y"])
# Initialize default coordinates values to 0
@ -1416,6 +1417,8 @@ class BaseNode(BaseObject):
hasSequenceOutput = Property(bool, hasSequenceOutputAttribute, notify=outputAttrEnabledChanged)
has3DOutput = Property(bool, has3DOutputAttribute, notify=outputAttrEnabledChanged)
isPlugin = Property(bool, lambda self: self.nodeDesc.isPlugin, constant=True)
isBuilt = Property(bool, lambda self: self.nodeDesc.isBuilt, constant=True)
class Node(BaseNode):
"""

View file

@ -17,10 +17,11 @@ import subprocess
import venv
import inspect
from meshroom.core import desc, hashValue
from meshroom.core import pluginsNodesFolder, pluginsPipelinesFolder, defaultCacheFolder, pluginCatalogFile
from meshroom.core import desc
from meshroom.core import pluginsNodesFolder, pluginsPipelinesFolder, pluginCatalogFile, defaultCacheFolder
from meshroom.core import meshroomFolder
from meshroom.core.graph import loadGraph
from meshroom.core import hashValue
#where the executables are (eg meshroom compute)
meshroomBinDir = os.path.abspath(os.path.join(meshroomFolder, "..", "bin"))
@ -54,6 +55,9 @@ class PluginParams():
if "pipelineFolder" in jsonData.keys():
self.pipelineFolder = os.path.join(pluginUrl, jsonData["pipelineFolder"])
def getEnvName(envContent):
return "meshroom_plugin_"+hashValue(envContent)
def _dockerImageExists(image_name, tag='latest'):
"""
Check if the desired image:tag exists
@ -103,11 +107,14 @@ def getVenvExe(venvPath):
raise FileNotFoundError(f"Python executable not found in the specified virtual environment: "+executable)
return executable
def getVenvPath(envName):
return os.path.join(defaultCacheFolder, envName)
def _venvExists(envName):
"""
Check if the following virtual env exists
"""
return os.path.isdir(os.path.join(defaultCacheFolder, envName))
return os.path.isdir(getVenvPath(envName))
def installPlugin(pluginUrl):
"""
@ -163,7 +170,11 @@ def installPlugin(pluginUrl):
if isLocal:
os.symlink(pluginParam.nodesFolder, intallFolder)
if os.path.isdir(pluginParam.pipelineFolder):
os.symlink(pluginParam.pipelineFolder, os.path.join(pluginsPipelinesFolder, pluginParam.pluginName))
pipelineFolderLink = os.path.join(pluginsPipelinesFolder, pluginParam.pluginName)
if os.path.exists(pipelineFolderLink):
logging.warn("Plugin already installed, will overwrite")
os.unlink(pipelineFolderLink)
os.symlink(pluginParam.pipelineFolder, pipelineFolderLink)
else:
copy_tree(pluginParam.nodesFolder, intallFolder)
if os.path.isdir(pluginParam.pipelineFolder):
@ -204,87 +215,62 @@ def uninstallPlugin(pluginUrl):
else:
os.removedirs(pluginUrl)
class PluginNode(desc.Node):
def isBuilt(nodeDesc):
"""
Class to be used to make a plugin node, you need to overwrite envType and envFile
Check if the env needs to be build for a specific nodesc.
"""
@property
def envType(cls):
"""
Dynamic env type
"""
raise NotImplementedError("You must specify one or several envtype in the node description")
@property
def envFile(cls):
"""
Env file used to build the environement, you may overwrite this to custom the behaviour
"""
raise NotImplementedError("You must specify an env file")
@property
def _envName(cls):
"""
Get the env name by hashing the env files, overwrite this to use a custom pre-build env
"""
with open(cls.envFile, 'r') as file:
envContent = file.read()
return "meshroom_plugin_"+hashValue(envContent)
def isBuild(cls):
"""
Check if the env needs to be build.
"""
if cls.envType == EnvType.NONE:
if nodeDesc.envType == EnvType.NONE:
return True
elif cls.envType == EnvType.PIP:
elif nodeDesc.envType == EnvType.PIP:
#NOTE: could find way to check for installed packages instead of rebuilding all the time
return False
elif cls.envType == EnvType.VENV:
return _venvExists(cls._envName)
elif cls.envType == EnvType.CONDA:
return _condaEnvExist(cls._envName)
elif cls.envType == EnvType.DOCKER:
return _dockerImageExists(cls._envName)
elif nodeDesc.envType == EnvType.VENV:
return _venvExists(nodeDesc._envName)
elif nodeDesc.envType == EnvType.CONDA:
return _condaEnvExist(nodeDesc._envName)
elif nodeDesc.envType == EnvType.DOCKER:
return _dockerImageExists(nodeDesc._envName)
def build(cls):
def build(nodeDesc):
"""
Perform the needed steps to prepare the environement in which to run the node.
"""
if cls.envType == EnvType.NONE:
if not hasattr(nodeDesc, 'envFile'):
raise RuntimeError("The nodedesc has no env file")
returnValue = 0
if nodeDesc.envType == EnvType.NONE:
pass
elif cls.envType == EnvType.PIP:
elif nodeDesc.envType == EnvType.PIP:
#install packages in the same python as meshroom
logging.info("Installing packages from "+ cls.envFile)
buildCommand = sys.executable+" -m pip install "+ cls.envFile
logging.info("Installing packages from "+ nodeDesc.envFile)
buildCommand = sys.executable+" -m pip install "+ nodeDesc.envFile
logging.info("Building with "+buildCommand+" ...")
returnValue = os.system(buildCommand)
logging.info("Done")
elif cls.envType == EnvType.VENV:
elif nodeDesc.envType == EnvType.VENV:
#create venv in default cache folder
logging.info("Creating virtual env "+os.path.join(defaultCacheFolder, cls._envName)+" from "+cls.envFile)
envPath = os.path.join(defaultCacheFolder, cls._envName)
envPath = getVenvPath(nodeDesc._envName)
logging.info("Creating virtual env "+envPath+" from "+nodeDesc.envFile)
venv.create(envPath, with_pip=True)
logging.info("Installing dependencies")
envExe = getVenvExe(envPath)
returnValue = os.system(_cleanEnvVarsRez()+envExe+" -m pip install -r "+ cls.envFile)
returnValue = os.system(_cleanEnvVarsRez()+envExe+" -m pip install -r "+ nodeDesc.envFile)
venvPythonLibFolder = os.path.join(os.path.dirname(envExe), '..', 'lib')
venvPythonLibFolder = [os.path.join(venvPythonLibFolder, p)
for p in os.listdir(venvPythonLibFolder) if p.startswith("python")][0]
os.symlink(meshroomFolder,os.path.join(venvPythonLibFolder, 'site-packages', 'meshroom'))
logging.info("Done")
elif cls.envType == EnvType.CONDA:
elif nodeDesc.envType == EnvType.CONDA:
#build a conda env from a yaml file
logging.info("Creating conda env "+cls._envName+" from "+cls.envFile)
logging.info("Creating conda env "+nodeDesc._envName+" from "+nodeDesc.envFile)
makeEnvCommand = ( _cleanEnvVarsRez()+" conda config --set channel_priority strict ; "
+" conda env create -v -v --name "+cls._envName
+" --file "+cls.envFile+" ")
+" conda env create -v -v --name "+nodeDesc._envName
+" --file "+nodeDesc.envFile+" ")
logging.info("Making conda env")
logging.info(makeEnvCommand)
returnValue = os.system(makeEnvCommand)
#find path to env's folder and add symlink to meshroom
condaPythonExecudable=subprocess.check_output(_cleanEnvVarsRez()+"conda run -n "+cls._envName
condaPythonExecudable=subprocess.check_output(_cleanEnvVarsRez()+"conda run -n "+nodeDesc._envName
+" python -c \"import sys; print(sys.executable)\"",
shell=True).strip().decode('UTF-8')
condaPythonLibFolder=os.path.join(os.path.dirname(condaPythonExecudable), '..', 'lib')
@ -292,28 +278,31 @@ class PluginNode(desc.Node):
for p in os.listdir(condaPythonLibFolder) if p.startswith("python")][0]
os.symlink(meshroomFolder,os.path.join(condaPythonLibFolder, 'meshroom'))
logging.info("Done making conda env")
elif cls.envType == EnvType.DOCKER:
elif nodeDesc.envType == EnvType.DOCKER:
#build docker image
logging.info("Creating image "+cls._envName+" from "+ cls.envFile)
buildCommand = "docker build -f "+cls.envFile+" -t "+cls._envName+" "+os.path.dirname(cls.envFile)
logging.info("Creating image "+nodeDesc._envName+" from "+ nodeDesc.envFile)
buildCommand = "docker build -f "+nodeDesc.envFile+" -t "+nodeDesc._envName+" "+os.path.dirname(nodeDesc.envFile)
logging.info("Building with "+buildCommand+" ...")
returnValue = os.system(buildCommand)
logging.info("Done")
else:
raise RuntimeError("Invalid env type")
if returnValue != 0:
raise RuntimeError("Something went wrong during build")
def getCommandLine(cls, chunk):
def getCommandLine(chunk):
"""
Return the command line needed to enter the environment + meshroom_compute
Will make meshroom available in the environment.
"""
nodeDesc=chunk.node.nodeDesc
if chunk.node.isParallelized:
raise RuntimeError("Parallelisation not supported for plugin nodes")
if chunk.node.graph.filepath == "":
raise RuntimeError("The project needs to be saved to use plugin nodes")
saved_graph = loadGraph(chunk.node.graph.filepath)
if (str(chunk.node) not in [str(f) for f in saved_graph._nodes._objects]
or chunk.node._uids[0] != saved_graph.findNode(str(chunk.node))._uids[0] ):
or chunk.node._uid != saved_graph.findNode(str(chunk.node))._uid ):
raise RuntimeError("The changes needs to be saved to use plugin nodes")
cmdPrefix = ""
@ -322,16 +311,16 @@ class PluginNode(desc.Node):
meshroomComputeArgs = "--node "+chunk.node.name+" \""+chunk.node.graph.filepath+"\""
pythonsetMeshroomPath = "export PYTHONPATH="+meshroomFolder+":$PYTHONPATH;"
if cls.envType == EnvType.VENV:
envPath = os.path.join(defaultCacheFolder, cls._envName)
if nodeDesc.envType == EnvType.VENV:
envPath = getVenvPath(nodeDesc._envName)
envExe = getVenvExe(envPath)
#make sure meshroom in in pythonpath and that we call the right python
cmdPrefix = _cleanEnvVarsRez()+pythonsetMeshroomPath+" "+envExe + " "+ meshroomCompute +" "
elif cls.envType == EnvType.CONDA:
elif nodeDesc.envType == EnvType.CONDA:
#NOTE: system env vars are not passed to conda run, we installed it 'manually' before
cmdPrefix = _cleanEnvVarsRez()+" conda run --cwd "+os.path.join(meshroomFolder, "..")\
+" --no-capture-output -n "+cls._envName+" "+" python "+meshroomCompute
elif cls.envType == EnvType.DOCKER:
+" --no-capture-output -n "+nodeDesc._envName+" "+" python "+meshroomCompute
elif nodeDesc.envType == EnvType.DOCKER:
#path to the selected plugin
classFile=inspect.getfile(chunk.node.nodeDesc.__class__)
pluginDir = os.path.abspath(os.path.realpath(os.path.join(os.path.dirname(classFile),"..")))
@ -345,24 +334,21 @@ class PluginNode(desc.Node):
envCommand = " --env PYTHONPATH=/meshroomFolder --env MESHROOM_NODES_PATH=/meshroomPlugin"
#adds the gpu arg if needed
runtimeArg=""
if cls.gpu != desc.Level.NONE:
if chunk.node.nodeDesc.gpu != desc.Level.NONE:
runtimeArg="--runtime=nvidia --gpus all"
#compose cl
cmdPrefix = "docker run -it --rm "+runtimeArg+" "+mountCommand+" "+envCommand+" "+cls._envName +" \"python /meshroomBinDir/meshroom_compute "
cmdPrefix = "docker run -it --rm "+runtimeArg+" "+mountCommand+" "+envCommand+" "+nodeDesc._envName +" \"python /meshroomBinDir/meshroom_compute "
meshroomComputeArgs="--node "+chunk.node.name+" "+chunk.node.graph.filepath+"\""
else:
raise RuntimeError("NodeType not recognised")
command=cmdPrefix+" "+meshroomComputeArgs
return command
#class that call command line nodes in an env
class PluginCommandLineNode(PluginNode, desc.CommandLineNode):
def buildCommandLine(self, chunk):
cmd = super().buildCommandLine(chunk)
#the process in Popen does not seem to use the right python, even if meshroom_compute is call within the env
#so in the case of command line using python, we have to make sur it is using the correct python
if self.envType == EnvType.VENV:
envPath = os.path.join(defaultCacheFolder, self._envName)
envExe = getVenvExe(envPath)
cmd=cmd.replace("python", envExe)
return cmd
# you may use these to explicitly define Pluginnodes
class PluginNode(desc.Node):
pass
class PluginCommandLineNode(desc.CommandLineNode):
pass

View file

@ -2,7 +2,7 @@
{
"pluginName":"Meshroom Research",
"pluginUrl":"https://github.com/alicevision/MeshroomResearch/",
"description":"Meshroom-Research comprises a collection of plugins for Meshroom, mostly develloped in-house at MikrosImage",
"description":"Meshroom-Research comprises a collection of experimental plugins for Meshroom",
"isCollection":true,
"nodeTypes":["Python", "Docker", "Conda"]
}

View file

@ -131,6 +131,73 @@ Page {
}
}
//File browser for plugin
Dialog {
id: pluginURLDialog
title: "Plugin URL"
height: 150
width: 300
standardButtons: StandardButton.Ok | StandardButton.Cancel
//focus: true
Column {
anchors.fill: parent
Text {
text: "Plugin URL"
height: 40
}
TextField {
id: urlInput
width: parent.width * 0.75
focus: true
}
}
onButtonClicked: {
if (clickedButton==StandardButton.Ok) {
console.log("Accepted " + clickedButton)
if (_reconstruction.installPlugin(urlInput.text)) {
pluginInstalledDialog.open()
} else {
pluginNotInstalledDialog.open()
}
}
}
}
// dialogs for plugins
MessageDialog {
id: pluginInstalledDialog
title: "Plugin installed"
modal: true
canCopy: false
Label {
text: "Plugin installed, please restart meshroom for the changes to take effect"
}
}
MessageDialog {
id: pluginNotInstalledDialog
title: "Plugin not installed"
modal: true
canCopy: false
Label {
text: "Something went wrong, plugin not installed"
}
}
// plugin installation from path or url
Platform.FolderDialog {
id: intallPluginDialog
options: Platform.FolderDialog.DontUseNativeDialog
title: "Install Plugin"
onAccepted: {
if (_reconstruction.installPlugin(currentFolder.toString())) {
pluginInstalledDialog.open()
} else {
pluginNotInstalledDialog.open()
}
}
}
Item {
id: computeManager
@ -525,6 +592,23 @@ Page {
}
}
Action {
id: installPluginFromFolderAction
text: "Install Plugin From Local Folder"
onTriggered: {
initFileDialogFolder(intallPluginDialog)
intallPluginDialog.open()
}
}
Action {
id: installPluginFromURLAction
text: "Install Plugin From URL"
onTriggered: {
pluginURLDialog.open()
}
}
header: RowLayout {
spacing: 0
MaterialToolButton {
@ -741,6 +825,18 @@ Page {
ToolTip.visible: hovered
ToolTip.text: removeImagesFromAllGroupsAction.tooltip
}
MenuItem {
action: installPluginFromFolderAction
ToolTip.visible: hovered
ToolTip.text: "Install plugin from a folder"
}
MenuItem {
action: installPluginFromURLAction
ToolTip.visible: hovered
ToolTip.text: "Install plugin from a local or online url"
}
}
MenuSeparator { }
Action {
@ -1214,6 +1310,17 @@ Page {
var n = _reconstruction.upgradeNode(node)
_reconstruction.selectedNode = n
}
onDoBuild: {
try {
_reconstruction.buildNode(node.name)
node.isNotBuilt=false
} catch (error) {
//NOTE: could do an error popup
console.log("Build error:")
console.log(error)
}
}
}
}
}

View file

@ -19,6 +19,9 @@ Item {
property bool readOnly: node.locked
/// Whether the node is in compatibility mode
readonly property bool isCompatibilityNode: node ? node.hasOwnProperty("compatibilityIssue") : false
/// Whether the node is a plugin that needs to be build
readonly property bool isPlugin: node ? node.isPlugin : false
property bool isNotBuilt: node ? (!node.isBuilt) : false
/// Mouse related states
property bool mainSelected: false
property bool selected: false
@ -28,7 +31,7 @@ Item {
property point position: Qt.point(x, y)
/// Styling
property color shadowColor: "#cc000000"
readonly property color defaultColor: isCompatibilityNode ? "#444" : !node.isComputable ? "#BA3D69" : activePalette.base
readonly property color defaultColor: isCompatibilityNode ? "#444" : (!node.isComputable ? "#BA3D69" : activePalette.base)
property color baseColor: defaultColor
property point mousePosition: Qt.point(mouseArea.mouseX, mouseArea.mouseY)
@ -233,6 +236,15 @@ Item {
}
}
// ToBuild icon for PluginNodes
Loader {
active: root.isPlugin && root.isNotBuilt
sourceComponent: ToBuildBadge {
sourceComponent: iconDelegate
}
}
// Data sharing indicator
// Note: for an unknown reason, there are some performance issues with the UI refresh.
// Example: a node duplicated 40 times will be slow while creating another identical node

View file

@ -18,9 +18,12 @@ Panel {
property bool readOnly: false
property bool isCompatibilityNode: node && node.compatibilityIssue !== undefined
property string nodeStartDateTime: ""
readonly property bool isPlugin: node ? node.isPlugin : false
readonly property bool isNotBuilt: node ? (!node.isBuilt) : false
signal attributeDoubleClicked(var mouse, var attribute)
signal upgradeRequest()
signal doBuild()
title: "Node" + (node !== null ? " - <b>" + node.label + "</b>" + (node.label !== node.defaultLabel ? " (" + node.defaultLabel + ")" : "") : "")
icon: MaterialLabel { text: MaterialIcons.tune }
@ -225,6 +228,17 @@ Panel {
}
}
Loader {
active: root.isPlugin && root.isNotBuilt
Layout.fillWidth: true
visible: active // for layout update
sourceComponent: ToBuildBadge {
onDoBuild: root.doBuild()
sourceComponent: bannerDelegate
}
}
Loader {
Layout.fillHeight: true
Layout.fillWidth: true

View file

@ -0,0 +1,66 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.11
import MaterialIcons 2.2
Loader {
id: root
sourceComponent: iconDelegate
signal doBuild()
property Component iconDelegate: Component {
Label {
text: MaterialIcons.warning
font.family: MaterialIcons.fontFamily
font.pointSize: 12
color: "#66207F"
MouseArea {
anchors.fill: parent
hoverEnabled: true
onPressed: mouse.accepted = false
ToolTip.text: "Node env needs to be built"
ToolTip.visible: containsMouse
}
}
}
property Component bannerDelegate: Component {
Pane {
padding: 6
clip: true
background: Rectangle { color: "#66207F" }
RowLayout {
width: parent.width
Column {
Layout.fillWidth: true
Label {
width: parent.width
elide: Label.ElideMiddle
font.bold: true
text: "Env needs to be built"
color: "white"
}
Label {
width: parent.width
elide: Label.ElideMiddle
color: "white"
}
}
Button {
visible: (parent.width > width) ? 1 : 0
palette.window: root.color
palette.button: Qt.darker(root.color, 1.2)
palette.buttonText: "white"
text: "Build"
onClicked: doBuild()
}
}
}
}
}

View file

@ -9,6 +9,7 @@ AttributePin 1.0 AttributePin.qml
AttributeEditor 1.0 AttributeEditor.qml
AttributeItemDelegate 1.0 AttributeItemDelegate.qml
CompatibilityBadge 1.0 CompatibilityBadge.qml
ToBuildBadge 1.0 ToBuildBadge.qml
CompatibilityManager 1.0 CompatibilityManager.qml
singleton GraphEditorSettings 1.0 GraphEditorSettings.qml
TaskManager 1.0 TaskManager.qml

View file

@ -132,73 +132,6 @@ ApplicationWindow {
}
}
//File browser for plugin
Dialog {
id: pluginURLDialog
title: "Plugin URL"
height: 150
width: 300
standardButtons: StandardButton.Ok | StandardButton.Cancel
//focus: true
Column {
anchors.fill: parent
Text {
text: "Plugin URL"
height: 40
}
TextField {
id: urlInput
width: parent.width * 0.75
focus: true
}
}
onButtonClicked: {
if (clickedButton==StandardButton.Ok) {
console.log("Accepted " + clickedButton)
if (_reconstruction.installPlugin(urlInput.text)) {
pluginInstalledDialog.open()
} else {
pluginNotInstalledDialog.open()
}
}
}
}
// dialogs for plugins
MessageDialog {
id: pluginInstalledDialog
title: "Plugin installed"
modal: true
canCopy: false
Label {
text: "Plugin installed, please restart meshroom for the changes to take effect"
}
}
MessageDialog {
id: pluginNotInstalledDialog
title: "Plugin not installed"
modal: true
canCopy: false
Label {
text: "Something went wrong, plugin not installed"
}
}
// plugin installation from path or url
Platform.FolderDialog {
id: intallPluginDialog
options: Platform.FolderDialog.DontUseNativeDialog
title: "Install Plugin"
onAccepted: {
if (_reconstruction.installPlugin(currentFolder.toString())) {
pluginInstalledDialog.open()
} else {
pluginNotInstalledDialog.open()
}
}
}
// Check if document has been saved
function ensureSaved(callback)
{

View file

@ -586,6 +586,14 @@ class Reconstruction(UIGraph):
localFile = prepareUrlLocalFile(url)
return installPlugin(localFile)
@Slot(str, result=bool)
def buildNode(self, nodeName):
print("***Building "+nodeName)
node = self._graph.node(nodeName)
from meshroom.core.plugin import isBuilt, build #lazy import to avoid circular dep
if not isBuilt(node.nodeDesc):
build(node.nodeDesc)
def onGraphChanged(self):
""" React to the change of the internal graph. """
self._liveSfmManager.reset()

View file

@ -6,6 +6,7 @@ from meshroom.core.graph import Graph
logging = logging.getLogger(__name__)
def test_pluginNodes():
#Dont run the tests in the CI as we are unable to install plugins beforehand
if "CI" in os.environ:
return
graph = Graph('')