Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added Backdrop Node for Meshroom graph #2574

Open
wants to merge 6 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions meshroom/core/desc.py
Original file line number Diff line number Diff line change
Expand Up @@ -702,6 +702,22 @@ class Node(object):
value="",
invalidate=False,
),
FloatParam(
name="nodeWidth",
label="Node Width",
description="The Node's Width.",
value=160,
range=None,
enabled=False # Hidden always
),
FloatParam(
name="nodeHeight",
label="Node Height",
description="The Node's Height.",
value=120,
range=None,
enabled=False # Hidden always
),
ColorParam(
name="color",
label="Color",
Expand Down Expand Up @@ -779,6 +795,68 @@ def __init__(self):
def processChunk(self, chunk):
pass

def stopProcess(self, chunk):
pass


class Backdrop(InputNode):
""" A Backdrop for other nodes.
"""

# The internal inputs' of Backdrop Node needs a Integer Field to determine the font size for the comment
internalInputs = [
StringParam(
name="invalidation",
label="Invalidation Message",
description="A message that will invalidate the node's output folder.\n"
"This is useful for development, we can invalidate the output of the node when we modify the code.\n"
"It is displayed in bold font in the invalidation/comment messages tooltip.",
value="",
semantic="multiline",
advanced=True,
uidIgnoreValue="", # If the invalidation string is empty, it does not participate to the node's UID
),
StringParam(
name="comment",
label="Comments",
description="User comments describing this specific node instance.\n"
"It is displayed in regular font in the invalidation/comment messages tooltip.",
value="",
semantic="multiline",
invalidate=False,
),
IntParam(
name="fontSize",
label="Font Size",
description="The Font size for the User Comment on the Backdrop.",
value=12,
range=(6, 100, 1),
),
FloatParam(
name="nodeWidth",
label="Node Width",
description="The Backdrop Node's Width.",
value=600,
range=None,
enabled=False # Hidden always
),
FloatParam(
name="nodeHeight",
label="Node Height",
description="The Backdrop Node's Height.",
value=400,
range=None,
enabled=False # Hidden always
),
ColorParam(
name="color",
label="Color",
description="Custom color for the node (SVG name or hexadecimal code).",
value="",
invalidate=False,
)
]

Comment on lines +802 to +859
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason why we totally overwrite the internal inputs instead of using the ones from the parent class and adding "Font Size" to it?

With the current way, there's a lot of duplication and the "Node's Label" internal attribute is lost, which I think is a shame since we may want to name the backdrop itself, and not just use the "Comments" attribute. This would prevent having several backdrops named "Backdrop", "Backdrop2", "Backdrop3", and so on... if we want to name them. Similarly, the "Invalidation Message" is currently preserved although it has no impact anywhere so far.


class CommandLineNode(Node):
"""
Expand Down
149 changes: 148 additions & 1 deletion meshroom/core/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from meshroom.core import Version
from meshroom.core.attribute import Attribute, ListAttribute, GroupAttribute
from meshroom.core.exception import StopGraphVisit, StopBranchVisit
from meshroom.core.node import nodeFactory, Status, Node, CompatibilityNode
from meshroom.core.node import nodeFactory, Status, Node, CompatibilityNode, Position

# Replace default encoder to support Enums

Expand Down Expand Up @@ -56,6 +56,130 @@ def GraphModification(graph):
graph.updateEnabled = enabled


class Region:
""" Defines the boundaries for a Region on the 2d Plane (Graph in our Context).
"""

class Range:
""" Defines a Range between two points in the Graph or 2d Plane, inclusive of the 2nd Point.
"""

def __init__(self, x, y):
""" Constructor.

Args:
x (int | float): An integer or float start.
y (int | float): An integer or float end.
"""
# Internal Coords
self._x = x
self._y = y

def __repr__(self):
""" Represents the instance.
"""
return f"Range::{self._x}, {self._y}"

def __contains__(self, _i):
""" Returns True if the provided integer or float falls between the start and the end point of the range.
"""
return self._x < _i <= self._y


def __init__(self, x=0, y=0, right=0, bottom=0):
""" Constructor.

Args:
x (int | float): The x coordinate of the top-left point on the Region.
y (int | float): The y coordinate of the top-left point on the Region.
right (int | float): The x coordinate of the bottom-right point on the Region.
bottom (int | float): The y coordinate of the bottom-right point on the Region.
"""
# The coords of the Region can be represented as
# (x, y)
# .------------------------.
# | |
# | |
# | |
# | |
# '------------------------'
# (right, bottom)
self._x = x
self._y = y
self._right = right
self._bottom = bottom

# Properties
x = property(lambda self: self._x)
y = property(lambda self: self._y)
right = property(lambda self: self._right)
bottom = property(lambda self: self._bottom)

def __contains__(self, point):
""" Returns True if the provided Point is present in the Region.
"""
return self.contains(point)

def __repr__(self):
""" Represents the instance.
"""
return f"Region::{self.points()}"

# Public
def xrange(self):
""" Defines the Range between the left most and right most x-coordinate.

Returns:
Region.Range. Range of the left most and right most x-coordinate.
"""
return Region.Range(self._x, self._right)

def yrange(self):
""" Defines the Range between the top most and bottom most y-coordinate.

Returns:
Region.Range. Range of the top most and bottom most y-coordinate.
"""
return Region.Range(self._y, self._bottom)

def contains(self, point):
""" Returns True if the provided point is present inside the Region.

Args:
point (Position) A 2d Point position.

Returns:
bool. True if the point is in the Region else False.
"""
return point.x in self.xrange() and point.y in self.yrange()

def points(self):
""" A Region can be represented by basic 2 points defining its top-left and bottom right position.

Returns:
list<Position>. Array of Positions for the Region.
"""
return [Position(self._x, self._y), Position(self._right, self._bottom)]

def containsRegion(self, region):
""" Returns True if the provided region belongs to the current Region.

Args:
region (Region): The region to check for.

Returns:
bool. True if the provided region belongs to the current Region.
"""
# Check if both top-left and bottom-right points of the region fall in the current region
for point in region.points():
# If any of the point is outside of the -> The region can be safely marked as not in current region
if point not in self:
return False

# Else it belongs
return True


class Edge(BaseObject):

def __init__(self, src, dst, parent=None):
Expand Down Expand Up @@ -165,6 +289,9 @@ class Graph(BaseObject):
"""
_cacheDir = ""

# Graph's Region Of Interest
ROI = Region

class IO(object):
""" Centralize Graph file keys and IO version. """
__version__ = "2.0"
Expand Down Expand Up @@ -679,6 +806,26 @@ def nodeOutEdges(self, node):
""" Return the list of edges starting from this node """
return [edge for edge in self.edges if edge.src.node == node]

def nodesInRegion(self, region):
""" Returns the Nodes present in this region.

Args:
region (Graph.ROI): Region to look for nodes.

Returns:
list<Node>. Array of Nodes present in the Region.
"""
# A node with 40 pixels inside the backdrop in terms of height could be considered a candidate ?
nodes = []
for node in self._nodes.values():
# Node's Region
noder = Graph.ROI(node.x, node.y, node.x + node.nodeWidth, node.y + 40)
# If the provided region contains the node region -> add the node to the array of nodes
if region.containsRegion(noder):
nodes.append(node)

return nodes

@changeTopology
def removeNode(self, nodeName):
"""
Expand Down
Loading
Loading