diff --git a/.github/workflows/full_documentation.yml b/.github/workflows/full_documentation.yml index 3180ecd363b..f15fecf6e22 100644 --- a/.github/workflows/full_documentation.yml +++ b/.github/workflows/full_documentation.yml @@ -67,11 +67,16 @@ jobs: echo "::set-output name=PYAEDT_VERSION::$(python -c "from pyaedt import __version__; print(__version__)")" echo "PyAEDT version is: $(python -c "from pyaedt import __version__; print(__version__)")" - - name: Create Documentations + - name: Create HTML Documentations run: | testenv\Scripts\Activate.ps1 sphinx-build -j auto --color -b html -a doc/source doc/_build/html + - name: Create PDF Documentations + run: | + testenv\Scripts\Activate.ps1 + sphinx-build -j auto --color -b pdf -a doc/source doc/_build/pdf + - name: Upload HTML documentation artifact uses: actions/upload-artifact@v3 with: @@ -86,6 +91,21 @@ jobs: path: doc/_build/html/EDBAPI retention-days: 7 + - name: Upload PDF documentation artifact + uses: actions/upload-artifact@v3 + with: + name: documentation-pdf + path: doc/_build/pdf + retention-days: 7 + + - name: Release + uses: softprops/action-gh-release@v1 + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') + with: + generate_release_notes: true + files: | + doc/_build/pdf + doc-deploy-stable: name: Deploy stable documentation runs-on: ubuntu-latest @@ -148,5 +168,4 @@ jobs: host-url: ${{ vars.MEILISEARCH_HOST_URL }} api-key: ${{ env.MEILISEARCH_API_KEY }} doc-artifact-name: documentation-html-edb # Add only EDB API as page in this index. - pymeilisearchopts: --port \"8001\" #serve in another port - + pymeilisearchopts: --port 8001 #serve in another port \ No newline at end of file diff --git a/.github/workflows/nightly-docs.yml b/.github/workflows/nightly-docs.yml index 4fab4a02151..2aa10e4283c 100644 --- a/.github/workflows/nightly-docs.yml +++ b/.github/workflows/nightly-docs.yml @@ -92,7 +92,7 @@ jobs: host-url: ${{ vars.MEILISEARCH_HOST_URL }} api-key: ${{ env.MEILISEARCH_API_KEY }} doc-artifact-name: documentation-html-edb # Add only EDB API as page in this index. - pymeilisearchopts: --port \"8001\" # serve in another port as 8000 is deafult + pymeilisearchopts: --port 8001 # serve in another port as 8000 is deafult # docstring_testing: # runs-on: Windows diff --git a/.github/workflows/unit_test_prerelease.yml b/.github/workflows/unit_test_prerelease.yml index 4a0b8fda383..1c79c395a48 100644 --- a/.github/workflows/unit_test_prerelease.yml +++ b/.github/workflows/unit_test_prerelease.yml @@ -74,6 +74,8 @@ jobs: pytest --tx 6*popen --durations=50 --dist loadfile -v --cov=pyaedt --cov-report=xml --junitxml=junit/test-results.xml --cov-report=html _unittest - uses: codecov/codecov-action@v3 + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} if: matrix.python-version == '3.8' name: 'Upload coverage to Codecov' diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index 916b9d4e1e6..a853245d5f8 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -28,6 +28,65 @@ concurrency: # A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: # This workflow contains a single job called "build" + build_solvers: + # The type of runner that the job will run on + runs-on: [ windows-latest, pyaedt ] + strategy: + matrix: + python-version: [ '3.10' ] + # Steps represent a sequence of tasks that will be executed as part of the job + steps: + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + + - name: 'Create virtual env' + run: | + Remove-Item D:\Temp\* -Recurse -Force -ErrorAction SilentlyContinue + python -m venv testenv_s + testenv_s\Scripts\Activate.ps1 + python -m pip install pip -U + python -m pip install wheel setuptools -U + python -c "import sys; print(sys.executable)" + + - name: 'Install pyaedt' + run: | + testenv_s\Scripts\Activate.ps1 + pip install . + pip install .[tests] + pip install pytest-azurepipelines + Copy-Item -Path "C:\actions-runner\opengl32.dll" -Destination "testenv_s\Lib\site-packages\vtkmodules" -Force + mkdir tmp + cd tmp + python -c "import pyaedt; print('Imported pyaedt')" + + # - name: "Check licences of packages" + # uses: pyansys/pydpf-actions/check-licenses@v2.0 + + - name: 'Unit testing' + timeout-minutes: 40 + run: | + testenv_s\Scripts\Activate.ps1 + Set-Item -Path env:PYTHONMALLOC -Value "malloc" + pytest --durations=50 -v --cov=pyaedt --cov-report=xml --cov-report=html --junitxml=junit/test-results.xml _unittest_solvers + + - uses: codecov/codecov-action@v3 + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + with: + name: 'Upload coverage to Codecov' + + - name: Upload pytest test results + uses: actions/upload-artifact@v3 + with: + name: pytest-results + path: junit/test-results.xml + # Use always() to always run this step to publish test results when there are test failures + if: ${{ always() }} + + build: # The type of runner that the job will run on runs-on: [windows-latest, pyaedt] @@ -44,7 +103,7 @@ jobs: - name: 'Create virtual env' run: | - Remove-Item D:\Temp\* -Recurse -Force + Remove-Item D:\Temp\* -Recurse -Force -ErrorAction SilentlyContinue python -m venv testenv testenv\Scripts\Activate.ps1 python -m pip install pip -U @@ -73,8 +132,10 @@ jobs: pytest -n 6 --dist loadfile --durations=50 -v --cov=pyaedt --cov-report=xml --cov-report=html --junitxml=junit/test-results.xml _unittest - uses: codecov/codecov-action@v3 - if: matrix.python-version == '3.10' - name: 'Upload coverage to Codecov' + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + with: + name: 'Upload coverage to Codecov' - name: Upload pytest test results uses: actions/upload-artifact@v3 diff --git a/.github/workflows/unit_tests_solvers.yml b/.github/workflows/unit_tests_solvers.bkp similarity index 100% rename from .github/workflows/unit_tests_solvers.yml rename to .github/workflows/unit_tests_solvers.bkp diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5011d6eed7c..351c7f7b187 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -17,7 +17,7 @@ exclude: | repos: - repo: https://github.com/psf/black - rev: 23.7.0 # IF VERSION CHANGES --> MODIFY "blacken-docs" MANUALLY AS WELL!! + rev: 23.9.1 # IF VERSION CHANGES --> MODIFY "blacken-docs" MANUALLY AS WELL!! hooks: - id: black args: @@ -60,7 +60,7 @@ repos: rev: 1.16.0 hooks: - id: blacken-docs - additional_dependencies: [black==23.7.0] + additional_dependencies: [black==23.9.1] # - repo: https://github.com/pycqa/pydocstyle # rev: 6.1.1 diff --git a/_unittest/example_models/T15/channel_4.sss b/_unittest/example_models/T15/channel_4.sss new file mode 100644 index 00000000000..50c01ac2d72 Binary files /dev/null and b/_unittest/example_models/T15/channel_4.sss differ diff --git a/_unittest/example_models/T15/ibis_ami_example_l32.so b/_unittest/example_models/T15/ibis_ami_example_l32.so new file mode 100644 index 00000000000..095643675d7 Binary files /dev/null and b/_unittest/example_models/T15/ibis_ami_example_l32.so differ diff --git a/_unittest/example_models/T15/ibis_ami_example_l64.so b/_unittest/example_models/T15/ibis_ami_example_l64.so new file mode 100644 index 00000000000..7e97f4645ee Binary files /dev/null and b/_unittest/example_models/T15/ibis_ami_example_l64.so differ diff --git a/_unittest/example_models/T15/ibis_ami_example_rx.ami b/_unittest/example_models/T15/ibis_ami_example_rx.ami new file mode 100644 index 00000000000..e4ed76725ee --- /dev/null +++ b/_unittest/example_models/T15/ibis_ami_example_rx.ami @@ -0,0 +1,25 @@ +(IBIS_AMI_EXAMPLE +(Reserved_Parameters + (AMI_Version (Usage Info) (Type String) (Value "5.1") + (Description "Valid for AMI_Version 5.1 and above")) + (Ignore_Bits (Usage Info)(Type Integer)(Default 4) + (Description "Ignore 4 bits to fill up tapped delay line")) + (Max_Init_Aggressors (Usage Info) (Type Integer)(Default 10) + (Description "Number of aggressors supported by the model")) + (Init_Returns_Impulse (Usage Info) (Type Boolean)(Default True) + (Description "AMI_Init function returns impulse and parameters_out")) + (GetWave_Exists (Usage Info)(Type Boolean)(Default True) + (Description "yes, it exists")) +) | End Reserved_Parameters +(Model_Specific + | method = 1 applies the tap weights inside AMI_Init() to the channel impulse. + | method = 2 applies the tap weights inside AMI_Getwave() to the wave. + | Results of both methods should be nearly identical in AMI, but not in VerifEye + | because we ignore the GetWave function there! method = 1 is faster + (method (Usage In)(Type Integer)(Default 1) ) + (clock_threshold (Usage In)(Type Float)(Default 0.4) + (Description "clock threshold for the receiver")) + (ffe_weight_1 (Usage In)(Type Float)(Default 1.0)) + (ffe_weight_2 (Usage In)(Type Float)(Default 0.0)) + (ffe_weight_3 (Usage In)(Type Float)(Default 0.0)) +)) diff --git a/_unittest/example_models/T15/ibis_ami_example_rx.ibs b/_unittest/example_models/T15/ibis_ami_example_rx.ibs new file mode 100644 index 00000000000..3c7e0ea3038 --- /dev/null +++ b/_unittest/example_models/T15/ibis_ami_example_rx.ibs @@ -0,0 +1,59 @@ +[IBIS Ver] 5.1 +[Disclaimer] + Example IBIS file that refers to example + Algorithmic Model. This file does not + contain a model for an actual physical + device. +[File Name] ibis_ami_example_rx.ibs +| +[Date] March 29, 2018 10:59:00 AM EDT +[File Rev] 0.2 +[Source] +| +[Notes] +| +[Component] example_device_rx +| +[Manufacturer] ANSYS, Inc. +| +[Package] +R_pkg 0.01m NA NA +L_pkg 0.001nH NA NA +C_pkg 0.001pF NA NA +| +[Pin] signal_name model_name R_pin L_pin C_pin + 11 example_rx_P example_model_rx NA NA NA + 12 example_rx_N example_model_rx NA NA NA +| +[Diff Pin] inv_pin vdiff tdelay_typ tdelay_min tdelay_max| + 11 12 0.2 NA NA NA + + +| ------------------------------------ +| +| model example_model_rx +| +[Model] example_model_rx +Model_type Input +C_comp 166.88f 166.88f 166.88f +Vinl = .5 +Vinh = .5 +[Temperature Range] 85 100 0 +[Voltage Range] 1. 1. 1. +[Algorithmic Model] +Executable Windows_Intel7_32 ibis_ami_example_w32.dll ibis_ami_example_rx.ami +Executable Windows_Intel10_64 ibis_ami_example_w64.dll ibis_ami_example_rx.ami +Executable Linux_Intel10_32 ibis_ami_example_l32.so ibis_ami_example_rx.ami +Executable Linux_Intel10_64 ibis_ami_example_l64.so ibis_ami_example_rx.ami +[End Algorithmic Model] +[GND_clamp] +-1 0.0 0.0 0.0 +1.5 0.0 0.0 0.0 +[POWER_clamp] +-1 0.0 0.0 0.0 +1.5 0.0 0.0 0.0 +| +| ------------------------------------ +| +| +[End] diff --git a/_unittest/example_models/T15/ibis_ami_example_tx.ami b/_unittest/example_models/T15/ibis_ami_example_tx.ami new file mode 100644 index 00000000000..a51bc35b2a3 --- /dev/null +++ b/_unittest/example_models/T15/ibis_ami_example_tx.ami @@ -0,0 +1,24 @@ +(IBIS_AMI_EXAMPLE +(Reserved_Parameters + (AMI_Version (Usage Info) (Type String) (Value "5.1") + (Description "Valid for AMI_Version 5.1 and above")) + (Ignore_Bits (Usage Info)(Type Integer)(Default 4) + (Description "Ignore 4 bits to fill up tapped delay line")) + (Max_Init_Aggressors (Usage Info) (Type Integer)(Default 10) + (Description "Number of aggressors supported by the model")) + (Init_Returns_Impulse (Usage Info) (Type Boolean)(Default True) + (Description "AMI_Init function returns impulse and parameters_out")) + (GetWave_Exists (Usage Info)(Type Boolean)(Default True) + (Description "yes, it exists")) +) | End Reserved_Parameters +(Model_Specific + | method = 1 applies the tap weights inside AMI_Init() to the channel impulse. + | method = 2 applies the tap weights inside AMI_Getwave() to the wave. + | Results of both methods should be nearly identical in AMI, but not in VerifEye + | because we ignore the GetWave function there! method = 1 is faster + (method (Usage In)(Type Integer)(Default 1)) + (ffe_weight_1 (Usage In)(Type Float)(Default 1.0)) + (ffe_weight_2 (Usage In)(Type Float)(Default 0.0)) + (ffe_weight_3 (Usage In)(Type Float)(Default 0.0)) +) +) diff --git a/_unittest/example_models/T15/ibis_ami_example_tx.ibs b/_unittest/example_models/T15/ibis_ami_example_tx.ibs new file mode 100644 index 00000000000..c52e504358c --- /dev/null +++ b/_unittest/example_models/T15/ibis_ami_example_tx.ibs @@ -0,0 +1,63 @@ +[IBIS Ver] 5.1 +[Disclaimer] + Example IBIS file that refers to example + Algorithmic Model. This file does not + contain a model for an actual physical + device. +[File Name] ibis_ami_example_tx.ibs +| +[Date] March 29, 2018 10:59:00 AM EDT +[File Rev] 0.3 +[Source] +| +[Notes] +| +[Component] example_device_tx +| +[Manufacturer] ANSYS, Inc. +| +[Package] +R_pkg 0.01m NA NA +L_pkg 0.001nH NA NA +C_pkg 0.001pF NA NA +| +[Pin] signal_name model_name R_pin L_pin C_pin + 14 example_tx_P example_model_tx NA NA NA + 15 example_tx_N example_model_tx NA NA NA + +[Diff Pin] inv_pin vdiff tdelay_typ tdelay_min tdelay_max| + 14 15 0.2 NA NA NA + + +|Model row 3 +| +|model example_model_tx +| +[Model] example_model_tx +Model_type Output +C_comp 64f 64f 64f +Cref =0 +[Model Spec] +Vmeas 0.0882 0.0882 0.0882 +Vref 0.0882 0.0882 0.0882 +[Algorithmic Model] +Executable Windows_Intel7_32 ibis_ami_example_w32.dll ibis_ami_example_tx.ami +Executable Windows_Intel10_64 ibis_ami_example_w64.dll ibis_ami_example_tx.ami +Executable Linux_Intel10_32 ibis_ami_example_l32.so ibis_ami_example_tx.ami +Executable Linux_Intel10_64 ibis_ami_example_l64.so ibis_ami_example_tx.ami +[End Algorithmic Model] +[Temperature Range] 85.0000 85.0000 85.0000 +[Voltage Range] 0.1694 0.1694 0.1694 +[Pulldown] +-2.50 -5.3821E-02 -5.3821E-02 -5.3821E-02 +0.00 0.0000E+00 0.0000E+00 0.0000E+00 +2.50 5.3821E-02 5.3821E-02 5.3821E-02 +[Pullup] +-2.50 5.3821E-02 5.3821E-02 5.3821E-02 +0.00 0.0000E+00 0.0000E+00 0.0000E+00 +2.50 -5.3821E-02 -5.3821E-02 -5.3821E-02 +[Ramp] +dV/dt_r 0.052/16.573p 0.052/16.573p 0.052/16.573p +dV/dt_f 0.052/17.251p 0.052/17.251p 0.052/17.251p + +[End] diff --git a/_unittest/example_models/T15/ibis_ami_example_w32.dll b/_unittest/example_models/T15/ibis_ami_example_w32.dll new file mode 100644 index 00000000000..2aed4a3e41c Binary files /dev/null and b/_unittest/example_models/T15/ibis_ami_example_w32.dll differ diff --git a/_unittest/example_models/T15/ibis_ami_example_w64.dll b/_unittest/example_models/T15/ibis_ami_example_w64.dll new file mode 100644 index 00000000000..bfdcdfa3542 Binary files /dev/null and b/_unittest/example_models/T15/ibis_ami_example_w64.dll differ diff --git a/_unittest/test_00_EDB.py b/_unittest/test_00_EDB.py index ecc931946eb..80203acd0f2 100644 --- a/_unittest/test_00_EDB.py +++ b/_unittest/test_00_EDB.py @@ -1610,6 +1610,9 @@ def test_120_edb_create_port(self): assert wave_port.pec_launch_width assert not wave_port.deembed assert wave_port.deembed_length == 0.0 + assert wave_port.do_renormalize + wave_port.do_renormalize = False + assert not wave_port.do_renormalize assert edb.hfss.create_differential_wave_port( traces[0].id, trace_paths[0][0], diff --git a/_unittest/test_15_ibs_reader.py b/_unittest/test_15_ibs_reader.py index 59a54ed7eed..e1d5453dacb 100644 --- a/_unittest/test_15_ibs_reader.py +++ b/_unittest/test_15_ibs_reader.py @@ -53,13 +53,13 @@ def test_01_read_ibis(self): assert ibis.components["MT47H64M4BP-3_25"].pins["A1_MT47H64M4BP-3_25_u26a_800_modified"].c_value == "0.59pF" # Add pin - ibis.components["MT47H32M8BP-3_25"].pins["A7_MT47H32M8BP-3_25_u26a_800_modified"].add() + ibis.components["MT47H32M8BP-3_25"].pins["A8_MT47H32M8BP-3_25_u26a_800_modified"].add() pin = ( ibis.components["MT47H32M8BP-3_25"] - .pins["A7_MT47H32M8BP-3_25_u26a_800_modified"] + .pins["A8_MT47H32M8BP-3_25_u26a_800_modified"] .insert(0.1016, 0.05334, 0.0) ) - assert pin.name == "CompInst@A7_MT47H32M8BP-3_25_u26a_800_modified" + assert pin.name == "CompInst@DQS#_MT47H32M8BP-3_25_u26a_800_modified" # Add buffer ibis.buffers["RDQS#_u26a_800_modified"].add() @@ -72,3 +72,9 @@ def test_02_read_ibis_from_circuit(self): ) assert len(ibis_model.components) == 6 assert len(ibis_model.models) == 17 + + def test_03_read_ibis_ami(self): + ibis_model = self.aedtapp.get_ibis_model_from_file( + os.path.join(local_path, "example_models", test_subfolder, "ibis_ami_example_tx.ibs"), is_ami=True + ) + assert ibis_model.buffers["example_model_tx_ibis_ami_example_tx"].insert(0, 0) diff --git a/_unittest_solvers/test_26_emit.py b/_unittest_solvers/test_26_emit.py index 01086d10ec5..a86b9b65ca4 100644 --- a/_unittest_solvers/test_26_emit.py +++ b/_unittest_solvers/test_26_emit.py @@ -688,7 +688,7 @@ def test_version(self, add_app): config["desktopVersion"] <= "2023.1", reason="Skipped on versions earlier than 2023.2", ) - def test_analyze_manually(self, add_app): + def test_basic_run(self, add_app): self.aedtapp = add_app(application=Emit) assert len(self.aedtapp.results.revisions) == 0 # place components and generate the appropriate number of revisions @@ -749,6 +749,7 @@ def test_analyze_manually(self, add_app): rev2 = self.aedtapp.results.analyze() domain2 = self.aedtapp.results.interaction_domain() domain2.set_receiver("MD400C") + domain2.set_interferer(rad3.name) if config["desktopVersion"] >= "2024.1": rev2.n_to_1_limit = 0 assert rev2.is_domain_valid(domain2) @@ -760,6 +761,7 @@ def test_analyze_manually(self, add_app): assert len(worst_domain.interferer_names) == 1 assert worst_domain.interferer_names[0] == rad3.name # rad3 has the higher transmit power domain2.set_receiver(rad3.name) + domain2.set_interferer(rad2.name) assert rev2.is_domain_valid(domain2) interaction3 = rev2.run(domain2) assert interaction3 is not None @@ -804,7 +806,7 @@ def test_optimal_n_to_1_feature(self, add_app): assert self.aedtapp.results.revisions[-1].n_to_1_limit == 0 # get number of 1-1 instances - assert self.aedtapp.results.revisions[-1].get_instance_count(domain) == 105702 + assert self.aedtapp.results.revisions[-1].get_instance_count(domain) == 52851 interaction = self.aedtapp.results.revisions[-1].run(domain) instance = interaction.get_worst_instance(ResultType.EMI) assert instance.get_value(ResultType.EMI) == 76.02 @@ -812,7 +814,7 @@ def test_optimal_n_to_1_feature(self, add_app): # rerun with N-1 self.aedtapp.results.revisions[-1].n_to_1_limit = 2**20 assert self.aedtapp.results.revisions[-1].n_to_1_limit == 2**20 - assert self.aedtapp.results.revisions[-1].get_instance_count(domain) == 23305632 + assert self.aedtapp.results.revisions[-1].get_instance_count(domain) == 11652816 interaction = self.aedtapp.results.revisions[-1].run(domain) instance = interaction.get_worst_instance(ResultType.EMI) domain2 = instance.get_domain() diff --git a/doc/Makefile b/doc/Makefile index cebf32ace12..77e93a7ae6e 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -29,6 +29,12 @@ clean: phtml: $(SPHINXBUILD) -M html "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -j auto +# Build pdf docs. +pdf: + @$(SPHINXBUILD) -M latex "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + cd $(BUILDDIR)/latex && latexmk -r latexmkrc -pdf *.tex -interaction=nonstopmode || true + (test -f $(BUILDDIR)/latex/*.pdf && echo pdf exists) || exit 1 + # build docs like the CI build cibuild: mkdir source/examples -p diff --git a/doc/source/conf.py b/doc/source/conf.py index 52ccb663c90..a15f421bb33 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -316,3 +316,19 @@ def setup(app): # Output file base name for HTML help builder. htmlhelp_basename = "pyaedtdoc" + +# -- Options for LaTeX output ------------------------------------------------ +latex_elements = {} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + ( + master_doc, + f"{project}-Documentation-{__version__}.tex", + f"{project} Documentation", + author, + "manual", + ), +] \ No newline at end of file diff --git a/examples/00-EDB/12_edb_sma_connector_on_board.py b/examples/00-EDB/12_edb_sma_connector_on_board.py index 2dfee2fc97f..ee73e020f51 100644 --- a/examples/00-EDB/12_edb_sma_connector_on_board.py +++ b/examples/00-EDB/12_edb_sma_connector_on_board.py @@ -45,17 +45,17 @@ # A stackup can be created by importing from a csv/xml file or adding layer by layer. # -edb.add_design_variable("DIEL_T", "0.15mm") +edb.add_design_variable("$DIEL_T", "0.15mm") edb.stackup.add_layer("BOT") -edb.stackup.add_layer("D5", "GND", layer_type="dielectric", thickness="DIEL_T", material="ANSYS_FR4") +edb.stackup.add_layer("D5", "GND", layer_type="dielectric", thickness="$DIEL_T", material="ANSYS_FR4") edb.stackup.add_layer("L5", "Diel", thickness="0.05mm") -edb.stackup.add_layer("D4", "GND", layer_type="dielectric", thickness="DIEL_T", material="ANSYS_FR4") +edb.stackup.add_layer("D4", "GND", layer_type="dielectric", thickness="$DIEL_T", material="ANSYS_FR4") edb.stackup.add_layer("L4", "Diel", thickness="0.05mm") -edb.stackup.add_layer("D3", "GND", layer_type="dielectric", thickness="DIEL_T", material="ANSYS_FR4") +edb.stackup.add_layer("D3", "GND", layer_type="dielectric", thickness="$DIEL_T", material="ANSYS_FR4") edb.stackup.add_layer("L3", "Diel", thickness="0.05mm") -edb.stackup.add_layer("D2", "GND", layer_type="dielectric", thickness="DIEL_T", material="ANSYS_FR4") +edb.stackup.add_layer("D2", "GND", layer_type="dielectric", thickness="$DIEL_T", material="ANSYS_FR4") edb.stackup.add_layer("L2", "Diel", thickness="0.05mm") -edb.stackup.add_layer("D1", "GND", layer_type="dielectric", thickness="DIEL_T", material="ANSYS_FR4") +edb.stackup.add_layer("D1", "GND", layer_type="dielectric", thickness="$DIEL_T", material="ANSYS_FR4") edb.stackup.add_layer("TOP", "Diel", thickness="0.05mm") ###################### @@ -136,9 +136,7 @@ # ~~~~~~~~~~~~ setup = edb.create_hfss_setup("Setup1") -setup.set_solution_single_frequency("5GHz", max_delta_s="0.01") - -setup.hfss_solver_settings.enhanced_low_freq_accuracy = True +setup.set_solution_single_frequency("5GHz", max_num_passes=2, max_delta_s="0.01") setup.hfss_solver_settings.order_basis = "first" ############################# @@ -155,7 +153,7 @@ frequency_sweep=[ ["linear count", "0", "1KHz", 1], ["log scale", "1KHz", "0.1GHz", 10], - ["linear scale", "0.1GHz", "10GHz", "0.1GHz"], + ["linear scale", "0.1GHz", "5GHz", "0.1GHz"], ], ) diff --git a/examples/07-EMIT/interference_gui.py b/examples/07-EMIT/interference_gui.py index 541fe2932bd..6d7c82ab58e 100644 --- a/examples/07-EMIT/interference_gui.py +++ b/examples/07-EMIT/interference_gui.py @@ -13,6 +13,7 @@ import sys from pyaedt.emit_core.emit_constants import InterfererType, ResultType, TxRxMode from pyaedt import Emit +from pyaedt import get_pyaedt_app import pyaedt import os import subprocess @@ -39,7 +40,7 @@ def install(package): install(package) # Import PySide6 and openpyxl libraries -from PySide6 import QtWidgets, QtUiTools, QtGui +from PySide6 import QtWidgets, QtUiTools, QtGui, QtCore from openpyxl.styles import PatternFill import openpyxl @@ -52,11 +53,10 @@ def install(package): # Launch EMIT non_graphical = False new_thread = True -d = pyaedt.launch_desktop(emitapp_desktop_version, non_graphical, new_thread) -emitapp = Emit(pyaedt.generate_unique_project_name()) +desktop = pyaedt.launch_desktop(emitapp_desktop_version, non_graphical, new_thread) # Add emitapi to system path -emit_path = emitapp._desktop_install_dir + "/Delcross" +emit_path = os.path.join(desktop.install_path, "Delcross") sys.path.append(emit_path) import EmitApiPython api = EmitApiPython.EmitApi() @@ -66,21 +66,41 @@ def install(package): Ui_MainWindow, _ = QtUiTools.loadUiType(ui_file) class DoubleDelegate(QtWidgets.QStyledItemDelegate): - def __init__(self, decimals): + def __init__(self, decimals, values, max_power, min_power): super().__init__() self.decimals = decimals + self.values = values + self.max_power = max_power + self.min_power = min_power def createEditor(self, parent, option, index): editor = super().createEditor(parent, option, index) if isinstance(editor, QtWidgets.QLineEdit): validator = QtGui.QDoubleValidator(parent) - validator.setDecimals(self.decimals) + num_rows = len(self.values) + cur_row = index.row() + if cur_row == 0: + min_val = self.values[1] + max_val = self.max_power + elif cur_row == num_rows - 1: + min_val = self.min_power + max_val = self.values[cur_row-1] + else: + min_val = self.values[cur_row + 1] + max_val = self.values[cur_row - 1] + validator.setRange(min_val, max_val, self.decimals) + validator.setNotation(QtGui.QDoubleValidator.Notation.StandardNotation) editor.setValidator(validator) return editor + def update_values(self, values): + self.values = values + class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): def __init__(self): super(MainWindow, self).__init__() + self.emitapp = None + self.populating_dropdown = False self.setupUi(self) self.setup_widgets() @@ -95,12 +115,14 @@ def setup_widgets(self): self.file_select_btn = self.findChild(QtWidgets.QToolButton, "file_select_btn") self.file_path_box = self.findChild(QtWidgets.QLineEdit, "file_path_box") self.design_name_dropdown = self.findChild(QtWidgets.QComboBox, "design_name_dropdown") + self.design_name_dropdown.setEnabled(True) self.tab_widget = self.findChild(QtWidgets.QTabWidget, "tab_widget") # Widget definitions for protection level classification self.protection_results_btn = self.findChild(QtWidgets.QPushButton, "protection_results_btn") self.protection_matrix = self.findChild(QtWidgets.QTableWidget, "protection_matrix") self.protection_legend_table = self.findChild(QtWidgets.QTableWidget, "protection_legend_table") + self.damage_check = self.findChild(QtWidgets.QCheckBox, "damage_check") self.overload_check = self.findChild(QtWidgets.QCheckBox, "overload_check") self.intermodulation_check = self.findChild(QtWidgets.QCheckBox, "intermodulation_check") @@ -110,6 +132,14 @@ def setup_widgets(self): self.radio_dropdown = self.findChild(QtWidgets.QComboBox, "radio_dropdown") self.protection_save_img_btn = self.findChild(QtWidgets.QPushButton, 'protection_save_img_btn') + # warning label + self.warning_label = self.findChild(QtWidgets.QLabel, "warnings") + myFont = QtGui.QFont() + myFont.setBold(True) + self.warning_label.setFont(myFont) + self.warning_label.setHidden(True) + self.design_name_dropdown.currentIndexChanged.connect(self.design_dropdown_changed) + # Setup for protection level buttons and table self.protection_results_btn.setEnabled(False) self.protection_export_btn.setEnabled(False) @@ -137,6 +167,13 @@ def setup_widgets(self): self.interference_results_btn = self.findChild(QtWidgets.QPushButton, "interference_results_btn") self.interference_matrix = self.findChild(QtWidgets.QTableWidget, "interference_matrix") self.interference_legend_table = self.findChild(QtWidgets.QTableWidget, "interference_legend_table") + + # set the items read only + for i in range(0, self.interference_legend_table.rowCount()): + item = self.interference_legend_table.item(i, 0) + item.setFlags(QtCore.Qt.ItemIsEnabled) + self.interference_legend_table.setItem(i, 0, item) + self.in_in_check = self.findChild(QtWidgets.QCheckBox, "in_in_check") self.in_out_check = self.findChild(QtWidgets.QCheckBox, "in_out_check") self.out_in_check = self.findChild(QtWidgets.QCheckBox, "out_in_check") @@ -188,8 +225,9 @@ def setup_widgets(self): v_header.setSectionResizeMode(3, QtWidgets.QHeaderView.ResizeMode.Stretch) # Input validation for protection level legend table - delegate = DoubleDelegate(decimals=2) - self.protection_legend_table.setItemDelegateForColumn(0, delegate) + self.delegate = DoubleDelegate(decimals=2, values=values, + max_power=1000, min_power=-200) + self.protection_legend_table.setItemDelegateForColumn(0, self.delegate) self.open_file_dialog() ############################################################################### @@ -204,11 +242,12 @@ def open_file_dialog(self): self.file_path_box.setText(fname[0]) # Close previous project and open specified one - emitapp.close_project() - emitapp.load_project(self.file_path_box.text()) + if self.emitapp is not None: + self.emitapp.close_project() + desktop_proj = desktop.load_project(self.file_path_box.text()) # Check if project is already open - if emitapp.lock_file == None: + if desktop_proj.lock_file == None: msg = QtWidgets.QMessageBox() msg.setWindowTitle("Error: Project already open") msg.setText("Project is locked. Close or remove the lock before proceeding. See AEDT log for more information.") @@ -216,13 +255,37 @@ def open_file_dialog(self): return # Populate design dropdown with all design names - designs = emitapp.oproject.GetDesigns() - emit_designs = [d for d in designs if d.GetDesignType() == "EMIT"] + designs = desktop_proj.design_list + emit_designs = [] + self.populating_dropdown = True self.design_name_dropdown.clear() - for d in emit_designs: - self.design_name_dropdown.addItem(d.GetName()) - - self.design_name_dropdown.setEnabled(True) + self.populating_dropdown = False + for d in designs: + design_type = desktop.design_type(desktop_proj.project_name, d) + if design_type == "EMIT": + emit_designs.append(d) + + # add warning if no EMIT design + # Note: this should never happen since loading a project without an EMIT design + # should add a blank EMIT design + self.warning_label.setHidden(True) + if len(emit_designs) == 0: + self.warning_label.setText("Warning: The project must contain at least one EMIT design.") + self.warning_label.setHidden(False) + return + + self.populating_dropdown = True + self.design_name_dropdown.addItems(emit_designs) + self.populating_dropdown = False + self.emitapp = get_pyaedt_app(desktop_proj.project_name, emit_designs[0]) + self.design_name_dropdown.setCurrentIndex(0) + + # check for at least 2 radios + radios = self.emitapp.modeler.components.get_radios() + self.warning_label.setHidden(True) + if len(radios) < 2: + self.warning_label.setText("Warning: The selected design must contain at least two radios.") + self.warning_label.setHidden(False) if self.radio_specific_levels.isEnabled(): self.radio_specific_levels.setChecked(False) @@ -235,19 +298,37 @@ def open_file_dialog(self): self.radio_specific_levels.setEnabled(True) self.protection_results_btn.setEnabled(True) self.interference_results_btn.setEnabled(True) - + + ############################################################################### + # Change design selection + # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # Refresh the warning messages when the selected design changes + + def design_dropdown_changed(self): + if self.populating_dropdown: + # don't load design's on initial project load + return + design_name = self.design_name_dropdown.currentText() + self.emitapp = get_pyaedt_app(self.emitapp.project_name, design_name) + # check for at least 2 radios + radios = self.emitapp.modeler.components.get_radios() + self.warning_label.setHidden(True) + if len(radios) < 2: + self.warning_label.setText("Warning: The selected design must contain at least two radios.") + self.warning_label.setHidden(False) + ############################################################################### - # Enable radio specific proteciton levels + # Enable radio specific protection levels # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Activate radio selection dropdown and initialize dictionary to store protection levels - # when the radio specific level dropdown is checked. + # when the radio-specific level dropdown is checked. def radio_specific(self): self.radio_dropdown.setEnabled(self.radio_specific_levels.isChecked()) self.radio_dropdown.clear() if self.radio_dropdown.isEnabled(): - emitapp.set_active_design(self.design_name_dropdown.currentText()) - radios = emitapp.modeler.components.get_radios() + self.emitapp.set_active_design(self.design_name_dropdown.currentText()) + radios = self.emitapp.modeler.components.get_radios() values = [float(self.protection_legend_table.item(row, 0).text()) for row in range(self.protection_legend_table.rowCount())] for radio in radios: if radios[radio].has_rx_channels(): @@ -271,6 +352,10 @@ def radio_dropdown_changed(self): item = self.protection_legend_table.item(row, 0) item.setText(str(self.protection_levels[self.radio_dropdown.currentText()][row])) self.changing = False + # update the validator so min/max for each row is properly set + values = [float(self.protection_legend_table.item(row, 0).text()) for row in + range(self.protection_legend_table.rowCount())] + self.delegate.update_values(values) ############################################################################### # Save legend table values @@ -280,12 +365,14 @@ def radio_dropdown_changed(self): def table_changed(self): if self.changing == False: - values = [float(self.protection_legend_table.item(row, 0).text()) for row in range(self.protection_legend_table.rowCount())] + values = [float(self.protection_legend_table.item(row, 0).text()) for row in + range(self.protection_legend_table.rowCount())] if self.radio_dropdown.currentText() == '': index = 'Global' else: index = self.radio_dropdown.currentText() self.protection_levels[index] = values + self.delegate.update_values(values) ############################################################################### # Save scenario matrix to as PNG file @@ -309,12 +396,15 @@ def save_image(self): # Write the scenario matrix results to an Excel file with color coding. def save_results_excel(self): - fname = QtWidgets.QFileDialog.getSaveFileName(self, "Save Scenario Matrix", "Protection Level Classification", "xlsx (*.xlsx)") - + defaultName = "" if self.tab_widget.currentIndex() == 0: table = self.protection_matrix + defaultName = "Protection Level Classification" else: table = self.interference_matrix + defaultName = "Interference Type Classification" + + fname = QtWidgets.QFileDialog.getSaveFileName(self, "Save Scenario Matrix", defaultName, "xlsx (*.xlsx)") if fname: workbook = openpyxl.Workbook() @@ -353,10 +443,10 @@ def interference_results(self): if self.previous_design != self.design_name_dropdown.currentText() or self.previous_project != self.file_path_box.text(): self.previous_design = self.design_name_dropdown.currentText() self.previous_project = self.file_path_box.text() - emitapp.set_active_design(self.design_name_dropdown.currentText()) + self.emitapp.set_active_design(self.design_name_dropdown.currentText()) # Check if file is read-only - if emitapp.save_project() == False: + if self.emitapp.save_project() == False: msg = QtWidgets.QMessageBox() msg.setWindowTitle("Writing Error") msg.setText("An error occured while writing to the file. Is it readonly? Disk full? See AEDT log for more information.") @@ -364,7 +454,7 @@ def interference_results(self): return # Get results and radios - self.rev = emitapp.results.analyze() + self.rev = self.emitapp.results.analyze() self.tx_interferer = InterfererType().TRANSMITTERS self.rx_radios = self.rev.get_receiver_names() self.tx_radios = self.rev.get_interferer_names(self.tx_interferer) @@ -378,11 +468,11 @@ def interference_results(self): # Iterate over all the transmitters and receivers and compute the power # at the input to each receiver due to each of the transmitters. Compute # which, if any, type of interference occured. - domain = emitapp.results.interaction_domain() + domain = self.emitapp.results.interaction_domain() self.all_colors, self.power_matrix = self.rev.interference_type_classification(domain, use_filter = True, filter_list = filter) # Save project and plot results on table widget - emitapp.save_project() + self.emitapp.save_project() self.populate_table() ############################################################################### @@ -404,10 +494,10 @@ def protection_results(self): if self.previous_design != self.design_name_dropdown.currentText() or self.previous_project != self.file_path_box.text(): self.previous_design = self.design_name_dropdown.currentText() self.previous_project = self.file_path_box.text() - emitapp.set_active_design(self.design_name_dropdown.currentText()) + self.emitapp.set_active_design(self.design_name_dropdown.currentText()) # Check if file is read-only - if emitapp.save_project() == False: + if self.emitapp.save_project() == False: msg = QtWidgets.QMessageBox() msg.setWindowTitle("Writing Error") msg.setText("An error occured while writing to the file. Is it readonly? Disk full? See AEDT log for more information.") @@ -416,7 +506,7 @@ def protection_results(self): # Get results and design radios self.tx_interferer = InterfererType().TRANSMITTERS - self.rev = emitapp.results.analyze() + self.rev = self.emitapp.results.analyze() self.rx_radios = self.rev.get_receiver_names() self.tx_radios = self.rev.get_interferer_names(self.tx_interferer) @@ -424,7 +514,7 @@ def protection_results(self): if self.tx_radios is None or self.rx_radios is None: return - domain = emitapp.results.interaction_domain() + domain = self.emitapp.results.interaction_domain() self.all_colors, self.power_matrix = self.rev.protection_level_classification(domain, self.global_protection_level, self.protection_levels['Global'], @@ -475,8 +565,8 @@ def closeEvent(self, event): msg.setWindowTitle("Closing GUI") msg.setText("Closing AEDT, please wait for GUI to close on its own.") x = msg.exec() - emitapp.close_project() - emitapp.close_desktop() + self.emitapp.close_project() + self.emitapp.close_desktop() ############################################################################### # Run GUI @@ -490,4 +580,4 @@ def closeEvent(self, event): window.show() app.exec() else: - emitapp.release_desktop(True,True) \ No newline at end of file + desktop.release_desktop(True, True) diff --git a/pyaedt/circuit.py b/pyaedt/circuit.py index a9b87a0d922..d31b7400da2 100644 --- a/pyaedt/circuit.py +++ b/pyaedt/circuit.py @@ -413,21 +413,25 @@ def create_schematic_from_netlist(self, file_to_import): return True @pyaedt_function_handler() - def get_ibis_model_from_file(self, path): + def get_ibis_model_from_file(self, path, is_ami=False): """Create an IBIS model based on the data contained in an IBIS file. Parameters ---------- path : str Path of the IBIS file. + is_ami : bool, optional + Whether if import an IBIS or an IBIS AMI. Returns ------- :class:`pyaedt.generic.ibis_reader.Ibis` IBIS object exposing all data from the IBIS file. """ - - reader = ibis_reader.IbisReader(path, self) + if is_ami: + reader = ibis_reader.AMIReader(path, self) + else: + reader = ibis_reader.IbisReader(path, self) reader.parse_ibis_file() return reader.ibis_model diff --git a/pyaedt/edb_core/edb_data/terminals.py b/pyaedt/edb_core/edb_data/terminals.py index 12e2786251f..95ec3d7b752 100644 --- a/pyaedt/edb_core/edb_data/terminals.py +++ b/pyaedt/edb_core/edb_data/terminals.py @@ -66,7 +66,11 @@ def is_circuit_port(self): @property def _port_post_processing_prop(self): """Get port post processing properties.""" - return self._edb_object.GetPortPostProcessingProp + return self._edb_object.GetPortPostProcessingProp() + + @_port_post_processing_prop.setter + def _port_post_processing_prop(self, value): + self._edb_object.SetPortPostProcessingProp(value) @property def do_renormalize(self): @@ -75,7 +79,9 @@ def do_renormalize(self): @do_renormalize.setter def do_renormalize(self, value): - self._port_post_processing_prop.DoRenormalize = value + ppp = self._port_post_processing_prop + ppp.DoRenormalize = value + self._port_post_processing_prop = ppp @property def name(self): diff --git a/pyaedt/edb_core/hfss.py b/pyaedt/edb_core/hfss.py index 487ffe927f2..72faf7b2584 100644 --- a/pyaedt/edb_core/hfss.py +++ b/pyaedt/edb_core/hfss.py @@ -797,6 +797,7 @@ def create_wave_port( wave_port.vertical_extent_factor = vertical_extent_factor wave_port.pec_launch_width = pec_launch_width wave_port.hfss_type = "Wave" + wave_port.do_renormalize = True if pos_edge_term: return port_name, wave_port else: diff --git a/pyaedt/edb_core/layout.py b/pyaedt/edb_core/layout.py index 61825a16fde..0b225fe2e70 100644 --- a/pyaedt/edb_core/layout.py +++ b/pyaedt/edb_core/layout.py @@ -259,12 +259,16 @@ def get_polygon_points(self, polygon): Parameters ---------- polygon : - Name of the polygon. + class: `pyaedt.edb_core.edb_data.primitives_data.EDBPrimitives` Returns ------- list - List of doubles. + List of tuples. Each tuple provides x, y point coordinate. If the length of two consecutives tuples + from the list equals 2, a segment is defined. The first tuple defines the starting point while the second + tuple the ending one. If the length of one tuple equals one, that means a polyline is defined and the value + is giving the arc height. Therefore to polyline is defined as starting point for the tuple + before in the list, the current one the arc height and the tuple after the polyline ending point. Examples -------- diff --git a/pyaedt/edb_core/siwave.py b/pyaedt/edb_core/siwave.py index 60051aaa64c..647f8afa3ef 100644 --- a/pyaedt/edb_core/siwave.py +++ b/pyaedt/edb_core/siwave.py @@ -216,16 +216,18 @@ def create_circuit_port_on_pin(self, pos_pin, neg_pin, impedance=50, port_name=N port_name : str, optional Port Name - >>> from pyaedt import Edb - >>> edbapp = Edb("myaedbfolder", "project name", "release version") - >>> pins = edbapp.components.get_pin_from_component("U2A5") - >>> edbapp.siwave.create_circuit_port_on_pin(pins[0], pins[1], 50, "port_name") - Returns ------- str Port Name. + Examples + -------- + + >>> from pyaedt import Edb + >>> edbapp = Edb("myaedbfolder", "project name", "release version") + >>> pins = edbapp.components.get_pin_from_component("U2A5") + >>> edbapp.siwave.create_circuit_port_on_pin(pins[0], pins[1], 50, "port_name") """ circuit_port = CircuitPort() circuit_port.positive_node.net = pos_pin.GetNet().GetName() @@ -991,7 +993,7 @@ def configure_siw_analysis_setup(self, simulation_setup=None, delete_existing_se Returns ------- - bool + bool ``True`` when successful, ``False`` when failed. """ @@ -1365,7 +1367,9 @@ def create_circuit_port_on_pin_group(self, pos_pin_group_name, neg_pin_group_nam neg_pin_group_name : str Name of the negative pin group. impedance : int, float, optional - Impedance of the source. + Impedance of the port. Default is ``50``. + name : str, optional + Port name. Returns ------- diff --git a/pyaedt/generic/ami.json b/pyaedt/generic/ami.json new file mode 100644 index 00000000000..ebf60ab72e1 --- /dev/null +++ b/pyaedt/generic/ami.json @@ -0,0 +1,69 @@ +{ + "Reserved_Parameters" : { + "AMI_Version": { + "Usage": "", + "Type": "", + "Format": { + "Value": "", + "Range": "", + "List": "", + "List_Tip": "", + "Corner": "", + "Increment": "", + "Steps": "", + "Table": "", + "Gaussian": "", + "Dual-Dirac": "", + "DjRj": "" + }, + "Default": "", + "Description": "", + "List_Tip": "" + }, + "Init_Returns_Impulse": "", + "GetWave_Exists": "", + "Use_Init_Output": "", + "Max_Init_Aggressors": "", + "Ignore_Bits": "", + "Resolve_Exists": "", + "Model_Name": "", + "Supporting_Files": "", + "DLL_Path": "", + "DLL_ID": "", + "Tx_Jitter": "", + "Tx_DCD": "", + "Tx_Rj": "", + "Tx_Dj": "", + "Tx_Sj": "", + "Tx_Sj_Frequency": "", + "Rx_DCD": "", + "Rx_Rj": "", + "Rx_Dj": "", + "Rx_Sj": "", + "Rx_Clock_PDF": "", + "Rx_Clock_Recovery_Mean": "", + "Rx_Clock_Recovery_Rj": "", + "Rx_Clock_Recovery_Dj": "", + "Rx_Clock_Recovery_Sj": "", + "Rx_Clock_Recovery_DCD": "", + "Rx_Receiver_Sensitivity": "", + "Rx_Noise": "", + "Rx_GaussianNoise": "", + "Rx_UniformNoise": "", + "Modulation": "", + "PAM4_Mapping": "", + "PAM4_UpperThreshold": "", + "PAM4_CenterThreshold": "", + "PAM4_LowerThreshold": "", + "PAM4_UpperEyeOffset": "", + "PAM4_CenterEyeOffset": "", + "PAM4_LowerEyeOffset": "", + "Ts4file": "", + "Tx_V": "", + "Tx_R": "", + "Rx_R": "" + }, + "Model_Specific": { + + } +} \ No newline at end of file diff --git a/pyaedt/generic/ibis_reader.py b/pyaedt/generic/ibis_reader.py index 03357e3aaa4..3a877113fae 100644 --- a/pyaedt/generic/ibis_reader.py +++ b/pyaedt/generic/ibis_reader.py @@ -1,10 +1,15 @@ +import json +import logging import os +import re +import traceback import pyaedt from pyaedt import settings from pyaedt.generic.general_methods import check_and_download_file from pyaedt.generic.general_methods import check_if_path_exists -from pyaedt.generic.general_methods import open_file + +logger = logging.getLogger(__name__) class Component: @@ -39,7 +44,7 @@ def manufacturer(self): Examples -------- - >>> ibis = ibis_reader.IbisReader(os.path.join(path_to_ibis_files, "u26a_800_modified.ibs"), circuit) + >>> ibis = ibis_reader.IbisReader(os.path.join(path_to_ibis_files, 'u26a_800_modified.ibs'), circuit) >>> ibis.components["MT47H64M4BP-3_25"].manufacturer 'Micron Technology, Inc.' @@ -69,7 +74,7 @@ def pins(self, value): self._pins = value -class Pin(Component): +class Pin: """Pin from a component with all its data feature. Parameters @@ -80,8 +85,9 @@ class Pin(Component): Circuit in which the pin will be added to. """ - def __init__(self, name, circuit): + def __init__(self, name, buffername, circuit): self._name = name + self._buffer_name = buffername self._circuit = circuit self._short_name = None self._signal = None @@ -103,6 +109,11 @@ def name(self): """ return self._name + @property + def buffer_name(self): + """Full name of the buffer including the component name and the ibis filename.""" + return self._buffer_name + @property def short_name(self): """Name of the pin without the name of the component. @@ -208,24 +219,28 @@ def c_value(self, value): def add(self): """Add a pin to the list of components in the Project Manager.""" - self._circuit.modeler.schematic.o_component_manager.AddSolverOnDemandModel( - self.name, - [ - "NAME:CosimDefinition", - "CosimulatorType:=", - 7, - "CosimDefName:=", - "DefaultIBISNetlist", - "IsDefinition:=", - True, - "Connect:=", - True, - "Data:=", - [], - "GRef:=", - [], - ], - ) + try: + return self._circuit.modeler.schematic.o_component_manager.AddSolverOnDemandModel( + self.buffer_name, + [ + "NAME:CosimDefinition", + "CosimulatorType:=", + 7, + "CosimDefName:=", + "DefaultIBISNetlist", + "IsDefinition:=", + True, + "Connect:=", + True, + "Data:=", + [], + "GRef:=", + [], + ], + ) + except: + logger.error("Error adding {} pin component.".format(self.short_name)) + return False def insert(self, x, y, angle=0.0): """Insert a pin at a defined location inside the graphical window. @@ -248,7 +263,7 @@ def insert(self, x, y, angle=0.0): return self._circuit.modeler.schematic.create_component( component_library=None, - component_name=self.name, + component_name=self.buffer_name, location=[x, y], angle=angle, ) @@ -372,6 +387,8 @@ def __init__(self): self._name = None self._clamp = None self._enable = None + self._ami = None + self._c_comp = None @property def name(self): @@ -409,6 +426,24 @@ def enable(self): def enable(self, value): self._enable = value + @property + def ami(self): + """Is model enabled or not.""" + return self._ami + + @ami.setter + def ami(self, value): + self._ami = value + + @property + def c_comp(self): + """Is model enabled or not.""" + return self._c_comp + + @c_comp.setter + def c_comp(self, value): + self._c_comp = value + class Ibis: """Ibis model with all data extracted: name, components, models. @@ -471,6 +506,67 @@ def buffers(self, value): self._buffers = value +class AMI: + """Ibis-AMI model with all data extracted: name, components, models. + + Parameters + ---------- + name : str + Name of ibis model. + circuit : class:`pyaedt.circuit.Circuit` + Circuit in which the ibis components will be used. + """ + + # Ibis reader must work independently or in Circuit. + def __init__(self, name, circuit): + self.circuit = circuit + self._name = name + self._components = {} + self._model_selectors = [] + self._models = [] + + @property + def name(self): + """Name of the ibis model.""" + return self._name + + @property + def components(self): + """List of all components included in the ibis file.""" + return self._components + + @components.setter + def components(self, value): + self._components = value + + @property + def model_selectors(self): + """List of all model selectors included in the ibis file.""" + return self._model_selectors + + @model_selectors.setter + def model_selectors(self, value): + self._model_selectors = value + + @property + def models(self): + """List of all models included in the ibis file.""" + return self._models + + @models.setter + def models(self, value): + self._models = value + + @property + def buffers(self): + """Buffers included into the ibis model.""" + return self._buffers + + @buffers.setter + def buffers(self, value): + self._buffers = value + + class IbisReader(object): """Reads *.ibis file content. Setup an Ibis object exposing all the extracted data. @@ -531,19 +627,20 @@ def parse_ibis_file(self): file_to_open = check_and_download_file(local_path, self._filename) else: file_to_open = self._filename + # Read *.ibis file. - with open_file(file_to_open, "r") as ibis_file: - while True: - current_line = ibis_file.readline() - if not current_line: - break - - if is_started_with(current_line, "[Component] "): - self.read_component(ibis, current_line, ibis_file) - elif is_started_with(current_line, "[Model] "): - self.read_model(ibis, current_line, ibis_file) - elif is_started_with(current_line, "[Model Selector] "): - self.read_model_selector(ibis, current_line, ibis_file) + ibis_info = ibis_parsing(self._filename) + component_selector = [ibis_info[item] for item in ibis_info if "component" in item] + + self.read_component(ibis, component_selector) + + model_selector = [ibis_info[item] for item in ibis_info if "model selector" in item] + self.read_model_selector(ibis, model_selector) + + # model = [ibis_info[item] for item in ibis_info if 'selector' not in item and 'model' in item] + model = [ibis_info[item] for item in ibis_info if re.match(r"^model\d*$", item) is not None] + + self.read_model(ibis, model) buffers = {} for model_selector in ibis.model_selectors: @@ -569,16 +666,16 @@ def parse_ibis_file(self): False, ] arg_buffers = ["NAME:Buffers"] - for buffer in buffers: - arg_buffers.append("{}:=".format(buffers[buffer].short_name)) + for buffer_item in buffers.values(): + arg_buffers.append("{}:=".format(buffer_item.short_name)) arg_buffers.append([True, "IbisSingleEnded"]) - model_names = [i.name for i in ibis.models] + model_selector_names = [i.name for i in ibis.model_selectors] arg_components = ["NAME:Components"] - for component in ibis.components: - arg_component = ["NAME:{}".format(ibis.components[component].name)] - for pin in ibis.components[component].pins: - arg_component.append("{}:=".format(ibis.components[component].pins[pin].short_name)) - if ibis.components[component].pins[pin].model not in model_names: + for comp_value in ibis.components.values(): + arg_component = ["NAME:{}".format(comp_value.name)] + for pin in comp_value.pins.values(): + arg_component.append("{}:=".format(pin.short_name)) + if pin.model not in model_selector_names: arg_component.append([False, False]) else: arg_component.append([True, False]) @@ -590,9 +687,10 @@ def parse_ibis_file(self): self._circuit.modeler.schematic.o_component_manager.ImportModelsFromFile(self._filename, args) self._ibis_model = ibis + return ibis_info # Model - def read_model(self, ibis, current_line, ibis_file): + def read_model(self, ibis, model_list): """Extracts model's info. Parameters @@ -605,43 +703,41 @@ def read_model(self, ibis, current_line, ibis_file): File's stream. """ - - if not is_started_with(current_line, "[Model] "): - return - - model = Model() - model.name = current_line.split("]")[1].strip() - current_line = ibis_file.readline().replace("\t", "").strip() - - while not is_started_with(current_line, "Model_type"): - current_line = ibis_file.readline().replace("\t", "").strip() - - iStart = current_line.index(" ", 1) - - if iStart > 0: - model.ModelType = current_line[iStart:].strip() - - # Clamp - while not current_line: - current_line = ibis_file.readline().strip.replace("clamp", "Clamp") - - if is_started_with(current_line, "[GND Clamp]"): - model.Clamp = True - break - elif is_started_with(current_line, "[GND_Clamp]"): - model.Clamp = True - break - elif is_started_with(current_line, "Enable ", True): - model.Enable = current_line[len("Enable") + 1 :].strip() - elif is_started_with(current_line, "[Rising Waveform]"): - break - elif is_started_with(current_line, "[Ramp]"): - break - - ibis.models.append(model) + for model_info in model_list: + model_spec_info = model_info["model"].strip().split("\n") + for idx, model_spec in enumerate(model_spec_info): + if not idx: + model = Model() + model.name = model_spec + else: + if is_started_with(model_spec.lower(), "model_type"): + # model.model_type = model_spec.split()[-1].strip() + model.model_type = model_spec.split()[-1].strip() + # iStart = model_spec.index(" ", 1) + # if iStart > 0: + # model.ModelType = model_spec[iStart:].strip() + elif is_started_with(model_spec.lower(), "c_comp"): + model.c_comp = model_spec.split()[1:] + + elif is_started_with(model_spec.lower(), "enable ", True): + model.enable = model_spec.split()[-1].strip() + + model_info_lower = {key.lower(): value for key, value in model_info.items()} + + if "gnd clamp" in [key.lower() for key in model_info.keys()]: + model.clamp = True + if "algorithmic model" in [key.lower() for key in model_info.keys()]: + matching_key = next((key for key in model_info.keys() if "algorithmic model" in key.lower()), None) + ami_info = model_info[matching_key][matching_key].split() + model.ami = model_info[matching_key][matching_key].split() + ibis.AMI = True + else: + ibis.AMI = False + + ibis.models.append(model) # Model Selector - def read_model_selector(self, ibis, current_line, ibis_file): + def read_model_selector(self, ibis, model_selector_list): """Extracts model selector's info. Parameters @@ -655,22 +751,18 @@ def read_model_selector(self, ibis, current_line, ibis_file): """ - if not is_started_with(current_line, "[Model Selector] "): - return - - model_selector = ModelSelector() - model_selector.model_selector_items = [] - model_selector.name = current_line.split("]")[1].strip() - - current_line = ibis_file.readline() + for model_selector_info in model_selector_list: + model_selector_info = model_selector_info["model selector"].strip().split("\n") + for idx, model in enumerate(model_selector_info): + if not idx: + model_selector = ModelSelector() + model_selector.model_selector_items = [] + model_selector.name = model + else: + model_selector.model_selector_items.append(self.make_model(model.strip())) - # Model Selector - while not is_started_with(current_line, "|") and current_line.strip() != "": - model_selector.model_selector_items.append(self.make_model(current_line.strip())) - current_line = ibis_file.readline() - - # ModelSelectorItem - ibis.model_selectors.append(model_selector) + # ModelSelectorItem + ibis.model_selectors.append(model_selector) @classmethod def make_model(cls, current_line): @@ -692,65 +784,38 @@ def make_model(cls, current_line): i_start = current_line.index(" ", 1) if i_start > 0: - item.name = current_line[i_start:].strip() + item.name = current_line[:i_start].strip() item.description = current_line[i_start:].strip() return item # Component - def read_component(self, ibis, current_line, ibis_file): + def read_component(self, ibis, comp_infos): """Extracts component's info. Parameters ---------- ibis : :class:`pyaedt.generic.ibis_reader.Ibis` ibis object containing all info. - current_line : str - Current line content. - ibis_file : TextIO - File's stream. + comp_infos : list """ - - if not is_started_with(current_line, "[Component] "): - return - - component = Component() - component.name = self.get_component_name(current_line) - current_line = ibis_file.readline() - - if is_started_with(current_line, "[Manufacturer]"): - component.manufacturer = current_line.replace("[Manufacturer]", "").strip() - - while True: - current_line = ibis_file.readline() - if is_started_with(current_line, "[Package]"): - break - - self.fill_package_info(component, current_line, ibis_file) - - # [pin] - while not is_started_with(current_line, "[Pin] "): - current_line = ibis_file.readline() - - while True: - current_line = ibis_file.readline() - if is_started_with(current_line, "|"): - break - - current_line = ibis_file.readline() - - while not is_started_with(current_line, "|"): - pin = self.make_pin_object(current_line, component.name, ibis) - component.pins[pin.name] = pin - current_line = ibis_file.readline() - if current_line == "": - break - - ibis.components[component.name] = component + if not isinstance(comp_infos, list): + comp_infos = [comp_infos] + for comp_info in comp_infos: + component = Component() + component.name = comp_info["component"] + component.manufacturer = comp_info["manufacturer"]["manufacturer"] + self.fill_package_info(component, comp_info["package"]["package"]) + pin_list = comp_info["pin"]["pin"].strip().split("\n")[1:] + for pin_info in pin_list: + pin = self.make_pin_object(pin_info, component.name, ibis) + component.pins[pin.name] = pin + + ibis.components[component.name] = component @classmethod - def fill_package_info(cls, component, current_line, ibis_file): + def fill_package_info(cls, component, pkg_info): """Extracts model's info. Parameters @@ -763,17 +828,14 @@ def fill_package_info(cls, component, current_line, ibis_file): File's stream. """ - while is_started_with(current_line, "|") or is_started_with(current_line, "["): - current_line = ibis_file.readline() - - if is_started_with(current_line, "R_pkg"): - component.R_pkg = current_line.strip() - current_line = ibis_file.readline() - elif is_started_with(current_line, "L_pkg"): - component.L_pkg = current_line.strip() - current_line = ibis_file.readline() - elif is_started_with(current_line, "C_pkg"): - component.C_pkg = current_line.strip() + pkg_info = pkg_info.strip().split("\n") + for rlc in pkg_info: + if is_started_with(rlc, "R_pkg"): + component.R_pkg = rlc.strip() + elif is_started_with(rlc, "L_pkg"): + component.L_pkg = rlc.strip() + elif is_started_with(rlc, "C_pkg"): + component.C_pkg = rlc.strip() @classmethod def get_component_name(cls, line): @@ -792,7 +854,6 @@ def get_component_name(cls, line): """ return line.replace("[Component]", "").strip() - # Pin def make_pin_object(self, line, component_name, ibis): """Extracts model's info. @@ -815,23 +876,35 @@ def make_pin_object(self, line, component_name, ibis): current_string = "" current_string = line.strip().replace("\t", " ") + pin_name = self.get_first_parameter(current_string) - pin = Pin(pin_name + "_" + component_name + "_" + ibis.name, ibis.circuit) - pin.short_name = pin_name - current_string = current_string[len(pin.short_name) + 1 :].strip() - pin.signal = self.get_first_parameter(current_string) + current_string = current_string[len(pin_name) + 1 :].strip() + + signal = self.get_first_parameter(current_string) + current_string = current_string[len(signal) + 1 :].strip() + + model = self.get_first_parameter(current_string) + current_string = current_string[len(model) + 1 :].strip() - current_string = current_string[len(pin.signal) + 1 :].strip() - pin.model = self.get_first_parameter(current_string) + r_value = self.get_first_parameter(current_string) + current_string = current_string[len(r_value) + 1 :].strip() - current_string = current_string[len(pin.model) + 1 :].strip() - pin.r_value = self.get_first_parameter(current_string) + l_value = self.get_first_parameter(current_string) + current_string = current_string[len(l_value) + 1 :].strip() - current_string = current_string[len(pin.r_value) + 1 :].strip() - pin.l_value = self.get_first_parameter(current_string) + c_value = self.get_first_parameter(current_string) - current_string = current_string[len(pin.l_value) + 1 :].strip() - pin.c_value = self.get_first_parameter(current_string) + pin = Pin( + pin_name + "_" + component_name + "_" + ibis.name, + signal + "_" + component_name + "_" + ibis.name, + ibis.circuit, + ) + pin.short_name = pin_name + pin.signal = signal + pin.model = model + pin.r_value = r_value + pin.l_value = l_value + pin.c_value = c_value return pin @@ -858,6 +931,128 @@ def get_first_parameter(cls, line): return data[0].strip() +class AMIReader(IbisReader): + """Reads *.ibis file content. + Setup an Ibis object exposing all the extracted data. + + Parameters + ---------- + filename : str + Name of ibis model. + circuit : class:`pyaedt.circuit.Circuit` + Circuit in which the ibis components will be used. + """ + + def __init__(self, filename, circuit): + self._filename = filename + self._circuit = circuit + self._ami_model = None + + @property + def ami_model(self): + "Ibis-AMI model gathering the entire set of data extracted from the \\*.ami file." + return self._ami_model + + def parse_ibis_file(self): + """Reads \\*.ami file content. + + Parameters + ---------- + filename : str + Name of ibis model. + circuit : class:`pyaedt.circuit.Circuit` + Circuit in which the ibis components will be used. + + Returns + ---------- + :class:`pyaedt.generic.ibis_reader.Ibis` + Ibis object exposing all data from the ibis file. + + Examples + -------- + Read u26a_800.ibs file provided in the AEDT suit installation. + >>> import os + >>> from pyaedt import Desktop + >>> from pyaedt.circuit import Circuit + >>> from pyaedt.generic import ibis_reader + >>> desktop = Desktop() + >>> circuit = Circuit() + >>> ibis = ibis_reader.IbisReader(os.path.join(desktop.install_path, "buflib", "IBIS", "u26a_800.ibs"), circuit) + """ + + if not check_if_path_exists(self._filename): + raise Exception("{} does not exist.".format(self._filename)) + + ami_name = pyaedt.generic.general_methods.get_filename_without_extension(self._filename) + ibis = AMI(ami_name, self._circuit) + if settings.remote_rpc_session_temp_folder: + local_path = os.path.join(settings.remote_rpc_session_temp_folder, os.path.split(self._filename)[-1]) + file_to_open = check_and_download_file(local_path, self._filename) + else: + file_to_open = self._filename + + # Read *.ibis file. + ibis_info = ibis_parsing(self._filename) + component_selector = [ibis_info[item] for item in ibis_info if "component" in item] + + self.read_component(ibis, component_selector) + + model_selector = [ibis_info[item] for item in ibis_info if "model selector" in item] + self.read_model_selector(ibis, model_selector) + + # model = [ibis_info[item] for item in ibis_info if 'selector' not in item and 'model' in item] + model = [ibis_info[item] for item in ibis_info if item.startswith("model")] + + self.read_model(ibis, model) + + buffers = {} + for model_selector in ibis.model_selectors: + buffer = Buffer(ami_name, model_selector.name, self._circuit) + buffers[buffer.name] = buffer + + for model in ibis.models: + buffer = Buffer(ami_name, model.name, self._circuit) + buffers[buffer.name] = buffer + + ibis.buffers = buffers + + if self._circuit: + args = [ + "NAME:Options", + "Mode:=", + 4, + "Overwrite:=", + False, + "SupportsSimModels:=", + False, + "LoadOnly:=", + False, + ] + arg_buffers = ["NAME:Buffers"] + for buffer in buffers: + arg_buffers.append("{}:=".format(buffers[buffer].short_name)) + arg_buffers.append([True, "IbisSingleEnded"]) + model_selector_names = [i.name for i in ibis.model_selectors] + arg_components = ["NAME:Components"] + for component in ibis.components: + arg_component = ["NAME:{}".format(ibis.components[component].name)] + for pin in ibis.components[component].pins: + arg_component.append("{}:=".format(ibis.components[component].pins[pin].short_name)) + if ibis.components[component].pins[pin].model not in model_selector_names: + arg_component.append([False, False]) + else: + arg_component.append([True, False]) + arg_components.append(arg_component) + + args.append(arg_buffers) + args.append(arg_components) + + self._circuit.modeler.schematic.o_component_manager.ImportModelsFromFile(self._filename, args) + + self._ibis_model = ibis + return ibis_info + + def is_started_with(src, find, ignore_case=True): """Verifies if a string content starts with a specific string or not. @@ -883,3 +1078,140 @@ def is_started_with(src, find, ignore_case=True): if ignore_case: return src.lower().startswith(find.lower()) return src.startswith(find) + + +def lowercase_json(json_data): + """Convert a json structure to lower case.""" + if isinstance(json_data, str): + return json_data.lower() + elif isinstance(json_data, dict): + return {lowercase_json(k): lowercase_json(v) for k, v in json_data.items()} + elif isinstance(json_data, list): + return [lowercase_json(item) for item in json_data] + else: + return json_data + + +def ibis_parsing(file): + """Open and parse ibis file using json Ibis template. + + Parameters + ---------- + file : str + File name to parse. + """ + ibis = {} + # OPEN AND READ IBIS FILE + with open(file, "r") as fp: + ibis_data = list(enumerate(fp)) + + with open(os.path.join(os.path.dirname(os.path.abspath(__file__)), "ibis_v7.json"), "r") as f: + ibis_ref = json.load(f) + ibis_ref = lowercase_json(ibis_ref) + + # FOR EACH LINE + try: + level = -1 + key_iter = [0, 0, 0, 0] + pre_key_ref = ["", "", "", ""] + pre_key_save = ["", "", "", ""] + for idx, line in ibis_data: + # COMMENT + if line[0] == "|": + pass + + # KEYWORD START + elif line[0] == "[": + # FIND IBIS KEYWORD : [keyword] + key = line.split("[")[-1].split("]")[0].replace("_", " ") + key_ref = key.lower() + # if key == 'End': + # print("JH") + val = line.split("]")[-1].strip() + if key_ref == "model": + pass + + if "end" in key_ref: + pass + + # FOR TOP LEVEL KEYWORD + elif key_ref in ibis_ref.keys(): + level = 0 + if key_ref in ibis.keys(): + key_iter[0] += 1 + key_save = key_ref + str(key_iter[0]) + else: + key_save = key_ref + + ibis[key_save] = {} + ibis[key_save][key_ref] = val + pre_key_ref[0] = key_ref + pre_key_save[0] = key_save + + # FOR 2ND LEVEL KEYWORD + elif key_ref in ibis_ref[pre_key_ref[0]].keys(): + level = 1 + if key_ref in ibis[pre_key_save[0]].keys(): + key_iter[1] += 1 + key_save = key_ref + str(key_iter[1]) + else: + key_save = key_ref + ibis[pre_key_save[0]][key_save] = {} + ibis[pre_key_save[0]][key_save][key_ref] = val + pre_key_ref[1] = key_ref + pre_key_save[1] = key_save + + # FOR third LEVEL KEYWORD + elif key_ref in ibis_ref[pre_key_ref[0]][pre_key_ref[1]].keys(): + level = 2 + if key_ref in ibis[pre_key_save[0]][pre_key_save[1]].keys(): + key_iter[2] += 1 + key_save = key_ref + str(key_iter[2]) + else: + key_save = key_ref + ibis[pre_key_save[0]][pre_key_save[1]][key_save] = {} + ibis[pre_key_save[0]][pre_key_save[1]][key_save][key_ref] = val + pre_key_ref[2] = key_ref + pre_key_save[2] = key_save + + # FOR 4TH LEVEL KEYWORD + elif key_ref in ibis_ref[pre_key_ref[0]][pre_key_ref[1]][pre_key_ref[2]].keys(): + level = 3 + if key_ref in ibis[pre_key_save[0]][pre_key_save[1]][pre_key_save[2]].keys(): + key_iter[3] += 1 + key_save = key_ref + str(key_iter[3]) + else: + key_save = key_ref + ibis[pre_key_save[0]][pre_key_save[1]][pre_key_save[2]][key_save] = {} + ibis[pre_key_save[0]][pre_key_save[1]][pre_key_save[2]][key_save][key_ref] = val + pre_key_ref[3] = key_ref + pre_key_save[3] = key_save + + else: + logger.error("Invalid IBIS Keyword : {}".format(key)) + return False + + # ALREADY FIND OUT KEYWORD + else: + # IF NOT BLANK LINE + if not line.strip() == "": + # FOR TOP LEVEL KEYWORD + if level == 0: + ibis[pre_key_save[0]][key_ref] += "\n" + line.strip() + + elif level == 1: + ibis[pre_key_save[0]][pre_key_save[1]][key_ref] += "\n" + line.strip() + + elif level == 2: + ibis[pre_key_save[0]][pre_key_save[1]][pre_key_save[2]][key_ref] += "\n" + line.strip() + + elif level == 3: + ibis[pre_key_save[0]][pre_key_save[1]][pre_key_save[2]][pre_key_save[3]][key_ref] += ( + "\n" + line.strip() + ) + except Exception: + logger.error(traceback.format_exc()) + return False + + # RETURN IBIS PARSING RESULT + return ibis diff --git a/pyaedt/generic/ibis_v7.json b/pyaedt/generic/ibis_v7.json new file mode 100644 index 00000000000..b98f09bb366 --- /dev/null +++ b/pyaedt/generic/ibis_v7.json @@ -0,0 +1,134 @@ +{ + "IBIS Ver": "", + "Comment Char": "", + "File Name": "", + "File Rev": "", + "Date": "", + "Source": "", + "Notes": "", + "Disclaimer": "", + "Copyright": "", + "Component": { + "Manufacturer": "", + "Package": "", + "Pin": "", + "Package Model": { + "Alternate Package Models": "" + }, + "Interconnect Model Group": "", + "Pin Mapping": "", + "Bus Label": "", + "Die Supply Pads": "", + "Diff Pin": "", + "Repeater Pin": "", + "Series Pin Mapping": "", + "Series Switch Groups": "", + "Node Declarations": "", + "Circuit Call": "", + "Begin EMI Component": { + "Pin EMI": "", + "Pin Domain EMI": "" + } + }, + "Model Selector": "", + "Model": { + "Model Spec": "", + "Receiver Thresholds": "", + "Add Submodel": "", + "Driver Schedule": "", + "Temperature Range": "", + "Voltage Range": "", + "Pullup Reference": "", + "Pulldown Reference": "", + "POWER Clamp Reference": "", + "GND Clamp Reference": "", + "External Reference": "", + "C Comp Corner": "", + "TTgnd": "", + "TTpower": "", + "Pulldown": "", + "Pullup": "", + "GND Clamp": "", + "POWER Clamp": "", + "ISSO PU": "", + "ISSO PD": "", + "Rgnd": "", + "Rpower": "", + "Rac": "", + "Cac": "", + "On": "", + "Off": "", + "R Series": "", + "L Series": "", + "Rl Series": "", + "C Series": "", + "Lc Seeries": "", + "Rc Series": "", + "Series Current": "", + "Series MOSFET": "", + "Ramp": "", + "Rising Waveform": { + "Composite Current": "" + }, + "Falling Waveform": { + "Composite Current": "" + }, + "Initial Delay": "", + "External Model": "", + "Algorithmic Model": "", + "Begin EMI Model": "" + }, + "Submodel": { + "Submodel Spec": "", + "POWER Pulse Table": "", + "GND Pulse Table": "", + "Pulldown": "", + "Pullup": "", + "GND Clamp": "", + "POWER Clamp": "", + "Ramp": "", + "Rising Waveform": "", + "Falling Waveform": "", + "Initial Delay": "" + }, + "External Circuit": "", + "Test Data": { + "Rising Waveform Near": "", + "Falling Waveform Near": "", + "Rising Waveform Far": "", + "Falling Waveform Far": "", + "Diff Rising Waveform Near": "", + "Diff Falling Waveform Near": "", + "Diff Rising Waveform Far": "", + "Diff Falling Waveform Far": "" + }, + "Test Load": "", + "Define Package Model": { + "Manufacturer": "", + "OEM": "", + "Description": "", + "Number Of Sections": "", + "Number of Pins": "", + "Pin Numbers": "", + "Merged Pins": "", + "Model Data": { + "Resistance Matrix": { + "Bandwidth": "", + "Row": "" + }, + "Inductance Matrix": { + "Bandwidth": "", + "Row": "" + }, + "Capacitance Matrix": { + "Bandwidth": "", + "Row": "" + } + } + }, + "Interconnect Model Set": { + "Manufacturer": "", + "Description": "", + "Interconnect Model": "" + } +} diff --git a/pyaedt/modeler/circuits/object3dcircuit.py b/pyaedt/modeler/circuits/object3dcircuit.py index 034eea0fda7..a2ea83591b0 100644 --- a/pyaedt/modeler/circuits/object3dcircuit.py +++ b/pyaedt/modeler/circuits/object3dcircuit.py @@ -305,19 +305,25 @@ class ComponentParameters(dict): def __setitem__(self, key, value): try: - self._component._oeditor.ChangeProperty( - [ - "NAME:AllTabs", - [ - "NAME:" + self._tab, - ["NAME:PropServers", self._component.composed_name], - ["NAME:ChangedProps", ["NAME:" + key, "Value:=", str(value)]], - ], - ] - ) + self._component._oeditor.SetPropertyValue(self._tab, self._component.composed_name, key, str(value)) dict.__setitem__(self, key, value) except: - self._component._circuit_components.logger.warning("Property %s has not been edited.Check if readonly", key) + try: + self._component._oeditor.ChangeProperty( + [ + "NAME:AllTabs", + [ + "NAME:" + self._tab, + ["NAME:PropServers", self._component.composed_name], + ["NAME:ChangedProps", ["NAME:" + key, "ButtonText:=", str(value)]], + ], + ] + ) + dict.__setitem__(self, key, value) + except: + self._component._circuit_components.logger.warning( + "Property %s has not been edited.Check if readonly", key + ) def __init__(self, component, tab, *args, **kw): dict.__init__(self, *args, **kw) diff --git a/pyproject.toml b/pyproject.toml index 0d25d52d58a..46a91367baa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,7 +40,7 @@ tests = [ "imageio==2.31.3", "joblib==1.3.2", "matplotlib==3.5.3; python_version <= '3.7'", - "matplotlib==3.7.2; python_version > '3.7'", + "matplotlib==3.7.3; python_version > '3.7'", "numpy==1.21.6; python_version <= '3.9'", "numpy==1.25.2; python_version > '3.9'", "openpyxl==3.1.2", @@ -52,7 +52,7 @@ tests = [ "pytest==7.4.2", "pytest-cov==4.1.0", "pytest-xdist==3.3.1", - "pyvista==0.42.1; python_version > '3.7'", + "pyvista==0.42.2; python_version > '3.7'", "pyvista==0.38.0; python_version <= '3.7'", "scikit-learn==1.3.0", "SRTM.py", @@ -60,7 +60,7 @@ tests = [ "scikit-rf", ] doc = [ - "ansys-sphinx-theme==0.11.1", + "ansys-sphinx-theme==0.11.2", "imageio==2.31.3", "imageio-ffmpeg==0.4.9", "ipython==8.13.0; python_version < '3.9'", @@ -69,18 +69,18 @@ doc = [ "joblib==1.3.2", "jupyterlab==4.0.5", "matplotlib==3.5.3; python_version <= '3.7'", - "matplotlib==3.7.2; python_version > '3.7'", + "matplotlib==3.7.3; python_version > '3.7'", "nbsphinx==0.9.3", "numpydoc==1.5.0", "osmnx", "pypandoc==1.11", "pytest-sphinx==0.5.0", - "pyvista==0.42.1; python_version > '3.7'", + "pyvista==0.42.2; python_version > '3.7'", "pyvista==0.38.0; python_version <= '3.7'", "recommonmark==0.7.1", "scikit-learn==1.3.0", "Sphinx==7.1.2; python_version <= '3.9'", - "Sphinx==7.2.5; python_version >= '3.9'", + "Sphinx==7.2.6; python_version >= '3.9'", "sphinx-autobuild==2021.3.14", "sphinx-autodoc-typehints==1.24.0", "sphinx-copybutton==0.5.2", @@ -96,7 +96,7 @@ doc = [ full = [ "imageio", "matplotlib==3.5.3; python_version <= '3.7'", - "matplotlib==3.7.2; python_version > '3.7'", + "matplotlib==3.7.3; python_version > '3.7'", "numpy==1.21.6; python_version <= '3.9'", "numpy==1.25.2; python_version > '3.9'", "pandas==1.3.5; python_version == '3.7'", @@ -104,7 +104,7 @@ full = [ "pandas==2.0.3; python_version == '3.8'", "pandas==2.1.0; python_version > '3.9'", "osmnx", - "pyvista==0.42.1; python_version > '3.7'", + "pyvista==0.42.2; python_version > '3.7'", "pyvista==0.38.0; python_version <= '3.7'", "SRTM.py", "utm", @@ -114,7 +114,7 @@ full = [ all = [ "imageio", "matplotlib==3.5.3; python_version <= '3.7'", - "matplotlib==3.7.2; python_version > '3.7'", + "matplotlib==3.7.3; python_version > '3.7'", "numpy==1.21.6; python_version <= '3.9'", "numpy==1.25.2; python_version > '3.9'", "pandas==1.3.5; python_version == '3.7'", @@ -122,7 +122,7 @@ all = [ "pandas==2.0.3; python_version == '3.8'", "pandas==2.1.0; python_version > '3.9'", "osmnx", - "pyvista==0.42.1; python_version > '3.7'", + "pyvista==0.42.2; python_version > '3.7'", "pyvista==0.38.0; python_version <= '3.7'", "SRTM.py", "utm",