From a18aad4373652056e3ded15bc1f4cde1bf679167 Mon Sep 17 00:00:00 2001 From: twotalesanimation <80506065+twotalesanimation@users.noreply.github.com> Date: Sat, 15 Aug 2026 19:24:46 +0200 Subject: [PATCH] fix(blender): hold a reference to the project enum items MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The project dropdown rendered garbled labels. EnumProperty does not copy the strings a callback returns — Python has to keep a reference or Blender reads freed memory, which shows as mojibake rather than as a crash. The callback built fresh tuples on every redraw, so each set was collected the moment it returned. The items are now built once when the project list changes and held at module level; the callback returns that list and allocates nothing, which also makes a per-redraw callback free rather than merely correct. Add-on 1.0.2. Co-Authored-By: Claude Opus 5 --- BlenderPanel/vfxreview_submit.py | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/BlenderPanel/vfxreview_submit.py b/BlenderPanel/vfxreview_submit.py index e6ca717..1530fc2 100644 --- a/BlenderPanel/vfxreview_submit.py +++ b/BlenderPanel/vfxreview_submit.py @@ -1,7 +1,7 @@ bl_info = { "name": "VFXReview — Submit Render", "author": "Two Tales", - "version": (1, 0, 1), + "version": (1, 0, 2), "blender": (3, 6, 0), "location": "Properties > Output > VFXReview", "description": "Queue this .blend on the studio render farm instead of rendering locally", @@ -101,12 +101,28 @@ def api_request(path, payload=None, method="GET"): _project_cache = [] +# Blender does not copy the strings an EnumProperty callback returns — Python +# must keep a reference to them or the UI renders freed memory, which shows up +# as garbled labels rather than as a crash. So the items are built once when the +# project list changes and held here; the callback only ever returns this list. +_enum_items = [("", "Refresh projects...", "")] + + +def rebuild_enum_items(): + global _enum_items + if _project_cache: + _enum_items = [ + (p["code"], "%s - %s" % (p["code"], p["name"]), "") + for p in _project_cache + ] + else: + _enum_items = [("", "Refresh projects...", "")] + return _enum_items + def project_items(self, context): - """Project dropdown. Cached because Blender calls this on every redraw.""" - if not _project_cache: - return [("", "Refresh projects…", "")] - return [(p["code"], "%s — %s" % (p["code"], p["name"]), "") for p in _project_cache] + """Called on every redraw, so it does no work and allocates nothing.""" + return _enum_items # ── Scene → job ─────────────────────────────────────────────────────────────── @@ -142,6 +158,7 @@ class VFXR_OT_refresh_projects(Operator): return {"CANCELLED"} _project_cache = data.get("projects", []) + rebuild_enum_items() self.report({"INFO"}, "%d project(s) loaded" % len(_project_cache)) return {"FINISHED"}