Skip to content

Commit

Permalink
Initial MVR-xchange support
Browse files Browse the repository at this point in the history
  • Loading branch information
vanous committed Dec 21, 2023
1 parent 36956f5 commit cc92c56
Show file tree
Hide file tree
Showing 44 changed files with 8,324 additions and 9 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ __pycache__
fixtures/__pycache__
panels/__pycache__
lib/
assets/mvrs/*
assets/profiles/*
assets/models/*/*
!assets/profiles/BlenderDMX*

*.zip
1 change: 1 addition & 0 deletions DEPENDENCIES.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ is a list of included libraries.
* 3DS Importer: https://projects.blender.org/blender/blender-addons-contrib.git
* ifaddr: https://github.com/pydron/ifaddr
* oscpy: https://github.com/kivy/oscpy
* zeroconf: https://github.com/python-zeroconf/python-zeroconf
148 changes: 144 additions & 4 deletions __init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
import json
import uuid as py_uuid
import re
from datetime import datetime
import pathlib

from dmx.pymvr import GeneralSceneDescription
from dmx.mvr import extract_mvr_textures, process_mvr_child_list
Expand All @@ -41,20 +43,24 @@
from dmx.panels.programmer import *
import dmx.panels.profiles as Profiles

from dmx.preferences import DMX_Preferences
from dmx.preferences import DMX_Preferences, DMX_Regenrate_UUID
from dmx.group import FixtureGroup
from dmx.osc_utils import DMX_OSC_Templates
from dmx.osc import DMX_OSC
from dmx.mdns import DMX_Zeroconf

from dmx.util import rgb_to_cmy, xyY2rgbaa, ShowMessageBox
from dmx.mvr_objects import DMX_MVR_Object
from dmx.mvr_xchange import *
from dmx.mvrx_protocol import DMX_MVR_X_Protocol

from bpy.props import (BoolProperty,
StringProperty,
IntProperty,
FloatProperty,
FloatVectorProperty,
PointerProperty,
EnumProperty,
CollectionProperty)

from bpy.types import (PropertyGroup,
Expand Down Expand Up @@ -85,6 +91,12 @@ class DMX_TempData(PropertyGroup):
description="When selecting a group, add to existing selection",
default = True)


mvr_xchange: PointerProperty(
name = "MVR-xchange",
type=DMX_MVR_Xchange
)

class DMX(PropertyGroup):

# Base classes to be registered
Expand All @@ -101,6 +113,12 @@ class DMX(PropertyGroup):
DMX_Universe,
DMX_Value,
DMX_PT_Setup,
DMX_OP_MVR_Download,
DMX_OP_MVR_Import,
DMX_MVR_Xchange_Commit,
DMX_MVR_Xchange_Client,
DMX_MVR_Xchange,
DMX_Regenrate_UUID,
DMX_Preferences)

# Classes to be registered
Expand Down Expand Up @@ -153,6 +171,9 @@ class DMX(PropertyGroup):
DMX_OT_Programmer_Set_Ignore_Movement,
DMX_OT_Programmer_Unset_Ignore_Movement,
DMX_PT_DMX_OSC,
DMX_PT_DMX_MVR_X,
DMX_UL_MVR_Commit,
DMX_OP_MVR_Test,
DMX_OT_Fixture_ForceRemove,
DMX_OT_Fixture_SelectNext,
DMX_OT_Fixture_SelectPrevious,
Expand Down Expand Up @@ -580,7 +601,7 @@ def onSelectGeometries(self, context):
def onLoggingLevel(self, context):
DMX_Log.log.setLevel(self.logging_level)

logging_level: bpy.props.EnumProperty(
logging_level: EnumProperty(
name= "Logging Level",
description= "logging level",
default = "ERROR",
Expand All @@ -604,7 +625,7 @@ def onVolumePreview(self, context):
# default = False,
# update = onVolumePreview)

volume_preview: bpy.props.EnumProperty(
volume_preview: EnumProperty(
name= "Simple beam",
description= "Display 'fake' beam cone",
default = "NONE",
Expand Down Expand Up @@ -699,6 +720,34 @@ def onUniverseN(self, context):
description="The network card/interface to listen for ArtNet DMX data",
items = DMX_Network.cards
)
#zeroconf - mvr-xchange

def onZeroconfEnable(self, context):
if self.zeroconf_enabled:
clients = bpy.context.window_manager.dmx.mvr_xchange.mvr_xchange_clients
clients.clear()
DMX_Zeroconf.enable()
else:
DMX_Zeroconf.disable()

def onMVR_xchange_enable(self, context):
if self.mvrx_enabled:
clients = context.window_manager.dmx.mvr_xchange
all_clients = context.window_manager.dmx.mvr_xchange.mvr_xchange_clients
selected = clients.selected_mvr_client
selected_client = None
for selected_client in all_clients:
if selected_client.station_uuid == selected:
break
if not selected_client:
return
print(selected_client.ip_address, selected_client.station_name)
DMX_MVR_X_Protocol.enable(selected_client)
else:
DMX_MVR_X_Protocol.disable()




# OSC functionality

Expand Down Expand Up @@ -760,6 +809,20 @@ def onArtNetEnable(self, context):
description="Port number of the host where you want to send the OSC signal",
default=42000
)

zeroconf_enabled : BoolProperty(
name = "Enable MVR-xchange discovery",
description="Enables MVR-xchange discovery",
default = False,
update = onZeroconfEnable
)

mvrx_enabled : BoolProperty(
name = "Enable MVR-xchange connection",
description="Connects to MVR-xchange client",
default = False,
update = onMVR_xchange_enable
)
# # DMX > ArtNet > Status

artnet_status : EnumProperty(
Expand Down Expand Up @@ -947,7 +1010,7 @@ def syncProgrammer(self):
self.programmer_tilt = data['Tilt']/127.0-1


fixtures_sorting_order: bpy.props.EnumProperty(
fixtures_sorting_order: EnumProperty(
name= "Sort by",
description= "Fixture sorting order",
default = "ADDRESS",
Expand Down Expand Up @@ -1095,6 +1158,77 @@ def ensureUniverseExists(self, universe):
self.addUniverse()
self.universes_n = len(self.universes)

def createMVR_Client(self, station_name, station_uuid, ip_address, port):
clients = bpy.context.window_manager.dmx.mvr_xchange.mvr_xchange_clients
for client in clients:
if client.station_uuid == station_uuid:
return # client already in the list

client = clients.add()
client.station_name = station_name
client.station_uuid = station_uuid
now = int(datetime.now().timestamp())
client.last_seen = now
client.ip_address = ip_address
client.port = port

def removeMVR_Client(self, station_name, station_uuid, ip_addres, port):
clients = bpy.context.window_manager.dmx.mvr_xchange.mvr_xchange_clients
for client in clients:
if client.station_uuid == station_uuid:
clients.remove(client)
break

def updateMVR_Client(self, station_name, station_uuid, ip_address, port):
clients = bpy.context.window_manager.dmx.mvr_xchange.mvr_xchange_clients
updated = False
for client in clients:
if client.station_uuid == station_uuid:
client.station_name = station_name
now = int(datetime.now().timestamp())
client.last_seen = now
client.ip_address = ip_address
client.port = port
updated = True
break
if not updated:
self.createMVR_Client(station_name, station_uuid, ip_address, port)

def createMVR_Commits(self, commits, station_uuid):
clients = bpy.context.window_manager.dmx.mvr_xchange.mvr_xchange_clients
for client in clients:
if client.station_uuid == station_uuid:
client.commits.clear()

for commit in commits:

if "FileName" in commit:
filename = commit["FileName"]
else:
filename = commit["Comment"]
if not len(filename):
filename = commit["FileUUID"]

now = int(datetime.now().timestamp())
client.last_seen = now
new_commit = client.commits.add()
new_commit.station_uuid = station_uuid
new_commit.comment = commit["Comment"]
new_commit.commit_uuid = commit["FileUUID"]
new_commit.file_size = commit["FileSize"]
new_commit.file_name = filename
new_commit.timestamp = now

def fetched_mvr_downloaded_file(self, commit):
clients = bpy.context.window_manager.dmx.mvr_xchange.mvr_xchange_clients
now = int(datetime.now().timestamp())
for client in clients:
if client.station_uuid == commit.station_uuid:
for c_commit in client.commits:
if c_commit.commit_uuid == commit.commit_uuid:
c_commit.timestamp_saved = now


# # Groups

def createGroup(self, name):
Expand Down Expand Up @@ -1174,6 +1308,8 @@ def render(self):
fixture.render()




# Handlers #


Expand Down Expand Up @@ -1224,6 +1360,8 @@ def onLoadFile(scene):
DMX_ArtNet.disable()
DMX_sACN.disable()
DMX_OSC.disable()
DMX_MVR_X_Protocol.disable()
DMX_Zeroconf.disable()

@bpy.app.handlers.persistent
def onUndo(scene):
Expand Down Expand Up @@ -1305,6 +1443,8 @@ def unregister():
DMX_ArtNet.disable()
DMX_sACN.disable()
DMX_OSC.disable()
DMX_MVR_X_Protocol.disable()
DMX_Zeroconf.disable()

try:
for cls in Profiles.classes:
Expand Down
72 changes: 72 additions & 0 deletions mdns.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
from types import DynamicClassAttribute
import bpy
from dmx.zeroconf import (
IPVersion,
ServiceBrowser,
ServiceStateChange,
Zeroconf,
)
from dmx.logging import DMX_Log
from typing import cast


class DMX_Zeroconf:
_instance = None

def __init__(self):
super(DMX_Zeroconf, self).__init__()
self.data = None
self.zeroconf = None
self.browser = None
self._dmx = bpy.context.scene.dmx

def callback(zeroconf: Zeroconf, service_type: str, name: str, state_change: ServiceStateChange) -> None:
#print(f"Service {name} of type {service_type} state changed: {state_change}")

info = zeroconf.get_service_info(service_type, name)
station_name = ""
station_uuid = ""
ip_address = ""
port = 0

if info:
addresses = ["%s:%d" % (addr, cast(int, info.port)) for addr in info.parsed_scoped_addresses()]
for address in addresses:
if "::" in address:
continue
ip_a, ip_port = address.split(":")
ip_address = ip_a
port = ip_port

if info.properties:
if b"StationName" in info.properties:
station_name = info.properties[b"StationName"].decode("utf-8")
if b"StationUUID" in info.properties:
station_uuid = info.properties[b"StationUUID"].decode("utf-")

if state_change is ServiceStateChange.Added:
DMX_Zeroconf._instance._dmx.createMVR_Client(station_name, station_uuid, ip_address, int(port))
elif state_change is ServiceStateChange.Updated:
DMX_Zeroconf._instance._dmx.updateMVR_Client(station_name, station_uuid, ip_address, int(port))
else: # removed
DMX_Zeroconf._instance._dmx.removeMVR_Client(station_name, station_uuid, ip_address, int(port))

@staticmethod
def enable():
if DMX_Zeroconf._instance:
return
DMX_Zeroconf._instance = DMX_Zeroconf()

services = ["_mvrxchange._tcp.local."]
DMX_Zeroconf._instance.zeroconf = Zeroconf(ip_version=IPVersion.V4Only)
DMX_Zeroconf._instance.browser = ServiceBrowser(DMX_Zeroconf._instance.zeroconf, services, handlers=[DMX_Zeroconf.callback])
DMX_Log.log.info("Enabling Zeroconf")
print("starting mvrx discovery")

@staticmethod
def disable():
if DMX_Zeroconf._instance:
DMX_Zeroconf._instance.zeroconf.close()
DMX_Zeroconf._instance = None
print("closing mvrx discovery")
DMX_Log.log.info("Disabling Zeroconf")
46 changes: 46 additions & 0 deletions mvr_xchange.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import bpy
from bpy.props import BoolProperty, CollectionProperty, EnumProperty, IntProperty, StringProperty
from bpy.types import PropertyGroup


class DMX_MVR_Xchange_Commit(PropertyGroup):
commit_uuid: StringProperty(name="File UUID")
comment: StringProperty(name="Comment")
file_name: StringProperty(name="File Name")
station_uuid: StringProperty(name="Station UUID")
file_size: IntProperty(name="File Size")
timestamp: IntProperty(name="Time of info")
timestamp_saved: IntProperty(name="Time of saving")
subscribed: BoolProperty(name="Subscribed to")


class DMX_MVR_Xchange_Client(PropertyGroup):
ip_address: StringProperty(name="IP Address")
port: IntProperty(name="Port")
subscribed: BoolProperty(name="Subscribed to")
last_seen: IntProperty(name="Last Seen Time")
station_name: StringProperty(name="Station Name")
station_uuid: StringProperty(name="Station UUID")
provider: StringProperty(name="Provider")
commits: CollectionProperty(name="Commits", type=DMX_MVR_Xchange_Commit)

def get_clients(self, context):
#print(self, context)
clients = bpy.context.window_manager.dmx.mvr_xchange.mvr_xchange_clients
data = []
for client in clients:
data.append((client.station_uuid, client.station_name, client.station_uuid))
return data

class DMX_MVR_Xchange(PropertyGroup):
selected_commit: IntProperty(default=0)
mvr_xchange_clients: CollectionProperty(
name = "MVR-xchange Clients",
type=DMX_MVR_Xchange_Client
)

selected_mvr_client: EnumProperty(
name = "Client",
description="",
items = DMX_MVR_Xchange_Client.get_clients
)
Loading

0 comments on commit cc92c56

Please sign in to comment.