6
$\begingroup$

I have numerous objects categories that I used to organize in Groups through a script in Blender 2.79.

I'm now trying to switch to blender 2.8 and use collections, but in the outliner the only listing order that I can get is by their creation. There is an option "sort alphabetically" but it only works for objects. In Blender 2.79 Groups were listed in alphabetical order, I don't understand why this has been changed.

Anyway, does anyone have an idea on how to circumvent this problem? Would it be possible with a script? There is no way I can switch to Blender 2.8 if I don't fix this issue! Thank you very much.

Federico

$\endgroup$
4
+50
$\begingroup$

Fully recursive and optionally case-sensitive:

import bpy

def sort_collection(collection, case = False):

  if collection.children is None: return

  children = sorted (
    collection.children, 
    key = lambda c: c.name if case else c.name.lower()
  )

  for child in children:  
    collection.children.unlink(child)
    collection.children.link(child)
    sort_collection(child)

# case_sensitive sort, (default is False)
case_sensitive = True

for scene in bpy.data.scenes:
    sort_collection(scene.collection, case_sensitive)
$\endgroup$
  • $\begingroup$ Neater, too... cheers!.. but it will put all capitalized items before all non-capitalized.. this may be what's wanted? $\endgroup$ – Robin Betts Dec 3 at 14:54
  • 1
    $\begingroup$ That should fix it. $\endgroup$ – Moog Dec 3 at 15:29
  • $\begingroup$ Thanks, this (and the discussion around the question) was great! It did lack something I needed (unlink/linking by default causes everything in a collection to become visible, a problem when you have a large enough number of collections/objects to want to do this in the first place), but that was only a 4-line fix. I've never made a Blender add-on before, but I packaged the result into a simple .py in case anyone wants to use this going forward: github.com/NeverConvex/blenderAlphabetize/blob/master/… $\endgroup$ – NeverConvex Dec 5 at 2:44
3
$\begingroup$

How about something along these lines?

import bpy

scn = bpy.context.scene
cols = scn.collection.children

sorted_cols = sorted (cols, key = lambda c: c.name.lower())

for col in sorted_cols:  
    cols.unlink(col)
    cols.link(col)

There may be problems with it.. I haven't covered all the bases, and you may want to do it recursively, etc..?

$\endgroup$
3
$\begingroup$

Once for each collection.

Just as I was about to answer this yesterday, my internet ran out, fortunately there is a bit of a point of difference.

To do this once for all collections, would add the root scene collections to bpy.data.collections and then sort the immediate children of each.

To sort all collections in blend in one fell swoop.

import bpy

collections = bpy.data.collections[:]
collections.extend(scene.collection for scene in bpy.data.scenes)
for col in collections:
    for c in sorted(col.children, key=lambda c: c.name):
        col.children.unlink(c)
        col.children.link(c)

Similarly to sort just a specific collection root, context.collection in example below

import bpy

context = bpy.context

def all_colls(c):
    def walk(col):
        yield col
        for c in col.children:
            yield from walk(c)
    return set(walk(c))

assert(context.collection) # poll cannot be None.

for col in all_colls(context.collection):
    for c in sorted(col.children, key=lambda c: c.name.lower()):
        col.children.unlink(c)
        col.children.link(c)

once again ensuring we only sort the immediate children of each collection once.

$\endgroup$
  • $\begingroup$ As usual, an education. Forgot set() was sorted. Now getting my head around yield from. $\endgroup$ – Robin Betts Dec 4 at 8:58
  • $\begingroup$ Using set akin to meshes set(o.data for o in scene.objects) can have length of 1 to objects collection. A set is an unordered data structure. $\endgroup$ – batFINGER Dec 4 at 10:40
  • $\begingroup$ ahh.. thanks. can't find consistency. Official docs emphasize a set is unordered, but sites like this say the constructor returns sorted elements. They're wrong. Turns out to be true for e.g. integers, but not strings.. maybe set() internally sorts by hash? $\endgroup$ – Robin Betts Dec 4 at 11:24
  • $\begingroup$ agree, that appears to be the case. $\endgroup$ – batFINGER Dec 4 at 11:33
