diff --git a/doc/source/User_guide/extensions.rst b/doc/source/User_guide/extensions.rst index 8c1ddb02ba0..add4b9797ac 100644 --- a/doc/source/User_guide/extensions.rst +++ b/doc/source/User_guide/extensions.rst @@ -20,7 +20,7 @@ Project extensions ~~~~~~~~~~~~~~~~~~ Pre-installed extensions are available at project level so they are available for all AEDT applications. -They are small automated workflow with a simple UI. +They are small automated workflows with a simple UI. .. grid:: 2 @@ -60,7 +60,7 @@ HFSS 3D Layout extensions ~~~~~~~~~~~~~~~~~~~~~~~~~ Pre-installed extensions are available at HFSS 3D Layout level. -They are small automated workflow with a simple UI. +They are small automated workflows with a simple UI. .. grid:: 2 @@ -79,12 +79,40 @@ They are small automated workflow with a simple UI. Generate arbitrary wave ports in HFSS. + .. grid-item-card:: Push excitation from file + :link: pyaedt_extensions_doc/hfss3dlayout/push_excitation + :link-type: doc + :margin: 2 2 0 0 + + Edit a source from file data in HFSS 3D Layout. + + .. grid-item-card:: Cutout + :link: pyaedt_extensions_doc/hfss3dlayout/cutout + :link-type: doc + :margin: 2 2 0 0 + + Advanced layout cutout. + + .. grid-item-card:: Export layout + :link: pyaedt_extensions_doc/hfss3dlayout/export_layout + :link-type: doc + :margin: 2 2 0 0 + + Export layout. + + .. grid-item-card:: Export to 3D + :link: pyaedt_extensions_doc/hfss3dlayout/export_3d + :link-type: doc + :margin: 2 2 0 0 + + Export layout to 3D. + HFSS extensions ~~~~~~~~~~~~~~~ Pre-installed extensions are available at HFSS level. -They are small automated workflow with a simple UI. +They are small automated workflows with a simple UI. .. grid:: 2 @@ -95,12 +123,19 @@ They are small automated workflow with a simple UI. Design a choke and import it in HFSS. + .. grid-item-card:: Push excitation from file + :link: pyaedt_extensions_doc/hfss/push_excitation + :link-type: doc + :margin: 2 2 0 0 + + Edit a source from file data in HFSS. + Icepak extensions ~~~~~~~~~~~~~~~~~ Pre-installed extensions are available at Icepak level. -They are small automated workflow with a simple UI. +They are small automated workflows with a simple UI. .. grid:: 2 @@ -112,6 +147,38 @@ They are small automated workflow with a simple UI. Import a CSV file containing sources layout and power dissipation information. +Circuit extensions +~~~~~~~~~~~~~~~~~~ + +Pre-installed extensions are available at Circuit level. +They are small automated workflows with a simple UI. + +.. grid:: 2 + + .. grid-item-card:: Schematic import + :link: pyaedt_extensions_doc/circuit/import_schematic + :link-type: doc + :margin: 2 2 0 0 + + Import different schematic files (.asc, .sp, .cir, .qcv) into Circuit. + + +Twin Builder extensions +~~~~~~~~~~~~~~~~~~~~~~~ + +Pre-installed extensions are available at Twin Builder level. +They are small automated workflows with a simple UI. + +.. grid:: 2 + + .. grid-item-card:: Convert to Circuit + :link: pyaedt_extensions_doc/twinbuilder/convert_to_circuit + :link-type: doc + :margin: 2 2 0 0 + + Convert Twin Builder design to Circuit. + + .. toctree:: :hidden: :maxdepth: 1 @@ -120,6 +187,8 @@ They are small automated workflow with a simple UI. pyaedt_extensions_doc/hfss3dlayout/index pyaedt_extensions_doc/hfss/index pyaedt_extensions_doc/icepak/index + pyaedt_extensions_doc/circuit/index + pyaedt_extensions_doc/twinbuilder/index Open source toolkits @@ -180,7 +249,7 @@ The Python script requires a common initial part to define the port and the vers active_project = app.active_project() active_design = app.active_design(active_project) - # no need to hardcode you application but get_pyaedt_app will detect it for you + # no need to hardcode your application but get_pyaedt_app detects it for you aedtapp = ansys.aedt.core.get_pyaedt_app(design_name=active_design.GetName(), desktop=app) # your workflow diff --git a/doc/source/User_guide/pyaedt_extensions_doc/circuit/import_schematic.rst b/doc/source/User_guide/pyaedt_extensions_doc/circuit/import_schematic.rst new file mode 100644 index 00000000000..7e1a04e9b4f --- /dev/null +++ b/doc/source/User_guide/pyaedt_extensions_doc/circuit/import_schematic.rst @@ -0,0 +1,58 @@ +Import schematic +================ + +Import different schematic files (.asc, .sp, .cir, .qcv) into Circuit. + +The extension provides a graphical user interface for configuration, +or it can be used in batch mode via command line arguments. + +The following image shows the extension user interface: + +.. image:: ../../../_static/extensions/import_schematic_ui.png + :width: 800 + :alt: Import schematic UI + + +Using the extension +-------------------- + +1. Open the **Automation** tab in the Circuit interface. +2. Locate and click the **Import Schematic** icon under the Extension Manager. +3. In the user interface: + - Browse for a supported schematic file. + - Select the file and ensure its path appears in the text field. + - Click **Import** to load the schematic into Circuit. +4. Toggle between light and dark themes using the button in the bottom-right corner. + + +Command line +------------ + +You can also launch the extension directly from the terminal. + +The script accepts the following argument: + +- ``**asc_file**``: + Specifies the path to the schematic file to be imported. + The file must exist and should have one of the supported extensions. + Example: `"C:/schematics/example.asc"` + +Use the following command syntax: + +.. toctree:: + :maxdepth: 2 + + ../commandline + + +Supported file types +-------------------- + +The following schematic file formats are supported: +- **.asc**: Schematic files +- **.sp**: SPICE netlist files +- **.cir**: Circuit schematic files +- **.qcv**: QCV-specific files + +Each file type can be imported using the user interface or through the command line. +Ensure that the file exists at the specified path and is properly formatted. \ No newline at end of file diff --git a/doc/source/User_guide/pyaedt_extensions_doc/circuit/index.rst b/doc/source/User_guide/pyaedt_extensions_doc/circuit/index.rst new file mode 100644 index 00000000000..9a7397697df --- /dev/null +++ b/doc/source/User_guide/pyaedt_extensions_doc/circuit/index.rst @@ -0,0 +1,11 @@ +Circuit extensions +================== + +.. grid:: 2 + + .. grid-item-card:: Import schematic + :link: import_schematic + :link-type: doc + :margin: 2 2 0 0 + + Import different schematic files (.asc, .sp, .cir, .qcv) into Circuit. diff --git a/doc/source/User_guide/pyaedt_extensions_doc/hfss/choke_designer.rst b/doc/source/User_guide/pyaedt_extensions_doc/hfss/choke_designer.rst index e13a30cf3d6..1ede4b5e769 100644 --- a/doc/source/User_guide/pyaedt_extensions_doc/hfss/choke_designer.rst +++ b/doc/source/User_guide/pyaedt_extensions_doc/hfss/choke_designer.rst @@ -1,9 +1,11 @@ Choke designer ============== -You can design a choke in HFSS. +The **Choke designer** extension enables users to create and customize choke configurations and export the generated +geometry to HFSS. -You can access the extension from the icon created on the **Automation** tab using the Extension Manager. +The extension provides a graphical user interface for configuration, +or it can be used in batch mode via command line arguments. The following image shows the extension user interface: @@ -12,14 +14,57 @@ The following image shows the extension user interface: :alt: Choke Designer UI -The available argument is: ``choke_config``. +Features +-------- -The ``choke_config`` parameter is a dictionary with choke configuration file content. See more information: :ref:`choke-file`. +- Configuring choke parameters including core dimensions, windings, layers, and material properties. +- Exporting designs to HFSS. +- Saving and loading configurations as JSON files. +- Switching between light and dark themes in the user interface. -You can also launch the extension user interface from the terminal. An example can be found here: +Using the extension +------------------- + +1. Open the **Automation** tab in the HFSS interface. +2. Locate and click the **Choke designer** icon under the Extension Manager. +3. In the user interface: + - Adjust configuration parameters in the **Left panel** using radio buttons for options such as number of windings and layer types. + - Modify detailed parameters for the core and windings in the **Right Panel** under respective tabs. + - Use the buttons at the bottom to: + - Save the current configuration as a `.json` file. + - Load an existing configuration file. + - Toggle between light and dark themes. +4. To export to HFSS, click **Export to HFSS**. Ensure that parameters are valid before exporting. +5. Adjust the settings for light or dark theme using the toggle button in the bottom-right corner. + + +Command line +------------ + +The extension can also be used directly via the command line for batch processing. + + +Use the following syntax to run the extension: .. toctree:: :maxdepth: 2 - ../commandline \ No newline at end of file + ../commandline + + +Example configuration file +-------------------------- + +Here is an example of a choke configuration in JSON format: :ref:`choke-file`. + +Ensure the parameters are valid before importing. + + +Validation rules +---------------- + +- The outer radius must be greater than the inner radius for the core and windings. +- Heights and wire diameters must be positive. + +The user interface provides detailed feedback on validation errors in the form of message boxes. diff --git a/doc/source/User_guide/pyaedt_extensions_doc/hfss/index.rst b/doc/source/User_guide/pyaedt_extensions_doc/hfss/index.rst index 461b8930112..c80a0b99976 100644 --- a/doc/source/User_guide/pyaedt_extensions_doc/hfss/index.rst +++ b/doc/source/User_guide/pyaedt_extensions_doc/hfss/index.rst @@ -9,3 +9,10 @@ HFSS extensions :margin: 2 2 0 0 Design a choke and import it in HFSS. + + .. grid-item-card:: Push excitation from file + :link: push_excitation + :link-type: doc + :margin: 2 2 0 0 + + Edit a source from file data in HFSS. diff --git a/doc/source/User_guide/pyaedt_extensions_doc/hfss/push_excitation.rst b/doc/source/User_guide/pyaedt_extensions_doc/hfss/push_excitation.rst new file mode 100644 index 00000000000..96346b0fe3f --- /dev/null +++ b/doc/source/User_guide/pyaedt_extensions_doc/hfss/push_excitation.rst @@ -0,0 +1,55 @@ +Push excitation from file +========================= + +The **push excitation from file** extension allows users to assign a time-domain excitation to a port in an HFSS design +by importing data from a file. + +The extension provides a graphical user interface (GUI) for configuration, +or it can be used in batch mode via command line arguments. + +Features +-------- + +- Automatically detects and lists available ports in the active HFSS design. +- Allows users to browse and select a time-domain excitation file. +- Supports both light and dark themes for the GUI. +- Assigns excitations programmatically using a batch-mode interface. +- Validates file paths and port selections to ensure proper configuration. + +The following image shows the extension user interface: + +.. image:: ../../../_static/extensions/push_excitation.png + :width: 800 + :alt: Push excitation UI + + +Using the extension +-------------------- + +1. Open the **Automation** tab in the HFSS interface. +2. Locate and click the **push excitation from file** icon under the Extension Manager. +3. The main window displays the following elements: + - **Port selection**: A dropdown menu to select the desired port from the HFSS design. + - **File browser**: A text box and button to select the excitation file. + - **Push excitation button**: A button to assign the excitation to the selected port. + - **Theme toggle**: A button to switch between light and dark themes. +3. Click **Push excitation** after selecting the port and file to apply the configuration. + + +Command line +------------ + +The extension can also be used directly via the command line for batch processing. + +Supported arguments include: + +- **file path**: Path to the excitation file. +- **choice**: Name of the port to assign the excitation. +- **is batch**: Boolean flag to enable batch mode. + +Use the following syntax to run the extension: + +.. toctree:: + :maxdepth: 2 + + ../commandline diff --git a/doc/source/User_guide/pyaedt_extensions_doc/hfss3dlayout/arbitrary_wave_port.rst b/doc/source/User_guide/pyaedt_extensions_doc/hfss3dlayout/arbitrary_wave_port.rst index 367cbcf796a..969c015869f 100644 --- a/doc/source/User_guide/pyaedt_extensions_doc/hfss3dlayout/arbitrary_wave_port.rst +++ b/doc/source/User_guide/pyaedt_extensions_doc/hfss3dlayout/arbitrary_wave_port.rst @@ -1,7 +1,21 @@ -Parametrize Layout -================== +Arbitrary wave port +=================== -This extension is used to generate arbitrary wave ports. +This extension automates the creation of arbitrary wave ports for PCB layouts or other design files. + +Features +-------- + +- Provides an intuitive user interface for selecting nets, defining the cutout type, and adjusting parameters such as expansion factor. +- Supports batch processing for automated execution via command line arguments. +- Customizable options for fixing disjoint nets, selecting the cutout type, and applying expansion factors. +- Integration with Ansys EDB for layout cutout creation and post-processing. + +The following image shows the extension user interface: + +.. image:: ../../../_static/extensions/arbitrary_waveport_ui.png + :width: 800 + :alt: Arbitrary Wave Port Creator UI It assumes that oblong voids are explicit and some pad-stack instances are inside to define terminal. After defining the working directory and the source file used for creating wave ports, the combobox for defining @@ -17,15 +31,36 @@ only folders are displayed since EDB is an AEDB folder. The tool also supports other format, when the user does not check `Import EDB` box, the following file formats are available: odb++, brd, mcm, or zip are allowed. -The extension is accessible through the icon created by the Extension Manager in the **Automation** tab. -The available arguments are: ``working_path``, ``source_path``, ``mounting_side``. +Using the extension +------------------- + +1. Open the **Automation** tab in the HFSS 3D Layout interface. +2. Locate and click the **Layout Cutout** icon under the Extension Manager. +3. The main window displays the following elements: + - **Working directory**: A text box to specify the directory where output files is saved. + - **Source layout**: A text box to specify the source layout design file (PCB files). + - **Mounting side**: A dropdown menu to select the mounting side (top or bottom) for the wave port. + - **Import EDB**: A checkbox to enable or disable the import of EDB files for the layout. + - **Theme toggle**: A button to switch between light and dark modes for the UI. +4. Select the desired options and click **Generate** to create the wave ports and the corresponding 3D component. + + +Command line +------------ + +The extension can also be used directly via the command line for batch processing. -``working_path`` and ``source_path`` define the working path and ECAD project path. -``mounting_side`` defines the port orientation in the layout. +Supported arguments include: -The extension user interface can also be launched from the terminal. An example can be found here: +- **working path**: The directory where output files are saved. +- **source_path**: The path to the source layout design file. +- **mounting side**: The side to mount the wave port on ("top" or "bottom"). +- **import edb**: Boolean flag to enable or disable EDB import. +- **is batch**: Boolean flag to enable batch mode (skip GUI). +- **is test**: Boolean flag to enable or disable test mode. +Use the following syntax to run the extension: .. toctree:: :maxdepth: 2 diff --git a/doc/source/User_guide/pyaedt_extensions_doc/hfss3dlayout/cutout.rst b/doc/source/User_guide/pyaedt_extensions_doc/hfss3dlayout/cutout.rst new file mode 100644 index 00000000000..8e6583c628e --- /dev/null +++ b/doc/source/User_guide/pyaedt_extensions_doc/hfss3dlayout/cutout.rst @@ -0,0 +1,59 @@ +Cutout +====== + +The **Layout cutout** extension allows users to create cutouts in HFSS 3D Layout designs based on selected nets. + +The extension provides a graphical user interface (GUI) for configuration, +or it can be used in batch mode via command line arguments. + + +Features +-------- + +- Provides an intuitive user interface for selecting nets, defining the cutout type, and adjusting parameters such as expansion factor. +- Supports batch processing for automated execution via command line arguments. +- Customizable options for fixing disjoint nets, selecting the cutout type, and applying expansion factors. +- Integration with Ansys EDB for layout cutout creation and post-processing. + +The following image shows the extension user interface: + +.. image:: ../../../_static/extensions/cutout_ui.png + :width: 800 + :alt: Parametrize Layout UI + + +Using the extension +------------------- + +1. Open the **Automation** tab in the HFSS 3D Layout interface. +2. Locate and click the **Layout Cutout** icon under the Extension Manager. +3. The main window displays the following elements: + - **Cutout type**: A dropdown menu to select the desired cutout type (ConvexHull, Bounding, Conforming). + - **Signal nets**: A button to apply selected signal nets from the layout. + - **Reference nets**: A button to apply selected reference nets. + - **Expansion factor**: A text box to define the expansion factor for the cutout (in mm). + - **Fix disjoint nets**: A checkbox to enable or disable fixing of disjoint nets. +4. Select the desired options and click **Create Cutout** to generate the cutout. + + +Command line +------------ + +The extension can also be used directly via the command line for batch processing. + +Supported arguments include: + + +- **choice**: Type of cutout to apply ("ConvexHull"). +- **signals**: List of signal nets to use for the cutout. +- **reference**: List of reference nets to use for the cutout. +- **expansion factor**: Expansion factor in "mm" for the cutout. +- **fix disjoints**: Boolean flag to enable or disable fixing of disjoint nets. +- **is batch**: Boolean flag to enable batch mode. + +Use the following syntax to run the extension: + +.. toctree:: + :maxdepth: 2 + + ../commandline diff --git a/doc/source/User_guide/pyaedt_extensions_doc/hfss3dlayout/export_3d.rst b/doc/source/User_guide/pyaedt_extensions_doc/hfss3dlayout/export_3d.rst new file mode 100644 index 00000000000..ff8842e755e --- /dev/null +++ b/doc/source/User_guide/pyaedt_extensions_doc/hfss3dlayout/export_3d.rst @@ -0,0 +1,49 @@ +Export to 3D +============ + +The **Export to 3D** extension allows users to export their HFSS 3D Layout designs into various formats including HFSS, +Q3D, Maxwell 3D, and Icepak, keeping the net names. + +The extension provides a graphical user interface (GUI) for configuration, +or it can be used in batch mode via command line arguments. + + +Features +-------- + +- **Support for various export formats**: Choose from `HFSS`, `Q3D`, `Maxwell 3D`, and `Icepak` export options. +- **User interface**: A simple user interface for selecting the desired export option. + +The following image shows the extension user interface: + +.. image:: ../../../_static/extensions/export_3d_ui.png + :width: 800 + :alt: Parametrize Layout UI + + +Using the extension +------------------- + +1. Open the **Automation** tab in the HFSS 3D Layout interface. +2. Locate and click the **Export to 3D** icon under the Extension Manager. +3. The main window displays a label, a combobox to choose an export option, and a button to initiate the export. +3. Click **Export** to export the design. + + +Command line +------------ + +The extension can also be used directly via the command line for batch processing. + +Supported arguments include: + +- **choice**: The export option to choose (`"Export to HFSS"`, `"Export to Q3D"`, `"Export to Maxwell 3D"`, +or `"Export to Icepak"`). +- **is batch**: Boolean flag to indicate if the extension should run in batch mode. + +Use the following syntax to run the extension: + +.. toctree:: + :maxdepth: 2 + + ../commandline diff --git a/doc/source/User_guide/pyaedt_extensions_doc/hfss3dlayout/export_layout.rst b/doc/source/User_guide/pyaedt_extensions_doc/hfss3dlayout/export_layout.rst new file mode 100644 index 00000000000..b471cd309f8 --- /dev/null +++ b/doc/source/User_guide/pyaedt_extensions_doc/hfss3dlayout/export_layout.rst @@ -0,0 +1,52 @@ +Export layout +============= + +The **Layout exporter** extension allows users to export various design data from an HFSS 3D Layout, such as IPC2581, +Bill of materials (BOM), and configuration files. + +The extension provides a graphical user interface for configuration, +or it can be used in batch mode via command line arguments. + + +Features +-------- + +- Allows users to select and export data in multiple formats: IPC2581, BOM, and configuration files. +- Automatically detects the active project and design in AEDT. +- Provides a user-friendly GUI to configure export options. +- Supports both light and dark themes for the GUI. +- Enables batch processing for automation or command-line integration. +- Validates user input for file paths and selected options. + +The following image shows the extension user interface: + +.. image:: ../../../_static/extensions/export_layout_ui.png + :width: 800 + :alt: Export Layout UI + +Using the extension +------------------- + +1. Open the **Automation** tab in the HFSS 3D Layout interface. +2. Locate and click the **Layout Cutout** icon under the Extension Manager. +3. Users can select from the following options: + - **Export IPC2581**: Checkbox to export the layout data as IPC2581. + - **Export Configuration**: Checkbox to export the configuration file. + - **Export BOM**: Checkbox to export the Bill of Materials (BOM). + - **Theme toggle**: Button to switch between light and dark themes. +4. Once the options are selected, click the **Export** button to generate the respective files. +5. The selected files are saved in the same directory as the AEDB project. + + +Command line +------------ + +The extension can also be used directly via the command line for batch processing. + +Use the following syntax to run the extension: + + +.. toctree:: + :maxdepth: 2 + + ../commandline diff --git a/doc/source/User_guide/pyaedt_extensions_doc/hfss3dlayout/index.rst b/doc/source/User_guide/pyaedt_extensions_doc/hfss3dlayout/index.rst index d6fbb5d07a8..df3d2bc7852 100644 --- a/doc/source/User_guide/pyaedt_extensions_doc/hfss3dlayout/index.rst +++ b/doc/source/User_guide/pyaedt_extensions_doc/hfss3dlayout/index.rst @@ -16,3 +16,31 @@ HFSS 3D Layout extensions :margin: 2 2 0 0 Generate arbitrary wave ports in HFSS + + .. grid-item-card:: Push excitation from file + :link: push_excitation + :link-type: doc + :margin: 2 2 0 0 + + Edit a source from file data in HFSS. + + .. grid-item-card:: Cutout + :link: cutout + :link-type: doc + :margin: 2 2 0 0 + + Advanced layout cutout. + + .. grid-item-card:: Export layout + :link: export_layout + :link-type: doc + :margin: 2 2 0 0 + + Export layout. + + .. grid-item-card:: Export to 3D + :link: export_3d + :link-type: doc + :margin: 2 2 0 0 + + Export layout to 3D. diff --git a/doc/source/User_guide/pyaedt_extensions_doc/hfss3dlayout/parametrize_edb.rst b/doc/source/User_guide/pyaedt_extensions_doc/hfss3dlayout/parametrize_edb.rst index 80b760d35de..c2b1611054b 100644 --- a/doc/source/User_guide/pyaedt_extensions_doc/hfss3dlayout/parametrize_edb.rst +++ b/doc/source/User_guide/pyaedt_extensions_doc/hfss3dlayout/parametrize_edb.rst @@ -1,10 +1,17 @@ Parametrize Layout ================== -You can parametrize stackup, materials, padstacks and traces of an existing 3D Layout design and also -and change the size of voids and polygons to conduct corner analysis. +The extension allows users to apply parametrization to layouts in HFSS 3D Layout designs. -The extension is accessible through the icon created by the Extension Manager in the **Automation** tab. +This includes layers, materials, padstacks, traces, and nets, providing a flexible way to create parametric models. + + +Features +-------- + +- **Graphical User Interface (GUI)**: Provides an intuitive interface to configure and apply parametrization to various design elements. +- **Customizable Parameters**: Allows users to parametrize layers, materials, padstacks, nets, and traces, with support for relative and absolute parameters. +- **Polygon and void expansion**: Supports the expansion of polygons and voids by a specified amount (in mm). The following image shows the extension user interface: @@ -13,20 +20,43 @@ The following image shows the extension user interface: :alt: Parametrize Layout UI -The available arguments are: ``aedb_path``, ``design_name``, ``parametrize_layers``, -``parametrize_materials``, ``parametrize_padstacks``, ``parametrize_traces``, ``nets_filter``, -``expansion_polygon_mm``, ``expansion_void_mm``, ``relative_parametric``, ``project_name``. +Using the extension +------------------- + +1. Open the **Automation** tab in the HFSS 3D Layout interface. +2. Locate and click the **Layout Parametrization** icon under the Extension Manager. +3. The main window displays the following elements: + - **Project Name**: A text box to specify the name for the new parametric project. + - **Use Relative Parameters**: A checkbox to enable or disable relative parameters for the design. + - **Parametrize Layers**: A checkbox to enable parametrization of layers in the layout. + - **Parametrize Materials**: A checkbox to enable parametrization of materials. + - **Parametrize Padstacks**: A checkbox to enable parametrization of padstacks. + - **Parametrize Traces**: A checkbox to enable parametrization of traces. + - **Extend Polygons (mm)**: A text box to define the expansion size for polygons in mm. + - **Extend Voids (mm)**: A text box to define the expansion size for voids in mm. + - **Select Nets**: A list box to select nets (or leave empty for all nets) that is parametrized. +4. Select the desired options and click **Create Parametric Model** to generate the parametric model. + + +Command line +------------ -``aedb_path`` and ``design_name`` define the source aedb project. -``parametrize_layers``, ``parametrize_materials``, ``parametrize_padstacks``, ``parametrize_traces`` -define which part of the aedb has to be parametrized while the ``nets_filter`` defines which net has to be included. -``expansion_polygon_mm`` and ``expansion_void_mm`` define if and which value of expansion has to be applied on -polygons and voids. -``relative_parametric`` defines if the parameters have to be considered as a delta of the original value. -``project_name`` is the new project name. +Supported arguments include: -The extension user interface can also be launched from the terminal. An example can be found here: +- **aedb path**: Path to the input AEDB file. +- **design name**: Name of the design in the AEDB file. +- **parametrize layers**: Boolean flag to enable parametrization of layers. +- **parametrize materials**: Boolean flag to enable parametrization of materials. +- **parametrize padstacks**: Boolean flag to enable parametrization of padstacks. +- **parametrize traces**: Boolean flag to enable parametrization of traces. +- **nets filter**: List of nets to apply parametrization to (leave empty for all nets). +- **expansion polygon mm**: Expansion size for polygons in mm. +- **expansion void mm**: Expansion size for voids in mm. +- **relative parametric**: Boolean flag to apply relative parameters. +- **project name**: Name for the new parametric project. +- **is batch**: Boolean flag to enable batch mode. +Use the following syntax to run the extension: .. toctree:: :maxdepth: 2 diff --git a/doc/source/User_guide/pyaedt_extensions_doc/hfss3dlayout/push_excitation.rst b/doc/source/User_guide/pyaedt_extensions_doc/hfss3dlayout/push_excitation.rst new file mode 100644 index 00000000000..49649e5ed3f --- /dev/null +++ b/doc/source/User_guide/pyaedt_extensions_doc/hfss3dlayout/push_excitation.rst @@ -0,0 +1,55 @@ +Push excitation from file +========================= + +The **push excitation from file** extension allows users to assign a time-domain excitation to a port in an HFSS design +by importing data from a file. + +The extension provides a graphical user interface (GUI) for configuration, +or it can be used in batch mode via command line arguments. + +Features +-------- + +- Automatically detects and lists available ports in the active HFSS design. +- Allows users to browse and select a time-domain excitation file. +- Supports both light and dark themes for the GUI. +- Assigns excitations programmatically using a batch-mode interface. +- Validates file paths and port selections to ensure proper configuration. + +The following image shows the extension user interface: + +.. image:: ../../../_static/extensions/push_excitation.png + :width: 800 + :alt: Push excitation UI + + +Using the extension +-------------------- + +1. Open the **Automation** tab in the HFSS 3D Layout interface. +2. Locate and click the **push excitation from file** icon under the Extension Manager. +3. The main window displays the following elements: + - **Port selection**: A dropdown menu to select the desired port from the HFSS design. + - **File browser**: A text box and button to select the excitation file. + - **Push excitation button**: A button to assign the excitation to the selected port. + - **Theme toggle**: A button to switch between light and dark themes. +3. Click **Push excitation** after selecting the port and file to apply the configuration. + + +Command line +------------ + +The extension can also be used directly via the command line for batch processing. + +Supported arguments include: + +- **file_path**: Path to the excitation file. +- **choice**: Name of the port to assign the excitation. +- **is batch**: Boolean flag to enable batch mode. + +Use the following syntax to run the extension: + +.. toctree:: + :maxdepth: 2 + + ../commandline diff --git a/doc/source/User_guide/pyaedt_extensions_doc/icepak/create_power_map.rst b/doc/source/User_guide/pyaedt_extensions_doc/icepak/create_power_map.rst index 583e440a892..ca6d4769ff6 100644 --- a/doc/source/User_guide/pyaedt_extensions_doc/icepak/create_power_map.rst +++ b/doc/source/User_guide/pyaedt_extensions_doc/icepak/create_power_map.rst @@ -1,29 +1,48 @@ Create power map ================ -You can import a CSV file containing sources layout and power dissipation information in to Icepak. - -You can access the extension from the icon created on the **Automation** tab using the Extension Manager. +The extension allows users to generate power maps for an Icepak design from a CSV file containing geometric +and source data. The following image shows the extension user interface: -.. image:: ../../../_static/extensions/create_power_map_ui.png +.. image:: ../../../_static/extensions/power_map_ui.png :width: 800 :alt: Create Power Map UI -The available argument is: ``file_path``. +Using the extension +------------------- -CSV file example: +1. Open the **Automation** tab in the Icepak interface. +2. Locate and click the **Power Map from File** icon under the Extension Manager. +3. The main window displays the following elements: + - **Browse file**: A button to open a file dialog and select the CSV file containing geometric and source data. + - **Theme toggle**: A button to switch between light and dark themes for the UI. + - **Create**: A button to initiate the power map creation process after selecting the CSV file. +4. Select the desired CSV file and click **Create** to generate the power maps. -:download:`CSV File example <../../../Resources/icepak_classic_powermap.csv>` +Command line +------------ +The extension can also be used directly via the command line for batch processing. +Supported arguments include: -You can also launch the extension user interface from the terminal. An example can be found here: +- **file path**: The path to the CSV file that contains geometric and source data. +- **is batch**: Boolean flag to enable batch mode (set to `True` for batch processing). +- **is test**: Boolean flag to indicate if the operation is a test (set to `False` in production). +Use the following syntax to run the extension in batch mode: .. toctree:: :maxdepth: 2 - ../commandline \ No newline at end of file + ../commandline + +Example configuration file +-------------------------- + +Here is an example of a power map file: + +:download:`CSV File example <../../../Resources/icepak_classic_powermap.csv>` diff --git a/doc/source/User_guide/pyaedt_extensions_doc/project/import_nastran.rst b/doc/source/User_guide/pyaedt_extensions_doc/project/import_nastran.rst index 68979e366af..b59f090e20c 100644 --- a/doc/source/User_guide/pyaedt_extensions_doc/project/import_nastran.rst +++ b/doc/source/User_guide/pyaedt_extensions_doc/project/import_nastran.rst @@ -1,9 +1,10 @@ Import Nastran ============== -You can import a Nastran or STL file in any 3D modeler. You can also preview the imported file and decimate it prior to import. +The extension allows users to import Nastran or STL files into AEDT. -You can access the extension from the icon created on the **Automation** tab using the Extension Manager. +The extension provides options for configuring the import process, including decimation, lightweight import, +and planar merging. The following image shows the extension user interface: @@ -12,18 +13,47 @@ The following image shows the extension user interface: :alt: Import Nastran UI -The available arguments are: ``file_path``, ``planar``, ``lightweight``, and ``decimate``. +Features +-------- -By enabling ``lightweight`` check box the user imports the STL as a lightweight object. -The ``decimate`` parameter indicates the percentage of triangles that have to be removed during simplification. -A value of ``0.2`` means that 20% of the triangles is removed from the data read from the -file before importing in AEDT. -The ``planar`` parameter indicates that touching triangles that lie on the same plane are merged. +- Importing Nastran or STL files into AEDT. +- Configuring the import process with options such as: + - Decimation factor for mesh reduction. + - Import as lightweight geometry (only for HFSS). + - Planar merging for geometry simplification. +- Previewing the geometry before final import. +- Switching between light and dark themes in the user interface. -You can also launch the extension user interface from the terminal. An example can be found here: +Using the extension +-------------------- + +1. Open the **Automation** tab. +2. Locate and click the **Import Nastran or STL File** icon under the Extension Manager. +3. In the user interface: + - Select a Nastran (`.nas`) or STL (`.stl`) file using the **Browse** button. + - Set the **Decimation factor** to control the level of mesh reduction. + - Enable **Lightweight Import** to import the geometry as lightweight (HFSS only). + - Enable **Planar Merge** to simplify planar surfaces in the geometry. + - Use the **Preview** button to preview the geometry before importing. +4. To import the geometry into AEDT, click **OK**. Ensure that the settings are correct before finalizing the import. +5. Adjust the theme (light or dark) using the theme toggle button at the bottom-right corner of the window. + +Command line +------------ + +The extension can also be used directly via the command line for batch processing. + +Supported arguments include: + +- **file_path**: Specifies the path to the Nastran or STL file to be imported. +- **lightweight**: Specifies whether to import the geometry as lightweight (True/False). +- **decimate**: Specifies the decimation factor for mesh reduction (float). +- **planar**: Specifies whether to enable planar merging (True/False). + +Use the following syntax to run the extension: .. toctree:: :maxdepth: 2 - ../commandline \ No newline at end of file + ../commandline diff --git a/doc/source/User_guide/pyaedt_extensions_doc/twinbuilder/convert_to_circuit.rst b/doc/source/User_guide/pyaedt_extensions_doc/twinbuilder/convert_to_circuit.rst new file mode 100644 index 00000000000..0f8e0901cb1 --- /dev/null +++ b/doc/source/User_guide/pyaedt_extensions_doc/twinbuilder/convert_to_circuit.rst @@ -0,0 +1,24 @@ +Convert to circuit +================== + +The extension allows users to create a circuit design +from a Twin Builder design by mapping components from Twin Builder to circuit components. + + +Features +-------- + +- Mapping Twin Builder components to corresponding circuit components. + + +Command line +------------ + +The extension can also be used directly via the command line for batch processing. + +Use the following command syntax: + +.. toctree:: + :maxdepth: 2 + + ../commandline diff --git a/doc/source/User_guide/pyaedt_extensions_doc/twinbuilder/index.rst b/doc/source/User_guide/pyaedt_extensions_doc/twinbuilder/index.rst new file mode 100644 index 00000000000..d641671326c --- /dev/null +++ b/doc/source/User_guide/pyaedt_extensions_doc/twinbuilder/index.rst @@ -0,0 +1,11 @@ +Twin Builder extensions +======================= + +.. grid:: 2 + + .. grid-item-card:: Convert to circuit + :link: convert_to_circuit + :link-type: doc + :margin: 2 2 0 0 + + Convert Twin Builder design to Circuit. diff --git a/doc/source/_static/extensions/arbitrary_waveport_ui.png b/doc/source/_static/extensions/arbitrary_waveport_ui.png new file mode 100644 index 00000000000..51eabdc460b Binary files /dev/null and b/doc/source/_static/extensions/arbitrary_waveport_ui.png differ diff --git a/doc/source/_static/extensions/create_power_map_ui.png b/doc/source/_static/extensions/create_power_map_ui.png deleted file mode 100644 index 4c1f7e0a61e..00000000000 Binary files a/doc/source/_static/extensions/create_power_map_ui.png and /dev/null differ diff --git a/doc/source/_static/extensions/cutout_ui.png b/doc/source/_static/extensions/cutout_ui.png new file mode 100644 index 00000000000..5687b4097a2 Binary files /dev/null and b/doc/source/_static/extensions/cutout_ui.png differ diff --git a/doc/source/_static/extensions/export_3d_ui.png b/doc/source/_static/extensions/export_3d_ui.png new file mode 100644 index 00000000000..161396c48bc Binary files /dev/null and b/doc/source/_static/extensions/export_3d_ui.png differ diff --git a/doc/source/_static/extensions/export_layout_ui.png b/doc/source/_static/extensions/export_layout_ui.png new file mode 100644 index 00000000000..ba6cbbc70cc Binary files /dev/null and b/doc/source/_static/extensions/export_layout_ui.png differ diff --git a/doc/source/_static/extensions/import_nastran_ui.png b/doc/source/_static/extensions/import_nastran_ui.png index cbfdeb41d41..98c1c219093 100644 Binary files a/doc/source/_static/extensions/import_nastran_ui.png and b/doc/source/_static/extensions/import_nastran_ui.png differ diff --git a/doc/source/_static/extensions/import_schematic_ui.png b/doc/source/_static/extensions/import_schematic_ui.png new file mode 100644 index 00000000000..eebc6fc87fc Binary files /dev/null and b/doc/source/_static/extensions/import_schematic_ui.png differ diff --git a/doc/source/_static/extensions/kernel_convert_ui.png b/doc/source/_static/extensions/kernel_convert_ui.png index 7dd2cf987d3..509be8f95d7 100644 Binary files a/doc/source/_static/extensions/kernel_convert_ui.png and b/doc/source/_static/extensions/kernel_convert_ui.png differ diff --git a/doc/source/_static/extensions/parametrize.png b/doc/source/_static/extensions/parametrize.png index 5cf38be8c98..fa062e92495 100644 Binary files a/doc/source/_static/extensions/parametrize.png and b/doc/source/_static/extensions/parametrize.png differ diff --git a/doc/source/_static/extensions/power_map_ui.png b/doc/source/_static/extensions/power_map_ui.png new file mode 100644 index 00000000000..b457a5f248a Binary files /dev/null and b/doc/source/_static/extensions/power_map_ui.png differ diff --git a/doc/source/_static/extensions/push_excitation.png b/doc/source/_static/extensions/push_excitation.png new file mode 100644 index 00000000000..711c3b5df98 Binary files /dev/null and b/doc/source/_static/extensions/push_excitation.png differ diff --git a/src/ansys/aedt/core/workflows/circuit/import_schematic.py b/src/ansys/aedt/core/workflows/circuit/import_schematic.py index a084d4de468..c4ce40b1723 100644 --- a/src/ansys/aedt/core/workflows/circuit/import_schematic.py +++ b/src/ansys/aedt/core/workflows/circuit/import_schematic.py @@ -20,7 +20,8 @@ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -import os.path + +from pathlib import Path import ansys.aedt.core from ansys.aedt.core import Circuit @@ -38,26 +39,26 @@ # Extension batch arguments extension_arguments = {"asc_file": ""} -extension_description = "Import schematic to Circuit." +extension_description = "Import schematic to Circuit" def frontend(): # pragma: no cover - import tkinter from tkinter import filedialog from tkinter import ttk import PIL.Image import PIL.ImageTk + from ansys.aedt.core.workflows.misc import ExtensionTheme master = tkinter.Tk() - - master.geometry("750x250") - master.title(extension_description) + # Detect if user closes the UI + master.flag = False + # Load the logo for the main window - icon_path = os.path.join(ansys.aedt.core.workflows.__path__[0], "images", "large", "logo.png") + icon_path = Path(ansys.aedt.core.workflows.__path__[0]) / "images" / "large" / "logo.png" im = PIL.Image.open(icon_path) photo = PIL.ImageTk.PhotoImage(im) @@ -66,57 +67,105 @@ def frontend(): # pragma: no cover # Configure style for ttk buttons style = ttk.Style() - style.configure("Toolbutton.TButton", padding=6, font=("Helvetica", 8)) + theme = ExtensionTheme() + + # Apply light theme initially + theme.apply_light_theme(style) + master.theme = "light" + + # Set background color of the window (optional) + master.configure(bg=theme.light["widget_bg"]) + + label2 = ttk.Label(master, text="Browse file:", style="PyAEDT.TLabel") + label2.grid(row=0, column=0, pady=10, padx=10) - var2 = tkinter.StringVar() - label2 = tkinter.Label(master, textvariable=var2) - var2.set("Browse file:") - label2.grid(row=0, column=0, pady=10) text = tkinter.Text(master, width=40, height=1) text.grid(row=0, column=1, pady=10, padx=5) + text.configure(bg=theme.light["pane_bg"], foreground=theme.light["text"], font=theme.default_font) def browse_asc_folder(): inital_dir = text.get("1.0", tkinter.END).strip() filename = filedialog.askopenfilename( - initialdir=os.path.dirname(inital_dir) if inital_dir else "/", + initialdir=Path(inital_dir).parent if inital_dir else "/", title="Select configuration file", filetypes=(("LTSPice file", "*.asc"), ("Spice file", "*.cir *.sp"), ("Qcv file", "*.qcv")), ) text.insert(tkinter.END, filename) - b1 = tkinter.Button(master, text="...", width=10, command=browse_asc_folder) + b1 = ttk.Button(master, text="...", width=10, command=browse_asc_folder, style="PyAEDT.TButton") b1.grid(row=0, column=2, pady=10) + def toggle_theme(): + if master.theme == "light": + set_dark_theme() + master.theme = "dark" + else: + set_light_theme() + master.theme = "light" + + def set_light_theme(): + master.configure(bg=theme.light["widget_bg"]) + text.configure(bg=theme.light["pane_bg"], foreground=theme.light["text"], font=theme.default_font) + theme.apply_light_theme(style) + change_theme_button.config(text="\u263D") # Sun icon for light theme + + def set_dark_theme(): + master.configure(bg=theme.dark["widget_bg"]) + text.configure(bg=theme.dark["pane_bg"], foreground=theme.dark["text"], font=theme.default_font) + theme.apply_dark_theme(style) + change_theme_button.config(text="\u2600") # Moon icon for dark theme + + # Create a frame for the toggle button to position it correctly + button_frame = ttk.Frame(master, style="PyAEDT.TFrame", relief=tkinter.SUNKEN, borderwidth=2) + button_frame.grid(row=1, column=2, pady=10, padx=10) # Place it in the second row, third column + + # Add the toggle theme button inside the frame + change_theme_button = ttk.Button(button_frame, text="\u263D", command=toggle_theme, style="PyAEDT.TButton") + change_theme_button.grid(row=0, column=0, padx=0) + def callback(): + master.flag = True master.asc_path_ui = text.get("1.0", tkinter.END).strip() master.destroy() - b3 = tkinter.Button(master, text="Ok", width=40, command=callback) + b3 = ttk.Button(master, text="Import", width=40, command=callback, style="PyAEDT.TButton") b3.grid(row=1, column=1, pady=10, padx=10) tkinter.mainloop() asc_file_ui = getattr(master, "asc_path_ui", extension_arguments["asc_file"]) - output_dict = { - "asc_file": asc_file_ui, - } + output_dict = {} + if master.flag: + output_dict = { + "asc_file": asc_file_ui, + } return output_dict def main(extension_args): - asc_file = extension_args["asc_file"] - if not os.path.exists(asc_file): - raise Exception("Error. File doesn't exists.") - cir = Circuit(design=os.path.split(asc_file)[-1][:-4]) - if asc_file.endswith(".asc"): - cir.create_schematic_from_asc_file(asc_file) - elif asc_file.endswith(".sp") or asc_file.endswith(".cir"): - cir.create_schematic_from_netlist(asc_file) - elif asc_file.endswith(".qcv"): - cir.create_schematic_from_mentor_netlist(asc_file) + asc_file = Path(extension_args["asc_file"]) + if not asc_file.exists(): + raise Exception("File does not exist.") + + app = ansys.aedt.core.Desktop( + new_desktop=False, + version=version, + port=port, + aedt_process_id=aedt_process_id, + student_version=is_student, + ) + + cir = Circuit(design=asc_file.stem) + + if asc_file.suffix == ".asc": + cir.create_schematic_from_asc_file(str(asc_file)) + elif asc_file.suffix in {".sp", ".cir"}: + cir.create_schematic_from_netlist(str(asc_file)) + elif asc_file.suffix == ".qcv": + cir.create_schematic_from_mentor_netlist(str(asc_file)) if not extension_args["is_test"]: # pragma: no cover - cir.release_desktop(False, False) + app.release_desktop(False, False) return True @@ -130,5 +179,6 @@ def main(extension_args): for output_name, output_value in output.items(): if output_name in extension_arguments: args[output_name] = output_value - - main(args) + main(args) + else: + main(args) diff --git a/src/ansys/aedt/core/workflows/hfss/choke_designer.py b/src/ansys/aedt/core/workflows/hfss/choke_designer.py index 0bd442fa66c..83488d92469 100644 --- a/src/ansys/aedt/core/workflows/hfss/choke_designer.py +++ b/src/ansys/aedt/core/workflows/hfss/choke_designer.py @@ -83,7 +83,7 @@ # Extension batch arguments extension_arguments = {"choke_config": {}} -extension_description = "Choke Designer in HFSS" +extension_description = "Choke Designer" def frontend(): # pragma: no cover @@ -99,10 +99,7 @@ def frontend(): # pragma: no cover # Create UI master = tkinter.Tk() - - master.geometry("900x800") - - master.title("Choke Designer") + master.title(extension_description) # Detect if user close the UI master.flag = False @@ -269,10 +266,12 @@ def toggle_theme(): master.theme = "light" def set_light_theme(): + master.configure(bg=theme.light["widget_bg"]) theme.apply_light_theme(style) change_theme_button.config(text="\u263D") def set_dark_theme(): + master.configure(bg=theme.dark["widget_bg"]) theme.apply_dark_theme(style) change_theme_button.config(text="\u2600") @@ -409,7 +408,7 @@ def main(extension_args): ] # Add second winding ports if it exists - if second_winding_list: + if second_winding_list: # pragma: no cover port_position_list.extend( [ # Second winding start position diff --git a/src/ansys/aedt/core/workflows/hfss/push_excitation_from_file.py b/src/ansys/aedt/core/workflows/hfss/push_excitation_from_file.py index d2283e5fa74..d520e4911c9 100644 --- a/src/ansys/aedt/core/workflows/hfss/push_excitation_from_file.py +++ b/src/ansys/aedt/core/workflows/hfss/push_excitation_from_file.py @@ -21,7 +21,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -import os.path +from pathlib import Path import ansys.aedt.core from ansys.aedt.core import Hfss @@ -50,6 +50,7 @@ def frontend(): # pragma: no cover import PIL.Image import PIL.ImageTk + from ansys.aedt.core.workflows.misc import ExtensionTheme # Get ports app = ansys.aedt.core.Desktop( @@ -61,6 +62,12 @@ def frontend(): # pragma: no cover ) active_project = app.active_project() + + if not active_project: + app.logger.error("No active project.") + output_dict = {} + return output_dict + active_design = app.active_design() project_name = active_project.GetName() @@ -78,13 +85,13 @@ def frontend(): # pragma: no cover # Create UI master = tkinter.Tk() + master.title(extension_description) - master.geometry("700x150") - - master.title("Assign push excitation to port from transient data") + # Detect if user closes the UI + master.flag = False # Load the logo for the main window - icon_path = os.path.join(ansys.aedt.core.workflows.__path__[0], "images", "large", "logo.png") + icon_path = Path(ansys.aedt.core.workflows.__path__[0]) / "images" / "large" / "logo.png" im = PIL.Image.open(icon_path) photo = PIL.ImageTk.PhotoImage(im) @@ -93,23 +100,30 @@ def frontend(): # pragma: no cover # Configure style for ttk buttons style = ttk.Style() - style.configure("Toolbutton.TButton", padding=6, font=("Helvetica", 8)) + theme = ExtensionTheme() + + # Apply light theme initially + theme.apply_light_theme(style) + master.theme = "light" - var = tkinter.StringVar() - label = tkinter.Label(master, textvariable=var) - var.set("Choose a port:") - label.grid(row=0, column=0, pady=10) - combo = ttk.Combobox(master, width=30) + # Set background color of the window (optional) + master.configure(bg=theme.light["widget_bg"]) + + label = ttk.Label(master, text="Choose a port:", style="PyAEDT.TLabel") + label.grid(row=0, column=0, pady=10, padx=10) + + combo = ttk.Combobox(master, width=30, style="PyAEDT.TCombobox") combo["values"] = port_selection combo.current(0) combo.grid(row=0, column=1, pady=10, padx=5) combo.focus_set() - var2 = tkinter.StringVar() - label2 = tkinter.Label(master, textvariable=var2) - var2.set("Browse file:") - label2.grid(row=1, column=0, pady=10) + + label2 = ttk.Label(master, text="Browse file:", style="PyAEDT.TLabel") + label2.grid(row=1, column=0, pady=10, padx=10) + text = tkinter.Text(master, width=50, height=1) text.grid(row=1, column=1, pady=10, padx=5) + text.configure(bg=theme.light["pane_bg"], foreground=theme.light["text"], font=theme.default_font) def browseFiles(): filename = filedialog.askopenfilename( @@ -119,39 +133,70 @@ def browseFiles(): ) text.insert(tkinter.END, filename) - b1 = tkinter.Button(master, text="...", width=10, command=browseFiles) - b1.grid(row=3, column=0) + b1 = ttk.Button(master, text="...", width=20, command=browseFiles, style="PyAEDT.TButton") b1.grid(row=1, column=2, pady=10) + def toggle_theme(): + if master.theme == "light": + set_dark_theme() + master.theme = "dark" + else: + set_light_theme() + master.theme = "light" + + def set_light_theme(): + master.configure(bg=theme.light["widget_bg"]) + text.configure(bg=theme.light["pane_bg"], foreground=theme.light["text"], font=theme.default_font) + theme.apply_light_theme(style) + change_theme_button.config(text="\u263D") # Sun icon for light theme + + def set_dark_theme(): + master.configure(bg=theme.dark["widget_bg"]) + text.configure(bg=theme.dark["pane_bg"], foreground=theme.dark["text"], font=theme.default_font) + theme.apply_dark_theme(style) + change_theme_button.config(text="\u2600") # Moon icon for dark theme + + # Create a frame for the toggle button to position it correctly + button_frame = ttk.Frame(master, style="PyAEDT.TFrame", relief=tkinter.SUNKEN, borderwidth=2) + button_frame.grid(row=2, column=2, pady=10, padx=10) + + # Add the toggle theme button inside the frame + change_theme_button = ttk.Button( + button_frame, width=20, text="\u263D", command=toggle_theme, style="PyAEDT.TButton" + ) + + change_theme_button.grid(row=0, column=0, padx=0) + def callback(): + master.flag = True master.choice_ui = combo.get() master.file_path_ui = text.get("1.0", tkinter.END).strip() master.destroy() - b = tkinter.Button(master, text="Ok", width=40, command=callback) + b = ttk.Button(master, text="Push Excitation", width=40, command=callback, style="PyAEDT.TButton") b.grid(row=2, column=1, pady=10) tkinter.mainloop() - file_path_ui = getattr(master, "file_path_ui", extension_arguments["file_path"]) + file_path_ui = Path(getattr(master, "file_path_ui", extension_arguments["file_path"])) choice_ui = getattr(master, "choice_ui", extension_arguments["choice"]) - if not file_path_ui or not os.path.isfile(file_path_ui): + if not file_path_ui.is_file(): app.logger.error("File does not exist.") if not choice_ui: app.logger.error("Excitation not found.") hfss.release_desktop(False, False) - - output_dict = {"choice": choice_ui, "file_path": file_path_ui} - + output_dict = {} + if master.flag and str(file_path_ui): + output_dict = {"choice": choice_ui, "file_path": str(file_path_ui)} return output_dict def main(extension_args): choice = extension_args["choice"] - file_path = extension_args["file_path"] + file_path = Path(extension_args["file_path"]) app = ansys.aedt.core.Desktop( new_desktop=False, @@ -162,17 +207,24 @@ def main(extension_args): ) active_project = app.active_project() + + if not active_project: # pragma: no cover + app.logger.error("No active project.") + active_design = app.active_design() + if not active_design: # pragma: no cover + app.logger.error("No active design.") + project_name = active_project.GetName() design_name = active_design.GetName() hfss = Hfss(project_name, design_name) - if not os.path.isfile(file_path): # pragma: no cover + if not file_path.is_file(): # pragma: no cover app.logger.error("File does not exist.") elif choice: - hfss.edit_source_from_file(assignment=choice, input_file=file_path, is_time_domain=True) + hfss.edit_source_from_file(assignment=choice, input_file=str(file_path), is_time_domain=True) app.logger.info("Excitation assigned correctly.") else: app.logger.error("Failed to select a port.") @@ -192,5 +244,6 @@ def main(extension_args): for output_name, output_value in output.items(): if output_name in extension_arguments: args[output_name] = output_value - - main(args) + main(args) + else: + main(args) diff --git a/src/ansys/aedt/core/workflows/hfss3dlayout/cutout.py b/src/ansys/aedt/core/workflows/hfss3dlayout/cutout.py index bbd0d4044ed..50be70465a9 100644 --- a/src/ansys/aedt/core/workflows/hfss3dlayout/cutout.py +++ b/src/ansys/aedt/core/workflows/hfss3dlayout/cutout.py @@ -21,7 +21,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -import os +from pathlib import Path import ansys.aedt.core from ansys.aedt.core import Hfss3dLayout @@ -51,6 +51,13 @@ def frontend(): # pragma: no cover + import tkinter + from tkinter import ttk + + import PIL.Image + import PIL.ImageTk + from ansys.aedt.core.workflows.misc import ExtensionTheme + app = ansys.aedt.core.Desktop( new_desktop=False, version=version, @@ -75,20 +82,16 @@ def frontend(): # pragma: no cover objs_net = {} for net in h3d.oeditor.GetNets(): objs_net[net] = h3d.modeler.objects_by_net(net) - import tkinter - from tkinter import ttk - - import PIL.Image - import PIL.ImageTk master = tkinter.Tk() - master.geometry("700x450") + master.title(extension_description) - master.title("Advanced Cutout") + # Detect if user closes the UI + master.flag = False # Load the logo for the main window - icon_path = os.path.join(os.path.dirname(ansys.aedt.core.workflows.__file__), "images", "large", "logo.png") + icon_path = Path(ansys.aedt.core.workflows.__path__[0]) / "images" / "large" / "logo.png" im = PIL.Image.open(icon_path) photo = PIL.ImageTk.PhotoImage(im) @@ -97,16 +100,22 @@ def frontend(): # pragma: no cover # Configure style for ttk buttons style = ttk.Style() - style.configure("Toolbutton.TButton", padding=6, font=("Helvetica", 10)) + theme = ExtensionTheme() + + # Apply light theme initially + theme.apply_light_theme(style) + master.theme = "light" - var = tkinter.StringVar() - label = tkinter.Label(master, textvariable=var) - var.set("Cutout Type:") - label.grid(row=0, column=0, pady=10) - combo = ttk.Combobox(master, width=40) # Set the width of the combobox + # Set background color of the window (optional) + master.configure(bg=theme.light["widget_bg"]) + + label = ttk.Label(master, text="Cutout Type:", style="PyAEDT.TLabel") + label.grid(row=0, column=0, pady=10, padx=10) + + combo = ttk.Combobox(master, width=40, style="PyAEDT.TCombobox") # Set the width of the combobox combo["values"] = ("ConvexHull", "Bounding", "Conforming") combo.current(0) - combo.grid(row=0, column=1, pady=10) + combo.grid(row=0, column=1, pady=10, padx=10) combo.focus_set() master.signal_ui = [i for i in h3d.modeler.signal_nets.keys()] @@ -139,40 +148,77 @@ def apply_reference(): var3.set("Empty selection. Select nets from layout and retry.") var2 = tkinter.StringVar() - label2 = tkinter.Label(master, textvariable=var2, relief=tkinter.RAISED) + label2 = ttk.Label(master, textvariable=var2, style="PyAEDT.TLabel") var2.set("Select") - label2.grid(row=1, column=2, pady=10) - b_sig = tkinter.Button(master, text="Select signal nets in layout and Apply", width=40, command=apply_signal) - b_sig.grid(row=1, column=1, pady=10) + label2.grid(row=1, column=2, pady=10, padx=10) + + b_sig = ttk.Button( + master, text="Select signal nets in layout and Apply", width=40, command=apply_signal, style="PyAEDT.TButton" + ) + b_sig.grid(row=1, column=1, pady=10, padx=10) + var3 = tkinter.StringVar() - label3 = tkinter.Label(master, textvariable=var3, relief=tkinter.RAISED) + label3 = ttk.Label(master, textvariable=var3, style="PyAEDT.TLabel") var3.set("Select") - label3.grid(row=2, column=2, pady=10) - b_ref = tkinter.Button(master, text="Apply Reference Nets", width=40, command=apply_reference) + label3.grid(row=2, column=2, pady=10, padx=10) + + b_ref = ttk.Button(master, text="Apply Reference Nets", width=40, command=apply_reference, style="PyAEDT.TButton") b_ref.grid(row=2, column=1, pady=10) - var_exp = tkinter.StringVar() - label_exp = tkinter.Label(master, textvariable=var_exp) - var_exp.set("Expansion factor(mm):") - label_exp.grid(row=3, column=0, pady=10) + label_exp = ttk.Label(master, text="Expansion factor (mm):", style="PyAEDT.TLabel") + label_exp.grid(row=3, column=0, pady=10, padx=10) + expansion = tkinter.Text(master, width=20, height=1) + expansion.configure(bg=theme.light["pane_bg"], foreground=theme.light["text"], font=theme.default_font) expansion.insert(tkinter.END, "3") - expansion.grid(row=3, column=1, pady=10, padx=5) - var_disj = tkinter.StringVar() - label_disj = tkinter.Label(master, textvariable=var_disj) - var_disj.set("Fix disjoint nets:") + expansion.grid(row=3, column=1, pady=10, padx=10) + + label_disj = ttk.Label(master, text="Fix disjoint nets:", style="PyAEDT.TLabel") label_disj.grid(row=4, column=0, pady=10) + disjoint_check = tkinter.IntVar() - check2 = tkinter.Checkbutton(master, width=30, variable=disjoint_check) - check2.grid(row=4, column=1, pady=10, padx=5) + check2 = ttk.Checkbutton(master, variable=disjoint_check, style="PyAEDT.TCheckbutton") + check2.grid(row=4, column=1, pady=10) + + def toggle_theme(): + if master.theme == "light": + set_dark_theme() + master.theme = "dark" + else: + set_light_theme() + master.theme = "light" + + def set_light_theme(): + master.configure(bg=theme.light["widget_bg"]) + expansion.configure(bg=theme.light["pane_bg"], foreground=theme.light["text"], font=theme.default_font) + theme.apply_light_theme(style) + change_theme_button.config(text="\u263D") # Sun icon for light theme + + def set_dark_theme(): + master.configure(bg=theme.dark["widget_bg"]) + expansion.configure(bg=theme.dark["pane_bg"], foreground=theme.dark["text"], font=theme.default_font) + theme.apply_dark_theme(style) + change_theme_button.config(text="\u2600") # Moon icon for dark theme + + # Create a frame for the toggle button to position it correctly + button_frame = ttk.Frame(master, style="PyAEDT.TFrame", relief=tkinter.SUNKEN, borderwidth=2) + button_frame.grid(row=6, column=2, pady=10, padx=10) + + # Add the toggle theme button inside the frame + change_theme_button = ttk.Button( + button_frame, width=20, text="\u263D", command=toggle_theme, style="PyAEDT.TButton" + ) + + change_theme_button.grid(row=0, column=0, padx=0) def callback(): + master.flag = True master.choice_ui = combo.get() master.disjoints_ui = True if disjoint_check.get() == 1 else False master.expansion_ui = expansion.get("1.0", tkinter.END).strip() master.destroy() - b = tkinter.Button(master, text="Create Cutout", width=40, command=callback) + b = ttk.Button(master, text="Create Cutout", width=40, command=callback, style="PyAEDT.TButton") b.grid(row=6, column=1, pady=10) tkinter.mainloop() @@ -182,15 +228,16 @@ def callback(): expansion_ui = getattr(master, "expansion_ui", extension_arguments["expansion_factor"]) signal_ui = getattr(master, "signal_ui", extension_arguments["signals"]) reference_ui = getattr(master, "reference_ui", extension_arguments["reference"]) - - output_dict = { - "choice": choice_ui, - "signals": signal_ui, - "reference": reference_ui, - "expansion_factor": expansion_ui, - "fix_disjoints": disjoints_ui, - } + output_dict = {} app.release_desktop(False, False) + if master.flag: + output_dict = { + "choice": choice_ui, + "signals": signal_ui, + "reference": reference_ui, + "expansion_factor": expansion_ui, + "fix_disjoints": disjoints_ui, + } return output_dict @@ -210,17 +257,17 @@ def main(extension_args): active_project = app.active_project() active_design = app.active_design() - aedb_path = os.path.join(active_project.GetPath(), active_project.GetName() + ".aedb") - new_path = aedb_path[:-5] + generate_unique_name("_cutout", n=2) + ".aedb" - edb = Edb(aedb_path, active_design.GetName().split(";")[1], edbversion=version) - edb.save_edb_as(new_path) + aedb_path = Path(active_project.GetPath()) / f"{active_project.GetName()}.aedb" + new_path = aedb_path.with_stem(aedb_path.stem + generate_unique_name("_cutout", n=2)) + edb = Edb(str(aedb_path), active_design.GetName().split(";")[1], edbversion=version) + edb.save_edb_as(str(new_path)) edb.cutout( signal_list=signal, reference_list=reference, extent_type=choice, expansion_size=float(expansion) / 1000, use_round_corner=False, - output_aedb_path=new_path, + output_aedb_path=str(new_path), open_cutout_at_end=True, use_pyaedt_cutout=True, number_of_threads=4, @@ -242,7 +289,10 @@ def main(extension_args): if disjoint: edb.nets.find_and_fix_disjoint_nets(reference) edb.close_edb() - h3d = Hfss3dLayout(new_path) + + # Open layout in HFSS 3D Layout + Hfss3dLayout(str(new_path)) + if not extension_args["is_test"]: # pragma: no cover app.logger.info("Project generated correctly.") app.release_desktop(False, False) @@ -259,5 +309,6 @@ def main(extension_args): for output_name, output_value in output.items(): if output_name in extension_arguments: args[output_name] = output_value - - main(args) + main(args) + else: + main(args) diff --git a/src/ansys/aedt/core/workflows/hfss3dlayout/export_layout.py b/src/ansys/aedt/core/workflows/hfss3dlayout/export_layout.py index 38c6bb7b562..288af100638 100644 --- a/src/ansys/aedt/core/workflows/hfss3dlayout/export_layout.py +++ b/src/ansys/aedt/core/workflows/hfss3dlayout/export_layout.py @@ -21,7 +21,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -import os +from pathlib import Path import ansys.aedt.core import ansys.aedt.core.workflows.hfss3dlayout @@ -48,15 +48,16 @@ def frontend(): # pragma: no cover import PIL.Image import PIL.ImageTk + from ansys.aedt.core.workflows.misc import ExtensionTheme master = tkinter.Tk() + master.title(extension_description) - master.geometry("700x450") - - master.title("Layout exporter") + # Detect if user closes the UI + master.flag = False # Load the logo for the main window - icon_path = os.path.join(os.path.dirname(ansys.aedt.core.workflows.__file__), "images", "large", "logo.png") + icon_path = Path(ansys.aedt.core.workflows.__path__[0]) / "images" / "large" / "logo.png" im = PIL.Image.open(icon_path) photo = PIL.ImageTk.PhotoImage(im) @@ -65,55 +66,90 @@ def frontend(): # pragma: no cover # Configure style for ttk buttons style = ttk.Style() - style.configure("Toolbutton.TButton", padding=6, font=("Helvetica", 10)) + theme = ExtensionTheme() + + # Apply light theme initially + theme.apply_light_theme(style) + master.theme = "light" + + # Set background color of the window (optional) + master.configure(bg=theme.light["widget_bg"]) + + label = ttk.Label(master, text="Export IPC2581:", style="PyAEDT.TLabel") + label.grid(row=0, column=0, pady=10, padx=10) - var = tkinter.StringVar() - label = tkinter.Label(master, textvariable=var) - var.set("Export IPC2581:") - label.grid(row=0, column=0, pady=10) ipc_check = tkinter.IntVar() - check = tkinter.Checkbutton(master, width=30, variable=ipc_check) + check = ttk.Checkbutton(master, width=0, variable=ipc_check, style="PyAEDT.TCheckbutton") check.grid(row=0, column=1, pady=10, padx=5) ipc_check.set(1) - var2 = tkinter.StringVar() - label2 = tkinter.Label(master, textvariable=var2) - var2.set("Export Configuration file:") - label2.grid(row=1, column=0, pady=10) + label2 = ttk.Label(master, text="Export Configuration file:", style="PyAEDT.TLabel") + label2.grid(row=1, column=0, pady=10, padx=10) + configuration_check = tkinter.IntVar() - check2 = tkinter.Checkbutton(master, width=30, variable=configuration_check) + check2 = ttk.Checkbutton(master, width=0, variable=configuration_check, style="PyAEDT.TCheckbutton") check2.grid(row=1, column=1, pady=10, padx=5) configuration_check.set(1) - var3 = tkinter.StringVar() - label3 = tkinter.Label(master, textvariable=var3) - var3.set("Export BOM file:") - label3.grid(row=2, column=0, pady=10) + label3 = ttk.Label(master, text="Export BOM file:", style="PyAEDT.TLabel") + label3.grid(row=2, column=0, pady=10, padx=10) + bom_check = tkinter.IntVar() - check3 = tkinter.Checkbutton(master, width=30, variable=bom_check) + check3 = ttk.Checkbutton(master, width=0, variable=bom_check, style="PyAEDT.TCheckbutton") check3.grid(row=2, column=1, pady=10, padx=5) bom_check.set(1) + def toggle_theme(): + if master.theme == "light": + set_dark_theme() + master.theme = "dark" + else: + set_light_theme() + master.theme = "light" + + def set_light_theme(): + master.configure(bg=theme.light["widget_bg"]) + theme.apply_light_theme(style) + change_theme_button.config(text="\u263D") # Sun icon for light theme + + def set_dark_theme(): + master.configure(bg=theme.dark["widget_bg"]) + theme.apply_dark_theme(style) + change_theme_button.config(text="\u2600") # Moon icon for dark theme + + # Create a frame for the toggle button to position it correctly + button_frame = ttk.Frame(master, style="PyAEDT.TFrame", relief=tkinter.SUNKEN, borderwidth=2) + button_frame.grid(row=3, column=1, pady=10) + + # Add the toggle theme button inside the frame + change_theme_button = ttk.Button( + button_frame, width=20, text="\u263D", command=toggle_theme, style="PyAEDT.TButton" + ) + + change_theme_button.grid(row=0, column=0, padx=0) + def callback(): + master.flag = True master.ipc_ui = True if ipc_check.get() == 1 else False master.confg_ui = True if configuration_check.get() == 1 else False master.bom_ui = True if bom_check.get() == 1 else False master.destroy() - b = tkinter.Button(master, text="Export", width=40, command=callback) - b.grid(row=3, column=1, pady=10) + b = ttk.Button(master, text="Export", width=30, command=callback, style="PyAEDT.TButton") + b.grid(row=3, column=0, pady=10, padx=10) tkinter.mainloop() ipc_ui = getattr(master, "ipc_ui", extension_arguments["export_ipc"]) confg_ui = getattr(master, "confg_ui", extension_arguments["export_configuration"]) bom_ui = getattr(master, "bom_ui", extension_arguments["export_bom"]) - - output_dict = { - "export_ipc": ipc_ui, - "export_configuration": confg_ui, - "export_bom": bom_ui, - } + output_dict = {} + if master.flag: + output_dict = { + "export_ipc": ipc_ui, + "export_configuration": confg_ui, + "export_bom": bom_ui, + } return output_dict @@ -131,16 +167,16 @@ def main(extension_args): active_project = app.active_project() active_design = app.active_design() - aedb_path = os.path.join(active_project.GetPath(), active_project.GetName() + ".aedb") - edb = Edb(aedb_path, active_design.GetName().split(";")[1], edbversion=version) + aedb_path = Path(active_project.GetPath()) / f"{active_project.GetName()}.aedb" + edb = Edb(str(aedb_path), active_design.GetName().split(";")[1], edbversion=version) if ipc: - ipc_file = aedb_path[:-5] + "_ipc2581.xml" + ipc_file = aedb_path.with_name(aedb_path.stem + "_ipc2581.xml") edb.export_to_ipc2581(ipc_file) if bom: - bom_file = aedb_path[:-5] + "_bom.csv" + bom_file = aedb_path.with_name(aedb_path.stem + "_bom.csv") edb.workflow.export_bill_of_materials(bom_file) if config: - config_file = aedb_path[:-5] + "_config.json" + config_file = aedb_path.with_name(aedb_path.stem + "_config.json") edb.configuration.export(config_file) if not extension_args["is_test"]: # pragma: no cover @@ -159,5 +195,6 @@ def main(extension_args): for output_name, output_value in output.items(): if output_name in extension_arguments: args[output_name] = output_value - - main(args) + main(args) + else: + main(args) diff --git a/src/ansys/aedt/core/workflows/hfss3dlayout/export_to_3d.py b/src/ansys/aedt/core/workflows/hfss3dlayout/export_to_3d.py index 3202a6d5de1..ae049bcddb1 100644 --- a/src/ansys/aedt/core/workflows/hfss3dlayout/export_to_3d.py +++ b/src/ansys/aedt/core/workflows/hfss3dlayout/export_to_3d.py @@ -21,7 +21,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -import os +from pathlib import Path import ansys.aedt.core import ansys.aedt.core.workflows.hfss3dlayout @@ -38,7 +38,7 @@ # Extension batch arguments extension_arguments = {"choice": "Export to HFSS"} -extension_description = "Export layout to 3D Modeler" +extension_description = "Export to 3D" suffixes = {"Export to HFSS": "HFSS", "Export to Q3D": "Q3D", "Export to Maxwell 3D": "M3D", "Export to Icepak": "IPK"} @@ -50,15 +50,16 @@ def frontend(): # pragma: no cover import PIL.Image import PIL.ImageTk + from ansys.aedt.core.workflows.misc import ExtensionTheme master = tkinter.Tk() + master.title(extension_description) - master.geometry("400x150") - - master.title("Export to 3D") + # Detect if user closes the UI + master.flag = False # Load the logo for the main window - icon_path = os.path.join(os.path.dirname(ansys.aedt.core.workflows.__file__), "images", "large", "logo.png") + icon_path = Path(ansys.aedt.core.workflows.__path__[0]) / "images" / "large" / "logo.png" im = PIL.Image.open(icon_path) photo = PIL.ImageTk.PhotoImage(im) @@ -67,33 +68,70 @@ def frontend(): # pragma: no cover # Configure style for ttk buttons style = ttk.Style() - style.configure("Toolbutton.TButton", padding=6, font=("Helvetica", 10)) + theme = ExtensionTheme() + + # Apply light theme initially + theme.apply_light_theme(style) + master.theme = "light" + + # Set background color of the window + master.configure(bg=theme.light["widget_bg"]) + + label = ttk.Label(master, text="Choose an option:", relief=tkinter.RAISED, style="PyAEDT.TLabel") + label.grid(row=0, column=0, columnspan=2, pady=10) - var = tkinter.StringVar() - label = tkinter.Label(master, textvariable=var, relief=tkinter.RAISED) - var.set("Choose an option:") - label.pack(pady=10) - combo = ttk.Combobox(master, width=40) # Set the width of the combobox + combo = ttk.Combobox(master, width=40, style="PyAEDT.TCombobox") # Set the width of the combobox combo["values"] = ("Export to HFSS", "Export to Q3D", "Export to Maxwell 3D", "Export to Icepak") combo.current(0) - combo.pack(pady=10) + combo.grid(row=1, column=0, columnspan=2, pady=10) combo.focus_set() + def toggle_theme(): + if master.theme == "light": + set_dark_theme() + master.theme = "dark" + else: + set_light_theme() + master.theme = "light" + + def set_light_theme(): + master.configure(bg=theme.light["widget_bg"]) + theme.apply_light_theme(style) + change_theme_button.config(text="\u263D") # Sun icon for light theme + + def set_dark_theme(): + master.configure(bg=theme.dark["widget_bg"]) + theme.apply_dark_theme(style) + change_theme_button.config(text="\u2600") # Moon icon for dark theme + + # Create a frame for the toggle button to position it correctly + button_frame = ttk.Frame(master, style="PyAEDT.TFrame", relief=tkinter.SUNKEN, borderwidth=2) + button_frame.grid(row=2, column=1, pady=10, padx=10, sticky="ew") + + # Add the toggle theme button inside the frame + change_theme_button = ttk.Button( + button_frame, width=20, text="\u263D", command=toggle_theme, style="PyAEDT.TButton" + ) + change_theme_button.grid(row=0, column=0, padx=0) + def callback(): + master.flag = True master.choice_ui = combo.get() master.destroy() - b = tkinter.Button(master, text="Export", width=40, command=callback) - b.pack(pady=10) + b = ttk.Button(master, text="Export", width=20, command=callback, style="PyAEDT.TButton") + b.grid(row=2, column=0, pady=10, padx=10) tkinter.mainloop() choice_ui = getattr(master, "choice_ui", extension_arguments["choice"]) - output_dict = { - "choice": choice_ui, - } + output_dict = {} + if master.flag: + output_dict = { + "choice": choice_ui, + } return output_dict @@ -163,5 +201,6 @@ def main(extension_args): for output_name, output_value in output.items(): if output_name in extension_arguments: args[output_name] = output_value - - main(args) + main(args) + else: + main(args) diff --git a/src/ansys/aedt/core/workflows/hfss3dlayout/generate_arbitrary_wave_ports.py b/src/ansys/aedt/core/workflows/hfss3dlayout/generate_arbitrary_wave_ports.py index 90363289d98..20336329276 100644 --- a/src/ansys/aedt/core/workflows/hfss3dlayout/generate_arbitrary_wave_ports.py +++ b/src/ansys/aedt/core/workflows/hfss3dlayout/generate_arbitrary_wave_ports.py @@ -21,7 +21,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -import os +from pathlib import Path import time from tkinter import filedialog from tkinter import messagebox @@ -52,18 +52,19 @@ def frontend(): # pragma: no cover import PIL.Image import PIL.ImageTk + from ansys.aedt.core.workflows.misc import ExtensionTheme master = tkinter.Tk() + master.minsize(1000, 220) + master.maxsize(1000, 220) - master.geometry("680x220") + master.title(extension_description) - master.minsize(680, 220) - master.maxsize(680, 220) - - master.title("Arbitrary wave-port generator") + # Detect if user closes the UI + master.flag = False # Load the logo for the main window - icon_path = os.path.join(os.path.dirname(ansys.aedt.core.workflows.__file__), "images", "large", "logo.png") + icon_path = Path(ansys.aedt.core.workflows.__path__[0]) / "images" / "large" / "logo.png" im = PIL.Image.open(icon_path) photo = PIL.ImageTk.PhotoImage(im) @@ -72,13 +73,19 @@ def frontend(): # pragma: no cover # Configure style for ttk buttons style = ttk.Style() - style.configure("Toolbutton.TButton", padding=6, font=("Helvetica", 10)) + theme = ExtensionTheme() + + # Apply light theme initially + theme.apply_light_theme(style) + master.theme = "light" + + # Set background color of the window (optional) + master.configure(bg=theme.light["widget_bg"]) work_dir_path = tkinter.StringVar() source_file_path = tkinter.StringVar() mounting_side_variable = tkinter.StringVar() import_edb_variable = tkinter.BooleanVar() - # import_3d_component_variable = tkinter.BooleanVar() def browse_workdir_call_back(): work_dir_path.set(filedialog.askdirectory()) @@ -97,33 +104,36 @@ def browse_source_file_call_back(): source_file.insert("end", source_file_path.get()) # Working directory - var1 = tkinter.StringVar() - label_work_dir = tkinter.Label(master, textvariable=var1) - var1.set("Working directory") + label_work_dir = ttk.Label(master, text="Working directory", style="PyAEDT.TLabel") label_work_dir.grid(row=0, column=0, pady=10) + work_dir = tkinter.Text(master, width=60, height=1) + work_dir.configure(bg=theme.light["pane_bg"], foreground=theme.light["text"], font=theme.default_font) + # work_dir.setvar(work_dir_path, "") work_dir.grid(row=0, column=1, pady=10, padx=5) - work_dir_button = tkinter.Button(master, text="Browse", command=browse_workdir_call_back) + work_dir_button = ttk.Button(master, text="Browse", command=browse_workdir_call_back, style="PyAEDT.TButton") work_dir_button.grid(row=0, column=2, sticky="E") # source layout - var2 = tkinter.StringVar() - label_source = tkinter.Label(master, textvariable=var2) - var2.set("Source layout") + label_source = ttk.Label(master, text="Source layout", style="PyAEDT.TLabel") label_source.grid(row=1, column=0, pady=10) + source_file = tkinter.Text(master, width=60, height=1) + source_file.configure(bg=theme.light["pane_bg"], foreground=theme.light["text"], font=theme.default_font) source_file.setvar(source_file_path.get(), "") source_file.grid(row=1, column=1, pady=10, padx=5) - source_file_button = tkinter.Button(master, text="Browse", command=browse_source_file_call_back) + + source_file_button = ttk.Button(master, text="Browse", command=browse_source_file_call_back, style="PyAEDT.TButton") source_file_button.grid(row=1, column=2, sticky="E") # mounting side - var3 = tkinter.StringVar() - label_combobox = tkinter.Label(master, textvariable=var3) - var3.set("Mounting side") + label_combobox = ttk.Label(master, text="Mounting side", style="PyAEDT.TLabel") label_combobox.grid(row=2, column=0) - mounting_side_combo_box = ttk.Combobox(master=master, width=10, textvariable=mounting_side_variable) + + mounting_side_combo_box = ttk.Combobox( + master=master, width=10, textvariable=mounting_side_variable, style="PyAEDT.TCombobox" + ) mounting_side_combo_box["values"] = ("top", "bottom") mounting_side_combo_box.grid(row=3, column=0, padx=5, pady=10) mounting_side_combo_box.set("top") @@ -131,11 +141,45 @@ def browse_source_file_call_back(): # checkbox import EDB import_edb_variable.set(True) - ttk.Checkbutton(master=master, text="Import EDB", variable=import_edb_variable).grid( + ttk.Checkbutton(master=master, text="Import EDB", variable=import_edb_variable, style="PyAEDT.TCheckbutton").grid( row=3, column=1, padx=5, pady=10 ) + def toggle_theme(): + if master.theme == "light": + set_dark_theme() + master.theme = "dark" + else: + set_light_theme() + master.theme = "light" + + def set_light_theme(): + master.configure(bg=theme.light["widget_bg"]) + source_file.configure(bg=theme.light["pane_bg"], foreground=theme.light["text"], font=theme.default_font) + work_dir.configure(bg=theme.light["pane_bg"], foreground=theme.light["text"], font=theme.default_font) + theme.apply_light_theme(style) + change_theme_button.config(text="\u263D") # Sun icon for light theme + + def set_dark_theme(): + master.configure(bg=theme.dark["widget_bg"]) + source_file.configure(bg=theme.dark["pane_bg"], foreground=theme.dark["text"], font=theme.default_font) + work_dir.configure(bg=theme.dark["pane_bg"], foreground=theme.dark["text"], font=theme.default_font) + theme.apply_dark_theme(style) + change_theme_button.config(text="\u2600") # Moon icon for dark theme + + # Create a frame for the toggle button to position it correctly + button_frame = ttk.Frame(master, style="PyAEDT.TFrame", relief=tkinter.SUNKEN, borderwidth=2) + button_frame.grid(row=4, column=2, pady=10, padx=10) + + # Add the toggle theme button inside the frame + change_theme_button = ttk.Button( + button_frame, width=20, text="\u263D", command=toggle_theme, style="PyAEDT.TButton" + ) + + change_theme_button.grid(row=0, column=0, padx=0) + def callback(): + master.flag = True master.working_path_ui = work_dir.get("1.0", tkinter.END).strip() master.source_path_ui = source_file.get("1.0", tkinter.END).strip() master.mounting_side_ui = mounting_side_combo_box.get() @@ -143,8 +187,8 @@ def callback(): master.destroy() # execute button - execute_button = tkinter.Button(master=master, text="Generate", command=callback) - execute_button.grid(row=4, column=0, padx=5, pady=10) + execute_button = ttk.Button(master=master, text="Generate", command=callback, width=20, style="PyAEDT.TButton") + execute_button.grid(row=4, column=0, padx=10, pady=10) tkinter.mainloop() @@ -152,25 +196,28 @@ def callback(): source_path_ui = getattr(master, "source_path_ui", extension_arguments["source_path"]) mounting_side_ui = getattr(master, "mounting_side_ui", extension_arguments["mounting_side"]) - output_dict = { - "working_path": working_path_ui, - "source_path": source_path_ui, - "mounting_side": mounting_side_ui, - } + output_dict = {} + if master.flag: + output_dict = { + "working_path": working_path_ui, + "source_path": source_path_ui, + "mounting_side": mounting_side_ui, + } return output_dict def main(extension_args): - working_dir = extension_args["working_path"] - edb_file = extension_args["source_path"] + working_dir = Path(extension_args["working_path"]) + edb_file = Path(extension_args["source_path"]) mounting_side_variable = extension_args["mounting_side"] - edb_project = os.path.join(working_dir, "arbitrary_wave_port.aedb") - out_3d_project = os.path.join(working_dir, "output_3d.aedt") - component_3d_file = os.path.join(working_dir, "wave_port.a3dcomp") - if os.path.exists(working_dir): - if len(os.listdir(working_dir)) > 0: # pragma: no cover + edb_project = working_dir / "arbitrary_wave_port.aedb" + out_3d_project = working_dir / "output_3d.aedt" + component_3d_file = working_dir / "wave_port.a3dcomp" + + if working_dir.exists(): + if len(list(working_dir.iterdir())) > 0: # pragma: no cover res = messagebox.askyesno( title="Warning", message="The selected working directory is not empty, " @@ -182,7 +229,7 @@ def main(extension_args): edb = Edb(edbpath=rf"{edb_file}", edbversion=version) if not edb.create_model_for_arbitrary_wave_ports( - temp_directory=working_dir, mounting_side=mounting_side_variable, output_edb=edb_project + temp_directory=str(working_dir), mounting_side=mounting_side_variable, output_edb=str(edb_project) ): messagebox.showerror( "EDB model failure", @@ -202,13 +249,13 @@ def main(extension_args): student_version=is_student, ) - hfss3d = Hfss3dLayout(project=edb_project, version=version) + hfss3d = Hfss3dLayout(project=str(edb_project), version=version) setup = hfss3d.create_setup("wave_ports") - setup.export_to_hfss(file_fullname=out_3d_project, keep_net_name=True) + setup.export_to_hfss(file_fullname=str(out_3d_project), keep_net_name=True) time.sleep(1) hfss3d.close_project() - hfss = Hfss(projectname=out_3d_project, specified_version=version, new_desktop_session=False) + hfss = Hfss(projectname=str(out_3d_project), specified_version=version, new_desktop_session=False) hfss.solution_type = "Modal" # Deleting dielectric objects @@ -233,8 +280,8 @@ def main(extension_args): hfss.wave_port(assignment=sheet.id, reference="GND", terminals_rename=False) # create 3D component - hfss.save_project(file_name=out_3d_project) - hfss.modeler.create_3dcomponent(input_file=component_3d_file) + hfss.save_project(file_name=str(out_3d_project)) + hfss.modeler.create_3dcomponent(input_file=str(component_3d_file)) hfss.logger.info( f"3D component with arbitrary wave ports has been generated. " f"You can import the file located in working directory {working_dir}" @@ -256,4 +303,6 @@ def main(extension_args): for output_name, output_value in output.items(): if output_name in extension_arguments: args[output_name] = output_value - main(args) + main(args) + else: + main(args) diff --git a/src/ansys/aedt/core/workflows/hfss3dlayout/parametrize_edb.py b/src/ansys/aedt/core/workflows/hfss3dlayout/parametrize_edb.py index ab7cd9413fd..c0b1e1409ef 100644 --- a/src/ansys/aedt/core/workflows/hfss3dlayout/parametrize_edb.py +++ b/src/ansys/aedt/core/workflows/hfss3dlayout/parametrize_edb.py @@ -21,7 +21,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -import os +from pathlib import Path import ansys.aedt.core from ansys.aedt.core import Hfss3dLayout @@ -75,26 +75,27 @@ def frontend(): # pragma: no cover active_project = app.active_project() active_project_path = active_project.GetPath() active_project_name = active_project.GetName() - aedb_path = os.path.join(active_project_path, active_project_name + ".aedb") + aedb_path = Path(active_project_path) / (active_project_name + ".aedb") active_design_name = app.active_design().GetName().split(";")[1] app.release_desktop(False, False) - edb = Edb(aedb_path, active_design_name, edbversion=version) + edb = Edb(str(aedb_path), active_design_name, edbversion=version) import tkinter from tkinter import ttk import PIL.Image import PIL.ImageTk + from ansys.aedt.core.workflows.misc import ExtensionTheme master = tkinter.Tk() + master.title(extension_description) - master.geometry("900x600") - - master.title("Parametrize Layout") + # Detect if user closes the UI + master.flag = False # Load the logo for the main window - icon_path = os.path.join(os.path.dirname(ansys.aedt.core.workflows.__file__), "images", "large", "logo.png") + icon_path = Path(ansys.aedt.core.workflows.__path__[0]) / "images" / "large" / "logo.png" im = PIL.Image.open(icon_path) photo = PIL.ImageTk.PhotoImage(im) @@ -103,82 +104,83 @@ def frontend(): # pragma: no cover # Configure style for ttk buttons style = ttk.Style() - style.configure("Toolbutton.TButton", padding=6, font=("Helvetica", 10)) + theme = ExtensionTheme() + + # Apply light theme initially + theme.apply_light_theme(style) + master.theme = "light" + + # Set background color of the window (optional) + master.configure(bg=theme.light["widget_bg"]) - var9 = tkinter.StringVar() - label9 = tkinter.Label(master, textvariable=var9) - var9.set("New project name: ") + label9 = ttk.Label(master, text="New project name: ", style="PyAEDT.TLabel") label9.grid(row=0, column=0, pady=10) + project_name = tkinter.Entry(master, width=30) + project_name.configure(bg=theme.light["pane_bg"], foreground=theme.light["text"], font=theme.default_font) project_name.insert(tkinter.END, generate_unique_name(active_project_name, n=2)) project_name.grid(row=0, column=1, pady=10, padx=5) - var10 = tkinter.StringVar() - label10 = tkinter.Label(master, textvariable=var10) - var10.set("Use relative parameters: ") + label10 = ttk.Label(master, text="Use relative parameters: ", style="PyAEDT.TLabel") label10.grid(row=0, column=2, pady=10) + relative = tkinter.IntVar() - check5 = tkinter.Checkbutton(master, width=30, variable=relative) + check5 = ttk.Checkbutton(master, variable=relative, style="PyAEDT.TCheckbutton") check5.grid(row=0, column=3, pady=10, padx=5) relative.set(default_values["relative"]) - var1 = tkinter.StringVar() - label1 = tkinter.Label(master, textvariable=var1) - var1.set("Parametrize Layers:") + label1 = ttk.Label(master, text="Parametrize Layers:", style="PyAEDT.TLabel") label1.grid(row=1, column=0, pady=10) + layers = tkinter.IntVar() - check1 = tkinter.Checkbutton(master, width=30, variable=layers) + check1 = ttk.Checkbutton(master, variable=layers, style="PyAEDT.TCheckbutton") check1.grid(row=1, column=1, pady=10, padx=5) layers.set(default_values["layer"]) - var2 = tkinter.StringVar() - label2 = tkinter.Label(master, textvariable=var2) - var2.set("Parametrize Materials:") + label2 = ttk.Label(master, text="Parametrize Materials:", style="PyAEDT.TLabel") label2.grid(row=1, column=2, pady=10) + materials = tkinter.IntVar() - check2 = tkinter.Checkbutton(master, width=30, variable=materials) + check2 = ttk.Checkbutton(master, variable=materials, style="PyAEDT.TCheckbutton") check2.grid(row=1, column=3, pady=10, padx=5) materials.set(default_values["material"]) - var3 = tkinter.StringVar() - label3 = tkinter.Label(master, textvariable=var3) - var3.set("Parametrize Padstacks:") + label3 = ttk.Label(master, text="Parametrize Padstacks:", style="PyAEDT.TLabel") label3.grid(row=2, column=0, pady=10) + padstacks = tkinter.IntVar() - check3 = tkinter.Checkbutton(master, width=30, variable=padstacks) + check3 = ttk.Checkbutton(master, variable=padstacks, style="PyAEDT.TCheckbutton") check3.grid(row=2, column=1, pady=10, padx=5) padstacks.set(default_values["padstacks"]) - var5 = tkinter.StringVar() - label5 = tkinter.Label(master, textvariable=var5) - var5.set("Extend Polygons (mm): ") + label5 = ttk.Label(master, text="Extend Polygons (mm): ", style="PyAEDT.TLabel") label5.grid(row=3, column=0, pady=10) + polygons = tkinter.Entry(master, width=30) + polygons.configure(bg=theme.light["pane_bg"], foreground=theme.light["text"], font=theme.default_font) polygons.insert(tkinter.END, "0") polygons.grid(row=3, column=1, pady=10, padx=5) - var6 = tkinter.StringVar() - label6 = tkinter.Label(master, textvariable=var6) - var6.set("Extend Voids (mm): ") + label6 = ttk.Label(master, text="Extend Voids (mm): ", style="PyAEDT.TLabel") label6.grid(row=3, column=2, pady=10) voids = tkinter.Entry(master, width=30) + voids.configure(bg=theme.light["pane_bg"], foreground=theme.light["text"], font=theme.default_font) voids.insert(tkinter.END, "0") voids.grid(row=3, column=3, pady=10, padx=5) - var7 = tkinter.StringVar() - label7 = tkinter.Label(master, textvariable=var7) - var7.set("Parametrize Nets:") + label7 = ttk.Label(master, text="Parametrize Nets:", style="PyAEDT.TLabel") label7.grid(row=4, column=0, pady=10) + nets = tkinter.IntVar() - check4 = tkinter.Checkbutton(master, width=30, variable=nets) + check4 = ttk.Checkbutton(master, variable=nets, style="PyAEDT.TCheckbutton") check4.grid(row=4, column=1, pady=10, padx=5) nets.set(default_values["nets"]) - var8 = tkinter.StringVar() - label8 = tkinter.Label(master, textvariable=var8) - var8.set("Select Nets(None for all):") + label8 = ttk.Label(master, text="Select Nets(None for all):", style="PyAEDT.TLabel") label8.grid(row=4, column=2, pady=10) + net_list = tkinter.Listbox(master, height=20, width=30, selectmode=tkinter.MULTIPLE) + net_list.configure(bg=theme.light["pane_bg"], foreground=theme.light["text"], font=theme.default_font) net_list.grid(row=4, column=3, pady=5) idx = 1 @@ -186,7 +188,41 @@ def frontend(): # pragma: no cover net_list.insert(idx, net) idx += 1 - master.flag = False + def toggle_theme(): + if master.theme == "light": + set_dark_theme() + master.theme = "dark" + else: + set_light_theme() + master.theme = "light" + + def set_light_theme(): + master.configure(bg=theme.light["widget_bg"]) + polygons.configure(bg=theme.light["pane_bg"], foreground=theme.light["text"], font=theme.default_font) + project_name.configure(bg=theme.light["pane_bg"], foreground=theme.light["text"], font=theme.default_font) + voids.configure(bg=theme.light["pane_bg"], foreground=theme.light["text"], font=theme.default_font) + net_list.configure(bg=theme.light["pane_bg"], foreground=theme.light["text"], font=theme.default_font) + theme.apply_light_theme(style) + change_theme_button.config(text="\u263D") # Sun icon for light theme + + def set_dark_theme(): + master.configure(bg=theme.dark["widget_bg"]) + polygons.configure(bg=theme.dark["pane_bg"], foreground=theme.dark["text"], font=theme.default_font) + project_name.configure(bg=theme.dark["pane_bg"], foreground=theme.dark["text"], font=theme.default_font) + voids.configure(bg=theme.dark["pane_bg"], foreground=theme.dark["text"], font=theme.default_font) + net_list.configure(bg=theme.dark["pane_bg"], foreground=theme.dark["text"], font=theme.default_font) + theme.apply_dark_theme(style) + change_theme_button.config(text="\u2600") # Moon icon for dark theme + + button_frame = ttk.Frame(master, style="PyAEDT.TFrame", relief=tkinter.SUNKEN, borderwidth=2) + button_frame.grid(row=5, column=2, pady=10, padx=10) + + # Add the toggle theme button inside the frame + change_theme_button = ttk.Button( + button_frame, width=20, text="\u263D", command=toggle_theme, style="PyAEDT.TButton" + ) + + change_theme_button.grid(row=0, column=0, padx=0) def callback(): master.flag = True @@ -203,7 +239,7 @@ def callback(): master.net_list_ui.append(net_list.get(i)) master.destroy() - b = tkinter.Button(master, text="Create Parametric Model", width=40, command=callback) + b = ttk.Button(master, text="Create Parametric Model", width=40, command=callback, style="PyAEDT.TButton") b.grid(row=5, column=1, pady=10) edb.close_edb() @@ -221,7 +257,7 @@ def callback(): relative_ui = getattr(master, "relative_ui", extension_arguments["relative_parametric"]) output_dict = { - "aedb_path": os.path.join(active_project_path, active_project_name + ".aedb"), + "aedb_path": str(Path(active_project_path) / (active_project_name + ".aedb")), "design_name": active_design_name, "parametrize_layers": layers_ui, "parametrize_materials": materials_ui, @@ -251,6 +287,7 @@ def main(extension_arguments): relative_ui = extension_arguments.get("relative_parametric", True) design_name_ui = extension_arguments.get("design_name", "") aedb_path_ui = extension_arguments.get("aedb_path", "") + if not aedb_path_ui: app = ansys.aedt.core.Desktop( new_desktop=False, @@ -261,19 +298,19 @@ def main(extension_arguments): ) active_project = app.active_project() active_design = app.active_design() - aedb_path_ui = os.path.join(active_project.GetPath(), active_project.GetName() + ".aedb") + aedb_path_ui = Path(active_project.GetPath()) / (active_project.GetName() + ".aedb") design_name_ui = active_design.GetName().split(";")[1] - edb = Edb(aedb_path_ui, design_name_ui, edbversion=version) + edb = Edb(str(aedb_path_ui), design_name_ui, edbversion=version) try: poly_ui = float(poly_ui) * 0.001 - except: + except Exception: # pragma: no cover poly_ui = None try: voids_ui = float(voids_ui) * 0.001 - except: + except Exception: # pragma: no cover voids_ui = None - new_project_aedb = os.path.join(os.path.dirname(aedb_path_ui), project_name_ui + ".aedb") + new_project_aedb = Path(aedb_path_ui).parent / (project_name_ui + ".aedb") edb.auto_parametrize_design( layers=layers_ui, materials=materials_ui, @@ -287,14 +324,14 @@ def main(extension_arguments): trace_net_filter=nets_filter_ui, use_single_variable_for_padstack_definitions=True, use_relative_variables=relative_ui, - output_aedb_path=new_project_aedb, + output_aedb_path=str(new_project_aedb), open_aedb_at_end=False, expand_polygons_size=poly_ui, expand_voids_size=voids_ui, ) edb.close_edb() if not extension_arguments["is_test"]: # pragma: no cover - h3d = Hfss3dLayout(new_project_aedb) + h3d = Hfss3dLayout(str(new_project_aedb)) h3d.logger.info("Project generated correctly.") h3d.release_desktop(False, False) return True diff --git a/src/ansys/aedt/core/workflows/hfss3dlayout/push_excitation_from_file_3dl.py b/src/ansys/aedt/core/workflows/hfss3dlayout/push_excitation_from_file_3dl.py index b8157f1e3ae..a86246c0f61 100644 --- a/src/ansys/aedt/core/workflows/hfss3dlayout/push_excitation_from_file_3dl.py +++ b/src/ansys/aedt/core/workflows/hfss3dlayout/push_excitation_from_file_3dl.py @@ -21,7 +21,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -import os.path +from pathlib import Path import ansys.aedt.core from ansys.aedt.core import Hfss3dLayout @@ -50,6 +50,7 @@ def frontend(): # pragma: no cover import PIL.Image import PIL.ImageTk + from ansys.aedt.core.workflows.misc import ExtensionTheme # Get ports app = ansys.aedt.core.Desktop( @@ -61,6 +62,12 @@ def frontend(): # pragma: no cover ) active_project = app.active_project() + + if not active_project: + app.logger.error("No active project.") + output_dict = {} + return output_dict + active_design = app.active_design() project_name = active_project.GetName() @@ -84,13 +91,13 @@ def frontend(): # pragma: no cover # Create UI master = tkinter.Tk() + master.title(extension_description) - master.geometry("700x150") - - master.title("Assign push excitation to port from transient data") + # Detect if user closes the UI + master.flag = False # Load the logo for the main window - icon_path = os.path.join(ansys.aedt.core.workflows.__path__[0], "images", "large", "logo.png") + icon_path = Path(ansys.aedt.core.workflows.__path__[0]) / "images" / "large" / "logo.png" im = PIL.Image.open(icon_path) photo = PIL.ImageTk.PhotoImage(im) @@ -99,23 +106,30 @@ def frontend(): # pragma: no cover # Configure style for ttk buttons style = ttk.Style() - style.configure("Toolbutton.TButton", padding=6, font=("Helvetica", 8)) + theme = ExtensionTheme() + + # Apply light theme initially + theme.apply_light_theme(style) + master.theme = "light" - var = tkinter.StringVar() - label = tkinter.Label(master, textvariable=var) - var.set("Choose a port:") - label.grid(row=0, column=0, pady=10) - combo = ttk.Combobox(master, width=30) + # Set background color of the window (optional) + master.configure(bg=theme.light["widget_bg"]) + + label = ttk.Label(master, text="Choose a port:", style="PyAEDT.TLabel") + label.grid(row=0, column=0, pady=10, padx=10) + + combo = ttk.Combobox(master, width=30, style="PyAEDT.TCombobox") combo["values"] = port_selection combo.current(0) combo.grid(row=0, column=1, pady=10, padx=5) combo.focus_set() - var2 = tkinter.StringVar() - label2 = tkinter.Label(master, textvariable=var2) - var2.set("Browse file:") - label2.grid(row=1, column=0, pady=10) + + label2 = ttk.Label(master, text="Browse file:", style="PyAEDT.TLabel") + label2.grid(row=1, column=0, pady=10, padx=10) + text = tkinter.Text(master, width=50, height=1) text.grid(row=1, column=1, pady=10, padx=5) + text.configure(bg=theme.light["pane_bg"], foreground=theme.light["text"], font=theme.default_font) def browseFiles(): filename = filedialog.askopenfilename( @@ -125,24 +139,55 @@ def browseFiles(): ) text.insert(tkinter.END, filename) - b1 = tkinter.Button(master, text="...", width=10, command=browseFiles) - b1.grid(row=3, column=0) + b1 = ttk.Button(master, text="...", width=20, command=browseFiles, style="PyAEDT.TButton") b1.grid(row=1, column=2, pady=10) + def toggle_theme(): + if master.theme == "light": + set_dark_theme() + master.theme = "dark" + else: + set_light_theme() + master.theme = "light" + + def set_light_theme(): + master.configure(bg=theme.light["widget_bg"]) + text.configure(bg=theme.light["pane_bg"], foreground=theme.light["text"], font=theme.default_font) + theme.apply_light_theme(style) + change_theme_button.config(text="\u263D") # Sun icon for light theme + + def set_dark_theme(): + master.configure(bg=theme.dark["widget_bg"]) + text.configure(bg=theme.dark["pane_bg"], foreground=theme.dark["text"], font=theme.default_font) + theme.apply_dark_theme(style) + change_theme_button.config(text="\u2600") # Moon icon for dark theme + + # Create a frame for the toggle button to position it correctly + button_frame = ttk.Frame(master, style="PyAEDT.TFrame", relief=tkinter.SUNKEN, borderwidth=2) + button_frame.grid(row=2, column=2, pady=10, padx=10) + + # Add the toggle theme button inside the frame + change_theme_button = ttk.Button( + button_frame, width=20, text="\u263D", command=toggle_theme, style="PyAEDT.TButton" + ) + + change_theme_button.grid(row=0, column=0, padx=0) + def callback(): + master.flag = True master.choice_ui = combo.get() master.file_path_ui = text.get("1.0", tkinter.END).strip() master.destroy() - b = tkinter.Button(master, text="Ok", width=40, command=callback) + b = ttk.Button(master, text="Push Excitation", width=40, command=callback, style="PyAEDT.TButton") b.grid(row=2, column=1, pady=10) tkinter.mainloop() - file_path_ui = getattr(master, "file_path_ui", extension_arguments["file_path"]) + file_path_ui = Path(getattr(master, "file_path_ui", extension_arguments["file_path"])) choice_ui = getattr(master, "choice_ui", extension_arguments["choice"]) - if not file_path_ui or not os.path.isfile(file_path_ui): + if not file_path_ui.is_file(): app.logger.error("File does not exist.") if not choice_ui: @@ -150,14 +195,15 @@ def callback(): hfss_3dl.release_desktop(False, False) - output_dict = {"choice": choice_ui, "file_path": file_path_ui} - + output_dict = {} + if master.flag and str(file_path_ui): + output_dict = {"choice": choice_ui, "file_path": str(file_path_ui)} return output_dict def main(extension_args): choice = extension_args["choice"] - file_path = extension_args["file_path"] + file_path = Path(extension_args["file_path"]) app = ansys.aedt.core.Desktop( new_desktop=False, @@ -168,8 +214,15 @@ def main(extension_args): ) active_project = app.active_project() + + if not active_project: # pragma: no cover + app.logger.error("No active project.") + active_design = app.active_design() + if not active_design: # pragma: no cover + app.logger.error("No active design.") + project_name = active_project.GetName() if active_design.GetDesignType() in ["HFSS 3D Layout Design"]: @@ -181,12 +234,12 @@ def main(extension_args): hfss_3dl = Hfss3dLayout(project_name, design_name) - if not os.path.isfile(file_path): # pragma: no cover + if not file_path.is_file(): # pragma: no cover app.logger.error("File does not exist.") elif choice: hfss_3dl.edit_source_from_file( source=choice, - input_file=file_path, + input_file=str(file_path), is_time_domain=True, ) app.logger.info("Excitation assigned correctly.") @@ -208,5 +261,6 @@ def main(extension_args): for output_name, output_value in output.items(): if output_name in extension_arguments: args[output_name] = output_value - - main(args) + main(args) + else: + main(args) diff --git a/src/ansys/aedt/core/workflows/icepak/power_map_from_csv.py b/src/ansys/aedt/core/workflows/icepak/power_map_from_csv.py index 6638dea5612..063b7d57205 100644 --- a/src/ansys/aedt/core/workflows/icepak/power_map_from_csv.py +++ b/src/ansys/aedt/core/workflows/icepak/power_map_from_csv.py @@ -22,7 +22,7 @@ # SOFTWARE. import csv -import os +from pathlib import Path import ansys.aedt.core from ansys.aedt.core import Icepak @@ -51,6 +51,7 @@ def frontend(): # pragma: no cover import PIL.Image import PIL.ImageTk + from ansys.aedt.core.workflows.misc import ExtensionTheme app = ansys.aedt.core.Desktop( new_desktop=False, @@ -76,13 +77,13 @@ def frontend(): # pragma: no cover # Create UI master = tkinter.Tk() + master.title(extension_description) - master.geometry("600x150") - - master.title("Create power maps from csv file") + # Detect if user closes the UI + master.flag = False # Load the logo for the main window - icon_path = os.path.join(ansys.aedt.core.workflows.__path__[0], "images", "large", "logo.png") + icon_path = Path(ansys.aedt.core.workflows.__path__[0]) / "images" / "large" / "logo.png" im = PIL.Image.open(icon_path) photo = PIL.ImageTk.PhotoImage(im) @@ -91,12 +92,20 @@ def frontend(): # pragma: no cover # Configure style for ttk buttons style = ttk.Style() - style.configure("Toolbutton.TButton", padding=6, font=("Helvetica", 8)) - var2 = tkinter.StringVar() - label2 = tkinter.Label(master, textvariable=var2) - var2.set("Browse file:") + theme = ExtensionTheme() + + # Apply light theme initially + theme.apply_light_theme(style) + master.theme = "light" + + # Set background color of the window (optional) + master.configure(bg=theme.light["widget_bg"]) + + label2 = ttk.Label(master, text="Browse file:", style="PyAEDT.TLabel") label2.grid(row=1, column=0, pady=10) + text = tkinter.Text(master, width=50, height=1) + text.configure(bg=theme.light["pane_bg"], foreground=theme.light["text"], font=theme.default_font) text.grid(row=1, column=1, pady=10, padx=5) def browseFiles(): @@ -107,28 +116,61 @@ def browseFiles(): ) text.insert(tkinter.END, filename) - b1 = tkinter.Button(master, text="...", width=10, command=browseFiles) + b1 = ttk.Button(master, text="...", width=10, command=browseFiles, style="PyAEDT.TButton") b1.grid(row=3, column=0) b1.grid(row=1, column=2, pady=10) + def toggle_theme(): + if master.theme == "light": + set_dark_theme() + master.theme = "dark" + else: + set_light_theme() + master.theme = "light" + + def set_light_theme(): + master.configure(bg=theme.light["widget_bg"]) + text.configure(bg=theme.light["pane_bg"], foreground=theme.light["text"], font=theme.default_font) + theme.apply_light_theme(style) + change_theme_button.config(text="\u263D") # Sun icon for light theme + + def set_dark_theme(): + master.configure(bg=theme.dark["widget_bg"]) + text.configure(bg=theme.dark["pane_bg"], foreground=theme.dark["text"], font=theme.default_font) + theme.apply_dark_theme(style) + change_theme_button.config(text="\u2600") # Moon icon for dark theme + + # Create a frame for the toggle button to position it correctly + button_frame = ttk.Frame(master, style="PyAEDT.TFrame", relief=tkinter.SUNKEN, borderwidth=2) + button_frame.grid(row=2, column=2, pady=10, padx=10) + + # Add the toggle theme button inside the frame + change_theme_button = ttk.Button( + button_frame, width=20, text="\u263D", command=toggle_theme, style="PyAEDT.TButton" + ) + + change_theme_button.grid(row=0, column=0, padx=0) + def callback(): + master.flag = True master.file_path_ui = text.get("1.0", tkinter.END).strip() master.destroy() - b = tkinter.Button(master, text="Create", width=40, command=callback) + b = ttk.Button(master, text="Create", width=40, command=callback, style="PyAEDT.TButton") b.grid(row=2, column=1, pady=10) tkinter.mainloop() - file_path_ui = getattr(master, "file_path_ui", extension_arguments["file_path"]) + file_path_ui = Path(getattr(master, "file_path_ui", extension_arguments["file_path"])) - if not file_path_ui or not os.path.isfile(file_path_ui): + if not file_path_ui or not file_path_ui.is_file(): app.logger.error("File does not exist.") ipk.release_desktop(False, False) - output_dict = {"file_path": file_path_ui} - + output_dict = {} + if master.flag and file_path_ui.is_file(): + output_dict = {"file_path": str(file_path_ui)} return output_dict @@ -273,5 +315,6 @@ def extract_info(csv_file): for output_name, output_value in output.items(): if output_name in extension_arguments: args[output_name] = output_value - - main(args) + main(args) + else: + main(args) diff --git a/src/ansys/aedt/core/workflows/misc.py b/src/ansys/aedt/core/workflows/misc.py index 20e7f5246f9..12abbce5fff 100644 --- a/src/ansys/aedt/core/workflows/misc.py +++ b/src/ansys/aedt/core/workflows/misc.py @@ -102,7 +102,15 @@ def __init__(self): "radiobutton_selected": "#E0E0E0", # Color when selected "radiobutton_unselected": "#FFFFFF", # Color when unselected "pane_bg": "#F0F0F0", # Background for PanedWindow - "sash_color": "#C0C0C0", # Color for sash (separator) in PanedWindo + "sash_color": "#C0C0C0", # Color for sash (separator) in PanedWindow + "combobox_bg": "#FFFFFF", # Matches widget_bg + "combobox_arrow_bg": "#E6E6E6", # Matches button_bg + "combobox_arrow_fg": "#000000", # Matches text + "combobox_readonly_bg": "#F0F0F0", # Matches tab_bg_inactive + "checkbutton_bg": "#FFFFFF", # Matches widget_bg + "checkbutton_fg": "#000000", # Matches text + "checkbutton_indicator_bg": "#D9D9D9", # Matches button_hover_bg + "checkbutton_active_bg": "#B8B8B8", # Matches button_active_bg } self.dark = { @@ -125,6 +133,14 @@ def __init__(self): "radiobutton_selected": "#45494A", # Color when selected "radiobutton_unselected": "#313335", # Color when unselected "pane_bg": "#2E2E2E", # Background for PanedWindow + "combobox_bg": "#313335", # Matches widget_bg + "combobox_arrow_bg": "#606060", # Matches button_hover_bg + "combobox_arrow_fg": "#FFFFFF", # Matches text + "combobox_readonly_bg": "#2E2E2E", # Matches pane_bg + "checkbutton_bg": "#313335", # Matches widget_bg + "checkbutton_fg": "#FFFFFF", # Matches text + "checkbutton_indicator_bg": "#2E2E2E", # Matches pane_bg + "checkbutton_active_bg": "#45494A", # Matches radiobutton_selected } # Set default font @@ -199,6 +215,36 @@ def _apply_theme(self, style, colors): background=[("selected", colors["radiobutton_selected"]), ("!selected", colors["radiobutton_unselected"])], ) + # Apply the colors and font to the style for Combobox + style.configure( + "PyAEDT.TCombobox", + fieldbackground=colors["combobox_bg"], + background=colors["combobox_arrow_bg"], + foreground=colors["text"], + font=self.default_font, + arrowcolor=colors["combobox_arrow_fg"], + ) + style.map( + "PyAEDT.TCombobox", + fieldbackground=[("readonly", colors["combobox_readonly_bg"])], + foreground=[("readonly", colors["text"])], + ) + + # Style for Checkbutton + style.configure( + "PyAEDT.TCheckbutton", + background=colors["checkbutton_bg"], + foreground=colors["checkbutton_fg"], + font=self.default_font, + indicatorcolor=colors["checkbutton_indicator_bg"], + focuscolor=colors["checkbutton_active_bg"], # For focus/active state + ) + style.map( + "PyAEDT.TCheckbutton", + background=[("active", colors["checkbutton_active_bg"])], + indicatorcolor=[("selected", colors["checkbutton_indicator_bg"])], + ) + def __string_to_bool(v): # pragma: no cover """Change string to bool.""" diff --git a/src/ansys/aedt/core/workflows/project/advanced_fields_calculator.py b/src/ansys/aedt/core/workflows/project/advanced_fields_calculator.py index 7ccd443f493..933cc160f7a 100644 --- a/src/ansys/aedt/core/workflows/project/advanced_fields_calculator.py +++ b/src/ansys/aedt/core/workflows/project/advanced_fields_calculator.py @@ -21,7 +21,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -import os.path +from pathlib import Path import ansys.aedt.core from ansys.aedt.core import get_pyaedt_app @@ -41,7 +41,7 @@ # Extension batch arguments extension_arguments = {"setup": "", "calculation": "", "assignment": []} -extension_description = "Simplified use of Fields Calculator" +extension_description = "Advanced fields calculator" def frontend(): # pragma: no cover @@ -51,6 +51,7 @@ def frontend(): # pragma: no cover import PIL.Image import PIL.ImageTk + from ansys.aedt.core.workflows.misc import ExtensionTheme # Get ports app = ansys.aedt.core.Desktop( @@ -73,15 +74,14 @@ def frontend(): # pragma: no cover aedtapp = get_pyaedt_app(project_name, design_name) # Load new expressions from file - current_directory = os.getcwd() - all_files = os.listdir(current_directory) - toml_files = [f for f in all_files if f.endswith(".toml")] + current_directory = Path.cwd() + toml_files = list(current_directory.glob("*.toml")) for toml_file in toml_files: aedtapp.post.fields_calculator.load_expression_file(toml_file) # Personal Lib directory - all_files = os.listdir(aedtapp.personallib) - toml_files = [os.path.join(aedtapp.personallib, f) for f in all_files if f.endswith(".toml")] + personal_lib_directory = Path(aedtapp.personallib) + toml_files = list(personal_lib_directory.glob("*.toml")) for toml_file in toml_files: aedtapp.post.fields_calculator.load_expression_file(toml_file) @@ -105,13 +105,13 @@ def frontend(): # pragma: no cover # Create UI master = tkinter.Tk() + master.title(extension_description) - master.geometry("700x150") - - master.title("Advanced fields calculator") + # Detect if user closes the UI + master.flag = False # Load the logo for the main window - icon_path = os.path.join(ansys.aedt.core.workflows.__path__[0], "images", "large", "logo.png") + icon_path = Path(ansys.aedt.core.workflows.__path__[0]) / "images" / "large" / "logo.png" im = PIL.Image.open(icon_path) photo = PIL.ImageTk.PhotoImage(im) @@ -120,60 +120,97 @@ def frontend(): # pragma: no cover # Configure style for ttk buttons style = ttk.Style() - style.configure("Toolbutton.TButton", padding=6, font=("Helvetica", 8)) + theme = ExtensionTheme() + + # Apply light theme initially + theme.apply_light_theme(style) + master.theme = "light" + + # Set background color of the window (optional) + master.configure(bg=theme.light["widget_bg"]) - var = tkinter.StringVar() - label = tkinter.Label(master, textvariable=var) - var.set("Solved setup:") + label = ttk.Label(master, text="Solved setup:", style="PyAEDT.TLabel") label.grid(row=0, column=0, pady=10, padx=15) - combo_setup = ttk.Combobox(master, width=30) + + combo_setup = ttk.Combobox(master, width=30, style="PyAEDT.TCombobox") combo_setup["values"] = available_setups combo_setup.current(0) combo_setup.grid(row=0, column=1, pady=10, padx=10) combo_setup.focus_set() - var = tkinter.StringVar() - label = tkinter.Label(master, textvariable=var) - var.set("Calculations:") + label = ttk.Label(master, text="Calculations:", style="PyAEDT.TLabel") label.grid(row=1, column=0, pady=10, padx=15) - combo_calculation = ttk.Combobox(master, width=30) + + combo_calculation = ttk.Combobox(master, width=30, style="PyAEDT.TCombobox") combo_calculation["values"] = list(available_descriptions.values()) combo_calculation.current(0) combo_calculation.grid(row=1, column=1, pady=10, padx=10) combo_calculation.focus_set() + def toggle_theme(): + if master.theme == "light": + set_dark_theme() + master.theme = "dark" + else: + set_light_theme() + master.theme = "light" + + def set_light_theme(): + master.configure(bg=theme.light["widget_bg"]) + theme.apply_light_theme(style) + change_theme_button.config(text="\u263D") # Sun icon for light theme + + def set_dark_theme(): + master.configure(bg=theme.dark["widget_bg"]) + theme.apply_dark_theme(style) + change_theme_button.config(text="\u2600") # Moon icon for dark theme + + # Create a frame for the toggle button to position it correctly + button_frame = ttk.Frame(master, style="PyAEDT.TFrame", relief=tkinter.SUNKEN, borderwidth=2) + button_frame.grid(row=2, column=2, pady=10, padx=10) + + # Add the toggle theme button inside the frame + change_theme_button = ttk.Button( + button_frame, width=20, text="\u263D", command=toggle_theme, style="PyAEDT.TButton" + ) + + change_theme_button.grid(row=0, column=0, padx=0) + def callback(): + master.flag = True master.setup = combo_setup.get() master.calculation = combo_calculation.get() master.destroy() - b = tkinter.Button(master, text="Ok", width=40, command=callback) + b = ttk.Button(master, text="Ok", width=40, command=callback, style="PyAEDT.TButton") b.grid(row=2, column=1, pady=10) tkinter.mainloop() - setup_ui = getattr(master, "setup", extension_arguments["setup"]) - if getattr(master, "setup"): - setup = setup_ui - else: - setup = extension_arguments["setup"] + output_dict = {} + + if master.flag: + setup_ui = getattr(master, "setup", extension_arguments["setup"]) + if getattr(master, "setup"): + setup = setup_ui + else: + setup = extension_arguments["setup"] - calculation_ui = getattr(master, "calculation", extension_arguments["calculation"]) - calculation = extension_arguments["setup"] + calculation_ui = getattr(master, "calculation", extension_arguments["calculation"]) + calculation = extension_arguments["setup"] - if getattr(master, "setup"): - calculation_description = calculation_ui - for k, v in available_descriptions.items(): - if calculation_description == v: - calculation = k - break + if getattr(master, "setup"): + calculation_description = calculation_ui + for k, v in available_descriptions.items(): + if calculation_description == v: + calculation = k + break - assignments = aedtapp.modeler.convert_to_selections(aedtapp.modeler.selections, True) + assignments = aedtapp.modeler.convert_to_selections(aedtapp.modeler.selections, True) + output_dict = {"setup": setup, "calculation": calculation, "assignment": assignments} app.release_desktop(False, False) - output_dict = {"setup": setup, "calculation": calculation, "assignment": assignments} - return output_dict @@ -216,16 +253,16 @@ def main(extension_args): # Load new expressions from file # Current directory - current_directory = os.getcwd() - all_files = os.listdir(current_directory) - toml_files = [f for f in all_files if f.endswith(".toml")] + # Load new expressions from file + current_directory = Path.cwd() + toml_files = list(current_directory.glob("*.toml")) for toml_file in toml_files: aedtapp.post.fields_calculator.load_expression_file(toml_file) # Personal Lib directory - all_files = os.listdir(aedtapp.personallib) - toml_files = [os.path.join(aedtapp.personallib, f) for f in all_files if f.endswith(".toml")] - for toml_file in toml_files: + personal_lib_directory = Path(aedtapp.personallib) + toml_files = list(personal_lib_directory.glob("*.toml")) + for toml_file in toml_files: # pragma: no cover aedtapp.post.fields_calculator.load_expression_file(toml_file) names = [] @@ -233,7 +270,7 @@ def main(extension_args): if not aedtapp.post.fields_calculator.is_general_expression(calculation): for assignment in assignments: assignment_str = assignment - if isinstance(assignment_str, FacePrimitive): + if isinstance(assignment_str, FacePrimitive): # pragma: no cover assignment_str = str(assignment.id) elif not isinstance(assignment_str, str): # pragma: no cover assignment_str = generate_unique_name(calculation) @@ -270,5 +307,6 @@ def main(extension_args): for output_name, output_value in output.items(): if output_name in extension_arguments: args[output_name] = output_value - - main(args) + main(args) + else: + main(args) diff --git a/src/ansys/aedt/core/workflows/project/import_nastran.py b/src/ansys/aedt/core/workflows/project/import_nastran.py index 906963fe23f..e156e8fcd5b 100644 --- a/src/ansys/aedt/core/workflows/project/import_nastran.py +++ b/src/ansys/aedt/core/workflows/project/import_nastran.py @@ -21,7 +21,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -import os.path +from pathlib import Path import ansys.aedt.core from ansys.aedt.core import get_pyaedt_app @@ -51,15 +51,16 @@ def frontend(): # pragma: no cover import PIL.Image import PIL.ImageTk + from ansys.aedt.core.workflows.misc import ExtensionTheme master = tkinter.Tk() - - master.geometry("750x250") - master.title("Import Nastran or STL file") + # Detect if user closes the UI + master.flag = False + # Load the logo for the main window - icon_path = os.path.join(ansys.aedt.core.workflows.__path__[0], "images", "large", "logo.png") + icon_path = Path(ansys.aedt.core.workflows.__path__[0]) / "images" / "large" / "logo.png" im = PIL.Image.open(icon_path) photo = PIL.ImageTk.PhotoImage(im) @@ -68,13 +69,20 @@ def frontend(): # pragma: no cover # Configure style for ttk buttons style = ttk.Style() - style.configure("Toolbutton.TButton", padding=6, font=("Helvetica", 8)) + theme = ExtensionTheme() + + # Apply light theme initially + theme.apply_light_theme(style) + master.theme = "light" + + # Set background color of the window (optional) + master.configure(bg=theme.light["widget_bg"]) + + label2 = ttk.Label(master, text="Browse file:", style="PyAEDT.TLabel") - var2 = tkinter.StringVar() - label2 = tkinter.Label(master, textvariable=var2) - var2.set("Browse file:") label2.grid(row=0, column=0, pady=10) text = tkinter.Text(master, width=40, height=1) + text.configure(bg=theme.light["pane_bg"], foreground=theme.light["text"], font=theme.default_font) text.grid(row=0, column=1, pady=10, padx=5) def browseFiles(): @@ -85,34 +93,64 @@ def browseFiles(): ) text.insert(tkinter.END, filename) - b1 = tkinter.Button(master, text="...", width=10, command=browseFiles) + b1 = ttk.Button(master, text="...", width=10, command=browseFiles, style="PyAEDT.TButton") b1.grid(row=0, column=2, pady=10) - var = tkinter.StringVar() - label = tkinter.Label(master, textvariable=var) - var.set("Decimation factor (0-0.9). It may affect results:") + label = ttk.Label(master, text="Decimation factor (0-0.9). It may affect results:", style="PyAEDT.TLabel") label.grid(row=1, column=0, pady=10) + check = tkinter.Text(master, width=20, height=1) + check.configure(bg=theme.light["pane_bg"], foreground=theme.light["text"], font=theme.default_font) check.insert(tkinter.END, "0.0") check.grid(row=1, column=1, pady=10, padx=5) - var = tkinter.StringVar() - label = tkinter.Label(master, textvariable=var) - var.set("Import as lightweight (only HFSS):") + label = ttk.Label(master, text="Import as lightweight (only HFSS):", style="PyAEDT.TLabel") label.grid(row=2, column=0, pady=10) light = tkinter.IntVar() - check2 = tkinter.Checkbutton(master, width=30, variable=light) + check2 = ttk.Checkbutton(master, variable=light, style="PyAEDT.TCheckbutton") check2.grid(row=2, column=1, pady=10, padx=5) - var = tkinter.StringVar() - label = tkinter.Label(master, textvariable=var) - var.set("Enable planar merge:") + label = ttk.Label(master, text="Enable planar merge:", style="PyAEDT.TLabel") label.grid(row=3, column=0, pady=10) planar = tkinter.IntVar(value=1) - check3 = tkinter.Checkbutton(master, width=30, variable=planar) + check3 = ttk.Checkbutton(master, variable=planar, style="PyAEDT.TCheckbutton") check3.grid(row=3, column=1, pady=10, padx=5) + def toggle_theme(): + if master.theme == "light": + set_dark_theme() + master.theme = "dark" + else: + set_light_theme() + master.theme = "light" + + def set_light_theme(): + master.configure(bg=theme.light["widget_bg"]) + text.configure(bg=theme.light["pane_bg"], foreground=theme.light["text"], font=theme.default_font) + check.configure(bg=theme.light["pane_bg"], foreground=theme.light["text"], font=theme.default_font) + theme.apply_light_theme(style) + change_theme_button.config(text="\u263D") # Sun icon for light theme + + def set_dark_theme(): + master.configure(bg=theme.dark["widget_bg"]) + text.configure(bg=theme.dark["pane_bg"], foreground=theme.dark["text"], font=theme.default_font) + check.configure(bg=theme.dark["pane_bg"], foreground=theme.dark["text"], font=theme.default_font) + theme.apply_dark_theme(style) + change_theme_button.config(text="\u2600") # Moon icon for dark theme + + # Create a frame for the toggle button to position it correctly + button_frame = ttk.Frame(master, style="PyAEDT.TFrame", relief=tkinter.SUNKEN, borderwidth=2) + button_frame.grid(row=5, column=2, pady=10, padx=10) + + # Add the toggle theme button inside the frame + change_theme_button = ttk.Button( + button_frame, width=20, text="\u263D", command=toggle_theme, style="PyAEDT.TButton" + ) + + change_theme_button.grid(row=0, column=0, padx=0) + def callback(): + master.flag = True master.decimate_ui = float(check.get("1.0", tkinter.END).strip()) master.lightweight_ui = True if light.get() == 1 else False master.planar_ui = True if planar.get() == 1 else False @@ -132,10 +170,10 @@ def preview(): simplify_stl(master.file_path_ui, decimation=master.decimate_ui, preview=True) - b2 = tkinter.Button(master, text="Preview", width=40, command=preview) + b2 = ttk.Button(master, text="Preview", width=40, command=preview, style="PyAEDT.TButton") b2.grid(row=5, column=0, pady=10, padx=10) - b3 = tkinter.Button(master, text="Ok", width=40, command=callback) + b3 = ttk.Button(master, text="Ok", width=40, command=callback, style="PyAEDT.TButton") b3.grid(row=5, column=1, pady=10, padx=10) tkinter.mainloop() @@ -145,22 +183,24 @@ def preview(): planar_ui = getattr(master, "planar_ui", extension_arguments["planar"]) file_path_ui = getattr(master, "file_path_ui", extension_arguments["file_path"]) - output_dict = { - "decimate": decimate_ui, - "lightweight": lightweight_ui, - "planar": planar_ui, - "file_path": file_path_ui, - } + output_dict = {} + if master.flag: + output_dict = { + "decimate": decimate_ui, + "lightweight": lightweight_ui, + "planar": planar_ui, + "file_path": file_path_ui, + } return output_dict def main(extension_args): - file_path = extension_args["file_path"] + file_path = Path(extension_args["file_path"]) lightweight = extension_args["lightweight"] decimate = extension_args["decimate"] planar = extension_args["planar"] - if os.path.exists(file_path): + if file_path.is_file(): app = ansys.aedt.core.Desktop( new_desktop=False, version=version, @@ -177,14 +217,14 @@ def main(extension_args): aedtapp = get_pyaedt_app(project_name, design_name) - if file_path.endswith(".nas"): + if file_path.suffix == ".nas": aedtapp.modeler.import_nastran( - file_path, import_as_light_weight=lightweight, decimation=decimate, enable_planar_merge=str(planar) + str(file_path), import_as_light_weight=lightweight, decimation=decimate, enable_planar_merge=str(planar) ) else: from ansys.aedt.core.visualization.advanced.misc import simplify_stl - outfile = simplify_stl(file_path, decimation=decimate) + outfile = simplify_stl(str(file_path), decimation=decimate) aedtapp.modeler.import_3d_cad( outfile, healing=False, create_lightweigth_part=lightweight, merge_planar_faces=planar ) @@ -214,4 +254,6 @@ def main(extension_args): for output_name, output_value in output.items(): if output_name in extension_arguments: args[output_name] = output_value - main(args) + main(args) + else: + main(args) diff --git a/src/ansys/aedt/core/workflows/project/kernel_converter.py b/src/ansys/aedt/core/workflows/project/kernel_converter.py index 8adb60ef14e..665761423a3 100644 --- a/src/ansys/aedt/core/workflows/project/kernel_converter.py +++ b/src/ansys/aedt/core/workflows/project/kernel_converter.py @@ -25,6 +25,7 @@ import logging import os.path +from pathlib import Path from ansys.aedt.core import Desktop from ansys.aedt.core import Hfss @@ -36,6 +37,7 @@ from ansys.aedt.core.generic.design_types import get_pyaedt_app from ansys.aedt.core.generic.filesystem import search_files from ansys.aedt.core.generic.general_methods import generate_unique_name +import ansys.aedt.core.workflows from ansys.aedt.core.workflows.misc import get_aedt_version from ansys.aedt.core.workflows.misc import get_arguments from ansys.aedt.core.workflows.misc import get_port @@ -59,50 +61,67 @@ def frontend(): # pragma: no cover from tkinter import filedialog from tkinter import ttk + import PIL.Image + import PIL.ImageTk + from ansys.aedt.core.workflows.misc import ExtensionTheme + master = tkinter.Tk() + master.title(extension_description) + + # Detect if user closes the UI + master.flag = False - master.geometry("750x250") + # Load the logo for the main window + icon_path = Path(ansys.aedt.core.workflows.__path__[0]) / "images" / "large" / "logo.png" + im = PIL.Image.open(icon_path) + photo = PIL.ImageTk.PhotoImage(im) - master.title("Convert File from 22R2") + # Set the icon for the main window + master.iconphoto(True, photo) # Configure style for ttk buttons style = ttk.Style() - style.configure("Toolbutton.TButton", padding=6, font=("Helvetica", 8)) + theme = ExtensionTheme() + + # Apply light theme initially + theme.apply_light_theme(style) + master.theme = "light" - var2 = tkinter.StringVar() - label2 = tkinter.Label(master, textvariable=var2) - var2.set("Browse file or folder:") + # Set background color of the window (optional) + master.configure(bg=theme.light["widget_bg"]) + + label2 = ttk.Label(master, text="Browse file or folder:", style="PyAEDT.TLabel") label2.grid(row=0, column=0, pady=10) text = tkinter.Text(master, width=40, height=1) + text.configure(bg=theme.light["pane_bg"], foreground=theme.light["text"], font=theme.default_font) text.grid(row=0, column=1, pady=10, padx=5) def edit_sols(self): sol["values"] = tuple(solutions_types[appl.get()].keys()) sol.current(0) - var = tkinter.StringVar() - label = tkinter.Label(master, textvariable=var) - var.set("Password (Encrypted 3D Component Only):") + label = ttk.Label(master, text="Password (Encrypted 3D Component Only):", style="PyAEDT.TLabel") label.grid(row=1, column=0, pady=10) + pwd = tkinter.Entry(master, width=20, show="*") + pwd.configure(bg=theme.light["pane_bg"], foreground=theme.light["text"], font=theme.default_font) pwd.insert(tkinter.END, "") pwd.grid(row=1, column=1, pady=10, padx=5) - var = tkinter.StringVar() - label = tkinter.Label(master, textvariable=var) - var.set("Application (3D Component Only):") + label = ttk.Label(master, text="Application (3D Component Only):", style="PyAEDT.TLabel") label.grid(row=2, column=0, pady=10) - appl = ttk.Combobox(master, width=40, validatecommand=edit_sols) # Set the width of the combobox + + appl = ttk.Combobox( + master, width=40, validatecommand=edit_sols, style="PyAEDT.TCombobox" + ) # Set the width of the combobox appl["values"] = ("HFSS", "Q3D Extractor", "Maxwell 3D", "Icepak") appl.current(0) appl.bind("<>", edit_sols) appl.grid(row=2, column=1, pady=10, padx=5) - var = tkinter.StringVar() - label = tkinter.Label(master, textvariable=var) - var.set("Solution (3D Component Only):") + label = ttk.Label(master, text="Solution (3D Component Only):", style="PyAEDT.TLabel") label.grid(row=3, column=0, pady=10) - sol = ttk.Combobox(master, width=40) # Set the width of the combobox + sol = ttk.Combobox(master, width=40, style="PyAEDT.TCombobox") # Set the width of the combobox sol["values"] = ttk.Combobox(master, width=40) # Set the width of the combobox sol["values"] = tuple(solutions_types["HFSS"].keys()) sol.current(0) @@ -116,10 +135,44 @@ def browseFiles(): ) text.insert(tkinter.END, filename) - b1 = tkinter.Button(master, text="...", width=10, command=browseFiles) + b1 = ttk.Button(master, text="...", width=10, command=browseFiles, style="PyAEDT.TButton") b1.grid(row=0, column=2, pady=10) + def toggle_theme(): + if master.theme == "light": + set_dark_theme() + master.theme = "dark" + else: + set_light_theme() + master.theme = "light" + + def set_light_theme(): + master.configure(bg=theme.light["widget_bg"]) + theme.apply_light_theme(style) + text.configure(bg=theme.light["pane_bg"], foreground=theme.light["text"], font=theme.default_font) + pwd.configure(bg=theme.light["pane_bg"], foreground=theme.light["text"], font=theme.default_font) + change_theme_button.config(text="\u263D") # Sun icon for light theme + + def set_dark_theme(): + master.configure(bg=theme.dark["widget_bg"]) + theme.apply_dark_theme(style) + text.configure(bg=theme.dark["pane_bg"], foreground=theme.dark["text"], font=theme.default_font) + pwd.configure(bg=theme.dark["pane_bg"], foreground=theme.dark["text"], font=theme.default_font) + change_theme_button.config(text="\u2600") # Moon icon for dark theme + + # Create a frame for the toggle button to position it correctly + button_frame = ttk.Frame(master, style="PyAEDT.TFrame", relief=tkinter.SUNKEN, borderwidth=2) + button_frame.grid(row=5, column=2, pady=10, padx=10) + + # Add the toggle theme button inside the frame + change_theme_button = ttk.Button( + button_frame, width=20, text="\u263D", command=toggle_theme, style="PyAEDT.TButton" + ) + + change_theme_button.grid(row=0, column=0, padx=0) + def callback(): + master.flag = True applications = {"HFSS": 0, "Icepak": 1, "Maxwell 3D": 2, "Q3D Extractor": 3} master.password_ui = pwd.get() master.application_ui = applications[appl.get()] @@ -127,7 +180,7 @@ def callback(): master.file_path_ui = text.get("1.0", tkinter.END).strip() master.destroy() - b3 = tkinter.Button(master, text="Ok", width=40, command=callback) + b3 = ttk.Button(master, text="Ok", width=40, command=callback, style="PyAEDT.TButton") b3.grid(row=5, column=1, pady=10, padx=10) tkinter.mainloop() @@ -137,12 +190,14 @@ def callback(): solution_ui = getattr(master, "solution_ui", extension_arguments["solution"]) file_path_ui = getattr(master, "file_path_ui", extension_arguments["file_path"]) - output_dict = { - "password": password_ui, - "application": application_ui, - "solution": solution_ui, - "file_path": file_path_ui, - } + output_dict = {} + if master.flag: + output_dict = { + "password": password_ui, + "application": application_ui, + "solution": solution_ui, + "file_path": file_path_ui, + } return output_dict @@ -324,4 +379,6 @@ def convert(args): for output_name, output_value in output.items(): if output_name in extension_arguments: args[output_name] = output_value - convert(args) + convert(args) + else: + convert(args) diff --git a/tests/system/solvers/test_45_workflows.py b/tests/system/solvers/test_45_workflows.py index 016aef68a55..76631ccbe9a 100644 --- a/tests/system/solvers/test_45_workflows.py +++ b/tests/system/solvers/test_45_workflows.py @@ -186,10 +186,6 @@ def test_07_twinbuilder_convert_circuit(self, add_app): assert main({"is_test": True}) - @pytest.mark.skipif( - TEST_REVIEW_FLAG, - reason="Test under review in 2024.2", - ) def test_08_configure_a3d(self, local_scratch): from ansys.aedt.core.workflows.project.configure_edb import main @@ -440,10 +436,6 @@ def test_10_push_excitation_3dl(self, local_scratch, desktop): # assert h3d.design_datasets h3d.close_project(h3d.project_name) - @pytest.mark.skipif( - TEST_REVIEW_FLAG, - reason="Test under review in 2024.2", - ) def test_11_cutout(self, add_app, local_scratch): from ansys.aedt.core.workflows.hfss3dlayout.cutout import main @@ -461,10 +453,6 @@ def test_11_cutout(self, add_app, local_scratch): ) app.close_project() - @pytest.mark.skipif( - TEST_REVIEW_FLAG, - reason="Test under review in 2024.2", - ) def test_12_export_layout(self, add_app, local_scratch): from ansys.aedt.core.workflows.hfss3dlayout.export_layout import main @@ -473,10 +461,6 @@ def test_12_export_layout(self, add_app, local_scratch): assert main({"is_test": True, "export_ipc": True, "export_configuration": True, "export_bom": True}) app.close_project() - @pytest.mark.skipif( - TEST_REVIEW_FLAG, - reason="Test under review in 2024.2", - ) def test_13_parametrize_layout(self, local_scratch): from ansys.aedt.core.workflows.hfss3dlayout.parametrize_edb import main @@ -513,10 +497,22 @@ def test_14_power_map_creation_ipk(self, local_scratch, add_app): def test_15_import_asc(self, local_scratch, add_app): aedtapp = add_app("Circuit", application=ansys.aedt.core.Circuit) - file_path = os.path.join(local_path, "example_models", "T21", "butter.asc") + from ansys.aedt.core.workflows.circuit.import_schematic import main + file_path = os.path.join(local_path, "example_models", "T21", "butter.asc") + assert main({"is_test": True, "asc_file": file_path}) + + file_path = os.path.join(local_path, "example_models", "T21", "netlist_small.cir") + assert main({"is_test": True, "asc_file": file_path}) + + file_path = os.path.join(local_path, "example_models", "T21", "Schematic1.qcv") assert main({"is_test": True, "asc_file": file_path}) + + file_path_invented = os.path.join(local_path, "example_models", "T21", "butter_invented.asc") + with pytest.raises(Exception) as execinfo: + main({"is_test": True, "asc_file": file_path_invented}) + assert execinfo.args[0] == "File does not exist." aedtapp.close_project() @pytest.mark.skipif(is_linux, reason="Not supported in Linux.")