From eb4056d25b585fbd05067c9b4c53b6f642689eda Mon Sep 17 00:00:00 2001 From: vanous Date: Sat, 16 Dec 2023 15:50:25 +0100 Subject: [PATCH] Use fixture UUIDs instead of names in groups - (this ensures group validy on fixture rename) - ensure unique UUIDs in fixtures - ensure unique UUIDs in groups - add migration --- __init__.py | 93 +++++++++++++++++++++++++++++++++++++++------- group.py | 10 ++--- mvr.py | 4 +- panels/fixtures.py | 1 - 4 files changed, 86 insertions(+), 22 deletions(-) diff --git a/__init__.py b/__init__.py index 265a6878..c99c8922 100644 --- a/__init__.py +++ b/__init__.py @@ -18,6 +18,7 @@ from threading import Timer import time import json +import uuid as py_uuid from dmx.pymvr import GeneralSceneDescription from dmx.mvr import extract_mvr_textures, process_mvr_child_list @@ -247,7 +248,7 @@ def prepare_empty_buffer(self, context): data_version: IntProperty( name = "BlenderDMX data version, bump when changing RNA structure and provide migration script", - default = 3, + default = 4, ) # New DMX Scene @@ -410,17 +411,73 @@ def migrations(self): for fixture in dmx.fixtures: if "uuid" not in fixture: print("Adding UUID to", fixture.name) - fixture.uuid = str(uuid.uuid4()) + fixture.uuid = str(py_uuid.uuid4()) print("Add UUID to groups, convert groups to json") for group in dmx.groups: if "uuid" not in group: print("Adding UUID to", group.name) - group.uuid = str(uuid.uuid4()) + group.uuid = str(py_uuid.uuid4()) print("Migrating group") group.dump = json.dumps([x[1:-1] for x in group.dump.strip('[]').split(', ')]) + if file_data_version < 4: + print("Running migration 3→4") + dmx = bpy.context.scene.dmx + + def findFixtureUuidDuplicates(uuid): + found = [] + for fixture in self.fixtures: + if fixture is None: + continue + if fixture.uuid == uuid: + found.append(fixture) + return found + + def findGroupUuidDuplicates(uuid): + found = [] + for group in self.groups: + if group is None: + continue + if group.uuid == uuid: + found.append(group) + return found + + print("Ensure unique fixture UUID") + duplicates = [] + for fixture in dmx.fixtures: + duplicates = findFixtureUuidDuplicates(fixture.uuid) + if len(duplicates) > 1: + for fixture in duplicates: + u = fixture.uuid + fixture.uuid = str(py_uuid.uuid4()) + print("Updating fixture", fixture.name, u, fixture.uuid) + + print("Ensure unique group UUID") + duplicates = [] + for group in dmx.groups: + duplicates = findGroupUuidDuplicates(group.uuid) + if len(duplicates) > 1: + for group in duplicates: + u = group.uuid + group.uuid = str(py_uuid.uuid4()) + print("Updating group", group.name, u, group.uuid) + + print("Convert groups from fixture names to UUIDs") + for group in dmx.groups: + grouped_fixtures = json.loads(group.dump) + uuid_list = [] + for g_fixture in grouped_fixtures: + if g_fixture in dmx.fixtures: + fixture = dmx.fixtures[g_fixture] + if fixture is not None: + uuid_list.append(fixture.uuid) + group.dump = json.dumps(uuid_list) + print("Groups updated") + + print("Migration done") + # add here another if statement for next migration condition... like: - # if file_data_version < 4: #... + # if file_data_version < 5: #... self.collection["DMX_DataVersion"] = self.data_version # set data version to current @@ -886,12 +943,13 @@ def addFixture(self, name, profile, universe, address, mode, gel_color, display_ # TODO: fix order of attributes to match fixture.build() bpy.app.handlers.depsgraph_update_post.clear() dmx = bpy.context.scene.dmx - dmx.fixtures.add() - dmx.fixtures[-1].build(name, profile, mode, universe, address, gel_color, display_beams, add_target, position, focus_point, uuid, fixture_id, custom_id, fixture_id_numeric, unit_number) + new_fixture = dmx.fixtures.add() + new_fixture.uuid = str(py_uuid.uuid4()) # ensure clean uuid + new_fixture.build(name, profile, mode, universe, address, gel_color, display_beams, add_target, position, focus_point, uuid, fixture_id, custom_id, fixture_id_numeric, unit_number) bpy.app.handlers.depsgraph_update_post.append(onDepsgraph) def removeFixture(self, fixture): - self.remove_fixture_from_groups(fixture.name) + self.remove_fixture_from_groups(fixture.uuid) for obj in fixture.collection.objects: bpy.data.objects.remove(obj) for obj in fixture.objects: @@ -908,11 +966,19 @@ def getFixture(self, collection): def findFixture(self, object): for fixture in self.fixtures: if fixture is None: - return None + continue if (object.name in fixture.collection.objects): return fixture return None + def findFixtureByUUID(self, uuid): + for fixture in self.fixtures: + if fixture is None: + continue + if fixture.uuid == uuid: + return fixture + return None + def selectedFixtures(self): selected = [] for fixture in self.fixtures: @@ -982,10 +1048,11 @@ def ensureUniverseExists(self, universe): def createGroup(self, name): dmx = bpy.context.scene.dmx - dmx.groups.add() - group = dmx.groups[-1] + group = dmx.groups.add() group.name = name + group.uuid = str(py_uuid.uuid4()) #ensure clean uuid group.update() + print(group.dump) if (not len(group.dump)): print("DMX Group: no fixture selected!") dmx.groups.remove(len(dmx.groups)-1) @@ -1005,12 +1072,12 @@ def renameGroup(self, i, name): def removeGroup(self, i): bpy.context.scene.dmx.groups.remove(i) - def remove_fixture_from_groups(self, fixture_name): + def remove_fixture_from_groups(self, fixture_uuid): dmx = bpy.context.scene.dmx for group in dmx.groups: dump = json.loads(group.dump) - if fixture_name in dump: - dump.remove(fixture_name) + if fixture_uuid in dump: + dump.remove(fixture_uuid) group.dump = json.dumps(dump) # # Preview Volume diff --git a/group.py b/group.py index 90bf3312..52455b3c 100644 --- a/group.py +++ b/group.py @@ -56,8 +56,7 @@ def update(self): # and repopulate it if (len(sel_fixtures)): #self.runtime[self.name] = sel_fixtures; - # TODO: it would be better to use fixture UUID - self.dump = json.dumps([fixture.name for fixture in sel_fixtures]) + self.dump = json.dumps([fixture.uuid for fixture in sel_fixtures]) else: self.dump = '' @@ -68,13 +67,14 @@ def select(self): # which seems to mess with the runtime volatile data declared as runtime # However, a better way should be considered (a Blender String Array) #for fixture in self.runtime[self.name]: - fixtures = bpy.context.scene.dmx.fixtures + dmx = bpy.context.scene.dmx if not bpy.context.window_manager.dmx.aditive_selection: bpy.ops.object.select_all(action='DESELECT') bpy.context.scene.dmx.updatePreviewVolume() - for fixture in [fixtures[fxt] for fxt in json.loads(self.dump)]: - fixture.select() + for fixture in [dmx.findFixtureByUUID(f_uuid) for f_uuid in json.loads(self.dump)]: + if fixture is not None: + fixture.select() bpy.context.scene.dmx.updatePreviewVolume() diff --git a/mvr.py b/mvr.py index 91074a80..14d3097f 100644 --- a/mvr.py +++ b/mvr.py @@ -401,8 +401,6 @@ def add_mvr_fixture( ) if fixture_group is not None: - fixture_name = f"{fixture.name} {layer_index}-{fixture_index}" - group = None if fixture_group.name in dmx.groups: group = dmx.groups[fixture_group.name] else: @@ -413,5 +411,5 @@ def add_mvr_fixture( dump = json.loads(group.dump) else: dump = [] - dump.append(fixture_name) + dump.append(fixture.uuid_number) group.dump = json.dumps(dump) diff --git a/panels/fixtures.py b/panels/fixtures.py index 0c358e2e..202ef196 100644 --- a/panels/fixtures.py +++ b/panels/fixtures.py @@ -298,7 +298,6 @@ def execute(self, context): dmx = scene.dmx selected = scene.dmx.selectedFixtures() context.window_manager.dmx.pause_render = True # pause renderer as partially imported fixture can cause issues during updates - # TODO: handle re-grouping if fixture name changed # Single fixture if (len(selected) == 1): fixture = selected[0]