4
$\begingroup$

Is it possible with Python to detect if a nodegroup is embedded in a frame in another blend file without adding it first?

I have several blend files with the following structure:

enter image description here

The main group always contains a certain name (in this example "_TEST"), and in it are other nodegroups. Some of them are embedded in frames, some are not.

And from another file, before I add/process a certain nodegroup from these files, I want to check if it is embedded in a frame or not.

Basically reading the file works fine, and I can add the nodes, but in this step I always get only the name of the group, but not its properties, like parent:

with bpy.data.libraries.load(filepath) as (data_from, data_to):
    for group_name in data_from.node_groups:
        print(group_name)

If I have a node group in the same blend file, it works fine with the following code:

maingroup = bpy.data.node_groups['_TEST'].nodes
for n in maingroup:
    if n.parent == maingroup['Frame']:
        print("Node '" + n.node_tree.name + "' is framed")

But when I try to read the information of a node group that is in another file, unfortunately it doesn't work.

Any suggestions for a solution?

$\endgroup$
5
  • 1
    $\begingroup$ Uh, I don't think so. We need some library (maybe it already exists) for parsing .blend files. I think currently one option is to run Blender in a separate process, opening a file and reading necessary information there… I think I've seen some existing answers on BSE using this technique. $\endgroup$ Aug 17, 2023 at 13:34
  • $\begingroup$ @MarkusvonBroady Thanks for your feedback! ...that's what I was afraid of. Is there instead a way to add the nodegroups, then process them, and finally reliably remove them (without accidentally deleting existing nodegroups)? $\endgroup$
    – quellenform
    Aug 17, 2023 at 13:39
  • 1
    $\begingroup$ But the question is why would you need to check this? What are you trying to solve? :D $\endgroup$ Aug 17, 2023 at 13:45
  • 1
    $\begingroup$ @HarryMcKenzie I want to create a submenu structure for node groups ...a further development of the "Node Presets" addon: github.com/quellenform/blender-NodePresetsExtended Here, all nodes that are packed into frames are to be put into a submenu. $\endgroup$
    – quellenform
    Aug 17, 2023 at 13:48
  • $\begingroup$ projects.blender.org/blender/blender-asset-tracer "BAT can be used as a Python library to inspect the contents of blend files, without having to open Blender itself. " $\endgroup$ Jan 23 at 10:38

2 Answers 2

3
$\begingroup$

Here's another approach using subprocess to spawn a separate process so you don't have to directly load and store the data into the current blend file's bpy.data, but unfortunately it is a bit slow. I'm not sure how frequently you will need to invoke this. If the call is just made once to make all the checks, then I guess it might suffice for your addon. But nevertheless it might still be interesting.

import subprocess

return_keyword = "RETURN_VALUE:"

def check_blend_file(file_path, group_name):

    script = """
import bpy
 
def load_test():
    file_path = "{}"
    group_name = "{}"
    with bpy.data.libraries.load(file_path) as (data_from, data_to):
        data_to.node_groups.append(group_name)
    maingroup = bpy.data.node_groups[group_name].nodes             
    for n in maingroup:
        if n.parent == maingroup['Frame']:
            return "Node '" + n.node_tree.name + "' is framed"
    return group_name + " not found"

result = load_test()
print("{}", result)
    """.format(file_path, group_name, return_keyword)

    command = ["blender", "--background", "--python-expr", script]

    result = subprocess.check_output(command, stderr=subprocess.STDOUT, text=True)
    return_value = "No result found"

    for line in result.splitlines():
        if return_keyword in line:
           return_value = line.replace(return_keyword, "")

    return return_value

return_value = check_blend_file("C:/Users/harry/Downloads/test.blend", "_TEST")

