diff --git a/capella_ros_tools/__main__.py b/capella_ros_tools/__main__.py index 2c124bb..0c3e197 100644 --- a/capella_ros_tools/__main__.py +++ b/capella_ros_tools/__main__.py @@ -84,7 +84,7 @@ def import_msgs( def export_capella( model: capellambse.MelodyModel, layer: str, - msg_path: pathlib.Path, + messages: pathlib.Path, ): """Export Capella data package to ROS messages. @@ -94,7 +94,8 @@ def export_capella( """ from capella_ros_tools.scripts import export_capella as exporter - exporter.Exporter(model, layer, msg_path)() + current_pkg = getattr(model, layer).data_package + exporter.export(current_pkg, messages) if __name__ == "__main__": diff --git a/capella_ros_tools/scripts/export_capella.py b/capella_ros_tools/scripts/export_capella.py index a63ba35..a10ecb4 100644 --- a/capella_ros_tools/scripts/export_capella.py +++ b/capella_ros_tools/scripts/export_capella.py @@ -5,7 +5,6 @@ import pathlib import re -import capellambse from capellambse.model.crosslayer import information from capella_ros_tools import data_model @@ -17,75 +16,64 @@ def _clean_name(name: str) -> str: return re.sub(r"\W", "", name) -class Exporter: - """Class for exporting a Capella data package as ROS messages.""" - - def __init__( - self, - model: capellambse.MelodyModel, - layer: str, - output_path: pathlib.Path, - ): - self._data_package = getattr(model, layer).data_package - output_path.mkdir(parents=True, exist_ok=True) - self._output_path = output_path - - def _handle_pkg( - self, - current_pkg: information.DataPkg, - current_path: pathlib.Path, - ): - for cls_obj in current_pkg.classes: - cls_def = data_model.MessageDef( - cls_obj.name, [], [], cls_obj.description +def export(current_pkg: information.DataPkg, current_path: pathlib.Path): + """Export a Capella data package to ROS messages.""" + for cls_obj in current_pkg.classes: + fields = [] + for prop_obj in cls_obj.owned_properties: + type_def = data_model.TypeDef( + name=prop_obj.type.name, + card=data_model.Range( + prop_obj.min_card.value, prop_obj.max_card.value + ), ) - for prop_obj in cls_obj.owned_properties: - type_def = data_model.TypeDef( - prop_obj.type.name, - data_model.Range( - prop_obj.min_card.value, prop_obj.max_card.value - ), - ) - prop_def = data_model.FieldDef( - type_def, prop_obj.name, prop_obj.description - ) - cls_def.fields.append(prop_def) - (current_path / f"{_clean_name(cls_obj.name)}.msg").write_text( - str(cls_def) + prop_def = data_model.FieldDef( + type=type_def, + name=prop_obj.name, + description=prop_obj.description, ) + fields.append(prop_def) + cls_def = data_model.MessageDef( + name=cls_obj.name, + fields=fields, + enums=[], + description=cls_obj.description, + ) + (current_path / f"{_clean_name(cls_obj.name)}.msg").write_text( + str(cls_def) + ) - for enum_obj in current_pkg.enumerations: - enum_def = data_model.EnumDef( - enum_obj.name, [], enum_obj.description + for enum_obj in current_pkg.enumerations: + literals = [] + for i, lit_obj in enumerate(enum_obj.owned_literals): + try: + type_name = lit_obj.value.type.name + except AttributeError: + type_name = "uint8" + try: + literal_value = lit_obj.value.value + except AttributeError: + literal_value = i + type_def = data_model.TypeDef( + type_name, data_model.Range("1", "1") ) - for i, lit_obj in enumerate(enum_obj.owned_literals): - try: - type_name = lit_obj.value.type.name - except AttributeError: - type_name = "uint8" - try: - literal_value = lit_obj.value.value - except AttributeError: - literal_value = i - type_def = data_model.TypeDef( - type_name, data_model.Range("1", "1") - ) - lit_def = data_model.ConstantDef( - type_def, - lit_obj.name, - literal_value, - lit_obj.description, - ) - enum_def.literals.append(lit_def) - (current_path / f"{_clean_name(enum_obj.name)}.msg").write_text( - str(enum_def) + lit_def = data_model.ConstantDef( + type=type_def, + name=lit_obj.name, + value=literal_value, + description=lit_obj.description, ) + literals.append(lit_def) + enum_def = data_model.EnumDef( + name=enum_obj.name, + literals=literals, + description=enum_obj.description, + ) + (current_path / f"{_clean_name(enum_obj.name)}.msg").write_text( + str(enum_def) + ) - for pkg_obj in current_pkg.packages: - pkg_path = current_path / _clean_name(pkg_obj.name) - pkg_path.mkdir(parents=True, exist_ok=True) - self._handle_pkg(pkg_obj, pkg_path) - - def __call__(self): - """Export the Capella data package as ROS messages.""" - self._handle_pkg(self._data_package, self._output_path) + for pkg_obj in current_pkg.packages: + pkg_path = current_path / _clean_name(pkg_obj.name) + pkg_path.mkdir(parents=True, exist_ok=True) + export(pkg_obj, pkg_path) diff --git a/docs/source/howtos.rst b/docs/source/howtos.rst index 86a8e3d..779fe02 100644 --- a/docs/source/howtos.rst +++ b/docs/source/howtos.rst @@ -17,24 +17,35 @@ Import ROS2 Messages: --------------------- .. code-block:: bash - $ python -m capella_ros_tools import tests/data/example_msgs tests/data/empty_model la --exists-action=skip --port=5000 --no-deps + $ python -m capella_ros_tools import \ + tests/data/data_model/example_msgs \ + tests/data/empty_project_52 la \ + --port=5000 --no-deps Import ROS2 Messages from Git Repository: ----------------------------------------- .. code-block:: bash - $ python -m capella_ros_tools import git+https://github.com/DSD-DBS/dsd-ros-msg-definitions-oss tests/data/empty_model la --exists-action=skip --port=5000 + $ python -m capella_ros_tools import \ + git+https://github.com/DSD-DBS/dsd-ros-msg-definitions-oss \ + tests/data/empty_project_52 la \ + --port=5000 Export Capella data package: ------------------------------------ .. code-block:: bash - $ python -m capella_ros_tools export tests/data/melody_model_60 la tests/data/melody_msgs + $ python -m capella_ros_tools export \ + tests/data/melody_model_60 la \ + tests/data/melody_msgs Export Capella data package from Git Repository: -------------------------------------------------------- .. code-block:: bash - $ python -m capella_ros_tools export git+https://github.com/DSD-DBS/coffee-machine oa tests/data/coffee_msgs + $ python -m capella_ros_tools export \ + git+https://github.com/DSD-DBS/coffee-machine oa \ + tests/data/coffee_msgs -Note: When exporting Capella enumerations, if the enumeration values are not defined in the Capella model, the values will be set to 0, 1, 2, 3, etc. and the datatype will be set to unit8. +.. note:: + When exporting Capella enumerations, if the enumeration literal values are not defined in the Capella model, the values will be set to 0, 1, 2, 3, etc. and the value's type will be set to unit8. diff --git a/docs/source/index.rst b/docs/source/index.rst index f872e4a..d4c6365 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -18,41 +18,22 @@ Overview Capella ROS Tools is a command-line application written in Python, designed to facilitate the seamless integration of ROS2 and Capella MBSE tools. Key features include: -* generate ROS2 message files (.msg) from Capella models -* generate Capella elements and objects from ROS2 message files -* support input sources through both local file paths and Git repositories -* display snapshots of Capella model elements in a tree structure - -If you want a quickstart at how to use this tool, head right into the -:ref:`Usage section `. +* Export Capella model elements as ROS2 message (.msg) files. +* Import ROS2 message (.msg) files as Capella model elements. +* Works with local and remote message files/Capella projects. .. toctree:: :maxdepth: 2 :caption: Contents: -.. toctree:: - :maxdepth: 2 - :caption: Usage - usage - - -.. toctree:: - :maxdepth: 2 - :caption: Examples - howtos - -.. toctree:: - :maxdepth: 2 - :caption: ROS2 Messages Layout - messages .. toctree:: :maxdepth: 3 - :caption: API reference + :caption: API reference: code/modules diff --git a/docs/source/messages.rst b/docs/source/messages.rst index 6301af8..5046270 100644 --- a/docs/source/messages.rst +++ b/docs/source/messages.rst @@ -18,7 +18,7 @@ Class Definition * **Indented Comment Lines:** Comments on a line of their own but indented are added to the description of the last encountered property. * **Block Comments:** Comments on a line of their own and not indented are added to the description of the next properties until an empty line and the block comment has been used. -.. literalinclude:: ../../tests/data/example_msgs/msg/SampleClass.msg +.. literalinclude:: ../../tests/data/data_model/example_msgs/package1/msg/SampleClass.msg :language: python @@ -33,7 +33,7 @@ Enum definition * **Indented Comment Lines:** Comments on a line of their own but indented are added to the description of the last encountered enum literal. * **Block Comments:** Comments on a line of their own and not indented are added to the description of the next/current enum definition until an empty line and the block comment has been used. -.. literalinclude:: ../../tests/data/example_msgs/msg/SampleEnum.msg +.. literalinclude:: ../../tests/data/data_model/example_msgs/package1/msg/SampleEnum.msg :language: python Enum and Class Definition @@ -46,7 +46,7 @@ Enum and Class Definition * **Indented Comment Lines:** Comments on a line of their own but indented are added to the description of the last encountered property or enum literal. * **Block Comments:** Comments on a line of their own and not indented are added to the descriptions of the next properties or added to the descriptions of the next/current enum until an empty line and the block comment has been used. -.. literalinclude:: ../../tests/data/example_msgs/msg/SampleClassEnum.msg +.. literalinclude:: ../../tests/data/data_model/example_msgs/package2/msg/SampleClassEnum.msg :language: python Referencing enums @@ -61,7 +61,7 @@ In the Same File * Name matching takes precedence over type matching. -.. literalinclude:: ../../tests/data/example_msgs/msg/SampleClassEnum.msg +.. literalinclude:: ../../tests/data/data_model/example_msgs/package2/msg/SampleClassEnum.msg :language: python In another file @@ -72,8 +72,8 @@ In another file * **cf. :** The enum name was derived from the file name (excluding the extension). * **cf. , _XXX:** The enum name was derived from the longest common prefix of all enum literals in the definition. -.. literalinclude:: ../../tests/data/example_msgs/msg/SampleEnum.msg +.. literalinclude:: ../../tests/data/data_model/example_msgs/package1/msg/SampleEnum.msg :language: python -.. literalinclude:: ../../tests/data/example_msgs/msg/SampleClass.msg +.. literalinclude:: ../../tests/data/data_model/example_msgs/package1/msg/SampleClass.msg :language: python diff --git a/docs/source/usage.rst b/docs/source/usage.rst index 26dd736..207bfc2 100644 --- a/docs/source/usage.rst +++ b/docs/source/usage.rst @@ -14,32 +14,31 @@ Import ROS2 Messages: ---------------------- .. code-block:: bash - $ python -m capella_ros_tools import --port= --exists-action= --no-deps + $ python -m capella_ros_tools import \ + \ + \ + \ + --port= \ + --no-deps * ****, import ROS2 messages from * ****, export to Capella model * ****, use Capella model layer * **--port=**, start Capella model viewer at (optional) -* **--exists-action=**, action to take if a Capella element already exists (optional) - - * **skip**, skip elements - * **replace**, replace elements - * **abort**, abort import - * **ask**, ask the user (default) - * **--no-deps**, do not import ROS2 dependencies (e.g. std_msgs) -Tip: Use the `--port` option to start the Capella model viewer at a specific port. The Capella model viewer can then be downloaded to be viewed at a later time using the following command: -.. code-block:: bash - - $ wget http://localhost: -E -r +.. note:: + The `--port` option can be used to start the Capella model explorer on a specific port. The Capella model viewer can then be downloaded to be viewed at a later time using `wget` eg. `wget http://localhost: -E -r`. Export Capella Model (experimental): ------------------------------------ .. code-block:: bash - $ python -m capella_ros_tools export + $ python -m capella_ros_tools export \ + \ + \ + * ****, import Capella model from * ****, use Capella model layer diff --git a/pyproject.toml b/pyproject.toml index 3931f60..26b0c78 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,9 +30,6 @@ classifiers = [ dependencies = [ "click", "capellambse@ git+https://github.com/DSD-DBS/py-capellambse.git@decl-sync", - "capellambse_context_diagrams", - "fastapi", - "uvicorn[standard]", ] [project.urls]