Skip to content

Commit 1d89d80

Browse files
authored
Merge pull request #3154 from alicevision/feat/get_meshroom_cache_param
Add node to fetch parameter values from another Meshroom scene
2 parents 507e2dc + 5448135 commit 1d89d80

7 files changed

Lines changed: 453 additions & 33 deletions

File tree

bin/meshroom_batch

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,10 @@ args = parser.parse_args()
169169

170170
logging.getLogger().setLevel(logStringToPython[args.verbose])
171171

172+
if not args.save:
173+
# overrideCacheDir can only work if we want to save the scene
174+
args.overrideCacheDir = None
175+
172176
meshroom.core.initPlugins()
173177
meshroom.core.initNodes()
174178

@@ -185,6 +189,9 @@ with meshroom.core.graph.GraphModification(graph):
185189
graph.initFromTemplate(args.pipeline, copyOutputs=True if args.output else False)
186190

187191
if args.overrideCacheDir is not None:
192+
# If the graph is not saved yet we need to save it because the
193+
# explicit cache saves a relative path to the scene filepath
194+
graph.save(args.save)
188195
# Set the cache folder
189196
graph.setExplicitCacheDir(args.overrideCacheDir)
190197

meshroom/nodes/general/GenerateMeshroomScene.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# -*- coding: utf-8 -*-
22

3-
__version__ = "1.0"
3+
__version__ = "2.0"
44

55
import shlex
66
import logging
@@ -39,7 +39,6 @@ class GenerateMeshroomScene(desc.Node):
3939
),
4040
desc.ListAttribute(
4141
name="inputOverrides",
42-
label="Input Overrides",
4342
description="Overrides for the CameraInit nodes.",
4443
exposed=True,
4544
commandLineGroup="",
@@ -54,7 +53,7 @@ class GenerateMeshroomScene(desc.Node):
5453
),
5554
desc.ListAttribute(
5655
name="paramOverrides",
57-
label="Parameter overrides",
56+
label="Parameter Overrides",
5857
description="Overrides for the nodes in the Meshroom scene to create.",
5958
exposed=True,
6059
commandLineGroup="",
@@ -74,6 +73,13 @@ class GenerateMeshroomScene(desc.Node):
7473
value="",
7574
exposed=False
7675
),
76+
desc.File(
77+
name="setCacheDir",
78+
label="Cache Folder",
79+
description="Path to the cache folder.",
80+
value="",
81+
exposed=False
82+
),
7783
]
7884

