diff --git a/dmx.py b/dmx.py
index e912048..f4533bd 100644
--- a/dmx.py
+++ b/dmx.py
@@ -24,7 +24,14 @@
from .panels import recorder as recorder
from .panels import setup as setup
-from .panels import dmx as panels_dmx
+from .panels.protocols import protocols as panels_protocols
+from .panels.protocols import artnet as panels_artnet
+from .panels.protocols import sacn as panels_sacn
+from .panels.protocols import osc as panels_osc
+from .panels.protocols import psn as panels_psn
+from .panels.protocols import mvr as panels_mvr
+from .panels.protocols import universes as panels_universes
+from .panels.protocols import live as panels_live
from .panels import fixtures as fixtures
from .panels import groups as groups
from .panels import programmer as programmer
@@ -80,8 +87,8 @@ class DMX(PropertyGroup):
DMX_Universe,
DMX_Value,
setup.DMX_PT_Setup,
- panels_dmx.DMX_OP_MVR_Download,
- panels_dmx.DMX_OP_MVR_Import,
+ panels_mvr.DMX_OP_MVR_Download,
+ panels_mvr.DMX_OP_MVR_Import,
DMX_MVR_Xchange_Commit,
DMX_MVR_Xchange_Client,
DMX_MVR_Xchange,
@@ -94,13 +101,13 @@ class DMX(PropertyGroup):
classes_setup = (setup.DMX_OT_Setup_NewShow,)
- classes = ( panels_dmx.DMX_UL_Universe,
- panels_dmx.DMX_MT_Universe,
- panels_dmx.DMX_PT_DMX,
- panels_dmx.DMX_PT_DMX_Universes,
- panels_dmx.DMX_PT_DMX_LiveDMX,
- panels_dmx.DMX_PT_DMX_ArtNet,
- panels_dmx.DMX_PT_DMX_sACN,
+ classes = ( panels_protocols.DMX_PT_DMX,
+ panels_universes.DMX_UL_Universe,
+ panels_universes.DMX_MT_Universe,
+ panels_universes.DMX_PT_DMX_Universes,
+ panels_live.DMX_PT_DMX_LiveDMX,
+ panels_artnet.DMX_PT_DMX_ArtNet,
+ panels_sacn.DMX_PT_DMX_sACN,
setup.DMX_OT_Setup_Volume_Create,
setup.DMX_PT_Setup_Volume,
setup.DMX_PT_Setup_Viewport,
@@ -118,7 +125,7 @@ class DMX(PropertyGroup):
fixtures.DMX_OT_Fixture_Item,
fixtures.DMX_OT_Fixture_Profiles,
fixtures.DMX_OT_Fixture_Mode,
- panels_dmx.DMX_UL_LiveDMX_items,
+ panels_live.DMX_UL_LiveDMX_items,
fixtures.DMX_OT_Fixture_Add,
fixtures.DMX_OT_Fixture_Edit,
fixtures.DMX_OT_Fixture_Remove,
@@ -151,22 +158,22 @@ class DMX(PropertyGroup):
programmer.DMX_OT_Programmer_ResetTargets,
programmer.DMX_MT_PIE_Reset,
programmer.DMX_OT_Programmer_Unset_Ignore_Movement,
- panels_dmx.DMX_PT_DMX_OSC,
- panels_dmx.DMX_UL_Tracker,
- panels_dmx.DMX_OP_DMX_Tracker_Add,
- panels_dmx.DMX_OP_DMX_Tracker_Remove,
- panels_dmx.DMX_PT_DMX_Trackers,
- panels_dmx.DMX_OT_Tracker_Followers,
- panels_dmx.DMX_OT_Tracker_Followers_Add_Target,
- panels_dmx.DMX_OT_Tracker_Followers_Remove_Target,
- panels_dmx.DMX_UL_Tracker_Followers,
- panels_dmx.DMX_OP_Unlink_Fixture_Tracker,
- panels_dmx.DMX_OP_Link_Fixture_Tracker,
+ panels_osc.DMX_PT_DMX_OSC,
+ panels_psn.DMX_UL_Tracker,
+ panels_psn.DMX_OP_DMX_Tracker_Add,
+ panels_psn.DMX_OP_DMX_Tracker_Remove,
+ panels_psn.DMX_PT_DMX_Trackers,
+ panels_psn.DMX_OT_Tracker_Followers,
+ panels_psn.DMX_OT_Tracker_Followers_Add_Target,
+ panels_psn.DMX_OT_Tracker_Followers_Remove_Target,
+ panels_psn.DMX_UL_Tracker_Followers,
+ panels_psn.DMX_OP_Unlink_Fixture_Tracker,
+ panels_psn.DMX_OP_Link_Fixture_Tracker,
fixtures.DMX_UL_Fixtures,
- panels_dmx.DMX_PT_DMX_MVR_X,
- panels_dmx.DMX_UL_MVR_Commit,
- panels_dmx.DMX_OP_MVR_Refresh,
- panels_dmx.DMX_OP_MVR_Request,
+ panels_mvr.DMX_PT_DMX_MVR_X,
+ panels_mvr.DMX_UL_MVR_Commit,
+ panels_mvr.DMX_OP_MVR_Refresh,
+ panels_mvr.DMX_OP_MVR_Request,
fixtures.DMX_OT_Fixture_ForceRemove,
fixtures.DMX_OT_Fixture_SelectNext,
fixtures.DMX_OT_Fixture_SelectPrevious,
diff --git a/panels/dmx.py b/panels/dmx.py
deleted file mode 100644
index 11ffe10..0000000
--- a/panels/dmx.py
+++ /dev/null
@@ -1,628 +0,0 @@
-#
-# BlendexDMX > Panels > DMX
-#
-# - Setup DMX Universes
-# - Setup ArtNet (Future)
-#
-# http://www.github.com/open-stage/BlenderDMX
-#
-
-import os
-import bpy
-from bpy.props import StringProperty, IntProperty
-from bpy.types import Menu, Operator, Panel, UIList
-from ..mvrx_protocol import DMX_MVR_X_Client
-from ..data import DMX_Data
-from ..logging import DMX_Log
-import uuid as py_uuid
-from datetime import datetime
-from ..tracker import DMX_Tracker
-
-from ..i18n import DMX_Lang
-_ = DMX_Lang._
-
-class DMX_OP_DMX_Tracker_Add(Operator):
- bl_label = _("Add Tracker")
- bl_description = _("Adding a tracker")
- bl_idname = "dmx.dmx_add_tracker"
- bl_options = {"UNDO"}
-
- def execute(self, context):
- DMX_Tracker.add_tracker()
- return {"FINISHED"}
-
-class DMX_OP_DMX_Tracker_Remove(Operator):
- bl_label = _("Remove Tracker")
- bl_description = _("Removing a tracker")
- bl_idname = "dmx.dmx_remove_tracker"
- bl_options = {"UNDO"}
-
- uuid: StringProperty()
-
- def execute(self, context):
- DMX_Tracker.remove_tracker(self.uuid)
- return {"FINISHED"}
-
-
-class DMX_OP_MVR_Refresh(Operator):
- bl_label = _("Refresh")
- bl_description = _("Refresh connection")
- bl_idname = "dmx.mvr_refresh"
- bl_options = {"UNDO"}
-
- def execute(self, context):
- DMX_MVR_X_Client.re_join()
- return {"FINISHED"}
-
-
-class DMX_OP_MVR_Request(Operator):
- bl_label = _("Request latest version")
- bl_description = _("Sends the Request message")
- bl_idname = "dmx.mvr_request"
- bl_options = {"UNDO"}
-
- station_uuid: StringProperty()
-
- def execute(self, context):
- uuid = str(py_uuid.uuid4())
- mvr_commit = {"FileUUID": uuid, "StationUUID": self.station_uuid,
- "Comment": datetime.now().strftime("%H:%M:%S %B %d, %Y"), "FileSize": 0}
-
- last_commit = DMX_MVR_X_Client.create_self_request_commit(mvr_commit)
- if last_commit:
- DMX_MVR_X_Client.request_file(last_commit)
-
- return {"FINISHED"}
-
-class DMX_OP_MVR_Import(Operator):
- bl_label = _("Import")
- bl_description = _("Import commit")
- bl_idname = "dmx.mvr_import"
- bl_options = {"UNDO"}
-
- uuid: StringProperty()
-
- def execute(self, context):
- scene = context.scene
- dmx = scene.dmx
- ADDON_PATH = os.path.dirname(os.path.abspath(__file__))
- clients = context.window_manager.dmx.mvr_xchange
- all_clients = context.window_manager.dmx.mvr_xchange.mvr_xchange_clients
- selected = clients.selected_mvr_client
- for client in all_clients:
- if client.station_uuid == selected:
- break
- for commit in client.commits:
- if commit.commit_uuid == self.uuid:
- DMX_Log.log.info(f"import {commit}")
- path = os.path.join(ADDON_PATH, "..", "assets", "mvrs", f"{commit.commit_uuid}.mvr")
- DMX_Log.log.info(path)
- dmx.addMVR(path)
- break
- return {"FINISHED"}
-
-class DMX_OP_MVR_Download(Operator):
- bl_label = _("Download")
- bl_description = _("Download commit")
- bl_idname = "dmx.mvr_download"
- bl_options = {"UNDO"}
-
- uuid: StringProperty()
-
- def execute(self, context):
- DMX_Log.log.info("downloading")
-
- clients = context.window_manager.dmx.mvr_xchange
- all_clients = clients.mvr_xchange_clients
- selected = clients.selected_mvr_client
- for client in all_clients:
- if client.station_uuid == selected:
- break
- DMX_Log.log.info(f"got client {client.station_name}")
- for commit in client.commits:
- DMX_Log.log.info(commit.commit_uuid)
- if commit.commit_uuid == self.uuid:
- DMX_Log.log.info(f"downloading {commit}")
- DMX_MVR_X_Client.request_file(commit)
- break
-
- return {"FINISHED"}
-
-class DMX_UL_MVR_Commit(UIList):
- def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
- scene = context.scene
- dmx = scene.dmx
- icon = "GROUP_VERTEX"
- #layout.context_pointer_set("mvr_xchange_clients", item)
- col = layout.column()
- col.label(text = f"{item.comment}", icon="CHECKBOX_HLT" if item.timestamp_saved else "CHECKBOX_DEHLT")
- col = layout.column()
- col.operator("dmx.mvr_download", text="", icon="IMPORT").uuid = item.commit_uuid
- col.enabled = dmx.mvrx_enabled
- col = layout.column()
- col.operator("dmx.mvr_import", text="", icon="CHECKBOX_HLT").uuid = item.commit_uuid
- col.enabled = item.timestamp_saved > 0
-
-
-# List #
-
-class DMX_UL_Tracker(UIList):
- def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
- ob = data
- icon = "FILE_VOLUME"
- if self.layout_type in {'DEFAULT', 'COMPACT'}:
- col = layout.column()
- col.ui_units_x = 3
- col = layout.column()
- col.prop(item, "name", text="", emboss=False)
- col = layout.column()
- col.prop(item, "enabled", text="")
- col = layout.column()
- col.operator("dmx.dmx_remove_tracker", text="", icon="TRASH").uuid = item.uuid
- elif self.layout_type in {'GRID'}:
- layout.alignment = 'CENTER'
- layout.label(text=str(item.id), icon=icon)
-
-class DMX_UL_Universe(UIList):
- def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
- ob = data
- icon = "FILE_VOLUME"
- if self.layout_type in {'DEFAULT', 'COMPACT'}:
- col = layout.column()
- col.label(text=f"{item.id}", icon=icon)
- col.ui_units_x = 3
- col = layout.column()
- col.prop(item, "name", text="", emboss=False)
- col = layout.column()
- col.label(text=item.input)
- elif self.layout_type in {'GRID'}:
- layout.alignment = 'CENTER'
- layout.label(text=str(item.id), icon=icon)
-
-
-
-class DMX_OP_Link_Fixture_Tracker(Operator):
- bl_label = _("Link Fixture to Tracker")
- bl_description = _("Link fixture to a tracker")
- bl_idname = "dmx.psn_tracker_follower_link"
- bl_options = {"UNDO"}
-
- fixture_uuid: StringProperty()
- tracker_uuid: StringProperty()
- tracker_index: IntProperty()
-
- def execute(self, context):
- scene = context.scene
- dmx = scene.dmx
- dmx = scene.dmx
- layout = self.layout
- target = None
- for tracker in dmx.trackers:
- if tracker.uuid == self.tracker_uuid:
- for idx, obj in enumerate(tracker.collection.objects):
- if idx == self.tracker_index:
- target = obj
-
- if target is not None:
- for fixture in dmx.fixtures:
- if fixture.uuid == self.fixture_uuid:
- for obj in fixture.objects:
- if obj.name == "Target":
- constraint = obj.object.constraints.new(type='COPY_LOCATION')
- constraint.target = target
-
- return {"FINISHED"}
-
-class DMX_OP_Unlink_Fixture_Tracker(Operator):
- bl_label = _("Unlink Fixture from Tracker")
- bl_description = _("Unlink fixture from a tracker")
- bl_idname = "dmx.psn_tracker_follower_unlink"
- bl_options = {"UNDO"}
-
- fixture_uuid: StringProperty()
- tracker_uuid: StringProperty()
-
- def execute(self, context):
- scene = context.scene
- dmx = scene.dmx
- layout = self.layout
- for fixture in dmx.fixtures:
- if fixture.uuid == self.fixture_uuid:
- for obj in fixture.objects:
- if obj.name == "Target":
- for constraint in obj.object.constraints:
- if constraint.target != None:
- if constraint.target.get("uuid", None) == self.tracker_uuid:
- obj.object.constraints.remove(constraint)
-
-
- return {"FINISHED"}
-
-
-
-class DMX_UL_Tracker_Followers(UIList):
-
- tracker_uuid: StringProperty()
- tracker_index: IntProperty()
-
- def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
- fixture = item
- scene = context.scene
- dmx = scene.dmx
- icon = "FILE_VOLUME"
- self.tracker_uuid = context.window_manager.dmx.selected_tracker
- self.tracker_index = context.window_manager.dmx.selected_tracker_index
-
- linked = None
- for obj in fixture.objects:
- if obj.name == "Target":
- linked = False
- for const in obj.object.constraints:
- if const.target is not None:
- if const.target.get("uuid", None) == self.tracker_uuid:
- linked = True
-
- if self.layout_type in {'DEFAULT', 'COMPACT'}:
- col = layout.column()
- col.ui_units_x = 3
- col = layout.column()
- col.prop(item, "name", text="", emboss=False)
- col = layout.column()
- if linked is None:
- layout.label(text="", icon="CANCEL")
- elif linked is True:
- op = col.operator("dmx.psn_tracker_follower_unlink", icon="LINKED", text="")
- op.fixture_uuid = fixture.uuid
- op.tracker_uuid = self.tracker_uuid
- else:
- op = col.operator("dmx.psn_tracker_follower_link", icon="UNLINKED", text="")
- op.fixture_uuid = fixture.uuid
- op.tracker_uuid = self.tracker_uuid
- op.tracker_index = self.tracker_index
- elif self.layout_type in {'GRID'}:
- layout.alignment = 'CENTER'
- layout.label(text=item.name, icon=icon)
-
-
-class DMX_OT_Tracker_Followers(Operator):
- bl_label = _("Tracker Followers")
- bl_idname = "dmx.psn_tracker_followers"
- bl_description = _("Link followers to a tracker")
- bl_options = {'UNDO'}
-
- tracker_uuid: StringProperty()
- tracker_index: IntProperty()
- fixture_i: IntProperty()
-
- def draw(self, context):
- layout = self.layout
- scene = context.scene
- dmx = scene.dmx
- context.window_manager.dmx.selected_tracker = self.tracker_uuid
- context.window_manager.dmx.selected_tracker_index = self.tracker_index
- layout.template_list("DMX_UL_Tracker_Followers", "", dmx, "fixtures", self, "fixture_i")
-
-
- def execute(self, context):
- return {'FINISHED'}
- return {'CANCELLED'}
-
- def invoke(self, context, event):
- #self.name = "Group " + str(len(context.scene.dmx.groups)+1)
- wm = context.window_manager
- return wm.invoke_props_dialog(self)
-
-# Menus #
-
-class DMX_MT_Universe(Menu):
- bl_label = _("DMX > Universe Menu")
- bl_idname = "DMX_MT_Universe"
-
- def draw(self, context):
- layout = self.layout
- scene = context.scene
- dmx = scene.dmx
-
- # "Add"
- row = layout.row()
- #row.operator("dmx.add_universe", text="Add", icon="ADD")
-
-
-# Sub-panels #
-class DMX_PT_DMX_OSC(Panel):
- bl_label = _("OSC")
- bl_idname = "DMX_PT_DMX_OSC"
- bl_parent_id = "DMX_PT_DMX"
- bl_space_type = "VIEW_3D"
- bl_region_type = "UI"
- bl_category = "DMX"
- bl_context = "objectmode"
- bl_options = {'DEFAULT_CLOSED'}
-
- def draw(self, context):
- layout = self.layout
- dmx = context.scene.dmx
-
- row = layout.row()
- row.prop(dmx, "osc_enabled")
- row = layout.row()
- row.prop(dmx, "osc_target_address")
- row.enabled = not dmx.osc_enabled
- row = layout.row()
- row.prop(dmx, "osc_target_port")
- row.enabled = not dmx.osc_enabled
-
-class DMX_PT_DMX_Trackers(Panel):
- bl_label = _("PSN")
- bl_idname = "DMX_PT_DMX_Trackers"
- bl_parent_id = "DMX_PT_DMX"
- bl_space_type = "VIEW_3D"
- bl_region_type = "UI"
- bl_category = "DMX"
- bl_context = "objectmode"
- bl_options = {'DEFAULT_CLOSED'}
-
- def draw(self, context):
- layout = self.layout
- dmx = context.scene.dmx
- layout.operator("dmx.dmx_add_tracker", text="Add tracker", icon="PLUS")
- layout.template_list("DMX_UL_Tracker", "", dmx, "trackers", dmx, "trackers_i")
-
- if (dmx.trackers_i < len(dmx.trackers)):
- tracker = dmx.trackers[dmx.trackers_i]
- layout.prop(tracker, "name")
- layout.prop(tracker, "ip_address")
- layout.prop(tracker, "ip_port")
- layout.prop(tracker, "enabled")
- add_trackers = True
- for idx, obj in enumerate(tracker.collection.objects):
- row = layout.row()
- col = row.column()
- op=col.operator("dmx.psn_tracker_followers", text=_(f"Link Followers to {obj.name}"), icon="LINKED")
- op.tracker_uuid = tracker.uuid
- op.tracker_index = idx
- if len(tracker.collection.objects) > 1:
- col = row.column()
- op = col.operator("dmx.psn_remove_tracker_followers_target", text="", icon="TRASH")
- op.object_name = obj.name
- op.tracker_uuid = tracker.uuid
-
- if idx >= 9: # we support max 10 trackers, edit tracker.py and psn.py if more is needed
- add_trackers = False
- row = layout.row()
- op = row.operator("dmx.psn_add_tracker_followers_target", text=_("Add Tracking Target"), icon="PLUS")
- op.tracker_uuid = tracker.uuid
- row.enabled = add_trackers
-
-
-
-class DMX_OT_Tracker_Followers_Remove_Target(Operator):
- bl_label = _("Remove Followers Target")
- bl_idname = "dmx.psn_remove_tracker_followers_target"
- bl_description = _("Remove target")
- bl_options = {'UNDO'}
-
- object_name: StringProperty()
- tracker_uuid: StringProperty()
-
- def execute(self, context):
- dmx = context.scene.dmx
- for tracker in dmx.trackers:
- if tracker.uuid == self.tracker_uuid:
- if self.object_name in tracker.collection.objects:
- rem_obj = tracker.collection.objects[self.object_name]
- bpy.data.objects.remove(rem_obj)
-
- return {"FINISHED"}
-
-class DMX_OT_Tracker_Followers_Add_Target(Operator):
- bl_label = _("Add Followers Target")
- bl_idname = "dmx.psn_add_tracker_followers_target"
- bl_description = _("Add target")
- bl_options = {'UNDO'}
-
- tracker_uuid: StringProperty()
-
- def execute(self, context):
- dmx = context.scene.dmx
- for tracker in dmx.trackers:
- if tracker.uuid == self.tracker_uuid:
- for obj in tracker.collection.objects:
- duplicate_obj = obj.copy()
- tracker.collection.objects.link(duplicate_obj)
- break
-
- return {"FINISHED"}
-
-class DMX_PT_DMX_MVR_X(Panel):
- bl_label = _("MVR-xchange")
- bl_idname = "DMX_PT_DMX_MVR_Xchange"
- bl_parent_id = "DMX_PT_DMX"
- bl_space_type = "VIEW_3D"
- bl_region_type = "UI"
- bl_category = "DMX"
- bl_context = "objectmode"
- bl_options = {'DEFAULT_CLOSED'}
-
- def draw(self, context):
- layout = self.layout
- dmx = context.scene.dmx
-
- row = layout.row()
- row.prop(dmx, "zeroconf_enabled")
- row = layout.row()
-
- clients = context.window_manager.dmx.mvr_xchange
- all_clients = clients.mvr_xchange_clients
- if not all_clients:
- selected = None
- else:
- selected = clients.selected_mvr_client
-
- client = None
- for client in all_clients:
- if client.station_uuid ==selected:
- break
-
- row.prop(clients, "selected_mvr_client", text="")
- row.enabled = not dmx.mvrx_enabled
- row = layout.row()
- col = row.column()
- row.prop(dmx, "mvrx_enabled")
- col1 = row.column()
- col1.operator("dmx.mvr_refresh", text="", icon="FILE_REFRESH")
- col2 = row.column()
- if client:
- col2.operator("dmx.mvr_request", text="", icon="IMPORT").station_uuid = client.station_uuid
- col1.enabled = col2.enabled = dmx.mvrx_enabled
- #row.operator("dmx.mvr_test", text="Test", icon="CANCEL")
- row.enabled = len(all_clients) > 0
- if not client:
- return
- row = layout.row()
- row.label(text = f"{client.station_name}", icon = "LINKED" if dmx.mvrx_enabled else "UNLINKED")
- layout.template_list(
- "DMX_UL_MVR_Commit",
- "",
- client,
- "commits",
- clients,
- "selected_commit",
- rows=4,
- )
-
-class DMX_PT_DMX_Universes(Panel):
- bl_label = _("Universes")
- bl_idname = "DMX_PT_DMX_Universes"
- bl_parent_id = "DMX_PT_DMX"
- bl_space_type = "VIEW_3D"
- bl_region_type = "UI"
- bl_category = "DMX"
- bl_context = "objectmode"
- bl_options = {'DEFAULT_CLOSED'}
-
- def draw(self, context):
- layout = self.layout
- dmx = context.scene.dmx
-
- layout.prop(dmx, "universes_n", text=_("Universes"))
-
- layout.template_list("DMX_UL_Universe", "", dmx, "universes", dmx, "universe_list_i")
-
- if (dmx.universe_list_i < dmx.universes_n):
- universe = dmx.universes[dmx.universe_list_i]
- layout.prop(universe, "name")
- layout.prop(universe, "input")
-
- #layout.menu("dmx.menu.universe", text="...", icon="FILE_VOLUME")
-
-class DMX_PT_DMX_ArtNet(Panel):
- bl_label = _("Art-Net")
- bl_idname = "DMX_PT_DMX_ArtNet"
- bl_parent_id = "DMX_PT_DMX"
- bl_space_type = "VIEW_3D"
- bl_region_type = "UI"
- bl_category = "DMX"
- bl_context = "objectmode"
- bl_options = {'DEFAULT_CLOSED'}
-
- def draw(self, context):
- layout = self.layout
- dmx = context.scene.dmx
-
- artnet_universes = []
- for universe in dmx.universes:
- if universe.input == "ARTNET":
- artnet_universes.append(universe)
-
- row = layout.row()
- row.prop(dmx, "artnet_ipaddr", text=_("IPv4"))
- row.enabled = not dmx.artnet_enabled
-
- row = layout.row()
- row.prop(dmx, "artnet_enabled")
- row.enabled = len(artnet_universes)>0
- row = layout.row()
- row.label(text=_("Art-Net set for {} universe(s)").format(len(artnet_universes)))
- layout.label(text=_("Status") + ": " + layout.enum_item_name(dmx, 'artnet_status', dmx.artnet_status))
-
-class DMX_PT_DMX_sACN(Panel):
- bl_label = _("sACN")
- bl_idname = "DMX_PT_DMX_sACN"
- bl_parent_id = "DMX_PT_DMX"
- bl_space_type = "VIEW_3D"
- bl_region_type = "UI"
- bl_category = "DMX"
- bl_context = "objectmode"
- bl_options = {'DEFAULT_CLOSED'}
-
- def draw(self, context):
- layout = self.layout
- dmx = context.scene.dmx
-
- sacn_universes = []
- for index, universe in enumerate(dmx.universes):
- if index == 0: # invalid for sACN
- continue
- if universe.input == "sACN":
- sacn_universes.append(universe)
-
- row = layout.row()
- row.prop(dmx, "sacn_enabled")
- row.enabled = len(sacn_universes)>0
- row = layout.row()
- row.label(text=_("sACN set for {} universe(s)").format(len(sacn_universes)))
- layout.label(text=_("Status") + ": " + dmx.sacn_status)
-
-class DMX_UL_LiveDMX_items(UIList):
- def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
- layout.alignment = 'CENTER'
- layout.label(text=f"{index+1}: {DMX_Data._live_view_data[index]}")
-
- def invoke(self, context, event):
- pass
-
-class DMX_PT_DMX_LiveDMX(Panel):
- bl_label = _("Live DMX")
- bl_idname = "DMX_PT_DMX_LiveDMX"
- bl_parent_id = "DMX_PT_DMX"
- bl_space_type = "VIEW_3D"
- bl_region_type = "UI"
- bl_category = "DMX"
- bl_context = "objectmode"
- bl_options = {'DEFAULT_CLOSED'}
-
-
- def draw(self, context):
- layout = self.layout
- dmx = context.scene.dmx
- selected_universe = dmx.get_selected_live_dmx_universe()
- if selected_universe is None: # this should not happen
- raise ValueError("Missing selected universe, as if DMX base class is empty...")
-
- row = layout.row()
- row.prop(dmx, "selected_live_dmx", text=_("Source"))
- row = layout.row()
- col = row.column()
- col.label(text=f"{selected_universe.id}")
- col.ui_units_x = 2
- col = row.column()
- row.label(text=f"{selected_universe.name}")
- col = row.column()
- row.label(text=f"{selected_universe.input}")
- layout.template_list("DMX_UL_LiveDMX_items", "", dmx, "dmx_values", dmx, "dmx_value_index", type='GRID')
-
-# Panel #
-
-class DMX_PT_DMX(Panel):
- bl_label = _("Protocols")
- bl_idname = "DMX_PT_DMX"
- bl_space_type = "VIEW_3D"
- bl_region_type = "UI"
- bl_category = "DMX"
- bl_context = "objectmode"
- bl_options = {'DEFAULT_CLOSED'}
-
- def draw(self, context):
- layout = self.layout
- dmx = context.scene.dmx
diff --git a/panels/protocols/artnet.py b/panels/protocols/artnet.py
new file mode 100644
index 0000000..27c3aaf
--- /dev/null
+++ b/panels/protocols/artnet.py
@@ -0,0 +1,52 @@
+# Copyright Hugo Aboud
+#
+# This file is part of BlenderDMX.
+#
+# BlenderDMX is free software: you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your option)
+# any later version.
+#
+# BlenderDMX is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+# more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program. If not, see .
+
+from bpy.types import Panel
+
+from ...i18n import DMX_Lang
+_ = DMX_Lang._
+
+class DMX_PT_DMX_ArtNet(Panel):
+ bl_label = _("Art-Net")
+ bl_idname = "DMX_PT_DMX_ArtNet"
+ bl_parent_id = "DMX_PT_DMX"
+ bl_space_type = "VIEW_3D"
+ bl_region_type = "UI"
+ bl_category = "DMX"
+ bl_context = "objectmode"
+ bl_options = {'DEFAULT_CLOSED'}
+
+ def draw(self, context):
+ layout = self.layout
+ dmx = context.scene.dmx
+
+ artnet_universes = []
+ for universe in dmx.universes:
+ if universe.input == "ARTNET":
+ artnet_universes.append(universe)
+
+ row = layout.row()
+ row.prop(dmx, "artnet_ipaddr", text=_("IPv4"))
+ row.enabled = not dmx.artnet_enabled
+
+ row = layout.row()
+ row.prop(dmx, "artnet_enabled")
+ row.enabled = len(artnet_universes)>0
+ row = layout.row()
+ row.label(text=_("Art-Net set for {} universe(s)").format(len(artnet_universes)))
+ layout.label(text=_("Status") + ": " + layout.enum_item_name(dmx, 'artnet_status', dmx.artnet_status))
+
diff --git a/panels/protocols/live.py b/panels/protocols/live.py
new file mode 100644
index 0000000..77a84b5
--- /dev/null
+++ b/panels/protocols/live.py
@@ -0,0 +1,62 @@
+# Copyright vanous
+#
+# This file is part of BlenderDMX.
+#
+# BlenderDMX is free software: you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your option)
+# any later version.
+#
+# BlenderDMX is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+# more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program. If not, see .
+
+from bpy.types import Menu, Operator, Panel, UIList
+from ...data import DMX_Data
+
+from ...i18n import DMX_Lang
+_ = DMX_Lang._
+
+
+class DMX_UL_LiveDMX_items(UIList):
+ def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
+ layout.alignment = 'CENTER'
+ layout.label(text=f"{index+1}: {DMX_Data._live_view_data[index]}")
+
+ def invoke(self, context, event):
+ pass
+
+class DMX_PT_DMX_LiveDMX(Panel):
+ bl_label = _("Live DMX")
+ bl_idname = "DMX_PT_DMX_LiveDMX"
+ bl_parent_id = "DMX_PT_DMX"
+ bl_space_type = "VIEW_3D"
+ bl_region_type = "UI"
+ bl_category = "DMX"
+ bl_context = "objectmode"
+ bl_options = {'DEFAULT_CLOSED'}
+
+
+ def draw(self, context):
+ layout = self.layout
+ dmx = context.scene.dmx
+ selected_universe = dmx.get_selected_live_dmx_universe()
+ if selected_universe is None: # this should not happen
+ raise ValueError("Missing selected universe, as if DMX base class is empty...")
+
+ row = layout.row()
+ row.prop(dmx, "selected_live_dmx", text=_("Source"))
+ row = layout.row()
+ col = row.column()
+ col.label(text=f"{selected_universe.id}")
+ col.ui_units_x = 2
+ col = row.column()
+ row.label(text=f"{selected_universe.name}")
+ col = row.column()
+ row.label(text=f"{selected_universe.input}")
+ layout.template_list("DMX_UL_LiveDMX_items", "", dmx, "dmx_values", dmx, "dmx_value_index", type='GRID')
+
diff --git a/panels/protocols/mvr.py b/panels/protocols/mvr.py
new file mode 100644
index 0000000..9924e38
--- /dev/null
+++ b/panels/protocols/mvr.py
@@ -0,0 +1,187 @@
+# Copyright vanous
+#
+# This file is part of BlenderDMX.
+#
+# BlenderDMX is free software: you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your option)
+# any later version.
+#
+# BlenderDMX is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+# more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program. If not, see .
+
+import os
+from bpy.props import StringProperty
+from bpy.types import Operator, Panel, UIList
+from ...mvrx_protocol import DMX_MVR_X_Client
+from ...logging import DMX_Log
+import uuid as py_uuid
+from datetime import datetime
+
+from ...i18n import DMX_Lang
+_ = DMX_Lang._
+
+
+class DMX_OP_MVR_Refresh(Operator):
+ bl_label = _("Refresh")
+ bl_description = _("Refresh connection")
+ bl_idname = "dmx.mvr_refresh"
+ bl_options = {"UNDO"}
+
+ def execute(self, context):
+ DMX_MVR_X_Client.re_join()
+ return {"FINISHED"}
+
+
+class DMX_OP_MVR_Request(Operator):
+ bl_label = _("Request latest version")
+ bl_description = _("Sends the Request message")
+ bl_idname = "dmx.mvr_request"
+ bl_options = {"UNDO"}
+
+ station_uuid: StringProperty()
+
+ def execute(self, context):
+ uuid = str(py_uuid.uuid4())
+ mvr_commit = {"FileUUID": uuid, "StationUUID": self.station_uuid,
+ "Comment": datetime.now().strftime("%H:%M:%S %B %d, %Y"), "FileSize": 0}
+
+ last_commit = DMX_MVR_X_Client.create_self_request_commit(mvr_commit)
+ if last_commit:
+ DMX_MVR_X_Client.request_file(last_commit)
+
+ return {"FINISHED"}
+
+class DMX_OP_MVR_Import(Operator):
+ bl_label = _("Import")
+ bl_description = _("Import commit")
+ bl_idname = "dmx.mvr_import"
+ bl_options = {"UNDO"}
+
+ uuid: StringProperty()
+
+ def execute(self, context):
+ scene = context.scene
+ dmx = scene.dmx
+ ADDON_PATH = os.path.dirname(os.path.abspath(__file__))
+ clients = context.window_manager.dmx.mvr_xchange
+ all_clients = context.window_manager.dmx.mvr_xchange.mvr_xchange_clients
+ selected = clients.selected_mvr_client
+ for client in all_clients:
+ if client.station_uuid == selected:
+ break
+ for commit in client.commits:
+ if commit.commit_uuid == self.uuid:
+ DMX_Log.log.info(f"import {commit}")
+ path = os.path.join(ADDON_PATH, "..", "..", "assets", "mvrs", f"{commit.commit_uuid}.mvr")
+ DMX_Log.log.info(path)
+ dmx.addMVR(path)
+ break
+ return {"FINISHED"}
+
+class DMX_OP_MVR_Download(Operator):
+ bl_label = _("Download")
+ bl_description = _("Download commit")
+ bl_idname = "dmx.mvr_download"
+ bl_options = {"UNDO"}
+
+ uuid: StringProperty()
+
+ def execute(self, context):
+ DMX_Log.log.info("downloading")
+
+ clients = context.window_manager.dmx.mvr_xchange
+ all_clients = clients.mvr_xchange_clients
+ selected = clients.selected_mvr_client
+ for client in all_clients:
+ if client.station_uuid == selected:
+ break
+ DMX_Log.log.info(f"got client {client.station_name}")
+ for commit in client.commits:
+ DMX_Log.log.info(commit.commit_uuid)
+ if commit.commit_uuid == self.uuid:
+ DMX_Log.log.info(f"downloading {commit}")
+ DMX_MVR_X_Client.request_file(commit)
+ break
+
+ return {"FINISHED"}
+
+class DMX_UL_MVR_Commit(UIList):
+ def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
+ scene = context.scene
+ dmx = scene.dmx
+ icon = "GROUP_VERTEX"
+ #layout.context_pointer_set("mvr_xchange_clients", item)
+ col = layout.column()
+ col.label(text = f"{item.comment}", icon="CHECKBOX_HLT" if item.timestamp_saved else "CHECKBOX_DEHLT")
+ col = layout.column()
+ col.operator("dmx.mvr_download", text="", icon="IMPORT").uuid = item.commit_uuid
+ col.enabled = dmx.mvrx_enabled
+ col = layout.column()
+ col.operator("dmx.mvr_import", text="", icon="CHECKBOX_HLT").uuid = item.commit_uuid
+ col.enabled = item.timestamp_saved > 0
+
+
+
+class DMX_PT_DMX_MVR_X(Panel):
+ bl_label = _("MVR-xchange")
+ bl_idname = "DMX_PT_DMX_MVR_Xchange"
+ bl_parent_id = "DMX_PT_DMX"
+ bl_space_type = "VIEW_3D"
+ bl_region_type = "UI"
+ bl_category = "DMX"
+ bl_context = "objectmode"
+ bl_options = {'DEFAULT_CLOSED'}
+
+ def draw(self, context):
+ layout = self.layout
+ dmx = context.scene.dmx
+
+ row = layout.row()
+ row.prop(dmx, "zeroconf_enabled")
+ row = layout.row()
+
+ clients = context.window_manager.dmx.mvr_xchange
+ all_clients = clients.mvr_xchange_clients
+ if not all_clients:
+ selected = None
+ else:
+ selected = clients.selected_mvr_client
+
+ client = None
+ for client in all_clients:
+ if client.station_uuid ==selected:
+ break
+
+ row.prop(clients, "selected_mvr_client", text="")
+ row.enabled = not dmx.mvrx_enabled
+ row = layout.row()
+ col = row.column()
+ row.prop(dmx, "mvrx_enabled")
+ col1 = row.column()
+ col1.operator("dmx.mvr_refresh", text="", icon="FILE_REFRESH")
+ col2 = row.column()
+ if client:
+ col2.operator("dmx.mvr_request", text="", icon="IMPORT").station_uuid = client.station_uuid
+ col1.enabled = col2.enabled = dmx.mvrx_enabled
+ #row.operator("dmx.mvr_test", text="Test", icon="CANCEL")
+ row.enabled = len(all_clients) > 0
+ if not client:
+ return
+ row = layout.row()
+ row.label(text = f"{client.station_name}", icon = "LINKED" if dmx.mvrx_enabled else "UNLINKED")
+ layout.template_list(
+ "DMX_UL_MVR_Commit",
+ "",
+ client,
+ "commits",
+ clients,
+ "selected_commit",
+ rows=4,
+ )
+
diff --git a/panels/protocols/osc.py b/panels/protocols/osc.py
new file mode 100644
index 0000000..b30ffce
--- /dev/null
+++ b/panels/protocols/osc.py
@@ -0,0 +1,45 @@
+# Copyright vanous
+#
+# This file is part of BlenderDMX.
+#
+# BlenderDMX is free software: you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your option)
+# any later version.
+#
+# BlenderDMX is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+# more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program. If not, see .
+
+from bpy.types import Panel
+
+from ...i18n import DMX_Lang
+_ = DMX_Lang._
+
+class DMX_PT_DMX_OSC(Panel):
+ bl_label = _("OSC")
+ bl_idname = "DMX_PT_DMX_OSC"
+ bl_parent_id = "DMX_PT_DMX"
+ bl_space_type = "VIEW_3D"
+ bl_region_type = "UI"
+ bl_category = "DMX"
+ bl_context = "objectmode"
+ bl_options = {'DEFAULT_CLOSED'}
+
+ def draw(self, context):
+ layout = self.layout
+ dmx = context.scene.dmx
+
+ row = layout.row()
+ row.prop(dmx, "osc_enabled")
+ row = layout.row()
+ row.prop(dmx, "osc_target_address")
+ row.enabled = not dmx.osc_enabled
+ row = layout.row()
+ row.prop(dmx, "osc_target_port")
+ row.enabled = not dmx.osc_enabled
+
diff --git a/panels/protocols/protocols.py b/panels/protocols/protocols.py
new file mode 100644
index 0000000..cedb8e7
--- /dev/null
+++ b/panels/protocols/protocols.py
@@ -0,0 +1,35 @@
+# Copyright Hugo Aboud, vanous
+#
+# This file is part of BlenderDMX.
+#
+# BlenderDMX is free software: you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your option)
+# any later version.
+#
+# BlenderDMX is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+# more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program. If not, see .
+
+from bpy.types import Panel
+
+from ...i18n import DMX_Lang
+
+_ = DMX_Lang._
+
+
+class DMX_PT_DMX(Panel):
+ bl_label = _("Protocols")
+ bl_idname = "DMX_PT_DMX"
+ bl_space_type = "VIEW_3D"
+ bl_region_type = "UI"
+ bl_category = "DMX"
+ bl_context = "objectmode"
+ bl_options = {"DEFAULT_CLOSED"}
+
+ def draw(self, context):
+ pass
diff --git a/panels/protocols/psn.py b/panels/protocols/psn.py
new file mode 100644
index 0000000..e00f4a5
--- /dev/null
+++ b/panels/protocols/psn.py
@@ -0,0 +1,289 @@
+# Copyright vanous
+#
+# This file is part of BlenderDMX.
+#
+# BlenderDMX is free software: you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your option)
+# any later version.
+#
+# BlenderDMX is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+# more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program. If not, see .
+
+import os
+import bpy
+from bpy.props import StringProperty, IntProperty
+from bpy.types import Menu, Operator, Panel, UIList
+from ...mvrx_protocol import DMX_MVR_X_Client
+from ...data import DMX_Data
+from ...logging import DMX_Log
+import uuid as py_uuid
+from datetime import datetime
+from ...tracker import DMX_Tracker
+
+from ...i18n import DMX_Lang
+_ = DMX_Lang._
+
+class DMX_OP_DMX_Tracker_Add(Operator):
+ bl_label = _("Add Tracker")
+ bl_description = _("Adding a tracker")
+ bl_idname = "dmx.dmx_add_tracker"
+ bl_options = {"UNDO"}
+
+ def execute(self, context):
+ DMX_Tracker.add_tracker()
+ return {"FINISHED"}
+
+class DMX_OP_DMX_Tracker_Remove(Operator):
+ bl_label = _("Remove Tracker")
+ bl_description = _("Removing a tracker")
+ bl_idname = "dmx.dmx_remove_tracker"
+ bl_options = {"UNDO"}
+
+ uuid: StringProperty()
+
+ def execute(self, context):
+ DMX_Tracker.remove_tracker(self.uuid)
+ return {"FINISHED"}
+
+
+
+class DMX_UL_Tracker(UIList):
+ def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
+ ob = data
+ icon = "FILE_VOLUME"
+ if self.layout_type in {'DEFAULT', 'COMPACT'}:
+ col = layout.column()
+ col.ui_units_x = 3
+ col = layout.column()
+ col.prop(item, "name", text="", emboss=False)
+ col = layout.column()
+ col.prop(item, "enabled", text="")
+ col = layout.column()
+ col.operator("dmx.dmx_remove_tracker", text="", icon="TRASH").uuid = item.uuid
+ elif self.layout_type in {'GRID'}:
+ layout.alignment = 'CENTER'
+ layout.label(text=str(item.id), icon=icon)
+
+
+
+
+class DMX_OP_Link_Fixture_Tracker(Operator):
+ bl_label = _("Link Fixture to Tracker")
+ bl_description = _("Link fixture to a tracker")
+ bl_idname = "dmx.psn_tracker_follower_link"
+ bl_options = {"UNDO"}
+
+ fixture_uuid: StringProperty()
+ tracker_uuid: StringProperty()
+ tracker_index: IntProperty()
+
+ def execute(self, context):
+ scene = context.scene
+ dmx = scene.dmx
+ dmx = scene.dmx
+ layout = self.layout
+ target = None
+ for tracker in dmx.trackers:
+ if tracker.uuid == self.tracker_uuid:
+ for idx, obj in enumerate(tracker.collection.objects):
+ if idx == self.tracker_index:
+ target = obj
+
+ if target is not None:
+ for fixture in dmx.fixtures:
+ if fixture.uuid == self.fixture_uuid:
+ for obj in fixture.objects:
+ if obj.name == "Target":
+ constraint = obj.object.constraints.new(type='COPY_LOCATION')
+ constraint.target = target
+
+ return {"FINISHED"}
+
+class DMX_OP_Unlink_Fixture_Tracker(Operator):
+ bl_label = _("Unlink Fixture from Tracker")
+ bl_description = _("Unlink fixture from a tracker")
+ bl_idname = "dmx.psn_tracker_follower_unlink"
+ bl_options = {"UNDO"}
+
+ fixture_uuid: StringProperty()
+ tracker_uuid: StringProperty()
+
+ def execute(self, context):
+ scene = context.scene
+ dmx = scene.dmx
+ layout = self.layout
+ for fixture in dmx.fixtures:
+ if fixture.uuid == self.fixture_uuid:
+ for obj in fixture.objects:
+ if obj.name == "Target":
+ for constraint in obj.object.constraints:
+ if constraint.target != None:
+ if constraint.target.get("uuid", None) == self.tracker_uuid:
+ obj.object.constraints.remove(constraint)
+
+
+ return {"FINISHED"}
+
+
+
+class DMX_UL_Tracker_Followers(UIList):
+
+ tracker_uuid: StringProperty()
+ tracker_index: IntProperty()
+
+ def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
+ fixture = item
+ scene = context.scene
+ dmx = scene.dmx
+ icon = "FILE_VOLUME"
+ self.tracker_uuid = context.window_manager.dmx.selected_tracker
+ self.tracker_index = context.window_manager.dmx.selected_tracker_index
+
+ linked = None
+ for obj in fixture.objects:
+ if obj.name == "Target":
+ linked = False
+ for const in obj.object.constraints:
+ if const.target is not None:
+ if const.target.get("uuid", None) == self.tracker_uuid:
+ linked = True
+
+ if self.layout_type in {'DEFAULT', 'COMPACT'}:
+ col = layout.column()
+ col.ui_units_x = 3
+ col = layout.column()
+ col.prop(item, "name", text="", emboss=False)
+ col = layout.column()
+ if linked is None:
+ layout.label(text="", icon="CANCEL")
+ elif linked is True:
+ op = col.operator("dmx.psn_tracker_follower_unlink", icon="LINKED", text="")
+ op.fixture_uuid = fixture.uuid
+ op.tracker_uuid = self.tracker_uuid
+ else:
+ op = col.operator("dmx.psn_tracker_follower_link", icon="UNLINKED", text="")
+ op.fixture_uuid = fixture.uuid
+ op.tracker_uuid = self.tracker_uuid
+ op.tracker_index = self.tracker_index
+ elif self.layout_type in {'GRID'}:
+ layout.alignment = 'CENTER'
+ layout.label(text=item.name, icon=icon)
+
+
+class DMX_OT_Tracker_Followers(Operator):
+ bl_label = _("Tracker Followers")
+ bl_idname = "dmx.psn_tracker_followers"
+ bl_description = _("Link followers to a tracker")
+ bl_options = {'UNDO'}
+
+ tracker_uuid: StringProperty()
+ tracker_index: IntProperty()
+ fixture_i: IntProperty()
+
+ def draw(self, context):
+ layout = self.layout
+ scene = context.scene
+ dmx = scene.dmx
+ context.window_manager.dmx.selected_tracker = self.tracker_uuid
+ context.window_manager.dmx.selected_tracker_index = self.tracker_index
+ layout.template_list("DMX_UL_Tracker_Followers", "", dmx, "fixtures", self, "fixture_i")
+
+
+ def execute(self, context):
+ return {'FINISHED'}
+ return {'CANCELLED'}
+
+ def invoke(self, context, event):
+ #self.name = "Group " + str(len(context.scene.dmx.groups)+1)
+ wm = context.window_manager
+ return wm.invoke_props_dialog(self)
+
+
+class DMX_PT_DMX_Trackers(Panel):
+ bl_label = _("PSN")
+ bl_idname = "DMX_PT_DMX_Trackers"
+ bl_parent_id = "DMX_PT_DMX"
+ bl_space_type = "VIEW_3D"
+ bl_region_type = "UI"
+ bl_category = "DMX"
+ bl_context = "objectmode"
+ bl_options = {'DEFAULT_CLOSED'}
+
+ def draw(self, context):
+ layout = self.layout
+ dmx = context.scene.dmx
+ layout.operator("dmx.dmx_add_tracker", text="Add tracker", icon="PLUS")
+ layout.template_list("DMX_UL_Tracker", "", dmx, "trackers", dmx, "trackers_i")
+
+ if (dmx.trackers_i < len(dmx.trackers)):
+ tracker = dmx.trackers[dmx.trackers_i]
+ layout.prop(tracker, "name")
+ layout.prop(tracker, "ip_address")
+ layout.prop(tracker, "ip_port")
+ layout.prop(tracker, "enabled")
+ add_trackers = True
+ for idx, obj in enumerate(tracker.collection.objects):
+ row = layout.row()
+ col = row.column()
+ op=col.operator("dmx.psn_tracker_followers", text=_(f"Link Followers to {obj.name}"), icon="LINKED")
+ op.tracker_uuid = tracker.uuid
+ op.tracker_index = idx
+ if len(tracker.collection.objects) > 1:
+ col = row.column()
+ op = col.operator("dmx.psn_remove_tracker_followers_target", text="", icon="TRASH")
+ op.object_name = obj.name
+ op.tracker_uuid = tracker.uuid
+
+ if idx >= 9: # we support max 10 trackers, edit tracker.py and psn.py if more is needed
+ add_trackers = False
+ row = layout.row()
+ op = row.operator("dmx.psn_add_tracker_followers_target", text=_("Add Tracking Target"), icon="PLUS")
+ op.tracker_uuid = tracker.uuid
+ row.enabled = add_trackers
+
+
+
+class DMX_OT_Tracker_Followers_Remove_Target(Operator):
+ bl_label = _("Remove Followers Target")
+ bl_idname = "dmx.psn_remove_tracker_followers_target"
+ bl_description = _("Remove target")
+ bl_options = {'UNDO'}
+
+ object_name: StringProperty()
+ tracker_uuid: StringProperty()
+
+ def execute(self, context):
+ dmx = context.scene.dmx
+ for tracker in dmx.trackers:
+ if tracker.uuid == self.tracker_uuid:
+ if self.object_name in tracker.collection.objects:
+ rem_obj = tracker.collection.objects[self.object_name]
+ bpy.data.objects.remove(rem_obj)
+
+ return {"FINISHED"}
+
+class DMX_OT_Tracker_Followers_Add_Target(Operator):
+ bl_label = _("Add Followers Target")
+ bl_idname = "dmx.psn_add_tracker_followers_target"
+ bl_description = _("Add target")
+ bl_options = {'UNDO'}
+
+ tracker_uuid: StringProperty()
+
+ def execute(self, context):
+ dmx = context.scene.dmx
+ for tracker in dmx.trackers:
+ if tracker.uuid == self.tracker_uuid:
+ for obj in tracker.collection.objects:
+ duplicate_obj = obj.copy()
+ tracker.collection.objects.link(duplicate_obj)
+ break
+
+ return {"FINISHED"}
+
diff --git a/panels/protocols/sacn.py b/panels/protocols/sacn.py
new file mode 100644
index 0000000..da59063
--- /dev/null
+++ b/panels/protocols/sacn.py
@@ -0,0 +1,50 @@
+# Copyright vanous
+#
+# This file is part of BlenderDMX.
+#
+# BlenderDMX is free software: you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your option)
+# any later version.
+#
+# BlenderDMX is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+# more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program. If not, see .
+
+from bpy.types import Panel
+
+from ...i18n import DMX_Lang
+_ = DMX_Lang._
+
+class DMX_PT_DMX_sACN(Panel):
+ bl_label = _("sACN")
+ bl_idname = "DMX_PT_DMX_sACN"
+ bl_parent_id = "DMX_PT_DMX"
+ bl_space_type = "VIEW_3D"
+ bl_region_type = "UI"
+ bl_category = "DMX"
+ bl_context = "objectmode"
+ bl_options = {'DEFAULT_CLOSED'}
+
+ def draw(self, context):
+ layout = self.layout
+ dmx = context.scene.dmx
+
+ sacn_universes = []
+ for index, universe in enumerate(dmx.universes):
+ if index == 0: # invalid for sACN
+ continue
+ if universe.input == "sACN":
+ sacn_universes.append(universe)
+
+ row = layout.row()
+ row.prop(dmx, "sacn_enabled")
+ row.enabled = len(sacn_universes)>0
+ row = layout.row()
+ row.label(text=_("sACN set for {} universe(s)").format(len(sacn_universes)))
+ layout.label(text=_("Status") + ": " + dmx.sacn_status)
+
diff --git a/panels/protocols/universes.py b/panels/protocols/universes.py
new file mode 100644
index 0000000..7c18b68
--- /dev/null
+++ b/panels/protocols/universes.py
@@ -0,0 +1,77 @@
+# Copyright Hugo Aboud
+#
+# This file is part of BlenderDMX.
+#
+# BlenderDMX is free software: you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your option)
+# any later version.
+#
+# BlenderDMX is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+# more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program. If not, see .
+
+from bpy.types import Menu, Panel, UIList
+
+from ...i18n import DMX_Lang
+
+_ = DMX_Lang._
+
+
+class DMX_MT_Universe(Menu):
+ bl_label = _("DMX > Universe Menu")
+ bl_idname = "DMX_MT_Universe"
+
+ def draw(self, context):
+ layout = self.layout
+ scene = context.scene
+ dmx = scene.dmx
+
+ # "Add"
+ row = layout.row()
+ # row.operator("dmx.add_universe", text="Add", icon="ADD")
+
+
+class DMX_UL_Universe(UIList):
+ def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
+ ob = data
+ icon = "FILE_VOLUME"
+ if self.layout_type in {"DEFAULT", "COMPACT"}:
+ col = layout.column()
+ col.label(text=f"{item.id}", icon=icon)
+ col.ui_units_x = 3
+ col = layout.column()
+ col.prop(item, "name", text="", emboss=False)
+ col = layout.column()
+ col.label(text=item.input)
+ elif self.layout_type in {"GRID"}:
+ layout.alignment = "CENTER"
+ layout.label(text=str(item.id), icon=icon)
+
+
+class DMX_PT_DMX_Universes(Panel):
+ bl_label = _("Universes")
+ bl_idname = "DMX_PT_DMX_Universes"
+ bl_parent_id = "DMX_PT_DMX"
+ bl_space_type = "VIEW_3D"
+ bl_region_type = "UI"
+ bl_category = "DMX"
+ bl_context = "objectmode"
+ bl_options = {"DEFAULT_CLOSED"}
+
+ def draw(self, context):
+ layout = self.layout
+ dmx = context.scene.dmx
+
+ layout.prop(dmx, "universes_n", text=_("Universes"))
+
+ layout.template_list("DMX_UL_Universe", "", dmx, "universes", dmx, "universe_list_i")
+
+ if dmx.universe_list_i < dmx.universes_n:
+ universe = dmx.universes[dmx.universe_list_i]
+ layout.prop(universe, "name")
+ layout.prop(universe, "input")