-
Notifications
You must be signed in to change notification settings - Fork 443
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
Package Group UI #171
Package Group UI #171
Conversation
This is a quick update in order to provide better package experience in the editor. Though ultimately I believe this system needs to be remade and possibly added to ryvencore. Packages are now added in a list -per name. There's also a trick to include "subpackages". Packages can be named as {root}.{subpackage}. There are no subpackages of subpackages, it only works for one level of grouping. By clicking on the last node of a package, the corresponding nodes are shown for Ryven.
I don't see a problem here. Strictly speaking, ryvencore does not have an import mechanism, so there's nothing to break. Registering nodes in a ryvencore session means providing their class, and I'd like to keep it this way. ryvencore should contain only the absolute core functionality for the idea behind the project. It is not specific to Ryven - in fact ryvencore is used by projects that don't care about Ryven at all. Developer focused features like a packaging system etc. should be defined on a higher level, coupled with a particular implementation, because there's about a million different design choices.
Theoretically yes, but Python packaging is a pain, and conceptually Ryven node packages are not (and don't have to be) this complicated. I don't see an immediate argument against supporting it, but I think requiring it would significantly raise the entry barrier.
I like this idea! Some immediate thoughts:
|
I believe these are reasonable thoughts. Upon looking at it more carefully, there indeed seems to be no reason for more than 1 level of depth. It seems to be a really good balance for a graph-like system. I do find some utility functions in ryvencore could be useful though. It would be providing a good system for package handling from the get go. As per the
EDIT: Oops, I used my other account to post here. |
That's fair. But why not keeping Ryven-specific ryvencore utils in the Ryven repo? We could possibly detach it from Ryven a little, though, such that others can easily use ryvencore together with only Ryven's packaging. I don't think that's necessary right now but if someone ever needs this, I don't see strong reasons against it.
I would think so.
Hmm, maybe.
|
I can see that working, might give it a try tomorrow. |
I noticed there is no apparent difference in the code between having subfolders or not. The real difference is that a single folder enforces different names. I believe it should be up to the user to arrange them however he likes. If both are done, we simply need to decide on naming conventions. Firstly, I added
or
depending on the way they are imported. Exporting sub-packages was as simple as:
All that's left is the handling of conflict when we have a subfolder with a nodes.py. But I suppose it's as easy as using the folder name when we encounter such a situation. Any more thoughts or dislikes? |
-There is no notion of a sub-package as in a class, it's still the same NodesPackage. 'export_nodes' now builds a dict of str to (nodes, datas). Keys are a combination of the package and subpackage names. Subpackages can be in a folder, where the main nodes.py can be called anything and simply imports the sub-packages. On each sub-package, 'export_sub_package' is called to extend the package name. A name can be given as a function parameter. If not, it uses the module name if it isn't named "nodes.py", in which case it uses the folder name. Sub-packages can exist in a subfolder or the main nodes.py folder, in which case the name should be different.
Thanks for looking into this! Some notes:
I think we should also remove the path again after the file is loaded. Assume the following structure:
if
Please don't access stack frames. This breaks invariants assumed by lots of surrounding software, such as debuggers. Been there, done that ;) This is the reason why some commands (such as |
I just noticed, |
Well, I did quite a bit of digging... Firstly, the
By doing that and also providing a package prefix, i.e. suppose we have At this point, I'm wondering if it would be just better to include the package location to If you don't know how to solve any of the above, the only way I can think of it working is to rename the nodes.py files to the corresponding folder names (i.e a The other way is it leave it as is, appending to the |
Thanks for this, you're right, the previous I noticed I think I misread your original comment on python packages, I was thinking about packaging to PyPI. Normal Python packages (folders with I just wrote a quick prototype for a python packages based import mechanism with the following characteristics
It's super simple def load_from_file(file: str = None, components_list: [str] = None) -> tuple:
"""
Imports specified components from a python module with given file path.
"""
if components_list is None:
components_list = []
dirpath, filename = os.path.split(file)
parent_dirpath, pkg_name = os.path.split(dirpath)
mod_name = filename.split('.')[0]
name = f"{pkg_name}.{mod_name}" # e.g. built_in.nodes
if parent_dirpath not in sys.path:
sys.path.append(parent_dirpath)
mod = importlib.import_module(name)
comps = tuple([getattr(mod, c) for c in components_list])
return comps I think I didn't consider originally that I could just add the package's parent directory to |
If you agree this would be easiest and I don't suddenly remember other reasons why I didn't do it this way, I guess we could do that to actually support multi-module node packages, which we need for subpackages.
To stay close to the name def export_sub_nodes(name: str, node_types:[Type[Node]], data_types: [Type[Data]] = None):
"""
Exports / exposes nodes to Ryven as a subpackage of the currently loaded package for use in flows.
Do not call this function in a node package's nodes.py file, call export_nodes instead.
"""
NodesEnvRegistry.current_sub_package = sub_pkg_name
export_nodes(node_types, data_types)
NodesEnvRegistry.current_sub_package = None |
Is there any reason for the initialization to go through the nodes.py file? Consider a pkg_test package with sub-packages std and linalg:
Option 1) Package loading in root nodes.py: #import root nodes here
...
#import subpackage nodes
from .std import nodes
from .linalg import nodes Option 2) Package loading in init: #import root nodes here
from . import nodes
#import subpackage nodes
from .std import nodes
from .linalg import nodes This way, we simply import a python package, i.e
Yes, that would be a better name. On another note, I have 3 other separate ideas which I'm working on, in case you want to include them in this request:
1 should be easy to include in this pull request. 2 and 3 might take a few more days. |
-Package importing now done with importlib.import_module -Nodes and Flows windows are now docks -Console is now a dock underneath the central window
Regarding your example, I think
I think this would be more problematic than a
I don't have a strong preference on that, I think it's nice to have PRs isolated on specific features, but if you're doing all your work on one branch already and you'd have to pull that apart, I'm also fine with extending this PR.
Sure, if that's easy.
That is the case to a large extent, and the main reason why
Do you mean something like a |
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.
the docking is nice! Some notes:
- it seems I cannot hide the console anymore
- also I think the console is generally too large, now especially since the left side panel holds more stuff now, maybe we can place it beneath the widget that holds the current flow? (and also make it as dockable like the others?)
I agree with the loading. Pushed it to work like we discussed, along with an example in the pkg_test folder. I also pushed docks for the Nodes, Flows and Console widgets. Will see what I can do about the Variables and Settings. Small preview on how it works (I made the console dock only to the bottom)
I did notice that it's the case to a large extent. I also noticed though that, for the linalg test, nodes.py imports the guis and makes the connection inside the nodes themselves. Doesn't that make the nodes package ryven dependent, when we want it to be ryvencore dependent only? Perhaps I've missed something. But wouldn't defining the connection in a separate module solve the problem? (I just noticed the same problem exists by calling export_nodes inside the same module that defines the nodes, but that is trivial to solve by separating the export and the nodes to different files).
I misspoke. Meant to say nodes have guis. I was in fact talking about a browser-style inspector for the instantiated nodes. Something like Unity (https://docs.unity3d.com/Manual/UsingTheInspector.html) or any other Game Engine has. I believe it is essential if a node has many configurable properties. I'm relatively new to python type-hinting, inspecting and serializing (though it mostly works the same in every oop-feature language), but I think it could be used to make a generic Node Inspector / Editor. |
yup, just tried it myself, very cool!
Oh no it actually doesn't. That's why I separated GUI definitions and node definitions, because I want nodes to work without GUI by default.
Oh I think we're on the same page then. I've long wanted something like a DETAILS section (roughly where the SETTINGS is, which presents node properties and allows configuration. I would love an extension to the nodes API that allows for customized behavior by exposing properties, a bit like in Blender I think. edit
The dynamics of python's type system are indeed an obstacle for any kind of API that tries to not break all the time. Type hints have no semantic meaning, I can totally do |
Alright! About the console placement, should we make it dockable everywhere? Currently it's only at the bottom (options are left, top, right, bottom). I can make it closeable, but then we have to introduce a new item in the View tab or something to re-open it.
It's just that, despite it working exactly as you say, one still needs to import the node_env from ryven inside nodes.py. Most solutions I've worked with typically make the connection in a separate file or the gui file, in some form of metadata connection. At the very least, I believe that the GUI class should have which node it corresponds to, and not the other way around. If it doesn't make much sense in a python context, don't mind me :) Regarding the DETAILS or EDITOR or INSPECTOR, I'll be making SETTINGS and VARIABLES dockable to the current flow, so it shouldn't be a problem to add as many tabs as we want. I believe due to python's dynamic structure, the best way to go is the following: Define a GenericObjectGUI class, which takes a python object as an argument. For a generic implementation, one wants to be able to edit AT MOST all the attributes that are serialized. After deserialization, loop through the attributes, check if they are defined (keys must be defined in the serialization dict) and with a value. If they are (which they should be), make a widget based on common types, if they are a common type, or insert another GenericObjectGUI widget recursively. This is generic enough, especially for Ryven. I'm not sure if there is a catch and this system would break. The only Ryven dependent thing though, is that nested objects must provide a way to retrieve a serialized version of themselves, perhaps a wrapper class if needed. Ryvencore doesn't provide an automatic serialization system that goes through the attributes, right? One has to explicitly define get_state() and set_state(). Which means that nested objects must also have the same functionality. Do you find this overly complex, should we opt for something simpler? edit
I just noticed this. Nevertheless, I think it's something that could be explored, as I defined above. If you think it can't or that I'm wrong somewhere, do tell. |
sure, or is there any reason against?
why would the way it was before (dragging the splitter handle) not work?
That's fair, but for Ryven somewhat over-complicated IMO.
Hm I'm not sure about that. One node can have one GUI, and one GUI can be applied to multiple different nodes. But one node should never be applied to multiple different GUIs. But that is indeed only my own intuition, and I don't think there are any strong conventions about that in Python, so I'd be open to look at alternatives but I would rather postpone that for now.
I do have some ideas. Could you open an issue? |
-Settings, Variables, Log and Source code are on the left of each flow view as dockable windows. -Each flow view has a menu button that we can append to. Currently it provides a way to close the docked windows. -Added the dock window functionality to View/Windows as well -Docks can also be toggled on/off by right clicking anywhere on a dockable window. -FlowUI is now a QMainWindow for serialization purposes. The Main Ryven window and all the FlowUIs are now saved as well when saving a project. Their state and geometry is retained.
Also, I'd like to mention that what I find missing from Ryven the most, apart from the Inspector GUI, is the ability to run the flow only once with the click of a button and preventing any gui changes from updating the flow or executing it again. We had a discussion over mail for this a few days ago. I'll also be working on it, but I don't know how generic enough it could / should be to be included in vanilla Ryven. The idea is configuring a graph and then running it only once or providing a custom script that determines how the graph is updated (i.e. in a for loop). This would be extremely helpful in situtations where you want to define Input Nodes that acquire streamed data, go through these specific nodes in a for loop and update them every N Frames / second. Or simply for running your flow only once and outputting the results. |
-Added the ability to select and delete connections. -Implemented the paint method for ConnectionItem so that it doesn't draw its bounding rect when selected. Just a notice, FlowView and FlowCommands are in the same module but depend on each other. We cannot import FlowView to FlowCommands for better type hinting due to partial initialization caused by circular imports. Perhaps the code in FlowCommands should be added in FlowView, rather than being kept as a separate module?
We can now save the current flow uis layout as a template to instantiate the a new layout. The option is as the flow views Menu/Flow Templates
I actually have no clue, if the problem is reducible to a simple example someone in the qt forum can maybe resolve it in a matter of seconds.
Just make sure you do that on a different branch and not clutter this PR too much, otherwise it will take long until I can merge.
What you show looks cool! If I notice sth once I try, I will tell.
pyside6 is not the default because currently it breaks a few things in buggy ways, but I think I didn't take the time yet to track everything down. If you're working on pyside6 and come across particular bugs, fixes would be highly appreciated, but preferable in another PR. |
As I noted in my email response, I don't think this kind of flow execution control should be part of Ryven right now, since it's highly use-case specific and can be implemented within node packages. For example, instead of using connections to distribute actual streams of data, it can make sense to instead use connections to distribute handles to the data streams which are controlled externally (like a socket, or a file descriptor). If strong patterns and repeating use cases emerge in the future, I'm totally open to look at possible integrations, but for now I'd stick with node packages (and rather extend node package capabilities - as we're doing right now - instead of the editor). |
I don't quite understand the examples. What benefit would the developer of a package have by wrapping the imports of the UI in a function? The function must still be called. I can see the decorator doing the work of the if in my example, but someone still has to call that function in As for the decoration of the GUI, I think at that point it's a developer's mistake. All we can do is keep a set of all the nodes types whose guis have been explicitly set and throw an exception for safety purposes. As you said, though, I don't think it's that big a problem. I'll be waiting for this PR to close before I start a new one. |
Ryven would call it. Currently this would happen at package import time but it doesn't have to, deferring GUI imports has already been on my radar. It would open some use cases and push the developer to avoid GUI-dependent semantics. I think I prefer the decorator option, it doesn't really matter for the package dev but gives Ryven more control.
yeah, we might just implement some sanity checks here, but it's not top priority
I'll try to look over all the code asap as soon as the packaging changes are done. |
Got it. Should look nicer.
The last push in this PR had all the functionality I wanted to add with the packages and the UI quality of life. If I've forgotten anything, do tell. Otherwise, should be ready for reviewing. |
notes on semantics:
notes on styling:
I can fix the styling on your branch if you prefer. Footnotes
|
Do you find using the black formatter disruptive? It's PEP 8 compliant. The only thing that one might dislike is that its default settings force the use of double quotes for strings, which I can disable. (meaning that formatting doesn't consider quotes, it can be either single or double) If not, I'm using VS Code to develop. If you have a specific formatting plugin you're using, tell me and I'll use that. |
A formatter is just as good as its configuration and I don't have a formal configuration of Ryven code base styling. There are many choices even beyond PEP 8 that require additional circumspection. Of course you are free (and encouraged) to use it, but you should still make sure the styling matches the rest of the code base. |
Fixed
Removed any new union type notation. Furthermore, I had a lot of generic standard type hinting, i.e
fixed, tested by loading the example projects
Formatted using the aforementioned formatter. Styling obeys the rules you mentioned. The way some imports are typed have been affected as well [from . import N1, N2, .... Nn] are broken in lines if there are too many imports happening As per the 3.9+ type hinting, I find it quite intuitive and a must for modern python. I commented with a |
-Fixed auto discover -Fixed any 3.9+ type hinting -Fixed geometry and state crashing on old projects. -Formatted using black formatter with a -l100 line parameter to stay close to the code base.
-Fixed some forgotten 3.9+ type hinting
Fixed a description for better clarity.
-Fixed a forgotten 3.9+ type hinting
I've checked my last commits for Ryven. Tried it for 3.6, 3.7, 3.8 (3.9+ is working as expected).
These aren't changes I made. I've tested this PR in 3.8 after fixing the issue and it works fine. Haven't tested in 3.6, 3.7. As it currently stands, the main branch for Ryven works with 3.9 and above. I have finished with my changes. Please look them over. I'm leaving the 3.6, 3.7 and 3.8 issues to you (although I genuinely believe bumping the min req to 3.7 or 3.8 or even 3.9 wouldn't be bad, considering 3.8 will reach end of life next year) |
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.
Looks good! Some of the styling changes are questionable IMO (formatters are terrible at understanding when ambiguity is useful for insight) but not a big deal for me.
I might do minor cosmetic changes to the restyling but not in this PR |
thanks that's good to know
I don't have plans to move further into the present than what's currently alive (e.g. 3.8). Now 3.6 has reached EOL long ago (3.7 also) but I still see people using it sometimes, so if it's not a major difference for us I guess we can keep it flexible. |
Good to know!
Yeah, I'm also not a fan of some of the things it did. Regarding af91367, there's another type hint I forgot right under the one you fixed. I can fix it in the PR for the gui decorators if you want to.
I was mainly talking about the library I mention in #173 which has a 3.7 min req, but other than that, I don't think it's a problem. Regarding that issue, I believe we should avoid ryven specific stuff and simply require a function or a class be implemented that attaches the Inspector it creates to a ryven window. I'll comment on that issue again and I'll wait for your reply there, I'm currently testing this. As for the GUI decorators and deferring of gui loading, it's ready. I'll be making a new PR unless there is something you want to clarify first. |
oh how did I not see that; I'll fix it when I look into the other version compatibility issues
The concept of a mutable tree of properties could be very useful beyond the UI. To make future extension easy, this system should be really simple, and optimally prevents misuse by design. Looking forward to see what you're thinking of.
Cool! Nothing from my side. |
commit 783f371 Merge: c8e1147 3043123 Author: George Papadoulis <[email protected]> Date: Fri Nov 24 23:11:25 2023 +0200 Merge remote-tracking branch 'upstream/master' into gui_dev commit 3043123 Merge: 998e552 af91367 Author: Leon Thomm <[email protected]> Date: Fri Nov 24 22:02:44 2023 +0100 Merge pull request leon-thomm#171 from HeftyCoder/master This PR - adds sub-package functionality (`export_sub_nodes()`) (WIP) - adds a tree view for (sub-)packages - introduces dockable widgets - adds project-persistent window geometry - makes connections selectable commit c8e1147 Author: George Papadoulis <[email protected]> Date: Fri Nov 24 19:06:28 2023 +0200 Update utils.py commit 39fa412 Author: George Papadoulis <[email protected]> Date: Fri Nov 24 18:58:39 2023 +0200 Defer GUI loading -Implemented a on_gui_load decorator which defers Node GUI loading -Added a protection mechanism for when defining a GUI for the same node twice commit 00427cf Merge: caf6f88 1e9ddd0 Author: George Papadoulis <[email protected]> Date: Fri Nov 24 14:41:44 2023 +0200 Merge branch 'master' into gui_dev commit caf6f88 Author: George Papadoulis <[email protected]> Date: Mon Nov 20 15:45:17 2023 +0200 Implemented decorator GUI import -Defined a node_gui decorator in gui_env that allows for connecting the node to the gui -Defined a function in gui_env that returns if Ryven is in gui mode -Re-factored the packages to work with the new decorator system -Added a decorator to defer gui functions for package importing -Added a api.py for convenience. Might not be needed later
Sorry for posting in a closed PR, but I just realized... Lets say we have this structure.
Don't we already have a completely defined tree - as nested as we like - for any package? If
With this, we can easily define a tree:
In this context, the string |
What's the issue here? Of course you can have arbitrary sub-python-package nesting, but not sub-ryven-package nesting.1
not sure what you mean by this Footnotes
|
also, I'm wondering now, in order to increase flexibility and decrease confusion potential between python and ryven packaging, maybe should relax e.g.: from ryven.node_env import ...
# some basic node defs
export_nodes(...)
# some node defs
export_sub_nodes('special_nodes', ...)
# some more node defs
export_sub_nodes('more_special_nodes', ...) what do you think? |
The proposed method above does the following:
Using the method above, a python package class that is Essentially it creates a ryven package tree using python package rules and the special
The I believe it solves a lot of problems. If you think the tight coupling with the python package system or the special rule of the |
Certainly one can also just export a sub-package with name As I understand it you propose to change Is that correct? In that case |
I indeed am reconsidering this. I also believe it won't have a problem with UI issues. Let the developers define their packages as well as they like.
s = f'{node_type.__module__}.{node_type.__name__}' # same thing can be applied to data types. That way, a class Or almost the same thing. Every class' path residing in a package's or sub-(sub-...)-package's root is "polluted" with the
or in a tree structure:
We're essentially letting python create the package tree by deciding on a simple rule for any As a side-node, so I don't forget, I believe the general approach to exporting in any file should be: try:
from ryven.node_env import export_nodes
export_nodes(...)
except:
pass which makes the package completely ryvencore compatible. (Also, I can even argue that |
This is a quick update in order to provide better package experience in the editor. Though ultimately I believe the NodesPackage system needs to be remade and possibly added to ryvencore (Changing the identifier prefix outside ryvencore - along with other things - leads to Ryven specific importing. Furthermore, classes with the same name from different packages break the importing. Couldn't importing eventually be remade to work by using python packages?)
Packages are now added in a list -per name. There's also a trick to include "subpackages". Packages can be named as {root}.{subpackage}. There are no subpackages of subpackages, it only works for one level of grouping. By clicking on the last node of a package, the corresponding nodes are shown for Ryven.
Example: Suppose we want to divide our nodes into simple and advanced.
Option 1: Simply have the folders linalg and std and they'll appear in the editor.
Option 2: Bundle them into a "virtual" package, i.e test. Folders have to be renamed test.lnalg and test.std
Current display problem: Packages imported with the same prefix (i.e test.linalg and test.std) in the menu before creating the session are displaying only the prefix in the UI.