Skip to content

Commit 562b967

Browse files
authored
adapter.aasx: Add support for loading/saving thumbnails in AASX package (#436)
adapter.aasx: Add support for loading and saving thumbnails in AASX package Previously, the `AASXReader` did not load the thumbnail in the `read_into` function, instead a separate function needed to be called. This behavior did also occur in `AASXWriter` with writing the thumbnail. This was unintuitive, as it required a separat call of the load/save function. This PR implements the loading/storing of the thumbnail directly in the loading/storing of other supplementary files. Fixes #435
1 parent 38eebeb commit 562b967

File tree

1 file changed

+64
-28
lines changed

1 file changed

+64
-28
lines changed

sdk/basyx/aas/adapter/aasx.py

Lines changed: 64 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -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

293321
class 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

Comments
 (0)