1
$\begingroup$

Over on this question I found out about io_blend_utils/blend/blendfile.py, which is a one-file-module to read a blender file without actually loading it into blender. I need to read the elements of a CollectionProperty, which I am currently only able to do for the first element.

I fear this is a very specific and advanced question. I am hoping @ideasman42 may be able to help?

My use case

I need to crawl through a bunch of .blend files to find all the texture atlases (created with the uv_texture_atlas plugin) contained in them. Since there are many and large .blend files, I do not want to have the overhead of loading all the files into blender.

My code so far

    def group_iterator(block):
        """
        Iterate over a IDPropertyGroup BlendFileBlock.
        :param block: block to iterate over
        :return: All IDProperty blocks in the group or list
        """
        i = block.get_pointer((b'data', b'group', b'first'))
        while i is not None:
            yield i
            i = i.get_pointer(b'next')

    atlases = collections.OrderedDict()
    for blend_file_path in blends:
        with blendfile.open_blend(blend_file_path) as blend:
            scenes = [block for block in blend.blocks if block.code == b'SC']

            for scene in scenes:
                # get custom properties
                properties = scene.get_pointer((b'id', b'properties'))

                if properties is None:
                    continue

                # iterate through all the property groups
                for itor in group_iterator(properties):
                    if itor.get(b'name') == "ms_lightmap_groups":
                        group = itor.get_pointer((b'data', b'pointer'))

                        if group is not None:
                            # iterate over all the properties
                            for prop in group_iterator(group):
                                name = prop.get(b'name')

                                if name == 'name':
                                    offset = prop.get((b'data', b'pointer'))
                                    length = prop.get(b'len')
                                    blk = blend.block_from_offset.get(offset)
                                    blend.handle.seek(blk.file_offset, os.SEEK_SET)
                                    atlas_name = blendfile.DNA_IO.read_string0(blend.handle, length)
                                elif name == 'resolutionX':
                                    resX = prop.get((b'data', b'val'))
                                elif name == 'resolutionY':
                                    resY = prop.get((b'data', b'val'))

                            atlases[atlas_name] = (2**(resX+8), 2**(resY+8))
                            print("Found atlas %s" % atlas_name)

Thank you so much in advance (I have been trying to figure this out for over a day now)!

Regards, Jonathan.

$\endgroup$
  • $\begingroup$ So, I hear @ideasman42 left blender. In this case I will figure this out myself and post an aswer, once I found out. Also, there is #blenderpython IRC, I will post my question there, too. $\endgroup$ – Squareys Sep 5 '16 at 11:23
1
$\begingroup$

blendfile.py actually supports getting elements of a collection property with some super neat and simple API!

    for blend_file_path in blends:
        with blendfile.open_blend(blend_file_path) as blend:
            scenes = [block for block in blend.blocks if block.code == b'SC']

            for scene in scenes:
                # get custom properties
                properties = scene.get_pointer((b'id', b'properties'))

                if properties is None:
                    continue

                # iterate through all the property groups
                itor = properties.get_pointer((b'data', b'group', b'first'))
                while itor is not None:
                    if itor.get(b'name') == "ms_lightmap_groups":
                        length = itor.get(b'len')

                        for i in range(0, length):
                            group = itor.get_pointer((b'data', b'pointer'))

                            if group is not None:
                                # iterate over all the properties
                                # (!) i specifies the index in the collection
                                prop = group.get_pointer((b'data', b'group', b'first'), base_index=i)  
                                while prop is not None:
                                    name = prop.get(b'name')

                                    if name == 'name':
                                        offset = prop.get((b'data', b'pointer'))
                                        length = prop.get(b'len')
                                        blk = blend.block_from_offset.get(offset)
                                        blend.handle.seek(blk.file_offset, os.SEEK_SET)
                                        atlas_name = blendfile.DNA_IO.read_string0(blend.handle, length)
                                    elif name == 'resolutionX':
                                        resX = prop.get((b'data', b'val'))
                                    elif name == 'resolutionY':
                                        resY = prop.get((b'data', b'val'))

                                    prop = prop.get(b'next')

                                atlases[atlas_name] = (2**(resX+8), 2**(resY+8))
                                print("Found atlas %s" % atlas_name)

                    properties_itor = properties_itor.get_pointer(b'next')

BlendFileBlock.get(...) and BlendFileBlock.get_pointer(...) both have an optional base_index parameter which is gets you exactly this.

May this be of use to someone else out there :)

Happy blending! :D

| improve this answer | |
$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.