Skip to content

Quicker Text Editing in Blender (Part 13): CollectionProperty, KeyMapItem ID, Preferences Pitfalls

  • by

There were many pits and I fell in them all

Background

When making videos in Blender’s VSE I use a lot of text sequences for captions and the like. Doing common operations — like changing colour, location, size etc — can be slow when using the GUI, so I am writing an addon called QTE (Quicker Text Editing, pronounced ‘cutie’) to make the process faster. This series of posts outlines the process and tries to explain parts of the Blender API that I interacted with along the way.

Show all parts (click to expand) Parts:
  1. Part 1: What We Want To Do & Proof of Concept
  2. Part 2: Operators, Dynamic Classes & a Basic Script
  3. Part 3: Keymaps
  4. Part 4: User Preferences (& Panels)
  5. Part 5: QTE Alpha Release
  6. Part 6: Preferences and the right keymap
  7. Part 7: QTE Beta Release (& ‘Remove Binding’ Operator)
  8. Part 8: Bugs & Features
  9. Part 9: Prototyping Appearing Text
  10. Interlude: Why is my Blender addon panel property read only? (Solved)
  11. Part 10: Beta 2 Release (+ Development Hurdles)
  12. Part 11: Reflection and Next Steps
  13. Part 12: Preferences + Properties = Bugs
  14. Part 13: CollectionProperty, KeyMapItem ID, Preferences Pitfalls

Context

In part 12 I mentioned the issue with storing operator properties in a KeyMapItem- the tl;dr is that updating the properties from a panel (eg in addon preferences) doesn’t seem to properly update the values if the key binding is not updated as well. Having run into the issue of something (panel options) seemingly not updating before, I thought I’d try a similar approach, ie: store the options elsewhere, and pass in a reference to the operator to tell it where to find the options. This approach works (spoilers!) but led me down a long rabbit hole of trying to figure out appropriate data structures, implementing and unimplementing solutions, trying workarounds and then trying to figure out workarounds for problems with the original workarounds…


I am no professional programmer, but it was the kind of experience that wonderfully demonstrates that measuring someone’s performance by line of code for the folly that it is. I spent a day thinking, writing, testing, scrubbing what was written, re-writing, re-testing, (repeat), consulting API docs, forum posts and QAs…

Having working familiarity with a system — which I don’t yet have but is slowly growing! — is valuable in and of itself. It’s one of those soft attributes which is hard to quantify, but aids just about every aspect of development.


I have collected some of the pitfalls here partly as a testament to what I did, as by raw output it didn’t feel like much (though that is not a great metric on which to judge, see the aside); and partly so that is someone else is pulling their hair out, they can at least know it wasn’t just them.

Pitfalls

KeyMapItem operator properties don’t update properly

This is the one we discussed in the last part. Just seeing if you’re paying attention!

KeyMapItem IDs change to fill gap if an item is removed

Simply put, if you use the KeyMapItem’s .id property to track and sync options, the saved value will become invalid if a KeyMapItem is remove()’d and that KMI is not the last one added:

Aside: I’m not sure why Blender uses negative integers for user-added KeyMapItems, but it does help to distinguish them.

The change of KeyMapItem IDs is not instant

If you have an operator which removes a keymapitem, and the other corresponding data, like a preference CollectionProperty entry, you might think- “I can update the ids of any that are out of sync”. Well no:

I’m not sure why, but setting context.preferences.is_dirty = True might do it. I say ‘might’ because…

Assignment fails with ‘int’ object does not support item assignment

To be honest, I only noticed this issue when looking through my console scrollback history- by this point my brain just saw errors with what I was doing and assumed it was more of the same.

This appears to simply be a thinko- looking at my code it seems I used the returned value[s] from another function incorrectly- I assumed it would give me a collection of matching objects; instead it returns a list of the ids to the matching objects.

I’d complain to whoever wrote the function for not being pythonic, but…

CollectionProperty class’ find() method doesn’t seem to usefully work

I was trying to remove an item from a CollectionProperty, like in this BSE QA. The QA explains that you can use either the remove() or clear() methods, depending on how much you want to remove, it is unclear on how to get the index of the element you want to remove.

The second answer on the QA suggests using find(), which is documented albeit in a hard-to-find place, and so it has contextual help in Blender’s python console:

However, for whatever reason I could not get it to return anything other than -1.

You can’t inherit from / extend the CollectionProperty (seemingly)

Given the lack of ease of removing items, I thought I’d implement my own class (with blackjack etc) with convenience functions, which I started working on

However, trying to do so gives an error:

TypeError: cannot create 'builtin_function_or_method' instances

Some quick searching leads me to believe this is due to how the class is implemented on the C side of Blender’s python API, though I could be wrong.

Accessing a CollectionProperty via property is different from accessing it via subscripting

That is:
context.preferences.addons[__name__].preferences.my_collectionproperty
gives a different object from
context.preferences.addons[__name__].preferences["my_collectionproperty"]

I’ve used subscript notation to access properties that have update defined without creating a circular reference, so I thought that I could do the same again to pass in a reference (more accurately: a key) to the preset set I wanted to updated.

The dot notation access seems to give the actual object (that is the class which you have to look for specifically in the docs, mentioned above), whereas subscripting gives a list of items instead.

Looking at the objects inside each:

Once again, the dot notation access seems to give the “full” version of the object, whereas the subscript gives a slightly different representation.

You can go further and examine the IDPropertyGroup objects and see that they do have the data that they should:

The CollectionProperty list accessed via subscript does not support del

While the two data structures above look similar, it isn’t possible to mimic a remove() action by trying to del a member of the ColelctionProperty list:

Whereas this of course works with regular lists, as intended:

I thought the objects might be silently immutable / readonly, but you can update individual properties just fine:

???

No doubt if I could locate the relevant part of the C code implementing these types (probably could if I had to) and actually have a decent appreciation of what the code was doing (probably not) I would understand this behaviour- unfortunately I am stuck with the caveman approach of trying things out and then trying alternatives when they don’t work.

Next Steps

I still want to transition the ‘original four’ text operators to store their options in the addon preferences structure, with a uuid to keep the options and keymapitem in sync. I should also move the options for the ‘split to appearing words’ operator to preferences from window_manager, but that should hopefully be a much simpler operation.

Phew.

Tell us what's on your mind

Discover more from Rob's Blog

Subscribe now to keep reading and get access to the full archive.

Continue reading