mirror of
https://github.com/alicevision/Meshroom.git
synced 2025-08-02 00:08:29 +02:00
Merge pull request #94 from alicevision/dev_duplicateNodes
GraphEditor: allow nodes duplication
This commit is contained in:
commit
c3bf936b9f
4 changed files with 132 additions and 21 deletions
|
@ -51,6 +51,15 @@ else:
|
|||
|
||||
stringIsLinkRe = re.compile('^\{[A-Za-z]+[A-Za-z0-9_.]*\}$')
|
||||
|
||||
|
||||
def isLink(value):
|
||||
"""
|
||||
Return whether the given argument is a link expression.
|
||||
A link expression is a string matching the {nodeName.attrName} pattern.
|
||||
"""
|
||||
return isinstance(value, basestring) and stringIsLinkRe.match(value)
|
||||
|
||||
|
||||
def isCollection(v):
|
||||
return isinstance(v, collections.Iterable) and not isinstance(v, basestring)
|
||||
|
||||
|
@ -170,7 +179,7 @@ class Attribute(BaseObject):
|
|||
if self._value == value:
|
||||
return
|
||||
|
||||
if isinstance(value, Attribute) or (isinstance(value, basestring) and stringIsLinkRe.match(value)):
|
||||
if isinstance(value, Attribute) or (isinstance(value, basestring) and isLink(value)):
|
||||
# if we set a link to another attribute
|
||||
self._value = value
|
||||
else:
|
||||
|
@ -236,7 +245,7 @@ class Attribute(BaseObject):
|
|||
if isinstance(v, Attribute):
|
||||
g.addEdge(v, self)
|
||||
self._value = ""
|
||||
elif self.isInput and isinstance(v, basestring) and stringIsLinkRe.match(v):
|
||||
elif self.isInput and isinstance(v, basestring) and isLink(v):
|
||||
# value is a link to another attribute
|
||||
link = v[1:-1]
|
||||
linkNode, linkAttr = link.split('.')
|
||||
|
|
|
@ -288,6 +288,61 @@ class UIGraph(QObject):
|
|||
""" Reset 'attribute' to its default value """
|
||||
self.push(commands.SetAttributeCommand(self._graph, attribute, attribute.defaultValue()))
|
||||
|
||||
@Slot(graph.Node)
|
||||
def duplicateNode(self, srcNode, createEdges=True):
|
||||
"""
|
||||
Duplicate 'srcNode'.
|
||||
|
||||
Args:
|
||||
srcNode (graph.Node): the node to duplicate
|
||||
createEdges (bool): whether to replicate 'srcNode' edges on the duplicated node
|
||||
|
||||
Returns:
|
||||
graph.Node: the duplicated node
|
||||
"""
|
||||
serialized = srcNode.toDict()
|
||||
with self.groupedGraphModification("Duplicate Node {}".format(srcNode.name)):
|
||||
# skip edges: filter out attributes which are links
|
||||
if not createEdges:
|
||||
serialized["attributes"] = {k: v for k, v in serialized["attributes"].items() if not graph.isLink(v)}
|
||||
# create a new node of the same type and with the same attributes values
|
||||
node = self.addNode(serialized["nodeType"], **serialized["attributes"])
|
||||
return node
|
||||
|
||||
@Slot(graph.Node, result="QVariantList")
|
||||
def duplicateNodes(self, fromNode):
|
||||
"""
|
||||
Duplicate 'fromNode' and all the following nodes towards graph's leaves.
|
||||
|
||||
Args:
|
||||
fromNode (graph.Node): the node to start the duplication from
|
||||
|
||||
Returns:
|
||||
[graph.Nodes]: the duplicated nodes
|
||||
"""
|
||||
srcNodes, srcEdges = self._graph.nodesFromNode(fromNode)
|
||||
srcNodes = srcNodes[1:] # skip fromNode
|
||||
duplicates = {}
|
||||
with self.groupedGraphModification("Duplicate {} Nodes".format(len(srcNodes))):
|
||||
# duplicate the first node with its external edges
|
||||
duplicates[fromNode.name] = self.duplicateNode(fromNode)
|
||||
# duplicate all the following nodes and remap their edges internally
|
||||
for srcNode in srcNodes:
|
||||
duplicate = self.duplicateNode(srcNode, createEdges=False)
|
||||
duplicates[srcNode.name] = duplicate # original node to duplicate map
|
||||
# get link attributes
|
||||
links = {k: v for k, v in srcNode.toDict()["attributes"].items() if graph.isLink(v)}
|
||||
for attr, link in links.items():
|
||||
link = link[1:-1] # remove starting '{' and trailing '}'
|
||||
# get source node and attribute name
|
||||
edgeSrcNode, edgeSrcAttrName = link.split(".", 1)
|
||||
# if the edge's source node has been duplicated, use the duplicate
|
||||
# otherwise use the original node
|
||||
edgeSrcNode = duplicates.get(edgeSrcNode, self._graph.node(edgeSrcNode))
|
||||
self.addEdge(edgeSrcNode.attribute(edgeSrcAttrName), duplicate.attribute(attr))
|
||||
|
||||
return duplicates.values()
|
||||
|
||||
@Slot(graph.Attribute, QJsonValue)
|
||||
def appendAttribute(self, attribute, value=QJsonValue()):
|
||||
if isinstance(value, QJsonValue):
|
||||
|
|
|
@ -225,11 +225,34 @@ Item {
|
|||
|
||||
onAttributePinCreated: registerAttributePin(attribute, pin)
|
||||
|
||||
onPressed: draggable.selectNode(nodeDelegate)
|
||||
onPressed: {
|
||||
if(mouse.modifiers & Qt.AltModifier)
|
||||
{
|
||||
var delegates = duplicate(true)
|
||||
draggable.selectNode(delegates[0])
|
||||
}
|
||||
else
|
||||
draggable.selectNode(nodeDelegate)
|
||||
}
|
||||
|
||||
function duplicate(duplicateFollowingNodes) {
|
||||
var nodes = duplicateFollowingNodes ? uigraph.duplicateNodes(node) : [uigraph.duplicateNode(node)]
|
||||
var delegates = []
|
||||
var from = nodeRepeater.count - nodes.length
|
||||
var to = nodeRepeater.count
|
||||
for(var i=from; i < to; ++i)
|
||||
{
|
||||
delegates.push(nodeRepeater.itemAt(i))
|
||||
}
|
||||
doAutoLayout(from, to, x, y + (root.nodeHeight + root.gridSpacing))
|
||||
return delegates
|
||||
}
|
||||
|
||||
onDoubleClicked: root.nodeDoubleClicked(node)
|
||||
|
||||
onComputeRequest: uigraph.execute(node)
|
||||
onSubmitRequest: uigraph.submit(node)
|
||||
onDuplicateRequest: duplicate(duplicateFollowingNodes)
|
||||
onRemoveRequest: uigraph.removeNode(node)
|
||||
|
||||
Keys.onDeletePressed: uigraph.removeNode(node)
|
||||
|
@ -295,40 +318,53 @@ Item {
|
|||
draggable.y = bbox.y*draggable.scale*-1 + (root.height-bbox.height*draggable.scale)*0.5
|
||||
}
|
||||
|
||||
// Really basic auto-layout based on node depths
|
||||
function doAutoLayout()
|
||||
/** Basic auto-layout based on node depths
|
||||
* @param {int} from the index of the node to start the layout from (default: 0)
|
||||
* @param {int} to the index of the node end the layout at (default: nodeCount)
|
||||
* @param {real} startX layout origin x coordinate (default: 0)
|
||||
* @param {real} startY layout origin y coordinate (default: 0)
|
||||
*/
|
||||
function doAutoLayout(from, to, startX, startY)
|
||||
{
|
||||
var depthProperty = useMinDepth ? 'minDepth' : 'depth'
|
||||
var grid = new Array(nodeRepeater.count)
|
||||
for(var i=0; i< nodeRepeater.count; ++i)
|
||||
grid[i] = new Array(nodeRepeater.count)
|
||||
for(var i=0; i<nodeRepeater.count; ++i)
|
||||
{
|
||||
var obj = nodeRepeater.itemAt(i);
|
||||
}
|
||||
// default values
|
||||
from = from === undefined ? 0 : from
|
||||
to = to === undefined ? nodeRepeater.count : to
|
||||
startX = startX === undefined ? 0 : startX
|
||||
startY = startY === undefined ? 0 : startY
|
||||
|
||||
for(var i=0; i<nodeRepeater.count; ++i)
|
||||
var count = to - from;
|
||||
|
||||
var depthProperty = useMinDepth ? 'minDepth' : 'depth'
|
||||
var grid = new Array(count)
|
||||
|
||||
for(var i=0; i< count; ++i)
|
||||
grid[i] = new Array(count)
|
||||
|
||||
// retrieve reference depth from start node
|
||||
var zeroDepth = from > 0 ? nodeRepeater.itemAt(from).node[depthProperty] : 0
|
||||
|
||||
for(var i=0; i<count; ++i)
|
||||
{
|
||||
var obj = nodeRepeater.itemAt(i);
|
||||
var obj = nodeRepeater.itemAt(from + i);
|
||||
var j=0;
|
||||
while(1)
|
||||
{
|
||||
if(grid[obj.node[depthProperty]][j] == undefined)
|
||||
if(grid[obj.node[depthProperty]-zeroDepth][j] == undefined)
|
||||
{
|
||||
grid[obj.node[depthProperty]][j] = obj;
|
||||
grid[obj.node[depthProperty]-zeroDepth][j] = obj;
|
||||
break;
|
||||
}
|
||||
j++;
|
||||
}
|
||||
}
|
||||
for(var x= 0; x<nodeRepeater.count; ++x)
|
||||
for(var x=0; x<count; ++x)
|
||||
{
|
||||
for(var y=0; y<nodeRepeater.count; ++y)
|
||||
for(var y=0; y<count; ++y)
|
||||
{
|
||||
if(grid[x][y] != undefined)
|
||||
{
|
||||
grid[x][y].x = x * (root.nodeWidth + root.gridSpacing)
|
||||
grid[x][y].y = y * (root.nodeHeight + root.gridSpacing)
|
||||
grid[x][y].x = startX + x * (root.nodeWidth + root.gridSpacing)
|
||||
grid[x][y].y = startY + y * (root.nodeHeight + root.gridSpacing)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,9 +16,11 @@ Item {
|
|||
|
||||
signal computeRequest()
|
||||
signal submitRequest()
|
||||
signal duplicateRequest(var duplicateFollowingNodes)
|
||||
signal removeRequest()
|
||||
|
||||
implicitHeight: body.height
|
||||
objectName: node.name
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
|
@ -51,6 +53,15 @@ Item {
|
|||
onTriggered: Qt.openUrlExternally(node.internalFolder)
|
||||
}
|
||||
MenuSeparator {}
|
||||
MenuItem {
|
||||
text: "Duplicate"
|
||||
onTriggered: duplicateRequest(false)
|
||||
}
|
||||
MenuItem {
|
||||
text: "Duplicate From Here"
|
||||
onTriggered: duplicateRequest(true)
|
||||
}
|
||||
MenuSeparator {}
|
||||
MenuItem {
|
||||
text: "Clear Data"
|
||||
enabled: !root.readOnly
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue