diff --git a/.gitignore b/.gitignore index d88893d..a8263a8 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,5 @@ __pycache__ pymvr.egg-info/ devel/ *whl +*.mvr +uv.lock diff --git a/CHANGELOG.md b/CHANGELOG.md index 8428fe0..9f9a336 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ ### Changelog +### 1.0.0-dev0 + +* Breaking changes! +* Big refactor of structure - ensure that XML structure is followed in code +* Implemented all MVR fields +* Big refactor for export + #### 0.5.0 * Add classing to export diff --git a/README.md b/README.md index 1247961..268f860 100644 --- a/README.md +++ b/README.md @@ -34,31 +34,58 @@ python -m pip install https://codeload.github.com/open-stage/python-mvr/zip/refs ```python import pymvr -mvr_scene = pymvr.GeneralSceneDescription("mvr_file.mvr") +mvr_file = pymvr.GeneralSceneDescription("mvr_file.mvr") -for layer_index, layer in enumerate(mvr_scene.layers): +for layer_index, layer in enumerate(mvr_file.scene.layers): ... #process data ``` ### Writing ```python -fixtures_list = [] -mvr = pymvr.GeneralSceneDescriptionWriter() -pymvr.UserData().to_xml(parent=mvr.xml_root) -scene = pymvr.SceneElement().to_xml(parent=mvr.xml_root) -layers = pymvr.LayersElement().to_xml(parent=scene) -layer = pymvr.Layer(name="Test layer").to_xml(parent=layers) -child_list = pymvr.ChildList().to_xml(parent=layer) - -fixture = pymvr.Fixture(name="Test Fixture") # not really a valid fixture -child_list.append(fixture.to_xml()) -fixtures_list.append((fixture.gdtf_spec, fixture.gdtf_spec)) - -pymvr.AUXData().to_xml(parent=scene) - -mvr.files_list = list(set(fixtures_list)) -mvr.write_mvr("example.mvr") +import pymvr +from pathlib import Path + +# 1. Create a writer instance +mvr_writer = pymvr.GeneralSceneDescriptionWriter() + +# 2. Build the MVR object tree +# Create a scene object +scene_obj = pymvr.Scene() + +# Create layers and add them to the scene +layers = pymvr.Layers() +scene_obj.layers = layers + +# Create a layer and add it to the layers +layer = pymvr.Layer(name="Test layer") +layers.append(layer) + +# Create a child list for the layer +child_list = pymvr.ChildList() +layer.child_list = child_list + +# Create a fixture and add it to the child list +# Note: A valid fixture would require more attributes +fixture = pymvr.Fixture(name="Test Fixture") +child_list.fixtures.append(fixture) + +# 3. Serialize the scene object into the writer's XML root +scene_obj.to_xml(parent=mvr_writer.xml_root) + +# 4. Add any necessary files (like GDTF) to the MVR archive +# (This example fixture doesn't have a GDTF file, so this list will be empty) +files_to_pack = [] +if fixture.gdtf_spec: + # The list should contain tuples of (source_path, archive_name) + files_to_pack.append((fixture.gdtf_spec, fixture.gdtf_spec)) +mvr_writer.files_list = list(set(files_to_pack)) + +# 5. Write the MVR file +output_path = Path("example.mvr") +mvr_writer.write_mvr(output_path) + +print(f"MVR file written to {output_path.resolve()}") ``` See [BlenderDMX](https://github.com/open-stage/blender-dmx) and @@ -67,41 +94,7 @@ reference implementation. ## Status -- Reading: - - - Address - - Alignment - - AUXData - - ChildList - - Class - - Connection - - CustomCommand - - Data - - Fixture - - FocusPoint - - Geometries - - Geometry3D - - Gobo - - GroupObject - - Layer - - Mapping - - Overwrite - - Position - - Projector - - Protocol - - SceneObject - - Sources - - Support - - Symbol - - Symdef - - Truss - - UserData - - VideoScreen - -- Writing: - - Fixture - - Focus point - - creating MVR zip file +- Reading and Writing of all aspects of MVR should be covered. ## Development diff --git a/conftest.py b/conftest.py index f89dab7..3b47c0a 100644 --- a/conftest.py +++ b/conftest.py @@ -15,7 +15,7 @@ def mvr_scene(request): yield mvr_scene -@pytest.fixture(scope="session") +@pytest.fixture(scope="function") def pymvr_module(): yield pymvr diff --git a/pymvr/__init__.py b/pymvr/__init__.py index 7b8952d..114a6f0 100644 --- a/pymvr/__init__.py +++ b/pymvr/__init__.py @@ -1,12 +1,13 @@ -from typing import List, Union, Optional +from typing import List, Union from xml.etree import ElementTree from xml.etree.ElementTree import Element import zipfile import sys import uuid as py_uuid from .value import Matrix, Color # type: ignore +from enum import Enum -__version__ = "0.5.1" +__version__ = "1.0.0-dev0" def _find_root(pkg: "zipfile.ZipFile") -> "ElementTree.Element": @@ -26,8 +27,6 @@ def __init__(self, path=None): self._package = zipfile.ZipFile(path, "r") if self._package is not None: self._root = _find_root(self._package) - self._user_data = self._root.find("UserData") - self._scene = self._root.find("Scene") if self._root is not None: self._read_xml() @@ -37,21 +36,14 @@ def _read_xml(self): self.provider: str = self._root.get("provider", "") self.providerVersion: str = self._root.get("providerVersion", "") - layers_collect = self._scene.find("Layers") - if layers_collect is not None: - self.layers: List["Layer"] = [Layer(xml_node=i) for i in layers_collect.findall("Layer")] - else: - self.layers = [] - - aux_data_collect = self._scene.find("AUXData") + scene = self._root.find("Scene") + if scene is not None: + self.scene = Scene(xml_node=scene) - if aux_data_collect is not None: - self.aux_data = AUXData(xml_node=aux_data_collect) - else: - self.aux_data = None + user_data = self._root.find("UserData") - if self._user_data is not None: - self.user_data: List["Data"] = [Data(xml_node=i) for i in self._user_data.findall("Data")] + if user_data is not None: + self.user_data = UserData(xml_node=user_data) class GeneralSceneDescriptionWriter: @@ -81,32 +73,297 @@ def write_mvr(self, path=None): for file_path, file_name in self.files_list: try: z.write(file_path, arcname=file_name) - except Exception as e: + except Exception: print(f"File does not exist {file_path}") -class SceneElement: +class BaseNode: + def __init__(self, xml_node: "Element" = None): + if xml_node is not None: + self._read_xml(xml_node) + + def _read_xml(self, xml_node: "Element"): + pass + + +class ContainerNode(BaseNode): + def __init__(self, children=None, xml_node: "Element" = None, *args, **kwargs): + if children is None: + children = [] + self.children = children + super().__init__(xml_node, *args, **kwargs) + + def __iter__(self): + return iter(self.children) + + def __getitem__(self, item): + return self.children[item] + + def append(self, child): + self.children.append(child) + + def extend(self, children_list): + self.children.extend(children_list) + + def insert(self, index, child): + self.children.insert(index, child) + + def remove(self, child): + self.children.remove(child) + + def pop(self, index=-1): + return self.children.pop(index) + + def clear(self): + self.children.clear() + + def __len__(self): + return len(self.children) + def to_xml(self, parent: Element): - return ElementTree.SubElement(parent, "Scene") + element = ElementTree.SubElement(parent, type(self).__name__) + for child in self.children: + element.append(child.to_xml()) + return element + + +class Protocols(ContainerNode): + def _read_xml(self, xml_node: "Element"): + self.children = [Protocol(xml_node=i) for i in xml_node.findall("Protocol")] + +class Alignments(ContainerNode): + def _read_xml(self, xml_node: "Element"): + self.children = [Alignment(xml_node=i) for i in xml_node.findall("Alignment")] + + +class CustomCommands(ContainerNode): + def _read_xml(self, xml_node: "Element"): + self.children = [CustomCommand(xml_node=i) for i in xml_node.findall("CustomCommand")] + + +class Overwrites(ContainerNode): + def _read_xml(self, xml_node: "Element"): + self.children = [Overwrite(xml_node=i) for i in xml_node.findall("Overwrite")] + + +class Connections(ContainerNode): + def _read_xml(self, xml_node: "Element"): + self.children = [Connection(xml_node=i) for i in xml_node.findall("Connection")] + + +class Mappings(ContainerNode): + def _read_xml(self, xml_node: "Element"): + self.children = [Mapping(xml_node=i) for i in xml_node.findall("Mapping")] + + +class Scene(BaseNode): + def __init__( + self, + layers: "Layers" = None, + aux_data: "AUXData" = None, + xml_node: "Element" = None, + *args, + **kwargs, + ): + self.layers = layers + self.aux_data = aux_data + super().__init__(xml_node, *args, **kwargs) + + def _read_xml(self, xml_node: "Element"): + self.layers = Layers(xml_node=xml_node.find("Layers")) + + aux_data_collect = xml_node.find("AUXData") + + if aux_data_collect is not None: + self.aux_data = AUXData(xml_node=aux_data_collect) + else: + self.aux_data = None -class LayersElement: def to_xml(self, parent: Element): - return ElementTree.SubElement(parent, "Layers") + element = ElementTree.SubElement(parent, "Scene") + if self.layers: + self.layers.to_xml(element) + if self.aux_data: + self.aux_data.to_xml(element) + return element + +class Layers(BaseNode): + def __init__( + self, + layers: List["Layer"] = [], + xml_node: "Element" = None, + *args, + **kwargs, + ): + self.layers = layers + super().__init__(xml_node, *args, **kwargs) + + def _read_xml(self, xml_node: "Element"): + self.layers = [Layer(xml_node=i) for i in xml_node.findall("Layer")] -class UserData: def to_xml(self, parent: Element): - return ElementTree.SubElement(parent, "UserData") + element = ElementTree.SubElement(parent, "Layers") + for layer in self.layers: + element.append(layer.to_xml()) + return element + def __iter__(self): + return iter(self.layers) + + def __getitem__(self, item): + return self.layers[item] + + def append(self, layer: "Layer"): + self.layers.append(layer) + + def extend(self, layers: List["Layer"]): + self.layers.extend(layers) + + def insert(self, index: int, layer: "Layer"): + self.layers.insert(index, layer) + + def remove(self, layer: "Layer"): + self.layers.remove(layer) + + def pop(self, index: int = -1): + return self.layers.pop(index) + + def clear(self): + self.layers.clear() + + def __len__(self): + return len(self.layers) -class BaseNode: - def __init__(self, xml_node: "Element" = None): - if xml_node is not None: - self._read_xml(xml_node) + +class UserData(BaseNode): + def __init__( + self, + data: List["Data"] = [], + xml_node: "Element" = None, + *args, + **kwargs, + ): + self.data = data + super().__init__(xml_node, *args, **kwargs) def _read_xml(self, xml_node: "Element"): - pass + self.data = [Data(xml_node=i) for i in xml_node.findall("Data")] + + def to_xml(self, parent: Element): + element = ElementTree.SubElement(parent, type(self).__name__) + for _data in self.data: + element.append(_data.to_xml()) + return element + + +class ScaleHandelingEnum(Enum): + SCALE_KEEP_RATIO = "ScaleKeepRatio" + SCALE_IGNORE_RATIO = "ScaleIgnoreRatio" + KEEP_SIZE_CENTER = "KeepSizeCenter" + + +class ScaleHandeling(BaseNode): + def __init__( + self, + value: ScaleHandelingEnum = ScaleHandelingEnum.SCALE_KEEP_RATIO, + xml_node: "Element" = None, + *args, + **kwargs, + ): + self.value = value + super().__init__(xml_node, *args, **kwargs) + + def _read_xml(self, xml_node: "Element"): + if xml_node.text: + try: + self.value = ScaleHandelingEnum(xml_node.text) + except ValueError: + self.value = ScaleHandelingEnum.SCALE_KEEP_RATIO + else: + self.value = ScaleHandelingEnum.SCALE_KEEP_RATIO + + def to_xml(self, parent: Element): + element = ElementTree.SubElement(parent, "ScaleHandeling") + element.text = self.value.value + return element + + +class Network(BaseNode): + def __init__( + self, + geometry: str = "", + ipv4: Union[str, None] = None, + subnetmask: Union[str, None] = None, + ipv6: Union[str, None] = None, + dhcp: bool = False, + hostname: Union[str, None] = None, + xml_node: "Element" = None, + *args, + **kwargs, + ): + self.geometry = geometry + self.ipv4 = ipv4 + self.subnetmask = subnetmask + self.ipv6 = ipv6 + self.dhcp = dhcp + self.hostname = hostname + super().__init__(xml_node, *args, **kwargs) + + def _read_xml(self, xml_node: "Element"): + self.geometry = xml_node.attrib.get("geometry", "") + self.ipv4 = xml_node.attrib.get("ipv4") + self.subnetmask = xml_node.attrib.get("subnetmask") + self.ipv6 = xml_node.attrib.get("ipv6") + self.dhcp = xml_node.attrib.get("dhcp", "false").lower() in ("true", "1", "on") + self.hostname = xml_node.attrib.get("hostname") + + def to_xml(self, parent: Element): + attributes = {"geometry": self.geometry} + if self.ipv4: + attributes["ipv4"] = self.ipv4 + if self.subnetmask: + attributes["subnetmask"] = self.subnetmask + if self.ipv6: + attributes["ipv6"] = self.ipv6 + if self.dhcp: + attributes["dhcp"] = "true" + if self.hostname: + attributes["hostname"] = self.hostname + element = ElementTree.SubElement(parent, "Network", attributes) + return element + + +class Addresses(BaseNode): + def __init__( + self, + address: List["Address"] = [], + network: List["Network"] = [], + xml_node: "Element" = None, + *args, + **kwargs, + ): + self.address = address if address is not None else [] + self.network = network if network is not None else [] + super().__init__(xml_node, *args, **kwargs) + + def _read_xml(self, xml_node: "Element"): + self.address = [Address(xml_node=i) for i in xml_node.findall("Address")] + self.network = [Network(xml_node=i) for i in xml_node.findall("Network")] + + def to_xml(self, parent: Element): + if not self.address and not self.network: + return None + element = ElementTree.SubElement(parent, "Addresses") + for dmx_address in self.address: + dmx_address.to_xml(element) + for network_address in self.network: + network_address.to_xml(element) + return element + + def __len__(self): + return len(self.address) + len(self.network) class BaseChildNode(BaseNode): @@ -121,16 +378,16 @@ def __init__( fixture_id: Union[str, None] = None, fixture_id_numeric: int = 0, unit_number: int = 0, - fixture_type_id: int = 0, custom_id: int = 0, custom_id_type: int = 0, cast_shadow: bool = False, - addresses: List["Address"] = [], - alignments: List["Alignment"] = [], - custom_commands: List["CustomCommand"] = [], - overwrites: List["Overwrite"] = [], - connections: List["Connection"] = [], + addresses: "Addresses" = None, + alignments: "Alignments" = None, + custom_commands: "CustomCommands" = None, + overwrites: "Overwrites" = None, + connections: "Connections" = None, child_list: Union["ChildList", None] = None, + multipatch: Union[str, None] = None, *args, **kwargs, ): @@ -145,21 +402,22 @@ def __init__( self.fixture_id = fixture_id self.fixture_id_numeric = fixture_id_numeric self.unit_number = unit_number - self.fixture_type_id = fixture_type_id self.custom_id = custom_id self.custom_id_type = custom_id_type self.cast_shadow = cast_shadow - self.addresses = addresses - self.alignments = alignments - self.custom_commands = custom_commands - self.overwrites = overwrites - self.connections = connections + self.addresses = addresses if addresses is not None else Addresses() + self.alignments = alignments if alignments is not None else Alignments() + self.custom_commands = custom_commands if custom_commands is not None else CustomCommands() + self.overwrites = overwrites if overwrites is not None else Overwrites() + self.connections = connections if connections is not None else Connections() self.child_list = child_list + self.multipatch = multipatch super().__init__(*args, **kwargs) def _read_xml(self, xml_node: "Element"): self.name = xml_node.attrib.get("name") self.uuid = xml_node.attrib.get("uuid") + self.multipatch = xml_node.attrib.get("multipatch") _gdtf_spec = xml_node.find("GDTFSpec") if _gdtf_spec is not None: self.gdtf_spec = _gdtf_spec.text @@ -180,9 +438,6 @@ def _read_xml(self, xml_node: "Element"): if xml_node.find("UnitNumber") is not None: self.unit_number = int(xml_node.find("UnitNumber").text) - if xml_node.find("FixtureTypeId") is not None: - self.fixture_type_id = int(xml_node.find("FixtureTypeId").text or 0) - if xml_node.find("CustomId") is not None: self.custom_id = int(xml_node.find("CustomId").text or 0) @@ -190,21 +445,23 @@ def _read_xml(self, xml_node: "Element"): self.custom_id_type = int(xml_node.find("CustomIdType").text or 0) if xml_node.find("CastShadow") is not None: - self.cast_shadow = bool(xml_node.find("CastShadow").text) + text_value = (xml_node.find("CastShadow").text or "false").lower() + self.cast_shadow = text_value in ("true", "1") - if xml_node.find("Addresses") is not None: - self.addresses = [Address(xml_node=i) for i in xml_node.find("Addresses").findall("Address")] - if not len(self.addresses): - self.addresses = [Address(dmx_break=0, universe=0, address=0)] + addresses_node = xml_node.find("Addresses") + if addresses_node is not None: + self.addresses = Addresses(xml_node=addresses_node) + else: + self.addresses = Addresses() if xml_node.find("Alignments"): - self.alignments = [Alignment(xml_node=i) for i in xml_node.find("Alignments").findall("Alignment")] + self.alignments = Alignments(xml_node=xml_node.find("Alignments")) if xml_node.find("Connections"): - self.connections = [Connection(xml_node=i) for i in xml_node.find("Connections").findall("Connection")] + self.connections = Connections(xml_node=xml_node.find("Connections")) if xml_node.find("CustomCommands") is not None: - self.custom_commands = [CustomCommand(xml_node=i) for i in xml_node.find("CustomCommands").findall("CustomCommand")] + self.custom_commands = CustomCommands(xml_node=xml_node.find("CustomCommands")) if xml_node.find("Overwrites"): - self.overwrites = [Overwrite(xml_node=i) for i in xml_node.find("Overwrites").findall("Overwrite")] + self.overwrites = Overwrites(xml_node=xml_node.find("Overwrites")) if xml_node.find("Classing") is not None: self.classing = xml_node.find("Classing").text @@ -213,6 +470,44 @@ def _read_xml(self, xml_node: "Element"): def __str__(self): return f"{self.name}" + def populate_xml(self, element: Element): + Matrix(self.matrix.matrix).to_xml(element) + if self.classing: + ElementTree.SubElement(element, "Classing").text = self.classing + if self.gdtf_spec: + ElementTree.SubElement(element, "GDTFSpec").text = self.gdtf_spec + if self.gdtf_mode: + ElementTree.SubElement(element, "GDTFMode").text = self.gdtf_mode + if self.cast_shadow: + ElementTree.SubElement(element, "CastShadow").text = "true" + + if self.addresses: + self.addresses.to_xml(element) + + if self.alignments: + self.alignments.to_xml(element) + + if self.custom_commands: + self.custom_commands.to_xml(element) + + if self.overwrites: + self.overwrites.to_xml(element) + + if self.connections: + self.connections.to_xml(element) + + ElementTree.SubElement(element, "FixtureID").text = str(self.fixture_id) or "0" + ElementTree.SubElement(element, "FixtureIDNumeric").text = str(self.fixture_id_numeric) + if self.unit_number is not None: + ElementTree.SubElement(element, "UnitNumber").text = str(self.unit_number) + if self.custom_id_type is not None: + ElementTree.SubElement(element, "CustomIdType").text = str(self.custom_id_type) + if self.custom_id is not None: + ElementTree.SubElement(element, "CustomId").text = str(self.custom_id) + + if self.child_list: + self.child_list.to_xml(element) + class BaseChildNodeExtended(BaseChildNode): def __init__( @@ -236,12 +531,17 @@ def _read_xml(self, xml_node: "Element"): def __str__(self): return f"{self.name}" + def populate_xml(self, element: Element): + super().populate_xml(element) + if self.geometries: + self.geometries.to_xml(element) + class Data(BaseNode): def __init__( self, provider: str = "", - ver: str = "", + ver: str = "1", *args, **kwargs, ): @@ -256,6 +556,10 @@ def _read_xml(self, xml_node: "Element"): def __str__(self): return f"{self.provider} {self.ver}" + def to_xml(self): + attributes = {"name": self.name, "uuid": self.uuid} + return ElementTree.Element(type(self).__name__, provider=self.provider, ver=self.ver) + class AUXData(BaseNode): def __init__( @@ -283,6 +587,12 @@ def to_xml(self, parent: Element): element = ElementTree.SubElement(parent, type(self).__name__) for _class in self.classes: element.append(_class.to_xml()) + for symdef in self.symdefs: + element.append(symdef.to_xml()) + for position in self.positions: + element.append(position.to_xml()) + for mapping_definition in self.mapping_definitions: + element.append(mapping_definition.to_xml()) return element @@ -294,7 +604,7 @@ def __init__( size_x: int = 0, size_y: int = 0, source=None, - scale_handling=None, + scale_handling: "ScaleHandeling" = None, *args, **kwargs, ): @@ -303,21 +613,35 @@ def __init__( self.size_x = size_x self.size_y = size_y self.source = source - self.scale_handling = scale_handling + self.scale_handling = scale_handling if scale_handling is not None else ScaleHandeling() super().__init__(*args, **kwargs) def _read_xml(self, xml_node: "Element"): + self.name = xml_node.attrib.get("name") + self.uuid = xml_node.attrib.get("uuid") # TODO handle missing data... self.size_x = int(xml_node.find("SizeX").text) self.size_y = int(xml_node.find("SizeY").text) - self.source = xml_node.find("Source") # TODO - self.scale_handling = xml_node.find("ScaleHandeling").text # TODO ENUM + source_node = xml_node.find("Source") + if source_node is not None: + self.source = Source(xml_node=source_node) + scale_handling_node = xml_node.find("ScaleHandeling") + if scale_handling_node is not None: + self.scale_handling = ScaleHandeling(xml_node=scale_handling_node) + + def to_xml(self): + element = ElementTree.Element(type(self).__name__, name=self.name, uuid=self.uuid) + ElementTree.SubElement(element, "SizeX").text = str(self.size_x) + ElementTree.SubElement(element, "SizeY").text = str(self.size_y) + if self.scale_handling: + self.scale_handling.to_xml(element) + # TODO source + return element class Fixture(BaseChildNode): def __init__( self, - multipatch: Union[str, None] = None, focus: Union[str, None] = None, color: Union["Color", str, None] = Color(), dmx_invert_pan: bool = False, @@ -325,13 +649,13 @@ def __init__( position: Union[str, None] = None, function_: Union[str, None] = None, child_position: Union[str, None] = None, - protocols: List["Protocol"] = [], - mappings: List["Mapping"] = [], + protocols: "Protocols" = None, + mappings: "Mappings" = None, gobo: Union["Gobo", None] = None, + unit_number: int = 0, *args, **kwargs, ): - self.multipatch = multipatch self.focus = focus self.color = color self.dmx_invert_pan = dmx_invert_pan @@ -339,17 +663,15 @@ def __init__( self.position = position self.function_ = function_ self.child_position = child_position - self.protocols = protocols - self.mappings = mappings + self.protocols = protocols if protocols is not None else Protocols() + self.mappings = mappings if mappings is not None else Mappings() self.gobo = gobo + self.unit_number = unit_number super().__init__(*args, **kwargs) def _read_xml(self, xml_node: "Element"): super()._read_xml(xml_node) - if xml_node.attrib.get("multipatch") is not None: - self.multipatch = xml_node.attrib.get("multipatch") - if xml_node.find("Focus") is not None: self.focus = xml_node.find("Focus").text @@ -357,54 +679,67 @@ def _read_xml(self, xml_node: "Element"): self.color = Color(str_repr=xml_node.find("Color").text) if xml_node.find("DMXInvertPan") is not None: - self.dmx_invert_pan = bool(xml_node.find("DMXInvertPan").text) + text_value = (xml_node.find("DMXInvertPan").text or "false").lower() + self.dmx_invert_pan = text_value in ("true", "1") if xml_node.find("DMXInvertTilt") is not None: - self.dmx_invert_tilt = bool(xml_node.find("DMXInvertTilt").text) + text_value = (xml_node.find("DMXInvertTilt").text or "false").lower() + self.dmx_invert_tilt = text_value in ("true", "1") if xml_node.find("Position") is not None: self.position = xml_node.find("Position").text if xml_node.find("Function") is not None: - self.function_ = xml_node.find("Position").text + self.function_ = xml_node.find("Function").text if xml_node.find("ChildPosition") is not None: self.child_position = xml_node.find("ChildPosition").text if xml_node.find("Protocols"): - self.protocols = [Protocol(xml_node=i) for i in xml_node.find("Protocols").findall("Protocol")] + self.protocols = Protocols(xml_node=xml_node.find("Protocols")) if xml_node.find("Mappings") is not None: - self.mappings = [Mapping(xml_node=i) for i in xml_node.find("Mappings").findall("Mapping")] + self.mappings = Mappings(xml_node=xml_node.find("Mappings")) if xml_node.find("Gobo") is not None: - self.gobo = Gobo(xml_node.attrib.get("Gobo")) + self.gobo = Gobo(xml_node=xml_node.find("Gobo")) + if xml_node.find("UnitNumber") is not None: + self.unit_number = int(xml_node.find("UnitNumber").text) def to_xml(self): - fixture_element = ElementTree.Element(type(self).__name__, name=self.name, uuid=self.uuid) - - Matrix(self.matrix.matrix).to_xml(fixture_element) - ElementTree.SubElement(fixture_element, "GDTFSpec").text = self.gdtf_spec - ElementTree.SubElement(fixture_element, "GDTFMode").text = self.gdtf_mode - if self.focus is not None: - ElementTree.SubElement(fixture_element, "Focus").text = self.focus - - ElementTree.SubElement(fixture_element, "FixtureID").text = str(self.fixture_id) or "0" - ElementTree.SubElement(fixture_element, "FixtureIDNumeric").text = str(self.fixture_id_numeric) - ElementTree.SubElement(fixture_element, "UnitNumber").text = str(self.unit_number) - if self.classing is not None: - ElementTree.SubElement(fixture_element, "Classing").text = str(self.classing) - if self.custom_id: - ElementTree.SubElement(fixture_element, "CustomId").text = str(self.custom_id) - if self.custom_id_type: - ElementTree.SubElement(fixture_element, "CustomIdType").text = str(self.custom_id_type) + attributes = {"name": self.name, "uuid": self.uuid} + if self.multipatch: + attributes["multipatch"] = self.multipatch + element = ElementTree.Element(type(self).__name__, attributes) + self.populate_xml(element) + + if self.focus: + ElementTree.SubElement(element, "Focus").text = self.focus + if self.dmx_invert_pan: + ElementTree.SubElement(element, "DMXInvertPan").text = "true" + if self.dmx_invert_tilt: + ElementTree.SubElement(element, "DMXInvertTilt").text = "true" + if self.position: + ElementTree.SubElement(element, "Position").text = self.position + if self.function_: + ElementTree.SubElement(element, "Function").text = self.function_ + if self.child_position: + ElementTree.SubElement(element, "ChildPosition").text = self.child_position + + if len(self.protocols) > 0: + self.protocols.to_xml(element) + if isinstance(self.color, Color): - self.color.to_xml(fixture_element) - else: - Color(str_repr=self.color).to_xml(fixture_element) + self.color.to_xml(element) + elif self.color: + Color(str_repr=self.color).to_xml(element) + + if len(self.mappings) > 0: + self.mappings.to_xml(element) + if self.gobo: + element.append(self.gobo.to_xml()) - addresses = ElementTree.SubElement(fixture_element, "Addresses") - for address in self.addresses: - Address(address.dmx_break, address.universe, address.address).to_xml(addresses) - return fixture_element + ElementTree.SubElement(element, "UnitNumber").text = str(self.unit_number) + + return element def __str__(self): return f"{self.name}" @@ -441,6 +776,15 @@ def _read_xml(self, xml_node: "Element"): def __str__(self): return f"{self.name}" + def to_xml(self): + element = ElementTree.Element(type(self).__name__, name=self.name, uuid=self.uuid) + Matrix(self.matrix.matrix).to_xml(parent=element) + if self.classing: + ElementTree.SubElement(element, "Classing").text = self.classing + if self.child_list: + self.child_list.to_xml(parent=element) + return element + class ChildList(BaseNode): def __init__( @@ -515,7 +859,24 @@ def _read_xml(self, xml_node: "Element"): self.projectors = [Projector(xml_node=i) for i in xml_node.findall("Projector")] def to_xml(self, parent: Element): - return ElementTree.SubElement(parent, type(self).__name__) + element = ElementTree.SubElement(parent, type(self).__name__) + for fixture in self.fixtures: + element.append(fixture.to_xml()) + for focus_point in self.focus_points: + element.append(focus_point.to_xml()) + for group_object in self.group_objects: + element.append(group_object.to_xml()) + for scene_object in self.scene_objects: + element.append(scene_object.to_xml()) + for support in self.supports: + element.append(support.to_xml()) + for truss in self.trusses: + element.append(truss.to_xml()) + for video_screen in self.video_screens: + element.append(video_screen.to_xml()) + for projector in self.projectors: + element.append(projector.to_xml()) + return element class Layer(BaseNode): @@ -545,8 +906,12 @@ def _read_xml(self, xml_node: "Element"): if xml_node.find("Matrix") is not None: self.matrix = Matrix(str_repr=xml_node.find("Matrix").text) - def to_xml(self, parent: Element): - return ElementTree.SubElement(parent, type(self).__name__, name=self.name, uuid=self.uuid) + def to_xml(self): + element = ElementTree.Element(type(self).__name__, name=self.name, uuid=self.uuid) + Matrix(self.matrix.matrix).to_xml(parent=element) + if self.child_list: + self.child_list.to_xml(parent=element) + return element def __str__(self): return f"{self.name}" @@ -640,6 +1005,10 @@ def _read_xml(self, xml_node: "Element"): def __str__(self): return f"{self.name}" + def to_xml(self): + element = ElementTree.Element(type(self).__name__, name=self.name, uuid=self.uuid) + return element + class Symdef(BaseNode): def __init__( @@ -661,19 +1030,25 @@ def _read_xml(self, xml_node: "Element"): self.name = xml_node.attrib.get("name") self.uuid = xml_node.attrib.get("uuid") - self.symbol = [Symbol(xml_node=i) for i in xml_node.findall("Symbol")] - _geometry3d = [Geometry3D(xml_node=i) for i in xml_node.findall("Geometry3D")] - if xml_node.find("ChildList") is not None: - child_list = xml_node.find("ChildList") - - symbols = [Symbol(xml_node=i) for i in child_list.findall("Symbol")] - geometry3ds = [Geometry3D(xml_node=i) for i in child_list.findall("Geometry3D")] - self.symbol += symbols - _geometry3d += geometry3ds + child_list = xml_node.find("ChildList") + if child_list is not None: + self.symbol = [Symbol(xml_node=i) for i in child_list.findall("Symbol")] + _geometry3d = [Geometry3D(xml_node=i) for i in child_list.findall("Geometry3D")] + else: + self.symbol = [] + _geometry3d = [] # sometimes the list of geometry3d is full of duplicates, eliminate them here self.geometry3d = list(set(_geometry3d)) + def to_xml(self): + element = ElementTree.Element(type(self).__name__, name=self.name, uuid=self.uuid) + for geo in self.geometry3d: + element.append(geo.to_xml()) + for sym in self.symbol: + element.append(sym.to_xml()) + return element + class Geometry3D(BaseNode): def __init__( @@ -707,6 +1082,11 @@ def __ne__(self, other): def __hash__(self): return hash((self.file_name, str(self.matrix))) + def to_xml(self): + element = ElementTree.Element(type(self).__name__, fileName=self.file_name) + Matrix(self.matrix.matrix).to_xml(parent=element) + return element + class Symbol(BaseNode): def __init__( @@ -731,6 +1111,11 @@ def _read_xml(self, xml_node: "Element"): def __str__(self): return f"{self.uuid}" + def to_xml(self): + element = ElementTree.Element(type(self).__name__, uuid=self.uuid, symdef=self.symdef) + Matrix(self.matrix.matrix).to_xml(parent=element) + return element + class Geometries(BaseNode): def __init__( @@ -747,16 +1132,13 @@ def __init__( def _read_xml(self, xml_node: "Element"): self.symbol = [Symbol(xml_node=i) for i in xml_node.findall("Symbol")] self.geometry3d = [Geometry3D(xml_node=i) for i in xml_node.findall("Geometry3D")] - if xml_node.find("ChildList"): - child_list = xml_node.find("ChildList") - - symbols = [Symbol(xml_node=i) for i in child_list.findall("Symbol")] - geometry3ds = [Geometry3D(xml_node=i) for i in child_list.findall("Geometry3D")] - self.symbol += symbols # TODO remove this over time, children should only be in child_list - self.geometry3d += geometry3ds def to_xml(self, parent: Element): element = ElementTree.SubElement(parent, type(self).__name__) + for geo in self.geometry3d: + element.append(geo.to_xml()) + for sym in self.symbol: + element.append(sym.to_xml()) return element @@ -775,6 +1157,8 @@ def __init__( self.uuid = uuid self.matrix = matrix self.classing = classing + if geometries is None: + geometries = Geometries() self.geometries = geometries super().__init__(*args, **kwargs) @@ -795,46 +1179,138 @@ def __str__(self): def to_xml(self): element = ElementTree.Element(type(self).__name__, name=self.name, uuid=self.uuid) Matrix(self.matrix.matrix).to_xml(parent=element) - Geometries().to_xml(parent=element) + if self.classing: + ElementTree.SubElement(element, "Classing").text = self.classing + self.geometries.to_xml(parent=element) return element class SceneObject(BaseChildNodeExtended): - pass + def to_xml(self): + attributes = {"name": self.name, "uuid": self.uuid} + if self.multipatch: + attributes["multipatch"] = self.multipatch + element = ElementTree.Element(type(self).__name__, attributes) + self.populate_xml(element) + return element class Truss(BaseChildNodeExtended): - pass + def __init__( + self, + position: Union[str, None] = None, + function_: Union[str, None] = None, + child_position: Union[str, None] = None, + *args, + **kwargs, + ): + self.position = position + self.function_ = function_ + self.child_position = child_position + super().__init__(*args, **kwargs) + + def _read_xml(self, xml_node: "Element"): + super()._read_xml(xml_node) + if xml_node.find("Position") is not None: + self.position = xml_node.find("Position").text + if xml_node.find("Function") is not None: + self.function_ = xml_node.find("Function").text + if xml_node.find("ChildPosition") is not None: + self.child_position = xml_node.find("ChildPosition").text + + def to_xml(self): + attributes = {"name": self.name, "uuid": self.uuid} + if self.multipatch: + attributes["multipatch"] = self.multipatch + element = ElementTree.Element(type(self).__name__, attributes) + self.populate_xml(element) + if self.position: + ElementTree.SubElement(element, "Position").text = self.position + if self.function_: + ElementTree.SubElement(element, "Function").text = self.function_ + if self.child_position: + ElementTree.SubElement(element, "ChildPosition").text = self.child_position + return element class Support(BaseChildNodeExtended): def __init__( self, chain_length: float = 0, + position: Union[str, None] = None, + function_: Union[str, None] = None, *args, **kwargs, ): self.chain_length = chain_length + self.position = position + self.function_ = function_ super().__init__(*args, **kwargs) def _read_xml(self, xml_node: "Element"): - if xml_node.find("ChainLength") is None: - self.chain_length = float(xml_node.find("ChainLength").text or 0) + super()._read_xml(xml_node) + chain_length_node = xml_node.find("ChainLength") + if chain_length_node is not None: + self.chain_length = float(chain_length_node.text or 0) + + position_node = xml_node.find("Position") + if position_node is not None: + self.position = position_node.text + + function_node = xml_node.find("Function") + if function_node is not None: + self.function_ = function_node.text + + def to_xml(self): + attributes = {"name": self.name, "uuid": self.uuid} + if self.multipatch: + attributes["multipatch"] = self.multipatch + element = ElementTree.Element(type(self).__name__, attributes) + self.populate_xml(element) + + if self.position: + ElementTree.SubElement(element, "Position").text = self.position + + if self.function_: + ElementTree.SubElement(element, "Function").text = self.function_ + + ElementTree.SubElement(element, "ChainLength").text = str(self.chain_length) + + return element class VideoScreen(BaseChildNodeExtended): def __init__( self, sources: "Sources" = None, + function_: Union[str, None] = None, *args, **kwargs, ): self.sources = sources + self.function_ = function_ super().__init__(*args, **kwargs) def _read_xml(self, xml_node: "Element"): - if xml_node.find("Sources") is None: + super()._read_xml(xml_node) + if xml_node.find("Sources") is not None: self.sources = Sources(xml_node=xml_node.find("Sources")) + if xml_node.find("Function") is not None: + self.function_ = xml_node.find("Function").text + + def to_xml(self): + attributes = {"name": self.name, "uuid": self.uuid} + if self.multipatch: + attributes["multipatch"] = self.multipatch + element = ElementTree.Element(type(self).__name__, attributes) + self.populate_xml(element) + + if self.sources: + self.sources.to_xml(element) + if self.function_: + ElementTree.SubElement(element, "Function").text = self.function_ + + return element class Projector(BaseChildNodeExtended): @@ -848,14 +1324,27 @@ def __init__( super().__init__(*args, **kwargs) def _read_xml(self, xml_node: "Element"): - if xml_node.find("Projections") is None: - self.projections = Projections(xml_node.find("Projections")) + super()._read_xml(xml_node) + if xml_node.find("Projections") is not None: + self.projections = Projections(xml_node=xml_node.find("Projections")) + + def to_xml(self): + attributes = {"name": self.name, "uuid": self.uuid} + if self.multipatch: + attributes["multipatch"] = self.multipatch + element = ElementTree.Element(type(self).__name__, attributes) + self.populate_xml(element) + + if self.projections: + self.projections.to_xml(element) + + return element class Protocol(BaseNode): def __init__( self, - geometry: Union[str, None] = None, + geometry: Union[str, None] = "NetworkInOut_1", name: Union[str, None] = None, type_: Union[str, None] = None, version: Union[str, None] = None, @@ -880,11 +1369,26 @@ def _read_xml(self, xml_node: "Element"): def __str__(self): return f"{self.name}" + def to_xml(self): + attributes = {} + if self.geometry: + attributes["geometry"] = self.geometry + if self.name: + attributes["name"] = self.name + if self.type: + attributes["type"] = self.type + if self.version: + attributes["version"] = self.version + if self.transmission: + attributes["transmission"] = self.transmission + element = ElementTree.Element(type(self).__name__, attributes) + return element + class Alignment(BaseNode): def __init__( self, - geometry: Union[str, None] = None, + geometry: Union[str, None] = "Beam", up: Union[str, None] = "0,0,1", direction: Union[str, None] = "0,0,-1", *args, @@ -903,6 +1407,17 @@ def _read_xml(self, xml_node: "Element"): def __str__(self): return f"{self.geometry}" + def to_xml(self): + attributes = {} + if self.geometry: + attributes["geometry"] = self.geometry + if self.up: + attributes["up"] = self.up + if self.direction: + attributes["direction"] = self.direction + element = ElementTree.Element(type(self).__name__, attributes) + return element + class Overwrite(BaseNode): def __init__( @@ -923,6 +1438,13 @@ def _read_xml(self, xml_node: "Element"): def __str__(self): return f"{self.universal} {self.target}" + def to_xml(self): + attributes = {"universal": self.universal} + if self.target: + attributes["target"] = self.target + element = ElementTree.Element(type(self).__name__, attributes) + return element + class Connection(BaseNode): def __init__( @@ -946,6 +1468,15 @@ def _read_xml(self, xml_node: "Element"): def __str__(self): return f"{self.own} {self.other}" + def to_xml(self): + element = ElementTree.Element( + type(self).__name__, + own=self.own, + other=self.other, + toObject=self.to_object, + ) + return element + class Mapping(BaseNode): def __init__( @@ -955,7 +1486,7 @@ def __init__( uy: Union[int, None] = None, ox: Union[int, None] = None, oy: Union[int, None] = None, - rz: Union[int, None] = None, + rz: Union[float, None] = None, *args, **kwargs, ): @@ -969,27 +1500,52 @@ def __init__( def _read_xml(self, xml_node: "Element"): self.link_def = xml_node.attrib.get("linkedDef") - self.ux = int(xml_node.find("ux").text) - self.uy = int(xml_node.find("uy").text) - self.ox = int(xml_node.find("ox").text) - self.oy = int(xml_node.find("oy").text) - self.rz = int(xml_node.find("rz").text) + ux_node = xml_node.find("ux") + if ux_node is not None: + self.ux = int(ux_node.text) + uy_node = xml_node.find("uy") + if uy_node is not None: + self.uy = int(uy_node.text) + ox_node = xml_node.find("ox") + if ox_node is not None: + self.ox = int(ox_node.text) + oy_node = xml_node.find("oy") + if oy_node is not None: + self.oy = int(oy_node.text) + rz_node = xml_node.find("rz") + if rz_node is not None: + self.rz = float(rz_node.text) def __str__(self): return f"{self.link_def}" + def to_xml(self): + element = ElementTree.Element(type(self).__name__, linkedDef=self.link_def) + if self.ux is not None: + ElementTree.SubElement(element, "ux").text = str(self.ux) + if self.uy is not None: + ElementTree.SubElement(element, "uy").text = str(self.uy) + if self.ox is not None: + ElementTree.SubElement(element, "ox").text = str(self.ox) + if self.oy is not None: + ElementTree.SubElement(element, "oy").text = str(self.oy) + if self.rz is not None: + ElementTree.SubElement(element, "rz").text = str(self.rz) + return element + class Gobo(BaseNode): def __init__( self, rotation: Union[str, float, None] = None, filename: Union[str, None] = None, + xml_node: "Element" = None, *args, **kwargs, ): self.rotation = rotation self.filename = filename - super().__init__(*args, **kwargs) + super().__init__(xml_node, *args, **kwargs) def _read_xml(self, xml_node: "Element"): self.rotation = float(xml_node.attrib.get("rotation", 0)) @@ -998,6 +1554,11 @@ def _read_xml(self, xml_node: "Element"): def __str__(self): return f"{self.filename} {self.rotation}" + def to_xml(self): + element = ElementTree.Element(type(self).__name__, rotation=str(self.rotation)) + element.text = self.filename + return element + class CustomCommand(BaseNode): # TODO: split more: Body_Pan,f 50 @@ -1016,27 +1577,111 @@ def _read_xml(self, xml_node: "Element"): def __str__(self): return f"{self.custom_command}" + def to_xml(self): + element = ElementTree.Element(type(self).__name__) + element.text = self.custom_command + return element + + +class Projection(BaseNode): + def __init__( + self, + source: "Source" = None, + scale_handling: "ScaleHandeling" = None, + *args, + **kwargs, + ): + self.source = source + self.scale_handling = scale_handling if scale_handling is not None else ScaleHandeling() + super().__init__(*args, **kwargs) + + def _read_xml(self, xml_node: "Element"): + if xml_node.find("Source") is not None: + self.source = Source(xml_node=xml_node.find("Source")) + scale_handling_node = xml_node.find("ScaleHandeling") + if scale_handling_node is not None: + self.scale_handling = ScaleHandeling(xml_node=scale_handling_node) + + def to_xml(self): + element = ElementTree.Element(type(self).__name__) + if self.source: + element.append(self.source.to_xml()) + if self.scale_handling: + self.scale_handling.to_xml(element) + return element + class Projections(BaseNode): - ... - # todo + def __init__( + self, + projections: List["Projection"] = [], + xml_node: "Element" = None, + *args, + **kwargs, + ): + self.projections = projections + super().__init__(xml_node, *args, **kwargs) + + def _read_xml(self, xml_node: "Element"): + self.projections = [Projection(xml_node=i) for i in xml_node.findall("Projection")] + def to_xml(self, parent: Element): + element = ElementTree.SubElement(parent, type(self).__name__) + for projection in self.projections: + element.append(projection.to_xml()) + return element -class Sources(BaseNode): + +class Source(BaseNode): def __init__( self, linked_geometry: Union[str, None] = None, type_: Union[str, None] = None, + value: Union[str, None] = None, + xml_node: "Element" = None, *args, **kwargs, ): self.linked_geometry = linked_geometry self.type_ = type_ - super().__init__(*args, **kwargs) + self.value = value + super().__init__(xml_node, *args, **kwargs) def _read_xml(self, xml_node: "Element"): self.linked_geometry = xml_node.attrib.get("linkedGeometry") self.type_ = xml_node.attrib.get("type") + self.value = xml_node.text def __str__(self): return f"{self.linked_geometry} {self.type_}" + + def to_xml(self): + attributes = {} + if self.linked_geometry: + attributes["linkedGeometry"] = self.linked_geometry + if self.type_: + attributes["type"] = self.type_ + element = ElementTree.Element(type(self).__name__, attributes) + element.text = self.value + return element + + +class Sources(BaseNode): + def __init__( + self, + sources: List["Source"] = [], + xml_node: "Element" = None, + *args, + **kwargs, + ): + self.sources = sources + super().__init__(xml_node, *args, **kwargs) + + def _read_xml(self, xml_node: "Element"): + self.sources = [Source(xml_node=i) for i in xml_node.findall("Source")] + + def to_xml(self, parent: Element): + element = ElementTree.SubElement(parent, type(self).__name__) + for source in self.sources: + element.append(source.to_xml()) + return element diff --git a/pymvr/value.py b/pymvr/value.py index 702c3ec..cc59e68 100644 --- a/pymvr/value.py +++ b/pymvr/value.py @@ -1,6 +1,5 @@ from typing import List, Union from xml.etree import ElementTree -from xml.etree.ElementTree import Element # Data type that only allows a specific set of values, if given a value @@ -39,7 +38,7 @@ def __init__( self.x = float(str_repr.split(",")[0]) self.y = float(str_repr.split(",")[1]) self.Y = float(str_repr.split(",")[2]) - except: + except Exception: # Fail gracefully with default color (White) self.x = 0.3127 self.y = 0.3290 diff --git a/tests/test_example.py b/tests/test_example.py deleted file mode 100644 index 7efb073..0000000 --- a/tests/test_example.py +++ /dev/null @@ -1,21 +0,0 @@ -from pathlib import Path -import pymvr - - -def test_write_example_mvr_file(): - fixtures_list = [] - mvr = pymvr.GeneralSceneDescriptionWriter() - pymvr.UserData().to_xml(parent=mvr.xml_root) - scene = pymvr.SceneElement().to_xml(parent=mvr.xml_root) - layers = pymvr.LayersElement().to_xml(parent=scene) - layer = pymvr.Layer(name="Test layer").to_xml(parent=layers) - child_list = pymvr.ChildList().to_xml(parent=layer) - - fixture = pymvr.Fixture(name="Test Fixture") # not really a valid fixture - child_list.append(fixture.to_xml()) - fixtures_list.append((fixture.gdtf_spec, fixture.gdtf_spec)) - - pymvr.AUXData().to_xml(parent=scene) - - mvr.files_list = list(set(fixtures_list)) - mvr.write_mvr("example.mvr") diff --git a/tests/test_fixture_1_5.py b/tests/test_fixture_1_5.py index 974cdbd..9af5ac6 100644 --- a/tests/test_fixture_1_5.py +++ b/tests/test_fixture_1_5.py @@ -22,13 +22,13 @@ def process_mvr_child_list(child_list, mvr_scene): def process_mvr_fixture(fixture): assert fixture.gdtf_spec == "LED PAR 64 RGBW.gdtf" - assert fixture.addresses[0].universe == 1 # even though the uni is 0 in the file, 1 is by the spec - assert fixture.addresses[0].address == 1 # dtto + assert fixture.addresses.address[0].universe == 1 # even though the uni is 0 in the file, 1 is by the spec + assert fixture.addresses.address[0].address == 1 # dtto assert fixture.gdtf_mode == "Default" assert fixture.matrix.matrix[3] == [5.0, 5.0, 5.0, 0] @pytest.mark.parametrize("mvr_scene", [("basic_fixture.mvr",)], indirect=True) def test_fixture(mvr_scene): - for layer in mvr_scene.layers: + for layer in mvr_scene.scene.layers: process_mvr_child_list(layer.child_list, mvr_scene) diff --git a/tests/test_fixture_scene_object_1_5.py b/tests/test_fixture_scene_object_1_5.py index 3abfaac..6f50b0b 100644 --- a/tests/test_fixture_scene_object_1_5.py +++ b/tests/test_fixture_scene_object_1_5.py @@ -58,7 +58,7 @@ def process_classes(mvr_scene): @pytest.mark.parametrize("mvr_scene", [("scene_objects.mvr",)], indirect=True) def test_fixture(mvr_scene): - for layer in mvr_scene.layers: + for layer in mvr_scene.scene.layers: process_mvr_child_list(layer.child_list, mvr_scene) - process_classes(mvr_scene) + process_classes(mvr_scene.scene) diff --git a/tests/test_group_objects_1_4.py b/tests/test_group_objects_1_4.py index 72c94c3..0e9ce00 100644 --- a/tests/test_group_objects_1_4.py +++ b/tests/test_group_objects_1_4.py @@ -11,10 +11,10 @@ def test_version(mvr_scene): @pytest.mark.parametrize("mvr_scene", [("capture_demo_show.mvr",)], indirect=True) def test_auxdata(mvr_scene): """Check symdefs""" - assert mvr_scene.aux_data.symdefs[0].uuid == "12fcdd5e-4194-56a0-96de-1c3c4edf1cd3" + assert mvr_scene.scene.aux_data.symdefs[0].uuid == "12fcdd5e-4194-56a0-96de-1c3c4edf1cd3" @pytest.mark.parametrize("mvr_scene", [("capture_demo_show.mvr",)], indirect=True) def test_child_list(mvr_scene): - assert mvr_scene.layers[1].child_list.fixtures[0].uuid == "2e149740-6a41-bc43-bd59-8968781b11b9" - assert mvr_scene.layers[2].child_list.scene_objects[1].uuid == "d1d76649-35bd-9d49-baa4-abcb481fb2c8" + assert mvr_scene.scene.layers[1].child_list.fixtures[0].uuid == "2e149740-6a41-bc43-bd59-8968781b11b9" + assert mvr_scene.scene.layers[2].child_list.scene_objects[1].uuid == "d1d76649-35bd-9d49-baa4-abcb481fb2c8" diff --git a/tests/test_mvr_01_write_ours.py b/tests/test_mvr_01_write_ours.py index 3fc0731..4e55596 100644 --- a/tests/test_mvr_01_write_ours.py +++ b/tests/test_mvr_01_write_ours.py @@ -12,34 +12,21 @@ def process_mvr_child_list(child_list, mvr_scene): for group in child_list.group_objects: if group.child_list is not None: - new_fixtures = process_mvr_child_list( + new_fixtures, new_focus_points = process_mvr_child_list( group.child_list, mvr_scene, ) all_fixtures += new_fixtures - all_focus_points += all_focus_points + all_focus_points += new_focus_points return (all_fixtures, all_focus_points) @pytest.mark.parametrize("mvr_scene", [("basic_fixture.mvr",)], indirect=True) def test_write_mvr_file(mvr_scene): - fixtures_list = [] mvr = pymvr.GeneralSceneDescriptionWriter() - pymvr.UserData().to_xml(parent=mvr.xml_root) - scene = pymvr.SceneElement().to_xml(parent=mvr.xml_root) - layers = pymvr.LayersElement().to_xml(parent=scene) - layer = pymvr.Layer(name="My layer").to_xml(parent=layers) - child_list = pymvr.ChildList().to_xml(parent=layer) - for layer in mvr_scene.layers: - fixtures, focus_points = process_mvr_child_list(layer.child_list, mvr_scene) - for fixture in fixtures: - child_list.append(fixture.to_xml()) - fixtures_list.append((fixture.gdtf_spec, fixture.gdtf_spec)) - for point in focus_points: - child_list.append(point.to_xml()) + mvr_scene.scene.to_xml(mvr.xml_root) + mvr_scene.user_data.to_xml(mvr.xml_root) + # TODO: add back file iteration to include gdtf files in the zip file - mvr_scene.aux_data.to_xml(parent=scene) - - mvr.files_list = list(set(fixtures_list)) test_file_path = Path(Path(__file__).parent, "test.mvr") mvr.write_mvr(test_file_path) diff --git a/tests/test_mvr_02_read_ours.py b/tests/test_mvr_02_read_ours.py index c7ff4d4..4802030 100644 --- a/tests/test_mvr_02_read_ours.py +++ b/tests/test_mvr_02_read_ours.py @@ -22,13 +22,14 @@ def process_mvr_child_list(child_list, mvr_scene): def process_mvr_fixture(fixture): assert fixture.gdtf_spec == "LED PAR 64 RGBW.gdtf" - assert fixture.addresses[0].universe == 1 - assert fixture.addresses[0].address == 1 + assert fixture.addresses.address[0].universe == 1 + assert fixture.addresses.address[0].address == 1 assert fixture.gdtf_mode == "Default" assert fixture.matrix.matrix[3] == [5.0, 5.0, 5.0, 0] @pytest.mark.parametrize("mvr_scene", [("test.mvr",)], indirect=True) def test_fixture(mvr_scene): - for layer in mvr_scene.layers: + assert len(mvr_scene.scene.layers) > 0 + for layer in mvr_scene.scene.layers: process_mvr_child_list(layer.child_list, mvr_scene) diff --git a/tests/test_mvr_01_write_ours_json.py b/tests/test_mvr_03_write_ours_json.py similarity index 63% rename from tests/test_mvr_01_write_ours_json.py rename to tests/test_mvr_03_write_ours_json.py index 0af6e5b..19e454c 100644 --- a/tests/test_mvr_01_write_ours_json.py +++ b/tests/test_mvr_03_write_ours_json.py @@ -1,4 +1,3 @@ -import pytest from pathlib import Path import pymvr @@ -29,29 +28,31 @@ def test_write_from_json(): ] fixtures_list = [] mvr = pymvr.GeneralSceneDescriptionWriter() - pymvr.UserData().to_xml(parent=mvr.xml_root) - scene = pymvr.SceneElement().to_xml(parent=mvr.xml_root) - layers = pymvr.LayersElement().to_xml(parent=scene) - for layer in data: - new_layer = pymvr.Layer(name=layer["name"], uuid=layer["uuid"]).to_xml(parent=layers) - child_list = pymvr.ChildList().to_xml(parent=new_layer) - for fixture in layer["fixtures"]: + layers = pymvr.Layers() + for layer_data in data: + layer = pymvr.Layer(name=layer_data["name"], uuid=layer_data["uuid"]) + layers.append(layer) + child_list = pymvr.ChildList() + layer.child_list = child_list + + for fixture_data in layer_data["fixtures"]: new_addresses = [ - pymvr.Address(dmx_break=address["dmx_break"], address=address["address"], universe=address["universe"]) for address in fixture["addresses"] + pymvr.Address(dmx_break=address["dmx_break"], address=address["address"], universe=address["universe"]) for address in fixture_data["addresses"] ] + new_fixture = pymvr.Fixture( - name=fixture["gdtf_spec"], - uuid=fixture["uuid"], - gdtf_spec=fixture["gdtf_spec"], - gdtf_mode=fixture["gdtf_mode"], - fixture_id=fixture["fixture_id"], - addresses=new_addresses, + name=fixture_data["gdtf_spec"], + uuid=fixture_data["uuid"], + gdtf_spec=fixture_data["gdtf_spec"], + gdtf_mode=fixture_data["gdtf_mode"], + fixture_id=fixture_data["fixture_id"], + addresses=pymvr.Addresses(address=new_addresses), ) - child_list.append(new_fixture.to_xml()) + child_list.fixtures.append(new_fixture) fixtures_list.append((new_fixture.gdtf_spec, new_fixture.gdtf_spec)) - pymvr.AUXData().to_xml(parent=scene) - mvr.files_list = list(set(fixtures_list)) + scene = pymvr.Scene(layers=layers) + scene.to_xml(mvr.xml_root) test_file_path = Path(Path(__file__).parent, "test_json.mvr") mvr.write_mvr(test_file_path) diff --git a/tests/test_mvr_02_read_ours_json.py b/tests/test_mvr_04_read_ours_json.py similarity index 81% rename from tests/test_mvr_02_read_ours_json.py rename to tests/test_mvr_04_read_ours_json.py index f35b334..e66d5d9 100644 --- a/tests/test_mvr_02_read_ours_json.py +++ b/tests/test_mvr_04_read_ours_json.py @@ -10,6 +10,7 @@ def test_version(mvr_scene): def process_mvr_child_list(child_list, mvr_scene): + assert len(child_list.fixtures) == 2 for fixture in child_list.fixtures: process_mvr_fixture(fixture) for group in child_list.group_objects: @@ -22,15 +23,16 @@ def process_mvr_child_list(child_list, mvr_scene): def process_mvr_fixture(fixture): assert fixture.gdtf_spec == "BlenderDMX@Basic_LED_Bulb@ver2.gdtf" - assert fixture.addresses[0].dmx_break == 1 - assert fixture.addresses[0].universe == 1 + assert fixture.addresses.address[0].dmx_break == 1 + assert fixture.addresses.address[0].universe == 1 assert fixture.gdtf_mode == "Standard mode" assert fixture.matrix.matrix[3] == [0.0, 0.0, 0.0, 0] @pytest.mark.parametrize("mvr_scene", [("test_json.mvr",)], indirect=True) def test_fixture(mvr_scene): - for layer in mvr_scene.layers: + assert len(mvr_scene.scene.layers) > 0 + for layer in mvr_scene.scene.layers: assert layer.name == "Layer 1" assert layer.uuid == "1e4954b5-992c-4146-b71f-5b497834087f" process_mvr_child_list(layer.child_list, mvr_scene) diff --git a/tests/test_mvr_05_example.py b/tests/test_mvr_05_example.py new file mode 100644 index 0000000..0e9b432 --- /dev/null +++ b/tests/test_mvr_05_example.py @@ -0,0 +1,26 @@ +from pathlib import Path +import pymvr + + +def test_write_example_mvr_file(): + fixtures_list = [] + mvr = pymvr.GeneralSceneDescriptionWriter() + + layers = pymvr.Layers() + layer = pymvr.Layer(name="Test layer") + layers.layers.append(layer) + + child_list = pymvr.ChildList() + layer.child_list = child_list + + fixture = pymvr.Fixture(name="Test Fixture") # not really a valid fixture + child_list.fixtures.append(fixture) + fixtures_list.append((fixture.gdtf_spec, fixture.gdtf_spec)) + + scene = pymvr.Scene(layers=layers) + + scene.to_xml(parent=mvr.xml_root) + + mvr.files_list = list(set(fixtures_list)) + test_file_path = Path(Path(__file__).parent, "example.mvr") + mvr.write_mvr(test_file_path)