2222# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
2323# SOFTWARE.
2424
25- from typing import List , Optional
25+ import copy
26+ from typing import List , Optional , TYPE_CHECKING
2627from xml .etree import ElementTree
2728from xml .etree .ElementTree import Element
2829
2930from .base_node import BaseNode
3031from .dmxbreak import *
3132from .value import * # type: ignore
3233
34+ if TYPE_CHECKING :
35+ from . import FixtureType
36+
3337
3438def _matrix_to_str (matrix : "Matrix" ) -> str :
3539 if hasattr (matrix , "raw" ) and matrix .raw :
@@ -41,6 +45,25 @@ def _matrix_to_str(matrix: "Matrix") -> str:
4145
4246
4347class Geometries (list ):
48+ def _resolve_root_geometry (
49+ self ,
50+ fixture_type : "FixtureType" ,
51+ mode_name : Optional [str ] = None ,
52+ mode_index : Optional [int ] = None ,
53+ ):
54+ root_name = None
55+ if mode_name is not None :
56+ mode = fixture_type .dmx_modes .get_mode_by_name (mode_name )
57+ if mode is not None :
58+ root_name = mode .geometry
59+ elif mode_index is not None and len (fixture_type .dmx_modes ) > mode_index :
60+ root_name = fixture_type .dmx_modes [mode_index ].geometry
61+ if root_name is None and len (self ):
62+ root_name = self [0 ].name
63+ if root_name is None :
64+ return None
65+ return self .get_geometry_by_name (root_name )
66+
4467 def get_geometry_by_name (self , geometry_name ):
4568 """Operates on the while kinematic chain of the device"""
4669
@@ -84,6 +107,103 @@ def iterate_geometries(collector):
84107 iterate_geometries (root_geometry )
85108 return matched
86109
110+ def get_geometry_tree (
111+ self ,
112+ fixture_type : "FixtureType" ,
113+ mode_name : Optional [str ] = None ,
114+ mode_index : Optional [int ] = None ,
115+ ):
116+ root_geometry = self ._resolve_root_geometry (
117+ fixture_type ,
118+ mode_name = mode_name ,
119+ mode_index = mode_index ,
120+ )
121+ return self ._expand_tree (root_geometry , fixture_type )
122+
123+ def _expand_tree (self , geometry , fixture_type : "FixtureType" ):
124+ if geometry is None :
125+ return None
126+ geometry_copy = copy .deepcopy (geometry )
127+ children = []
128+ for child in self ._iter_children (geometry , fixture_type , True ):
129+ child_copy = self ._expand_tree (child , fixture_type )
130+ if child_copy is not None :
131+ children .append (child_copy )
132+ geometry_copy .geometries = Geometries (children )
133+ return geometry_copy
134+
135+ def _resolve_reference (self , geometry , fixture_type : "FixtureType" ):
136+ if isinstance (geometry , GeometryReference ):
137+ reference_name = geometry .geometry
138+ if reference_name :
139+ return fixture_type .geometries .get_geometry_by_name (reference_name )
140+ return None
141+
142+ def _iter_children (self , geometry , fixture_type : "FixtureType" , expand_references ):
143+ if isinstance (geometry , GeometryReference ):
144+ reference_geometry = self ._resolve_reference (geometry , fixture_type )
145+ if expand_references and reference_geometry is not None :
146+ return getattr (reference_geometry , "geometries" , [])
147+ return []
148+ return getattr (geometry , "geometries" , [])
149+
150+ def iter_tree (self , geometry , fixture_type : "FixtureType" , expand_references = True ):
151+ if geometry is None :
152+ return
153+ yield geometry
154+ for child in self ._iter_children (geometry , fixture_type , expand_references ):
155+ yield from self .iter_tree (child , fixture_type , expand_references )
156+
157+ def to_list (self , geometry , fixture_type : "FixtureType" , expand_references = True ):
158+ return list (self .iter_tree (geometry , fixture_type , expand_references ))
159+
160+ def as_dict (
161+ self ,
162+ geometry = None ,
163+ fixture_type : Optional ["FixtureType" ] = None ,
164+ mode_name : Optional [str ] = None ,
165+ mode_index : Optional [int ] = None ,
166+ ):
167+ if fixture_type is None :
168+ raise ValueError ("fixture_type is required to build geometry dicts." )
169+ if geometry is None :
170+ geometry = self ._resolve_root_geometry (
171+ fixture_type ,
172+ mode_name = mode_name ,
173+ mode_index = mode_index ,
174+ )
175+ if geometry is None :
176+ return None
177+ reference_geometry = self ._resolve_reference (geometry , fixture_type )
178+ model = geometry .model
179+ if model is None and reference_geometry is not None :
180+ model = reference_geometry .model
181+ data = {
182+ "name" : geometry .name ,
183+ "type" : type (geometry ).__name__ ,
184+ "model" : model ,
185+ "matrix" : geometry .position .matrix
186+ if hasattr (geometry , "position" )
187+ else None ,
188+ "reference" : geometry .geometry
189+ if isinstance (geometry , GeometryReference )
190+ else None ,
191+ }
192+ children = []
193+ for child in self ._iter_children (
194+ geometry ,
195+ fixture_type ,
196+ True ,
197+ ):
198+ child_data = self .as_dict (
199+ child ,
200+ fixture_type ,
201+ )
202+ if child_data is not None :
203+ children .append (child_data )
204+ data ["children" ] = children
205+ return data
206+
87207
88208class Geometry (BaseNode ):
89209 def __init__ (
0 commit comments