7985
outputs = [
@@ -123,7 +129,7 @@ def process(self, node):
123129
logging.info(f"Creating parent folder: {sceneRoot}")
124130
sceneRoot.mkdir(parents=True, exist_ok=True)
125131

126-
command = [self.pythonExecutable, str(_MESHROOM_BATCH), "-p", templateScene]
132+
command = [self.pythonExecutable, str(_MESHROOM_BATCH)]
127133
command += ["-p", templateScene]
128134
if inputOverrides:
129135
command += ["--input"] + inputOverrides
@@ -133,7 +139,9 @@ def process(self, node):
133139
command += ["--compute", "no"]
134140
if invalidationString := node.setInvalidationString.value:
135141
command += ["--setInvalidationString", invalidationString]
136-
142+
if cacheDir := node.setCacheDir.value:
143+
command += ["--overrideCacheDir", cacheDir]
144+
137145
# Launch subprocess
138146
logging.info(f"{'='*10} Command {'='*10}")
139147
logging.info(f"{shlex.join(command)}")
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
# -*- coding: utf-8 -*-
2+
3+
__version__ = "1.0"
4+
5+
import shlex
6+
from pathlib import Path
7+
from meshroom.core import desc
8+
9+
10+
SCRIPT = Path(__file__).parent / "scripts" / "extractMeshroomSceneParams.py"
11+
12+
13+
class GetMeshroomSceneParams(desc.CommandLineNode):
14+
"""Extract parameters from nodes of another scene.
15+
The output is a JSON file containing a list of items with the following keys:
16+
- node: node instance name
17+
- parameter: parameter path
18+
- value: extracted parameter value
19+
20+
For the parameter you can put parameters inside groups and lists too:
21+
- simple parameter: "paramName"
22+
- parameter inside a group: "groupParamName.paramName"
23+
- parameter inside a list: "listParamName[index]"
24+
"""
25+
26+
category = "Utils"
27+
28+
pythonExecutable = "python"
29+
commandLine = ""
30+
31+
def buildCommandLine(self, chunk):
32+
node = chunk.node
33+
34+
# Get request
35+
requestedParams = []
36+
for item in node.parameters.value:
37+
nodeName = item.nodeInstance.value.strip()
38+
paramPath = item.paramName.value.strip()
39+
if nodeName and paramPath:
40+
requestedParams.append(f"{nodeName}:{paramPath}")
41+
request = ";".join(requestedParams)
42+
43+
# Build command line
44+
cmdLine = f"{node.nodeDesc.pythonExecutable} {SCRIPT.as_posix()}"
45+
cmdLine += f" --scene {shlex.quote(node.scene.value)}"
46+
cmdLine += f" --request {shlex.quote(request)}"
47+
cmdLine += f" --output {shlex.quote(node.paramValuesDict.value)}"
48+
49+
if node.advanced.failOnMissingScene.value == True:
50+
cmdLine += " --failOnMissingScene"
51+
if node.advanced.failOnMissingParams.value == True:
52+
cmdLine += " --failOnMissingParams"
53+
54+
node.nodeDesc.commandLine = cmdLine
55+
return super().buildCommandLine(chunk)
56+
57+
inputs = [
58+
desc.File(
59+
name="scene",
60+
description="Meshroom scene.",
61+
value="",
62+
exposed=True,
63+
),
64+
desc.ListAttribute(
65+
name="parameters",
66+
description="List of node/parameter pairs to extract from the source scene.",
67+
exposed=True,
68+
commandLineGroup="",
69+
elementDesc=desc.GroupAttribute(
70+
name="parameter",
71+
exposed=True,
72+
items=[
73+
desc.StringParam(
74+
name="nodeInstance",
75+
label="Node Instance",
76+
description="Node instance name.",
77+
value="",
78+
exposed=True,
79+
),
80+
desc.StringParam(
81+
name="paramName",
82+
label="Parameter",
83+
description="Attribute path to extract (e.g. 'groupName.subParam').",
84+
value="",
85+
exposed=True,
86+
)
87+
]
88+
)
89+
),
90+
desc.GroupAttribute(
91+
name="advanced",
92+
items=[
93+
desc.BoolParam(
94+
name="failOnMissingScene",
95+
description="Fail if the scene doesn't exist.",
96+
value=True,
97+
invalidate=False
98+
),
99+
desc.BoolParam(
100+
name="failOnMissingParams",
101+
description="Fail if we don't find one or several params inside the scene.",
102+
value=True,
103+
invalidate=False
104+
),
105+
],
106+
advanced=True,
107+
)
108+
]
109+
110+
outputs = [
111+
desc.File(
112+
name="paramValuesDict",
113+
label="Values JSON",
114+
description="Path to the JSON file containing the extracted override strings.",
115+
value="{nodeCacheFolder}/values.json",
116+
)
117+
]
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
# -*- coding: utf-8 -*-
2+
3+
__version__ = "1.0"
4+
5+
import json
6+
import logging
7+
from pathlib import Path
8+
from meshroom.core import desc
9+
10+
11+
class UnwrapMeshroomSceneParam(desc.CommandLineNode):
12+
"""Unwrap the JSON file created by a GetMeshroomSceneParams node
13+
to expose its items to the graph.
14+
15+
It uses the jsonFile connection to fetch parameters from the
16+
GetMeshroomSceneParams node to fill the selectedParameter choices,
17+
so it's better to use it directly after this node.
18+
"""
19+
20+
category = "Utils"
21+
22+
inputs = [
23+
desc.File(
24+
name="jsonFile",
25+
label="JSON File",
26+
description=(
27+
"JSON file generated by a GetMeshroomSceneParams node.\n"
28+
"Note that the input must be a GetMeshroomSceneParams node "
29+
"because we use the input to fetch the choice parameters."
30+
),
31+
value="",
32+
exposed=True,
33+
),
34+
desc.ChoiceParam(
35+
name="selectedParameter",
36+
description="Parameter to fetch from the JSON file.",
37+
value="",
38+
values=[],
39+
exclusive=True,
40+
exposed=True,
41+
)
42+
]
43+
44+
outputs = [
45+
desc.StringParam(
46+
name="outputValue",
47+
description="Extracted value from the JSON file.",
48+
value=None,
49+
),
50+
]
51+
52+
@staticmethod
53+
def updateChoices(node):
54+
if not node.jsonFile.isLink:
55+
node.selectedParameter.setValues([])
56+
return
57+
inputNode = node.jsonFile.inputLink.node
58+
if inputNode.nodeType != "GetMeshroomSceneParams":
59+
node.selectedParameter.setValues([])
60+
return
61+
inputChoices = []
62+
for item in inputNode.parameters.value:
63+
nodeName = item.nodeInstance.value.strip()
64+
paramPath = item.paramName.value.strip()
65+
if nodeName and paramPath:
66+
inputChoices.append(f"{nodeName}:{paramPath}")
67+
68+
node.selectedParameter.setValues(inputChoices)
69+
70+
@staticmethod
71+
def setOutput(node):
72+
if not node.selectedParameter or not node.selectedParameter.value:
73+
return
74+
selectedParameter = node.selectedParameter.value
75+
logging.debug(f"[UnwrapMeshroomSceneParam] Selected parameter: {selectedParameter}")
76+
nodeInstance, param = selectedParameter.split(":", 1)
77+
logging.debug(f" nodeInstance: {nodeInstance}")
78+
logging.debug(f" param : {param}")
79+
jsonFile = node.jsonFile.value
80+
if not Path(jsonFile).exists():
81+
node.outputValue.value = ""
82+
return
83+
with open(jsonFile, "r") as f:
84+
data = json.load(f)
85+
logging.debug(f"JSON file : {jsonFile}:\n{data}")
86+
for item in data:
87+
if item.get("node") == nodeInstance and item.get("parameter") == param:
88+
node.outputValue.value = item.get("value")
89+
break
90+
else:
91+
node.outputValue.value = ""
92+
93+
def update(self, node):
94+
try:
95+
self.updateChoices(node)
96+
except Exception as e:
97+
logging.warning(f"[UnwrapMeshroomSceneParam] Failed to set the choices: {e}")
98+
99+
def processChunk(self, chunk):
100+
self.setOutput(chunk.node)

0 commit comments

Comments
 (0)