From f0efe6fa9e6f79c9f41fafc85a904771d2cb0679 Mon Sep 17 00:00:00 2001 From: vanous Date: Sun, 17 Dec 2023 13:47:40 +0100 Subject: [PATCH 1/4] Improve 'Re-address only' dialog --- panels/fixtures.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/panels/fixtures.py b/panels/fixtures.py index 49c17dd2..a0214753 100644 --- a/panels/fixtures.py +++ b/panels/fixtures.py @@ -206,11 +206,6 @@ def onProfile(self, context): #update = onAddTarget, default = True) - uuid: StringProperty( - name = "UUID", - description = "Unique ID, used for MVR", - default = str(py_uuid.uuid4()) - ) re_address_only: BoolProperty( name = "Re-address only", description="Do not rebuild the model structure", @@ -232,7 +227,8 @@ def onProfile(self, context): def draw(self, context): layout = self.layout col = layout.column() - col.prop(self, "name") + if not self.re_address_only: + col.prop(self, "name") col.context_pointer_set("add_edit_panel", self) text_profile = "GDTF Profile" if (self.profile != ""): @@ -241,11 +237,13 @@ def draw(self, context): text_profile = text_profile[0] + " > " + text_profile[1] else: text_profile = "Unknown manufacturer" + " > " + text_profile[0] - col.menu("DMX_MT_Fixture_Manufacturers", text = text_profile) + if not self.re_address_only: + col.menu("DMX_MT_Fixture_Manufacturers", text = text_profile) text_mode = "DMX Mode" if (self.mode != ""): text_mode = self.mode - col.menu("DMX_MT_Fixture_Mode", text = text_mode) + if not self.re_address_only: + col.menu("DMX_MT_Fixture_Mode", text = text_mode) col.prop(self, "universe") col.prop(self, "address") col.prop(self, "fixture_id") From b5cf5dc8fc56ef03b93efd2d7ccfc29e5716ca76 Mon Sep 17 00:00:00 2001 From: vanous Date: Sun, 17 Dec 2023 14:49:17 +0100 Subject: [PATCH 2/4] Allow to remove unselectable fixtures --- __init__.py | 5 +++++ panels/fixtures.py | 18 ++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/__init__.py b/__init__.py index 8e8eeb30..11ea8b20 100644 --- a/__init__.py +++ b/__init__.py @@ -152,6 +152,7 @@ class DMX(PropertyGroup): DMX_OT_Programmer_Set_Ignore_Movement, DMX_OT_Programmer_Unset_Ignore_Movement, DMX_PT_DMX_OSC, + DMX_OT_Fixture_ForceRemove, DMX_PT_Programmer) linkedToFile = False @@ -192,6 +193,10 @@ def unregister(): name = "DMX Address", default = True) + column_fixture_remove: BoolProperty( + name = "Remove Fixture", + default = False) + collection: PointerProperty( name = "DMX Collection", type = Collection) diff --git a/panels/fixtures.py b/panels/fixtures.py index a0214753..33796208 100644 --- a/panels/fixtures.py +++ b/panels/fixtures.py @@ -300,6 +300,7 @@ def execute(self, context): if (len(selected) == 1): fixture = selected[0] if (self.name != fixture.name and self.name in bpy.data.collections): + self.report({'ERROR'}, "Fixture named " + self.name + " already exists") return {'CANCELLED'} if not self.re_address_only: fixture.build(self.name, self.profile, self.mode, self.universe, self.address, self.gel_color, self.display_beams, self.add_target, uuid = fixture.uuid, fixture_id = fixture.fixture_id) @@ -318,6 +319,7 @@ def execute(self, context): for i, fixture in enumerate(selected): name = self.name + ' ' + str(i+1) if (name != fixture.name and name in bpy.data.collections): + self.report({'ERROR'}, "Fixture named " + self.name + " already exists") return {'CANCELLED'} for i, fixture in enumerate(selected): name = (self.name + ' ' + str(i+1)) if (self.name != '*') else fixture.name @@ -489,6 +491,17 @@ def execute(self, context): return {'FINISHED'} +class DMX_OT_Fixture_ForceRemove(Operator): + bl_label = "" + bl_idname = "dmx.force_remove_fixture" + bl_description = "Remove fixture" + bl_options = {'UNDO'} + + def execute(self, context): + print("Removing fixture", context.fixture) + dmx = context.scene.dmx + dmx.removeFixture(context.fixture) + return {'FINISHED'} class DMX_PT_Fixture_Columns_Setup(Panel): bl_label = "Display Columns" @@ -515,6 +528,8 @@ def draw(self, context): row = layout.row() row.prop(dmx, "column_dmx_address") row = layout.row() + row.prop(dmx, "column_fixture_remove") + row = layout.row() row.prop(dmx, "fixtures_sorting_order") @@ -586,5 +601,8 @@ def string_to_pairs(s, pairs=re.compile(r"(\D*)(\d*)").findall): c = row.column() c.label(text=f"{fixture.universe}.{fixture.address}") c.ui_units_x = 2 + + if dmx.column_fixture_remove: + row.operator('dmx.force_remove_fixture', icon="CANCEL") layout.menu('DMX_MT_Fixture', text="Fixtures", icon="OUTLINER_DATA_LIGHT") From 7be974cd860eab29c9253362f1c1f47d3ac457b6 Mon Sep 17 00:00:00 2001 From: vanous Date: Sun, 17 Dec 2023 14:57:10 +0100 Subject: [PATCH 3/4] Show number of fixtures in a group --- panels/groups.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/panels/groups.py b/panels/groups.py index a30f9bef..e7ef7da3 100644 --- a/panels/groups.py +++ b/panels/groups.py @@ -8,6 +8,7 @@ # import bpy +import json from bpy.props import (StringProperty) @@ -21,8 +22,9 @@ class DMX_UL_Group(UIList): def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index): icon = "GROUP_VERTEX" + fixture_count = len(json.loads(item.dump)) if self.layout_type in {'DEFAULT', 'COMPACT'}: - layout.label(text = item.name, icon = icon) + layout.label(text = f"{item.name} ({fixture_count})", icon = icon) elif self.layout_type in {'GRID'}: layout.alignment = 'CENTER' layout.label(text="", icon = icon) From ce239d739abe2e1c47e4a741ef4dbf28dc601fdf Mon Sep 17 00:00:00 2001 From: vanous Date: Sun, 17 Dec 2023 17:47:44 +0100 Subject: [PATCH 4/4] Ctrl + LEFT_ARROW, RIGHT_ARROW allow selecting previous/next fixtures (even in groups) --- __init__.py | 37 ++++++++++++++++++++++++++ fixture.py | 5 +++- panels/fixtures.py | 66 +++++++++++++++++++++++++++++++++++----------- 3 files changed, 92 insertions(+), 16 deletions(-) diff --git a/__init__.py b/__init__.py index 11ea8b20..7be8302f 100644 --- a/__init__.py +++ b/__init__.py @@ -19,6 +19,7 @@ import time import json import uuid as py_uuid +import re from dmx.pymvr import GeneralSceneDescription from dmx.mvr import extract_mvr_textures, process_mvr_child_list @@ -153,15 +154,30 @@ class DMX(PropertyGroup): DMX_OT_Programmer_Unset_Ignore_Movement, DMX_PT_DMX_OSC, DMX_OT_Fixture_ForceRemove, + DMX_OT_Fixture_SelectNext, + DMX_OT_Fixture_SelectPrevious, DMX_PT_Programmer) linkedToFile = False + _keymaps = [] def register(): for cls in DMX.classes_setup: bpy.utils.register_class(cls) + # register key shortcuts + wm = bpy.context.window_manager + km = wm.keyconfigs.addon.keymaps.new(name='3D View Generic', space_type='VIEW_3D') + kmi = km.keymap_items.new('dmx.fixture_next', 'RIGHT_ARROW', 'PRESS', ctrl=True) + DMX._keymaps.append((km, kmi)) + kmi = km.keymap_items.new('dmx.fixture_previous', 'LEFT_ARROW', 'PRESS', ctrl=True) + DMX._keymaps.append((km, kmi)) + def unregister(): + # unregister keymaps + for km, kmi in DMX._keymaps: km.keymap_items.remove(kmi) + DMX._keymaps.clear() + if (DMX.linkedToFile): for cls in DMX.classes: bpy.utils.unregister_class(cls) @@ -1002,6 +1018,27 @@ def selectedFixtures(self): break return selected + + def sortedFixtures(self): + def string_to_pairs(s, pairs=re.compile(r"(\D*)(\d*)").findall): + return [(text.lower(), int(digits or 0)) for (text, digits) in pairs(s)[:-1]] + + sorting_order = self.fixtures_sorting_order + + if sorting_order == "ADDRESS": + fixtures = sorted(self.fixtures, key=lambda c: string_to_pairs(str(c.universe*1000+c.address))) + elif sorting_order == "NAME": + fixtures = sorted(self.fixtures, key=lambda c: string_to_pairs(c.name)) + elif sorting_order == "FIXTURE_ID": + fixtures = sorted(self.fixtures, key=lambda c: string_to_pairs(str(c.fixture_id))) + elif sorting_order == "UNIT_NUMBER": + fixtures = sorted(self.fixtures, key=lambda c: string_to_pairs(str(c.unit_number))) + else: + fixtures = self.fixtures + + return fixtures + + def addMVR(self, file_name): bpy.app.handlers.depsgraph_update_post.clear() diff --git a/fixture.py b/fixture.py index deb7cba8..926e4e6d 100644 --- a/fixture.py +++ b/fixture.py @@ -767,6 +767,8 @@ def get_mobile_type(self, mobile_type): def get_root(self, model_collection): + if model_collection.objects is None: + return None for obj in model_collection.objects: if obj.get("geometry_root", False): return obj @@ -815,7 +817,8 @@ def select(self): def unselect(self): dmx = bpy.context.scene.dmx - self.objects["Root"].object.select_set(False) + if "Root" in self.objects: + self.objects["Root"].object.select_set(False) if ('Target' in self.objects): self.objects['Target'].object.select_set(False) if "2D Symbol" in self.objects: diff --git a/panels/fixtures.py b/panels/fixtures.py index 33796208..68c6c2fe 100644 --- a/panels/fixtures.py +++ b/panels/fixtures.py @@ -480,6 +480,56 @@ def execute(self, context): # Panel # +class DMX_OT_Fixture_SelectPrevious(Operator): + bl_label = " " + bl_idname = "dmx.fixture_previous" + bl_description = "Select Previous Fixture" + bl_options = {'UNDO'} + + def execute(self, context): + scene = context.scene + dmx = scene.dmx + selected_all = dmx.selectedFixtures() + fixtures = dmx.sortedFixtures() + + for fixture in fixtures: + fixture.unselect() + + for selected in selected_all: + for idx, fixture in enumerate(fixtures): + if fixture == selected: + idx -= 1 + if idx < 0: + idx = len(fixtures)-1 + fixtures[idx].select() + break + return {'FINISHED'} + +class DMX_OT_Fixture_SelectNext(Operator): + bl_label = " " + bl_idname = "dmx.fixture_next" + bl_description = "Select Next Fixture" + bl_options = {'UNDO'} + + def execute(self, context): + scene = context.scene + dmx = scene.dmx + selected_all = dmx.selectedFixtures() + fixtures = dmx.sortedFixtures() + + for fixture in fixtures: + fixture.unselect() + + for selected in selected_all: + for idx, fixture in enumerate(fixtures): + if fixture == selected: + idx += 1 + if idx > len(fixtures)-1: + idx = 0 + fixtures[idx].select() + break + return {'FINISHED'} + class DMX_OT_Fixture_Item(Operator): bl_label = "DMX > Fixture > Item" bl_idname = "dmx.fixture_item" @@ -543,9 +593,6 @@ class DMX_PT_Fixtures(Panel): bl_context = "objectmode" def draw(self, context): - def string_to_pairs(s, pairs=re.compile(r"(\D*)(\d*)").findall): - return [(text.lower(), int(digits or 0)) for (text, digits) in pairs(s)[:-1]] - layout = self.layout scene = context.scene dmx = scene.dmx @@ -553,18 +600,7 @@ def string_to_pairs(s, pairs=re.compile(r"(\D*)(\d*)").findall): if (len(scene.dmx.fixtures)): box = layout.box() col = box.column() - sorting_order = dmx.fixtures_sorting_order - - if sorting_order == "ADDRESS": - fixtures = sorted(dmx.fixtures, key=lambda c: string_to_pairs(str(c.universe*1000+c.address))) - elif sorting_order == "NAME": - fixtures = sorted(dmx.fixtures, key=lambda c: string_to_pairs(c.name)) - elif sorting_order == "FIXTURE_ID": - fixtures = sorted(dmx.fixtures, key=lambda c: string_to_pairs(str(c.fixture_id))) - elif sorting_order == "UNIT_NUMBER": - fixtures = sorted(dmx.fixtures, key=lambda c: string_to_pairs(str(c.unit_number))) - else: - fixtures = dmx.fixtures + fixtures = dmx.sortedFixtures() for fixture in fixtures: selected = False