|
16 | 16 | from meshroom.core import Version |
17 | 17 | from meshroom.core.attribute import Attribute, ListAttribute, GroupAttribute |
18 | 18 | from meshroom.core.exception import StopGraphVisit, StopBranchVisit |
19 | | -from meshroom.core.node import nodeFactory, Status, Node, CompatibilityNode |
| 19 | +from meshroom.core.node import nodeFactory, Status, Node, CompatibilityNode, Position |
20 | 20 |
|
21 | 21 | # Replace default encoder to support Enums |
22 | 22 |
|
@@ -56,6 +56,130 @@ def GraphModification(graph): |
56 | 56 | graph.updateEnabled = enabled |
57 | 57 |
|
58 | 58 |
|
| 59 | +class Region: |
| 60 | + """ Defines the boundaries for a Region on the 2d Plane (Graph in our Context). |
| 61 | + """ |
| 62 | + |
| 63 | + class Range: |
| 64 | + """ Defines a Range between two points in the Graph or 2d Plane, inclusive of the 2nd Point. |
| 65 | + """ |
| 66 | + |
| 67 | + def __init__(self, x, y): |
| 68 | + """ Constructor. |
| 69 | +
|
| 70 | + Args: |
| 71 | + x (int | float): An integer or float start. |
| 72 | + y (int | float): An integer or float end. |
| 73 | + """ |
| 74 | + # Internal Coords |
| 75 | + self._x = x |
| 76 | + self._y = y |
| 77 | + |
| 78 | + def __repr__(self): |
| 79 | + """ Represents the instance. |
| 80 | + """ |
| 81 | + return f"Range::{self._x}, {self._y}" |
| 82 | + |
| 83 | + def __contains__(self, _i): |
| 84 | + """ Returns True if the provided integer or float falls between the start and the end point of the range. |
| 85 | + """ |
| 86 | + return self._x < _i <= self._y |
| 87 | + |
| 88 | + |
| 89 | + def __init__(self, x=0, y=0, right=0, bottom=0): |
| 90 | + """ Constructor. |
| 91 | +
|
| 92 | + Args: |
| 93 | + x (int | float): The x coordinate of the top-left point on the Region. |
| 94 | + y (int | float): The y coordinate of the top-left point on the Region. |
| 95 | + right (int | float): The x coordinate of the bottom-right point on the Region. |
| 96 | + bottom (int | float): The y coordinate of the bottom-right point on the Region. |
| 97 | + """ |
| 98 | + # The coords of the Region can be represented as |
| 99 | + # (x, y) |
| 100 | + # .------------------------. |
| 101 | + # | | |
| 102 | + # | | |
| 103 | + # | | |
| 104 | + # | | |
| 105 | + # '------------------------' |
| 106 | + # (right, bottom) |
| 107 | + self._x = x |
| 108 | + self._y = y |
| 109 | + self._right = right |
| 110 | + self._bottom = bottom |
| 111 | + |
| 112 | + # Properties |
| 113 | + x = property(lambda self: self._x) |
| 114 | + y = property(lambda self: self._y) |
| 115 | + right = property(lambda self: self._right) |
| 116 | + bottom = property(lambda self: self._bottom) |
| 117 | + |
| 118 | + def __contains__(self, point): |
| 119 | + """ Returns True if the provided Point is present in the Region. |
| 120 | + """ |
| 121 | + return self.contains(point) |
| 122 | + |
| 123 | + def __repr__(self): |
| 124 | + """ Represents the instance. |
| 125 | + """ |
| 126 | + return f"Region::{self.points()}" |
| 127 | + |
| 128 | + # Public |
| 129 | + def xrange(self): |
| 130 | + """ Defines the Range between the left most and right most x-coordinate. |
| 131 | +
|
| 132 | + Returns: |
| 133 | + Region.Range. Range of the left most and right most x-coordinate. |
| 134 | + """ |
| 135 | + return Region.Range(self._x, self._right) |
| 136 | + |
| 137 | + def yrange(self): |
| 138 | + """ Defines the Range between the top most and bottom most y-coordinate. |
| 139 | +
|
| 140 | + Returns: |
| 141 | + Region.Range. Range of the top most and bottom most y-coordinate. |
| 142 | + """ |
| 143 | + return Region.Range(self._y, self._bottom) |
| 144 | + |
| 145 | + def contains(self, point): |
| 146 | + """ Returns True if the provided point is present inside the Region. |
| 147 | +
|
| 148 | + Args: |
| 149 | + point (Position) A 2d Point position. |
| 150 | +
|
| 151 | + Returns: |
| 152 | + bool. True if the point is in the Region else False. |
| 153 | + """ |
| 154 | + return point.x in self.xrange() and point.y in self.yrange() |
| 155 | + |
| 156 | + def points(self): |
| 157 | + """ A Region can be represented by basic 2 points defining its top-left and bottom right position. |
| 158 | +
|
| 159 | + Returns: |
| 160 | + list<Position>. Array of Positions for the Region. |
| 161 | + """ |
| 162 | + return [Position(self._x, self._y), Position(self._right, self._bottom)] |
| 163 | + |
| 164 | + def containsRegion(self, region): |
| 165 | + """ Returns True if the provided region belongs to the current Region. |
| 166 | +
|
| 167 | + Args: |
| 168 | + region (Region): The region to check for. |
| 169 | +
|
| 170 | + Returns: |
| 171 | + bool. True if the provided region belongs to the current Region. |
| 172 | + """ |
| 173 | + # Check if both top-left and bottom-right points of the region fall in the current region |
| 174 | + for point in region.points(): |
| 175 | + # If any of the point is outside of the -> The region can be safely marked as not in current region |
| 176 | + if point not in self: |
| 177 | + return False |
| 178 | + |
| 179 | + # Else it belongs |
| 180 | + return True |
| 181 | + |
| 182 | + |
59 | 183 | class Edge(BaseObject): |
60 | 184 |
|
61 | 185 | def __init__(self, src, dst, parent=None): |
@@ -165,6 +289,9 @@ class Graph(BaseObject): |
165 | 289 | """ |
166 | 290 | _cacheDir = "" |
167 | 291 |
|
| 292 | + # Graph's Region Of Interest |
| 293 | + ROI = Region |
| 294 | + |
168 | 295 | class IO(object): |
169 | 296 | """ Centralize Graph file keys and IO version. """ |
170 | 297 | __version__ = "2.0" |
@@ -679,6 +806,26 @@ def nodeOutEdges(self, node): |
679 | 806 | """ Return the list of edges starting from this node """ |
680 | 807 | return [edge for edge in self.edges if edge.src.node == node] |
681 | 808 |
|
| 809 | + def nodesInRegion(self, region): |
| 810 | + """ Returns the Nodes present in this region. |
| 811 | +
|
| 812 | + Args: |
| 813 | + region (Graph.ROI): Region to look for nodes. |
| 814 | +
|
| 815 | + Returns: |
| 816 | + list<Node>. Array of Nodes present in the Region. |
| 817 | + """ |
| 818 | + # A node with 40 pixels inside the backdrop in terms of height could be considered a candidate ? |
| 819 | + nodes = [] |
| 820 | + for node in self._nodes.values(): |
| 821 | + # Node's Region |
| 822 | + noder = Graph.ROI(node.x, node.y, node.x + node.nodeWidth, node.y + 40) |
| 823 | + # If the provided region contains the node region -> add the node to the array of nodes |
| 824 | + if region.containsRegion(noder): |
| 825 | + nodes.append(node) |
| 826 | + |
| 827 | + return nodes |
| 828 | + |
682 | 829 | @changeTopology |
683 | 830 | def removeNode(self, nodeName): |
684 | 831 | """ |
|
0 commit comments