-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
WIP interface categories #3146
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
base: develop
Are you sure you want to change the base?
WIP interface categories #3146
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| class Interface: | ||
| """Marker base class for node descriptor interfaces. | ||
|
|
||
| Inherit from this to declare a new interface. Classes that inherit from | ||
| an Interface are automatically discovered by InterfaceMeta and listed in | ||
| the ``interfaces`` attribute of the concrete node descriptor class. | ||
| """ | ||
|
|
||
| @staticmethod | ||
| def upstreamNodesWithInterface(node, attribute, interface_name): | ||
| result = [] | ||
| visited = set() | ||
| stack = [attribute] | ||
|
|
||
| while stack: | ||
| attr = stack.pop() | ||
| if not attr.isLink: | ||
| continue | ||
| source_node = attr.inputRootLink.node | ||
| if source_node in visited: | ||
| continue | ||
| visited.add(source_node) | ||
| if interface_name in source_node.nodeDesc.interfaces: | ||
| result.append(source_node) | ||
| continue | ||
| for _, input_attr in source_node.attributes.items(): | ||
| stack.append(input_attr) | ||
|
|
||
| return result | ||
|
|
||
| class InterfaceMeta(type): | ||
| """Metaclass that populates the ``interfaces`` attribute on any class that uses it. | ||
|
|
||
| When a class is created with this metaclass, ``interfaces`` is set to the list | ||
| of ``Interface`` subclass names found in the class's MRO (excluding the class | ||
| itself and the base ``Interface`` marker). | ||
| """ | ||
| def __new__(mcs, name, bases, namespace): | ||
| cls = super().__new__(mcs, name, bases, namespace) | ||
| cls.interfaces = [ | ||
| b.__name__ for b in type.mro(cls) | ||
| if b is not Interface and b is not cls | ||
| and isinstance(b, type) and issubclass(b, Interface) | ||
| ] | ||
|
Comment on lines
+40
to
+44
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would rename it |
||
| return cls | ||
|
|
||
| class InterfacedClass(metaclass=InterfaceMeta): | ||
| """Base class for objects that want automatic interface discovery via InterfaceMeta.""" | ||
| pass | ||
|
Comment on lines
+47
to
+49
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For clarity I would rename it |
||
|
|
||
|
|
||
| class FeatureProviderInterface(Interface): | ||
|
|
||
| def getFeaturesFolders(self, node) -> list: | ||
| raise NotImplementedError | ||
|
|
||
| def getDescriberTypes(self, node) -> list: | ||
| raise NotImplementedError | ||
|
|
||
| class MatchProviderInterface(Interface): | ||
|
|
||
| def getMatchesFolders(self, node) -> list: | ||
| raise NotImplementedError | ||
|
|
||
| class TrackProviderInterface(Interface): | ||
|
|
||
| def getTracksFile(self, node) -> str: | ||
| raise NotImplementedError | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -330,12 +330,6 @@ class Scene(UIGraph): | |||||||||||||||||
| "ApplyCalibration", "SfMExpanding", "SfMBootstraping"], | ||||||||||||||||||
| # All nodes generating depth map files | ||||||||||||||||||
| "allDepthMap": ["DepthMap", "DepthMapFilter"], | ||||||||||||||||||
| # Nodes that can be used to provide features folders to the UI | ||||||||||||||||||
| "featureProvider": ["FeatureExtraction", "FeatureMatching", "StructureFromMotion", "RomaReducer"], | ||||||||||||||||||
| # Nodes that can be used to provide matches folders to the UI | ||||||||||||||||||
| "matchProvider": ["FeatureMatching", "StructureFromMotion", "RomaReducer"], | ||||||||||||||||||
| # Nodes that can be used to provide tracks files to the UI | ||||||||||||||||||
| "trackProvider": ["TracksBuilding", "SfMBootstraping", "SfMExpanding"] | ||||||||||||||||||
| } | ||||||||||||||||||
| # Nodes accessed from the UI | ||||||||||||||||||
| uiNodes = [ | ||||||||||||||||||
|
|
@@ -418,9 +412,16 @@ def initActiveNodes(self): | |||||||||||||||||
| # For all nodes declared to be accessed by the UI | ||||||||||||||||||
| usedNodeTypes = {j for i in self.activeNodeCategories.values() for j in i} | ||||||||||||||||||
| allLoadedNodeTypes = set(meshroom.core.pluginManager.getRegisteredNodePlugins().keys()) | ||||||||||||||||||
|
|
||||||||||||||||||
| for nodeType, nodePlugin in meshroom.core.pluginManager.getRegisteredNodePlugins().items(): | ||||||||||||||||||
| nodeDesc = nodePlugin.nodeDescriptor | ||||||||||||||||||
| for interface in nodeDesc.interfaces: | ||||||||||||||||||
| if not self._activeNodes.get(interface): | ||||||||||||||||||
| self._activeNodes.add(ActiveNode(interface, parent=self)) | ||||||||||||||||||
|
|
||||||||||||||||||
| allUiNodes = set(self.uiNodes) | usedNodeTypes | allLoadedNodeTypes | ||||||||||||||||||
|
|
||||||||||||||||||
| for nodeType in allUiNodes: | ||||||||||||||||||
| for nodeType in allUiNodes: | ||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||
| self._activeNodes.add(ActiveNode(nodeType, parent=self)) | ||||||||||||||||||
|
|
||||||||||||||||||
| def clearActiveNodes(self): | ||||||||||||||||||
|
|
@@ -985,11 +986,38 @@ def setBuildingIntrinsics(self, value): | |||||||||||||||||
|
|
||||||||||||||||||
| pluginsReloaded = Signal(list, list) | ||||||||||||||||||
|
|
||||||||||||||||||
| @Slot(str, str, result="QVariant") | ||||||||||||||||||
| def callInterfaceMethod(self, interfaceName, methodName): | ||||||||||||||||||
| """Call a method defined by an interface on the active node for that interface. | ||||||||||||||||||
|
|
||||||||||||||||||
| Args: | ||||||||||||||||||
| interfaceName: name of the interface (e.g. "FeatureProviderInterface") | ||||||||||||||||||
| methodName: name of the method to call on the nodeDesc (e.g. "getFeaturesFolders") | ||||||||||||||||||
|
|
||||||||||||||||||
| Returns: | ||||||||||||||||||
| The result of the method call, or None if the interface/method is not available. | ||||||||||||||||||
| """ | ||||||||||||||||||
| entry = self.activeNodes.get(interfaceName) | ||||||||||||||||||
| if not entry or not entry.node: | ||||||||||||||||||
| return None | ||||||||||||||||||
| node = entry.node | ||||||||||||||||||
| method = getattr(node.nodeDesc, methodName, None) | ||||||||||||||||||
|
Comment on lines
+1003
to
+1004
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To make this call more robust and prevent potential node = entry.node
nodeDesc = getattr(node, "nodeDesc", None)
if not nodeDesc:
return None
method = getattr(nodeDesc, methodName, None) |
||||||||||||||||||
| if callable(method): | ||||||||||||||||||
| try: | ||||||||||||||||||
| return method(node) | ||||||||||||||||||
| except Exception as e: | ||||||||||||||||||
| logging.error(f"callInterfaceMethod: {interfaceName}.{methodName} raised: {e}") | ||||||||||||||||||
| return None | ||||||||||||||||||
| return None | ||||||||||||||||||
|
Comment on lines
+1005
to
+1011
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. On failure, it could try to load the next possible activeNode that implement this interface, using a while ? |
||||||||||||||||||
|
|
||||||||||||||||||
| @Slot(QObject) | ||||||||||||||||||
| def setActiveNode(self, node, categories=True, inputs=True): | ||||||||||||||||||
| """ Set node as the active node of its type and of its categories. | ||||||||||||||||||
| Also upgrade related input nodes. | ||||||||||||||||||
| """ | ||||||||||||||||||
| for interface in node.nodeDesc.interfaces: | ||||||||||||||||||
| self.activeNodes.getr(interface).node = node | ||||||||||||||||||
|
Comment on lines
+1018
to
+1019
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is a critical typo here:
Suggested change
|
||||||||||||||||||
|
|
||||||||||||||||||
| if categories: | ||||||||||||||||||
| for category, nodeTypes in self.activeNodeCategories.items(): | ||||||||||||||||||
| if node.nodeType in nodeTypes: | ||||||||||||||||||
|
|
||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In Python, subclasses of a class that implements an interface will also return
Trueforissubclass(b, Interface). This means that if a concrete node class (e.g.,MyNode) implementsFeatureProviderInterface, any subclass ofMyNode(e.g.,MySubNode) will mistakenly includeMyNodein itsinterfaceslist. To prevent concrete parent classes from being treated as interfaces, we should exclude classes that are instances ofInterfaceMeta(the metaclass for concrete interfaced classes) by checkingnot isinstance(b, mcs).