@@ -204,3 +204,172 @@ Maximum line length
204204Best practice is to keep the length at or below 120 characters for code,
205205and comments. Lines longer than this might not display properly on some terminals
206206and tools or might be difficult to follow.
207+
208+ Extension development guide
209+ ---------------------------
210+
211+ This section describes the steps to create and integrate a PyAEDT extension at the **project level **.
212+ Extensions are modular components that add functionality to the AEDT environment via the PyAEDT API.
213+ They follow a structured convention to ensure consistency, maintainability, and documentation.
214+
215+ .. note ::
216+
217+ To create an extension at an **application level **, for example Hfss, the process is similar.
218+ The main difference is that the extension file and documentation should be placed in the
219+ appropriate directory, for example ``src/ansys/aedt/core/extensions/hfss `` and
220+ ``doc/source/User_guide/pyaedt_extensions_doc/hfss `` respectively.
221+
222+ Step 1: Create the extension Python file
223+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
224+
225+ Navigate to the directory ``src/ansys/aedt/core/extension/project `` and create a new Python file for
226+ your extension. The file name should be descriptive and follow the format ``extension_name.py ``, where
227+ ``extension_name `` is a lowercase, hyphen-separated name that describes the extension's functionality.
228+ The extension file should follow the official
229+ `template <https://github.com/ansys/pyaedt/blob/main/src/ansys/aedt/core/extensions/templates/template_get_started.py >`_
230+ and contain at least two classes:
231+
232+ 1. A class that inherits from ``ExtensionCommon `` and implements the extension's logic. By inheriting from
233+ ``ExtensionCommon ``, the extension's theme and style are automatically set and can leverage standard
234+ extension methods like the theme button handling, access to the AEDT application, and more.
235+ The custom content of the extension should be defined in the ``add_extension_content `` method and
236+ should be called in the ``__init__ `` method of the class. This method is where you can define the
237+ user interface (UI) elements, such as buttons, text fields, and other widgets to display. Below is
238+ an example of how to create a basic extension class:
239+
240+ .. code-block :: python
241+
242+ from ansys.aedt.core.extensions import ExtensionCommon, ExtensionData
243+
244+ class MyExtension (ExtensionCommon ):
245+ def __init__ (self , * args , ** kwargs ):
246+ super ().__init__ (* args, ** kwargs)
247+ self .add_extension_content()
248+
249+ def add_extension_content (self ):
250+ # Define your UI elements here
251+ pass
252+
253+ 2. A data class that inherits from ``ExtensionCommonData ``. This class should define the data that is provided
254+ and computed through the UI. Below is an example of how to create a data class for your extension:
255+
256+ .. code-block :: python
257+
258+ from dataclasses import dataclass
259+ from dataclasses import field
260+
261+ @dataclass
262+ class MyExtensionData (ExtensionCommonData ):
263+ setup: str = " "
264+ assignments: list = field(default_factory = lambda : [])
265+
266+ Splitting the extension logic into two classes allows for better separation of concerns and makes it easier to
267+ test and maintain the code. The first class handles the UI and user interactions, while the second class
268+ manages the data and logic behind the extension. On top of those classes, the file should also define a
269+ ``main `` function that is used to run the core logic behind the extension. This function should ingest an
270+ instance of the data class defined in the second step. Below is an example of how to define the ``main ``
271+ function:
272+
273+ .. code-block :: python
274+
275+ def main (extension_data : MyExtensionData):
276+ if not data.setup:
277+ raise AEDTRuntimeError(" No setup provided to the extension." )
278+
279+ # Core logic of the extension goes here
280+
281+ Step 2: Add unit tests
282+ ~~~~~~~~~~~~~~~~~~~~~~
283+
284+ Create a test file in the ``tests/unit/extensions `` directory with the same name as your extension file, but with a
285+ ``test_ `` prefix. For example, if your extension file is named ``my_extension.py ``, the test file should be
286+ ``test_my_extension.py ``. This file should mainly contain unit tests for your extension's UI components. For example
287+ checking that clicking a button triggers the expected action or that the UI elements are correctly initialized.
288+ If your extension requires AEDT to run, you should patch every method that requires AEDT to run, so that the test
289+ can be run without an active AEDT instance. This is important because unit tests should be fast and not depend on
290+ an external application like AEDT. You can use the `unittest.mock ` library to patch methods and classes that
291+ require AEDT. A good example of such a test file is the
292+ `test_template_extension.py <https://github.com/ansys/pyaedt/blob/main/tests/unit/extensions/test_template_extension.py >`_
293+ file where the instantiation of the ``Desktop `` class is patched to avoid the need for an active AEDT instance. Below
294+ is an example of how to create a unit test for your extension:
295+
296+ .. code-block::python
297+
298+ from unittest.mock import patch
299+ from ansys.aedt.core.extensions.project.my_extension import MyExtension, MyExtensionData
300+
301+ @patch("ansys.aedt.core.extensions.misc.Desktop")
302+ def test_my_extension(mock_desktop):
303+ extension = MyExtension()
304+
305+ assert "My extension title" == extension.root.title()
306+ assert "light" == extension.root.theme
307+ assert "No active project" == extension.active_project_name
308+
309+ extension.root.destroy()
310+
311+ Step 3: Add system tests
312+ ~~~~~~~~~~~~~~~~~~~~~~~~
313+
314+ Like the previous step, create a test file in the ``tests/system/extensions `` directory with the same name as your
315+ extension file, but with a ``test_ `` prefix. However, contrary to unit tests, system tests are meant to be run with
316+ an active AEDT instance. These tests should validate the overall functionality of the extension, ensuring that it
317+ behaves as expected when integrated into the AEDT environment.
318+
319+ .. code-block::python
320+
321+ from ansys.aedt.core.extensions.project.my_extension import MyExtension, MyExtensionData
322+ from ansys.aedt.core import Hfss
323+
324+ def test_my_extension_system(add_app):
325+
326+ # Create some data in AEDT to test the extension
327+ aedt_app = add_app(application=Hfss, project_name="my_project", design_name="my_design")
328+ aedt_app["p1"] = "100mm"
329+ aedt_app["p2"] = "71mm"
330+ test_points = [["0mm", "p1", "0mm"], ["-p1", "0mm", "0mm"], ["-p1/2", "-p1/2", "0mm"], ["0mm", "0mm", "0mm"]]
331+ p = aedt_app.modeler.create_polyline(
332+ points=test_points, segment_type=PolylineSegment("Spline", num_points=4), name="spline_4pt"
333+ )
334+
335+ # Create the extension and set its data by clicking on the "Generate" button
336+ extension = MyExtension()
337+ extension.root.nametowidget("generate").invoke()
338+
339+ # Check that the extension logic executes correctly
340+ assert 2 == len(aedt_app.variable_manager.variables)
341+ assert main(extension.data)
342+ assert 7 == len(aedt_app.variable_manager.variables)
343+
344+ Step 4: Add the extension to the catalog
345+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
346+
347+ Add your extension to the catalog by creating a new entry in the
348+ ``toolkits_catalog.toml `` file located in the ``src/ansys/aedt/core/extensions/project `` directory.
349+ The entry should follow the format of existing entries, specifying the name, script, icon, and template.
350+ For example, to add your extension, you would add an entry like this:
351+
352+ .. code-block :: toml
353+
354+ [MyExtension]
355+ name = "My Extension"
356+ script = "my_extension.py"
357+ icon = "images/large/my_extension_icon.png"
358+ template = "run_pyaedt_toolkit_script"
359+
360+ The path to the image is relative to the directory where your extension is located. For example, if
361+ the extension is located in the ``src/ansys/aedt/core/extensions/project `` directory then, following
362+ the previous code block information, the path to the icon should be
363+ ``src/ansys/aedt/core/extensions/project/images/large/my_extension_icon.png ``.
364+
365+ Step 5: Add the extension to the documentation
366+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
367+
368+ To ensure that your extension is documented, you need to add a new card to the
369+ ``doc/source/User_guide/extensions.rst `` file. This card links to the extension's documentation page.
370+ The documentation page needs to be created in the ``doc/source/User_guide/pyaedt_extensions_doc/project ``
371+ directory and should contain a brief description of the extension, its functionality, and how to use it.
372+ Also, another card should be added to the
373+ ``doc/source/User_guide/pyaedt_extensions_doc/project/index.rst `` file to link to the extension's documentation page.
374+ This ensures that the extension is discoverable in the documentation from the multiple pages that list all the
375+ extensions available in PyAEDT.
0 commit comments