@@ -231,6 +231,8 @@ def _read_aas_part_into(self, part_name: str,
231231 read_identifiables .add (obj .id )
232232 if isinstance (obj , model .Submodel ):
233233 self ._collect_supplementary_files (part_name , obj , file_store )
234+ elif isinstance (obj , model .AssetAdministrationShell ):
235+ self ._collect_supplementary_files (part_name , obj , file_store )
234236
235237 def _parse_aas_part (self , part_name : str , ** kwargs ) -> model .DictObjectStore :
236238 """
@@ -261,33 +263,59 @@ def _parse_aas_part(self, part_name: str, **kwargs) -> model.DictObjectStore:
261263 raise ValueError (error_message )
262264 return model .DictObjectStore ()
263265
264- def _collect_supplementary_files (self , part_name : str , submodel : model .Submodel ,
266+ def _collect_supplementary_files (self , part_name : str ,
267+ root_element : Union [model .AssetAdministrationShell , model .Submodel ],
265268 file_store : "AbstractSupplementaryFileContainer" ) -> None :
266269 """
267- Helper function to search File objects within a single parsed Submodel, extract the referenced supplementary
268- files and update the File object's values with the absolute path.
270+ Helper function to search File objects within a single parsed AssetAdministrationShell or Submodel.
271+ Resolve their absolute paths, and update the corresponding File/Thumbnail objects with the absolute path.
269272
270- :param part_name: The OPC part name of the part the Submodel has been parsed from. This is used to resolve
273+ :param part_name: The OPC part name of the part the root_element has been parsed from. This is used to resolve
271274 relative file paths.
272- :param submodel : The Submodel to process
275+ :param root_element : The AssetAdministrationShell or Submodel to process
273276 :param file_store: The SupplementaryFileContainer to add the extracted supplementary files to
274277 """
275- for element in traversal .walk_submodel (submodel ):
276- if isinstance (element , model .File ):
277- if element .value is None :
278- continue
279- # Only absolute-path references and relative-path URI references (see RFC 3986, sec. 4.2) are considered
280- # to refer to files within the AASX package. Thus, we must skip all other types of URIs (esp. absolute
281- # URIs and network-path references)
282- if element .value .startswith ('//' ) or ':' in element .value .split ('/' )[0 ]:
283- logger .info (f"Skipping supplementary file { element .value } , since it seems to be an absolute URI or "
284- f"network-path URI reference" )
285- continue
286- absolute_name = pyecma376_2 .package_model .part_realpath (element .value , part_name )
287- logger .debug (f"Reading supplementary file { absolute_name } from AASX package ..." )
288- with self .reader .open_part (absolute_name ) as p :
289- final_name = file_store .add_file (absolute_name , p , self .reader .get_content_type (absolute_name ))
290- element .value = final_name
278+ if isinstance (root_element , model .AssetAdministrationShell ):
279+ if (root_element .asset_information .default_thumbnail and
280+ root_element .asset_information .default_thumbnail .path ):
281+ file_name = self ._add_supplementary_file (part_name ,
282+ root_element .asset_information .default_thumbnail .path ,
283+ file_store )
284+ if file_name :
285+ root_element .asset_information .default_thumbnail .path = file_name
286+ if isinstance (root_element , model .Submodel ):
287+ for element in traversal .walk_submodel (root_element ):
288+ if isinstance (element , model .File ):
289+ if element .value is None :
290+ continue
291+ final_name = self ._add_supplementary_file (part_name , element .value , file_store )
292+ if final_name :
293+ element .value = final_name
294+
295+ def _add_supplementary_file (self , part_name : str , file_path : str ,
296+ file_store : "AbstractSupplementaryFileContainer" ) -> Optional [str ]:
297+ """
298+ Helper function to extract a single referenced supplementary file
299+ and return the absolute path within the AASX package.
300+
301+ :param part_name: The OPC part name of the part the root_element has been parsed from. This is used to resolve
302+ relative file paths.
303+ :param file_path: The file path or URI reference of the supplementary file to be extracted
304+ :param file_store: The SupplementaryFileContainer to add the extracted supplementary files to
305+ :return: The stored file name as returned by *file_store*, or ``None`` if the reference was skipped.
306+ """
307+ # Only absolute-path references and relative-path URI references (see RFC 3986, sec. 4.2) are considered
308+ # to refer to files within the AASX package. Thus, we must skip all other types of URIs (esp. absolute
309+ # URIs and network-path references)
310+ if file_path .startswith ('//' ) or ':' in file_path .split ('/' )[0 ]:
311+ logger .info (f"Skipping supplementary file { file_path } , since it seems to be an absolute URI or "
312+ f"network-path URI reference" )
313+ return None
314+ absolute_name = pyecma376_2 .package_model .part_realpath (file_path , part_name )
315+ logger .debug (f"Reading supplementary file { absolute_name } from AASX package ..." )
316+ with self .reader .open_part (absolute_name ) as p :
317+ final_name = file_store .add_file (absolute_name , p , self .reader .get_content_type (absolute_name ))
318+ return final_name
291319
292320
293321class AASXWriter :
@@ -541,7 +569,8 @@ def write_all_aas_objects(self,
541569 contained objects into an ``aas_env`` part in the AASX package. If the ObjectStore includes
542570 :class:`~basyx.aas.model.submodel.Submodel` objects, supplementary files which are referenced by
543571 :class:`~basyx.aas.model.submodel.File` objects within those Submodels, are fetched from the ``file_store``
544- and added to the AASX package.
572+ and added to the AASX package. If the ObjectStore contains a thumbnail referenced by
573+ ``default_thumbnail`` in :class:`~basyx.aas.model.aas.AssetInformation`, it is also added to the AASX package.
545574
546575 .. attention::
547576
@@ -563,17 +592,24 @@ def write_all_aas_objects(self,
563592 logger .debug (f"Writing AASX part { part_name } with AAS objects ..." )
564593 supplementary_files : List [str ] = []
565594
595+ def _collect_supplementary_file (file_name : str ) -> None :
596+ # Skip File objects with empty value URI references that are considered to be no local file
597+ # (absolute URIs or network-path URI references)
598+ if file_name is None or file_name .startswith ('//' ) or ':' in file_name .split ('/' )[0 ]:
599+ return
600+ supplementary_files .append (file_name )
601+
566602 # Retrieve objects and scan for referenced supplementary files
567603 for the_object in objects :
604+ if isinstance (the_object , model .AssetAdministrationShell ):
605+ if (the_object .asset_information .default_thumbnail and
606+ the_object .asset_information .default_thumbnail .path ):
607+ _collect_supplementary_file (the_object .asset_information .default_thumbnail .path )
568608 if isinstance (the_object , model .Submodel ):
569609 for element in traversal .walk_submodel (the_object ):
570610 if isinstance (element , model .File ):
571- file_name = element .value
572- # Skip File objects with empty value URI references that are considered to be no local file
573- # (absolute URIs or network-path URI references)
574- if file_name is None or file_name .startswith ('//' ) or ':' in file_name .split ('/' )[0 ]:
575- continue
576- supplementary_files .append (file_name )
611+ if element .value :
612+ _collect_supplementary_file (element .value )
577613
578614 # Add aas-spec relationship
579615 if not split_part :
0 commit comments