2222# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
2323# SOFTWARE.
2424
25+ from copy import deepcopy
2526from typing import List , Union , Optional , Tuple
2627from xml .etree import ElementTree
2728from xml .etree .ElementTree import Element
@@ -80,7 +81,9 @@ def __exit__(self, exc_type, exc_val, exc_tb):
8081class GeneralSceneDescriptionWriter :
8182 """Creates MVR zip archive with packed GeneralSceneDescription xml and other files"""
8283
83- def __init__ (self ):
84+ def __init__ (
85+ self ,
86+ ):
8487 self .version_major : str = "1"
8588 self .version_minor : str = "6"
8689 self .provider : str = "pymvr"
@@ -94,6 +97,13 @@ def __init__(self):
9497 providerVersion = self .provider_version ,
9598 )
9699
100+ def serialize_scene (self , scene : "Scene" ):
101+ scene .to_xml (parent = self .xml_root )
102+
103+ def serialize_user_data (self , user_data : "UserData" ):
104+ if user_data :
105+ user_data .to_xml (parent = self .xml_root )
106+
97107 def write_mvr (self , path : Optional [str ] = None ):
98108 if path is not None :
99109 if sys .version_info >= (3 , 9 ):
@@ -381,32 +391,38 @@ def to_xml(self, parent: Element):
381391class Addresses (BaseNode ):
382392 def __init__ (
383393 self ,
384- address : Optional [List ["Address" ]] = None ,
385- network : Optional [List ["Network" ]] = None ,
394+ addresses : Optional [List ["Address" ]] = None ,
395+ networks : Optional [List ["Network" ]] = None ,
386396 xml_node : Optional ["Element" ] = None ,
387397 * args ,
388398 ** kwargs ,
389399 ):
390- self .address = address if address is not None else []
391- self .network = network if network is not None else []
400+ # Use plural names as primary fields
401+ # Accept legacy keyword names if passed
402+ if addresses is None and "address" in kwargs :
403+ addresses = kwargs .pop ("address" )
404+ if networks is None and "network" in kwargs :
405+ networks = kwargs .pop ("network" )
406+ self .addresses : List ["Address" ] = addresses if addresses is not None else []
407+ self .networks : List ["Network" ] = networks if networks is not None else []
392408 super ().__init__ (xml_node , * args , ** kwargs )
393409
394410 def _read_xml (self , xml_node : "Element" ):
395- self .address = [Address (xml_node = i ) for i in xml_node .findall ("Address" )]
396- self .network = [Network (xml_node = i ) for i in xml_node .findall ("Network" )]
411+ self .addresses = [Address (xml_node = i ) for i in xml_node .findall ("Address" )]
412+ self .networks = [Network (xml_node = i ) for i in xml_node .findall ("Network" )]
397413
398414 def to_xml (self , parent : Element ) -> Optional [Element ]:
399- if not self .address and not self .network :
415+ if not self .addresses and not self .networks :
400416 return None
401417 element = ElementTree .SubElement (parent , "Addresses" )
402- for dmx_address in self .address :
418+ for dmx_address in self .addresses :
403419 dmx_address .to_xml (element )
404- for network_address in self .network :
420+ for network_address in self .networks :
405421 network_address .to_xml (element )
406422 return element
407423
408424 def __len__ (self ):
409- return len (self .address ) + len (self .network )
425+ return len (self .addresses ) + len (self .networks )
410426
411427
412428class BaseChildNode (BaseNode ):
@@ -574,12 +590,10 @@ def populate_xml(self, element: Element):
574590
575591 if self .fixture_id is not None :
576592 ElementTree .SubElement (element , "FixtureID" ).text = str (self .fixture_id )
577-
578593 if self .fixture_id_numeric is not None :
579594 ElementTree .SubElement (element , "FixtureIDNumeric" ).text = str (
580595 self .fixture_id_numeric
581596 )
582-
583597 if self .unit_number is not None :
584598 ElementTree .SubElement (element , "UnitNumber" ).text = str (self .unit_number )
585599 if self .custom_id_type is not None :
@@ -620,8 +634,11 @@ def __str__(self):
620634
621635 def populate_xml (self , element : Element ):
622636 super ().populate_xml (element )
623- if self .geometries :
624- self .geometries .to_xml (element )
637+ if self .geometries is None :
638+ raise ValueError (
639+ f"{ type (self ).__name__ } '{ self .name } ' missing required Geometries"
640+ )
641+ self .geometries .to_xml (element )
625642
626643
627644class Data (BaseNode ):
@@ -634,6 +651,8 @@ def __init__(
634651 ):
635652 self .provider = provider
636653 self .ver = ver
654+ self .text : Optional [str ] = None
655+ self .extra_children : List [Element ] = []
637656 super ().__init__ (* args , ** kwargs )
638657
639658 def _read_xml (self , xml_node : "Element" ):
@@ -643,14 +662,20 @@ def _read_xml(self, xml_node: "Element"):
643662 ver = xml_node .attrib .get ("ver" )
644663 if ver is not None :
645664 self .ver = ver
665+ self .text = xml_node .text
666+ self .extra_children = [deepcopy (child ) for child in list (xml_node )]
646667
647668 def __str__ (self ):
648669 return f"{ self .provider } { self .ver } "
649670
650671 def to_xml (self ):
651- return ElementTree .Element (
672+ element = ElementTree .Element (
652673 type (self ).__name__ , provider = self .provider , ver = self .ver
653674 )
675+ element .text = self .text
676+ for child in self .extra_children :
677+ element .append (deepcopy (child ))
678+ return element
654679
655680
656681class AUXData (BaseNode ):
@@ -740,6 +765,8 @@ def _read_xml(self, xml_node: "Element"):
740765 self .scale_handling = ScaleHandeling (xml_node = scale_handling_node )
741766
742767 def to_xml (self ):
768+ if self .source is None :
769+ raise ValueError (f"MappingDefinition '{ self .name } ' missing required Source" )
743770 element = ElementTree .Element (
744771 type (self ).__name__ , name = self .name , uuid = self .uuid
745772 )
@@ -832,6 +859,13 @@ def to_xml(self):
832859 if self .multipatch :
833860 attributes ["multipatch" ] = self .multipatch
834861 element = ElementTree .Element (type (self ).__name__ , attributes )
862+ if self .multipatch is None :
863+ if self .fixture_id is None :
864+ self .fixture_id = "0"
865+ if self .fixture_id_numeric is None :
866+ self .fixture_id_numeric = 0
867+ if self .unit_number is None :
868+ self .unit_number = 0
835869 self .populate_xml (element )
836870
837871 if self .focus :
@@ -1416,6 +1450,11 @@ def to_xml(self):
14161450 if self .multipatch :
14171451 attributes ["multipatch" ] = self .multipatch
14181452 element = ElementTree .Element (type (self ).__name__ , attributes )
1453+ if self .multipatch is None :
1454+ if self .fixture_id is None :
1455+ self .fixture_id = "0"
1456+ if self .fixture_id_numeric is None :
1457+ self .fixture_id_numeric = 0
14191458 self .populate_xml (element )
14201459 if self .position :
14211460 ElementTree .SubElement (element , "Position" ).text = self .position
@@ -1459,6 +1498,11 @@ def to_xml(self):
14591498 if self .multipatch :
14601499 attributes ["multipatch" ] = self .multipatch
14611500 element = ElementTree .Element (type (self ).__name__ , attributes )
1501+ if self .multipatch is None :
1502+ if self .fixture_id is None :
1503+ self .fixture_id = "0"
1504+ if self .fixture_id_numeric is None :
1505+ self .fixture_id_numeric = 0
14621506 self .populate_xml (element )
14631507
14641508 if self .position :
@@ -1499,6 +1543,11 @@ def to_xml(self):
14991543 if self .multipatch :
15001544 attributes ["multipatch" ] = self .multipatch
15011545 element = ElementTree .Element (type (self ).__name__ , attributes )
1546+ if self .multipatch is None :
1547+ if self .fixture_id is None :
1548+ self .fixture_id = "0"
1549+ if self .fixture_id_numeric is None :
1550+ self .fixture_id_numeric = 0
15021551 self .populate_xml (element )
15031552
15041553 if self .sources :
@@ -1530,10 +1579,17 @@ def to_xml(self):
15301579 if self .multipatch :
15311580 attributes ["multipatch" ] = self .multipatch
15321581 element = ElementTree .Element (type (self ).__name__ , attributes )
1582+ if self .multipatch is None :
1583+ if self .fixture_id is None :
1584+ self .fixture_id = "0"
1585+ if self .fixture_id_numeric is None :
1586+ self .fixture_id_numeric = 0
15331587 self .populate_xml (element )
15341588
15351589 if self .projections :
15361590 self .projections .to_xml (element )
1591+ else :
1592+ raise ValueError (f"Projector '{ self .name } ' missing Projections" )
15371593
15381594 return element
15391595
@@ -1749,7 +1805,7 @@ def __init__(
17491805 * args ,
17501806 ** kwargs ,
17511807 ):
1752- self .rotation = rotation
1808+ self .rotation = 0.0 if rotation is None else rotation
17531809 self .filename = filename
17541810 super ().__init__ (xml_node , * args , ** kwargs )
17551811
@@ -1814,9 +1870,10 @@ def _read_xml(self, xml_node: "Element"):
18141870 self .scale_handling = ScaleHandeling (xml_node = scale_handling_node )
18151871
18161872 def to_xml (self ):
1873+ if self .source is None :
1874+ raise ValueError ("Projection missing required Source" )
18171875 element = ElementTree .Element (type (self ).__name__ )
1818- if self .source :
1819- element .append (self .source .to_xml ())
1876+ element .append (self .source .to_xml ())
18201877 if self .scale_handling :
18211878 self .scale_handling .to_xml (element )
18221879 return element
@@ -1840,6 +1897,8 @@ def _read_xml(self, xml_node: "Element"):
18401897
18411898 def to_xml (self , parent : Element ):
18421899 element = ElementTree .SubElement (parent , type (self ).__name__ )
1900+ if len (self .projections ) == 0 :
1901+ raise ValueError ("Projections missing Projection entries" )
18431902 for projection in self .projections :
18441903 element .append (projection .to_xml ())
18451904 return element
@@ -1895,6 +1954,8 @@ def _read_xml(self, xml_node: "Element"):
18951954
18961955 def to_xml (self , parent : Element ):
18971956 element = ElementTree .SubElement (parent , type (self ).__name__ )
1957+ if len (self .sources ) == 0 :
1958+ raise ValueError ("Sources missing Source entries" )
18981959 for source in self .sources :
18991960 element .append (source .to_xml ())
19001961 return element
0 commit comments