2
$\begingroup$

This does not seem to be possible at all just yet. There is discussions about this issue in various places, for instance here in the bug tracker. Using Python I also have not seen a way of doing this just yet. The sorting simply relates to the time at which a collection has been linked to the scene, and cannot really be changed.

The underlying issue seems to be that at the time of writing this, the relevant Python methods to sort are not implemented for collections. In Python, you'll need to implement at least code for the hidden __lt__ (less than), __gt__ (greather than), maybe also the __eq__ (is equal to) functions. Those functions are called when you do comparisons like A > B or A == B, see this for further reference. Furthermore, for sorting, they are the base for the comparison itself. When you sort someting complex like a collection object, which has many attributes, Python needs to be told what makes one collection "bigger" or "smaller" than the other. Obviously you want the name, but Python-wise you could also aim at an index, a hash, a postal code, or whatever. That's why those methods exist, in them you tell Python based on what > or < operate.

Now when you test this in Python, you can try these three examples. I am retrieving collection lists in various places and ask Python to sort them using the sorted() function:

sorted(bpy.data.collections)

Traceback (most recent call last):
  File "<blender_console>", line 1, in <module>
  TypeError: '<' not supported between instances of 'Collection' and 'Collection'`

The error thrown indicated that the __lt__ method has no code behind.

same of course with the keys() attribute:

sorted(bpy.data.collections.values())

Traceback (most recent call last):
  File "<blender_console>", line 1, in <module>
  TypeError: '<' not supported between instances of 'Collection' and 'Collection'

We can also test this for the LayerCollection type, which is what you see in reality in the outliner:

sorted(bpy.context.view_layer.active_layer_collection.children)

Traceback (most recent call last):
  File "<blender_console>", line 1, in <module>
  TypeError: '<' not supported between instances of 'LayerCollection' and 'LayerCollection'

The error in all cases remains the same. So what I'm aiming at with the explanation here is that sorting simply isn't implemented yet, and you can't really force it to happen from the Python side. You would probably need to unlink all collections from the scene first and relink them again in a defined order to enforce a sorted result. But since Collections can have nesting and multiple parents, this can become quite tricky.

$\endgroup$
  • $\begingroup$ I had the same fears as you.. but it seems all that's happening with unlink from children is that a whatever-level reference (which is maintained) is removed from, and then can be appended to, an underlying linked list? $\endgroup$ – Robin Betts Dec 3 at 14:00
  • $\begingroup$ You would probably need to unlink all collections from the scene first and relink them again in a defined order Thought to myself that's worth a try. IMO an outliner space filter enum would fit the bill akin to sort filter on list UI. There is no generic logic I can think of to make one collection or object greater or less than another, as there is with int or float or 'string` or Vector On a side note It appears the bpy.data collections eg 'objects' are sorted internally by name. $\endgroup$ – batFINGER Dec 4 at 10:26
  • $\begingroup$ @batFINGER Well the logic can only be the Collection name, as you and the others rightfully pointed out in the respective answers using lambda functions for the key. But as you state, this needs to be solved on the C++ level really and exposed as a filter enum or the like. There is no reason that crosses my mind why this should not be possible. $\endgroup$ – aliasguru Dec 4 at 13:55
  • $\begingroup$ To clarify 'guru, as I'm not sure we are on the same page. not as comparison methods eg __gt__ etc in the Class def. Having empty collection B > than bloated collection A based on name isn't the intended usage IMO. Instead of A > B would use A.name > B.name since str class has intrinsic comparison. Which is why sorted(bpy.data.collections.keys()) does work. $\endgroup$ – batFINGER Dec 5 at 13:20

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.