print("Return value:", return_value)
$\endgroup$
6
  • 1
    $\begingroup$ This looks very promising! Please give me a little time to try around with it.... $\endgroup$
    – quellenform
    Aug 19, 2023 at 11:33
  • 1
    $\begingroup$ Regarding the question asked, this solution works perfectly, so I mark it as Accepted Answer. However, it is unfortunately not suitable for my use case, because the background process simply takes too long. The script would be called only once, but always when the submenu is opened for the first time. Fortunately, I came up with a simpler solution in the meantime, and now control the submenu items via the name of the node group (via a split character). I will then publish the changes on Github. Thanks again! $\endgroup$
    – quellenform
    Aug 20, 2023 at 10:23
  • $\begingroup$ awesome! glad u were able to come up with a solution to your issue! $\endgroup$ Aug 20, 2023 at 10:29
  • $\begingroup$ ....But... I may solve it this way after all. Then I could even spare myself the different paths in the preferences and solve it there via a button, as well as use a JSON file as a cache. Hm. Off-topic, but what do you think about it? Chat? $\endgroup$
    – quellenform
    Aug 20, 2023 at 10:29
  • $\begingroup$ how does chat work? when u open blender doesnt all the loading for different addons happen there then u can cache the results? $\endgroup$ Aug 20, 2023 at 10:48
3
$\begingroup$

Thanks to @MarkusVonBroady for his contribution to the previous script version, here is the final script that should work. Since the libraries.load only loads object names, by adding the names we want to load to data_to, i.e. data_to.node_groups.append('_TEST'), only then will the data we specify get loaded into bpy.data after the with block exists, that is when the Context Manager's __exit__ method gets called. This way you can control what data to load and you can do your checks.

import bpy
 
def load_test():
    file_path = "C:/Users/harry/Downloads/test.blend"
    with bpy.data.libraries.load(file_path) as (data_from, data_to):
        data_to.node_groups.append('_TEST')
    maingroup = bpy.data.node_groups['_TEST'].nodes             
    for n in maingroup:
        if n.parent == maingroup['Frame']:
            print("Node '" + n.node_tree.name + "' is framed")
    return True
 
success = load_test() # succeeds and test for frame works
$\endgroup$
7
  • $\begingroup$ I think instead of data_to.node_groups = [group_name] you want data_to.node_groups.append(group_name), otherwise you will keep overwriting the node_groups, and will only import the last found node_group. Another problem is the general exception statement, I recommend you let the exception happen, and once it does, note the error type and ignore it specifically. $\endgroup$ Aug 18, 2023 at 12:53
  • $\begingroup$ And your problem seems to be, that you try to read the newly added node_group from bpy.data, but it's not there yet: the actual import happens at the end of the with block, when the context manager's __exit__ method is called. So you assemble a structure of things to load, then the with ends, the stuff in data_to is actually loaded, and then you can access it. $\endgroup$ Aug 18, 2023 at 12:55
  • $\begingroup$ Thanks for your answer! Yes, I had tried that with data_to, and as soon as the nodes are loaded with it, of course all information is available, but thereby all nodes are always imported (which I can't use in this case, unfortunately). ...unless the nodes imported in this way can be reliably removed again after parsing, but so far I have not succeeded in doing that either. $\endgroup$
    – quellenform
    Aug 18, 2023 at 12:56
  • 1
    $\begingroup$ @MarkusvonBroady hey cool you made it work! i'll update the answer! ok now i understand that the with completes the load only after it exists. thanks for your input! Since the libraries.load only loads all the names, by adding the names to the data_to like data_to.node_groups.append('_TEST') only then will the specific data we specify get loaded into bpy.data after the with exists, so not everything is loaded if i understood it correctly. $\endgroup$ Aug 18, 2023 at 13:18
  • 1
    $\begingroup$ @HarryMcKenzie Yes, that is correct. The addon is about reading the node groups contained in a blend file and wrapping them in a menu. Currently, however, all node groups are not listed hierarchically, which quickly becomes confusing with many groups. To create submenus, I would have to be able to recognize how they should be nested when reading the file, and a direct import does not help me. The idea of a subprocess sounds interesting though! I just have no idea how this works. And I wonder why Blender doesn't have a parser for its own file format, but that's another story. $\endgroup$
    – quellenform
    Aug 19, 2023 at 9:13

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .