diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 00000000000..ec0ee3183ad --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,36 @@ +# One commit hash per line, and all commits in the file will be ignored by git blame. + +# Apply flake8 policy for unused imports (#449) +f2ebb7a80978e1ea82328e9b6afd3f9484362552 +# Organize module imports (#448) +98bb0a1bfd27f8174ef86a131b3f6eed09f562ad +# Format line length (#445) +b82041bb75d71b8e11b54f9dcfdfc696a1bcf68c +# Format modeler files (#441) +0174ae92359f49fb8215f16d715f3afe8598745f +# Format example files (#444) +41eb277bc05b6736c674af33d5b601c3bc9a33d3 +# Format ipy unittests (#443) +522b13c6ed123be60eb6220bfe6fed35ba059ab8 +# Format unittests (#442) +11d60aa7e31f3494d8a73c0223c924ff17eec985 +# Format modules files (#436) +0798f3f4c2252c1c3c486b8400540939c5b42dc6 +# Format application files (#434) +19b758272a6d9551a8837b5f2d3a6283974d3367 +# Format pyaedt files with black (#433) +3c3f0c6e01336eae66199bb9e461319d18a435d7 +# Formatting - fix line length part 2 (#415) +58bbdc280545f7345b7999db0f17c3ff666e0be2 +# Formatting - fix line length part 1 (#414) +af6af2e6b5db5e1485bfddd2b9d0e363614ecfce +# Formatting - blank lines (#412) +babf97db629f2901f40ae080ac3a7e24084a1e87 +# Formatting - whitespace on blank lines (#409) +08e4ae515f7d5fd4a37e7baab7b321c907e66324 +# Formatting - indentations (#407) +9ddfe050d95feaa022cc30cc64acabee16029c07 +# Remove trailing whitespace (#404) +a4b5fe60c7d0a7db624d7ab160a0ce5e1f378759 +# Run autopep8 W291, remove trailing whitespace (#401) +159bb43d42ad502c2440531d71cf7df6c780a8c9 diff --git a/.github/workflows/ci_cd.yml b/.github/workflows/ci_cd.yml index b20d2f17963..49048e51208 100644 --- a/.github/workflows/ci_cd.yml +++ b/.github/workflows/ci_cd.yml @@ -523,7 +523,9 @@ jobs: uses: actions/checkout@v4 - name: Install the package requirements - run: pip install -e . + run: | + python -m pip install --upgrade pip + pip install -e . - name: Get the version to PyMeilisearch run: | diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 83d0e5ee859..8409e50c7a5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -48,7 +48,7 @@ repos: # validate GitHub workflow files - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.29.1 + rev: 0.29.2 hooks: - id: check-github-workflows diff --git a/README.md b/README.md index 435b79794af..fbefa97cc5a 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ [![PyAnsys](https://img.shields.io/badge/Py-Ansys-ffc107.svg?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAABDklEQVQ4jWNgoDfg5mD8vE7q/3bpVyskbW0sMRUwofHD7Dh5OBkZGBgW7/3W2tZpa2tLQEOyOzeEsfumlK2tbVpaGj4N6jIs1lpsDAwMJ278sveMY2BgCA0NFRISwqkhyQ1q/Nyd3zg4OBgYGNjZ2ePi4rB5loGBhZnhxTLJ/9ulv26Q4uVk1NXV/f///////69du4Zdg78lx//t0v+3S88rFISInD59GqIH2esIJ8G9O2/XVwhjzpw5EAam1xkkBJn/bJX+v1365hxxuCAfH9+3b9/+////48cPuNehNsS7cDEzMTAwMMzb+Q2u4dOnT2vWrMHu9ZtzxP9vl/69RVpCkBlZ3N7enoDXBwEAAA+YYitOilMVAAAAAElFTkSuQmCC)](https://docs.pyansys.com/) [![pypi](https://img.shields.io/pypi/v/pyaedt.svg?logo=python&logoColor=white)](https://pypi.org/project/pyaedt/) [![PyPIact](https://static.pepy.tech/badge/pyaedt/month)](https://www.pepy.tech/projects/pyaedt) -[![PythonVersion](https://img.shields.io/badge/python-3.7+-blue.svg)](https://www.python.org/downloads/) +[![PythonVersion](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/downloads/) [![GH-CI](https://github.com/ansys/pyaedt/actions/workflows/unit_tests.yml/badge.svg)](https://github.com/ansys/pyaedt/actions/workflows/unit_tests.yml) [![codecov](https://codecov.io/gh/ansys/pyaedt/branch/main/graph/badge.svg)](https://codecov.io/gh/ansys/pyaedt) [![MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/blog/license/mit)[![black](https://img.shields.io/badge/code%20style-black-000000.svg?style=flat)](https://github.com/psf/black)[![Anaconda](https://anaconda.org/conda-forge/pyaedt/badges/version.svg)](https://anaconda.org/conda-forge/pyaedt) diff --git a/README_CN.md b/README_CN.md index c2934de6367..000205ec7f9 100644 --- a/README_CN.md +++ b/README_CN.md @@ -10,7 +10,7 @@
English | 中文

-[![PyAnsys](https://img.shields.io/badge/Py-Ansys-ffc107.svg?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAABDklEQVQ4jWNgoDfg5mD8vE7q/3bpVyskbW0sMRUwofHD7Dh5OBkZGBgW7/3W2tZpa2tLQEOyOzeEsfumlK2tbVpaGj4N6jIs1lpsDAwMJ278sveMY2BgCA0NFRISwqkhyQ1q/Nyd3zg4OBgYGNjZ2ePi4rB5loGBhZnhxTLJ/9ulv26Q4uVk1NXV/f///////69du4Zdg78lx//t0v+3S88rFISInD59GqIH2esIJ8G9O2/XVwhjzpw5EAam1xkkBJn/bJX+v1365hxxuCAfH9+3b9/+////48cPuNehNsS7cDEzMTAwMMzb+Q2u4dOnT2vWrMHu9ZtzxP9vl/69RVpCkBlZ3N7enoDXBwEAAA+YYitOilMVAAAAAElFTkSuQmCC)](https://docs.pyansys.com/)[![pypi](https://img.shields.io/pypi/v/pyaedt.svg?logo=python&logoColor=white)](https://pypi.org/project/pyaedt/)[![PyPIact](https://pepy.tech/badge/pyaedt/month)](https://pypi.org/project/pyaedt/)[![PythonVersion](https://img.shields.io/badge/python-3.7+-blue.svg)](https://www.python.org/downloads/)[![GH-CI](https://github.com/ansys/pyaedt/actions/workflows/unit_tests.yml/badge.svg)](https://github.com/ansys/pyaedt/actions/workflows/unit_tests.yml)[![codecov](https://codecov.io/gh/ansys/pyaedt/branch/main/graph/badge.svg)](https://codecov.io/gh/ansys/pyaedt)[![MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/blog/license/mit)[![black](https://img.shields.io/badge/code%20style-black-000000.svg?style=flat)](https://github.com/psf/black)[![Anaconda](https://anaconda.org/conda-forge/pyaedt/badges/version.svg)](https://anaconda.org/conda-forge/pyaedt)[![pre-commit](https://results.pre-commit.ci/badge/github/ansys/pyaedt/main.svg)](https://results.pre-commit.ci/latest/github/ansys/pyaedt/main) +[![PyAnsys](https://img.shields.io/badge/Py-Ansys-ffc107.svg?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAABDklEQVQ4jWNgoDfg5mD8vE7q/3bpVyskbW0sMRUwofHD7Dh5OBkZGBgW7/3W2tZpa2tLQEOyOzeEsfumlK2tbVpaGj4N6jIs1lpsDAwMJ278sveMY2BgCA0NFRISwqkhyQ1q/Nyd3zg4OBgYGNjZ2ePi4rB5loGBhZnhxTLJ/9ulv26Q4uVk1NXV/f///////69du4Zdg78lx//t0v+3S88rFISInD59GqIH2esIJ8G9O2/XVwhjzpw5EAam1xkkBJn/bJX+v1365hxxuCAfH9+3b9/+////48cPuNehNsS7cDEzMTAwMMzb+Q2u4dOnT2vWrMHu9ZtzxP9vl/69RVpCkBlZ3N7enoDXBwEAAA+YYitOilMVAAAAAElFTkSuQmCC)](https://docs.pyansys.com/)[![pypi](https://img.shields.io/pypi/v/pyaedt.svg?logo=python&logoColor=white)](https://pypi.org/project/pyaedt/)[![PyPIact](https://pepy.tech/badge/pyaedt/month)](https://pypi.org/project/pyaedt/)[![PythonVersion](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/downloads/)[![GH-CI](https://github.com/ansys/pyaedt/actions/workflows/unit_tests.yml/badge.svg)](https://github.com/ansys/pyaedt/actions/workflows/unit_tests.yml)[![codecov](https://codecov.io/gh/ansys/pyaedt/branch/main/graph/badge.svg)](https://codecov.io/gh/ansys/pyaedt)[![MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/blog/license/mit)[![black](https://img.shields.io/badge/code%20style-black-000000.svg?style=flat)](https://github.com/psf/black)[![Anaconda](https://anaconda.org/conda-forge/pyaedt/badges/version.svg)](https://anaconda.org/conda-forge/pyaedt)[![pre-commit](https://results.pre-commit.ci/badge/github/ansys/pyaedt/main.svg)](https://results.pre-commit.ci/latest/github/ansys/pyaedt/main) ## PyAEDT 简介 diff --git a/_unittest/conftest.py b/_unittest/conftest.py index eef19475e3b..183590b6378 100644 --- a/_unittest/conftest.py +++ b/_unittest/conftest.py @@ -62,6 +62,7 @@ settings.enable_desktop_logs = False settings.desktop_launch_timeout = 180 settings.release_on_exception = False +settings.wait_for_license = True from ansys.aedt.core import Edb from ansys.aedt.core import Hfss @@ -197,13 +198,16 @@ def _method( test_project = None if not application: application = Hfss - return application( - project=test_project, - design=design_name, - solution_type=solution_type, - version=desktop_version, - non_graphical=NONGRAPHICAL, - ) + + args = { + "project": test_project, + "design": design_name, + "version": desktop_version, + "non_graphical": NONGRAPHICAL, + } + if solution_type: + args["solution_type"] = solution_type + return application(**args) return _method diff --git a/_unittest/example_models/T12/template.rpt b/_unittest/example_models/T12/template.rpt new file mode 100644 index 00000000000..bd301c52488 --- /dev/null +++ b/_unittest/example_models/T12/template.rpt @@ -0,0 +1,379 @@ +$begin 'ReportDefinitions' + $begin 'ReportSetup' + $begin 'Reports' + $begin 'Plot_6LKGC0' + ReportID=117 + $begin 'Report2D' + name='Plot_6LKGC0' + ReportID=117 + ReportType=9 + DisplayType=4 + Title='' + Domain='' + $begin 'Migration' + MigVersion(0, 0, 'mig(0.0)') + $end 'Migration' + $begin 'Graph2DsV2' + $begin 'Graph2D' + TraceDefID=116 + Type='Continuous' + Axis='Y1' + $end 'Graph2D' + $end 'Graph2DsV2' + $begin 'PlotDisplayDataManager' + NextUniqueID=9 + MoveBackwards=false + $begin 'PlotHeaderDataSource' + CompanyName='' + ShowDesignName=true + ProjectFileName='' + $end 'PlotHeaderDataSource' + StockNameIDMap(DataTable=3, Header=0, PrimarySweep=1) + $begin 'SourceList' + $end 'SourceList' + Version='17.0:20150830' + $begin 'DocAttributes' + $begin 'PlotAttributeStoreMap' + $end 'PlotAttributeStoreMap' + $end 'DocAttributes' + $begin 'DisplayTypeAttributes' + $begin 'PlotAttributeStoreMap' + $begin 'MainMapItem' + $begin 'SubMapItem' + DataSourceID=7 + $begin 'CurveCartesianAttribute' + YAxis='Y1' + $end 'CurveCartesianAttribute' + $end 'SubMapItem' + $end 'MainMapItem' + $begin 'MainMapItem' + $begin 'SubMapItem' + DataSourceID=7 + $begin 'CurveRenderAttribute' + $begin 'LineRenderAttribute' + LineStyle='Solid' + LineWidth=3 + ColorVersion=1 + LineColor(R=237, G=28, B=36) + $end 'LineRenderAttribute' + TraceType='Continuous' + SymbolType='HollowHorizontalLeftTriangle' + SymbolColor(R=155, G=93, B=112) + ShowSymbols=false + SymbolFrequency=15 + ShowArrows=false + $end 'CurveRenderAttribute' + $end 'SubMapItem' + $end 'MainMapItem' + $begin 'MainMapItem' + $begin 'SubMapItem' + DataSourceID=0 + $begin 'HeaderRenderAttribute' + $begin 'TitleFont' + $begin 'FontAttribute' + $begin 'Font' + HeightInPts=14 + Width=0 + Escapement=0 + Orientation=0 + Weight=400 + Italic=0 + Underline=0 + StrikeOut=0 + CharSet=0 + OutPrecision=7 + ClipPrecision=48 + Quality=6 + PitchAndFamily=0 + FaceName='Arial' + $end 'Font' + ColorVersion=1 + $end 'FontAttribute' + $end 'TitleFont' + $begin 'SubtitleFont' + $begin 'FontAttribute' + $begin 'Font' + HeightInPts=10 + Width=0 + Escapement=0 + Orientation=0 + Weight=400 + Italic=0 + Underline=0 + StrikeOut=0 + CharSet=0 + OutPrecision=7 + ClipPrecision=48 + Quality=6 + PitchAndFamily=0 + FaceName='Arial' + $end 'Font' + ColorVersion=1 + $end 'FontAttribute' + $end 'SubtitleFont' + $end 'HeaderRenderAttribute' + $end 'SubMapItem' + $end 'MainMapItem' + $end 'PlotAttributeStoreMap' + $end 'DisplayTypeAttributes' + $begin 'DocDefaultAttributes' + $begin 'PlotAttributeStoreMap' + $end 'PlotAttributeStoreMap' + $end 'DocDefaultAttributes' + $begin 'PerViewPlotAttributeStoreMap' + $begin 'MapItem' + ItemID=6 + $begin 'PlotAttributeStoreMap' + $begin 'MainMapItem' + $begin 'SubMapItem' + DataSourceID=5 + $begin 'BasicLayoutAttribute' + $begin 'LayoutRect' + Top=75 + Left=75 + Bottom=9925 + Right=814 + $end 'LayoutRect' + $end 'BasicLayoutAttribute' + $end 'SubMapItem' + $end 'MainMapItem' + $begin 'MainMapItem' + $begin 'SubMapItem' + DataSourceID=4 + $begin 'OverlayLayoutAttribute' + $begin 'BoundingRect' + Top=225 + Left=989 + Bottom=9775 + Right=9775 + $end 'BoundingRect' + ModifySize=false + ModifyPosition=false + $end 'OverlayLayoutAttribute' + $end 'SubMapItem' + $end 'MainMapItem' + $end 'PlotAttributeStoreMap' + PlotType=25 + $end 'MapItem' + $end 'PerViewPlotAttributeStoreMap' + IsViewAttribServer=false + ViewID=-1 + $begin 'SourceIDMap' + IDMapItem(116, 0, -1, 7) + $end 'SourceIDMap' + $begin 'TraceCharacteristicsMgr' + $end 'TraceCharacteristicsMgr' + $begin 'CartesianXMarkerManager' + RefMarkerID=-1 + CurrentMarkerID=-1 + $begin 'ReferenceCurves' + $end 'ReferenceCurves' + $end 'CartesianXMarkerManager' + $begin 'CartesianYMarkerManager' + $end 'CartesianYMarkerManager' + XAxisStackID=-1 + $begin 'AllTransSrcDwg' + $begin 'PT' + ID=25 + TransSrcDwg(-1, 0, 5) + $end 'PT' + $end 'AllTransSrcDwg' + $begin 'AllPtSVID' + $end 'AllPtSVID' + $end 'PlotDisplayDataManager' + $end 'Report2D' + $end 'Plot_6LKGC0' + $end 'Reports' + $end 'ReportSetup' + $begin 'Reports' + $begin 'Plot_6LKGC0' + ReportID=117 + ReportName='Plot_6LKGC0' + $begin 'TraceDef' + TraceDefinitionType='TraceDefinition' + $begin 'DesignSolnDefn' + $begin 'DESIGN_SOLUTION_SIM_VALUE_CONTEXT' + DesignID=4 + SolutionID=5484 + $begin 'REPORT_TYPE_SIM_VALUE_CONTEXT' + ReportType=9 + SimValueContext(0, 0, 2, 0, false, false, 49, 1, 0, 1, 1, '', 0, 0, 'SourceContext', false, '0') + $end 'REPORT_TYPE_SIM_VALUE_CONTEXT' + $end 'DESIGN_SOLUTION_SIM_VALUE_CONTEXT' + $end 'DesignSolnDefn' + ID=116 + VersionID=2217 + Name='db(PeakRealizedGain)' + TieNameToExpr=true + $begin 'Components' + $begin 'TraceComponentDefinition' + Expr='Freq' + $end 'TraceComponentDefinition' + $begin 'TraceComponentDefinition' + Expr='db(PeakRealizedGain)' + $end 'TraceComponentDefinition' + $end 'Components' + $begin 'ExtendedTraceInfo' + NumPoints=0 + TraceType=0 + Offset=0 + XLabel='' + SamplingPeriod='0' + SamplingPeriodOffset='0' + AutoDelay=true + DelayValue='0ps' + AutoCompCrossAmplitude=true + CrossingAmplitude='0mV' + YAxis=1 + AutoCompEyeMeasurementPoint=true + EyeMeasurementPoint='0ps' + EyePamLow() + EyePamVRef() + EyePamHigh() + EyePamNames() + EyePamStrictVRef=false + $end 'ExtendedTraceInfo' + $begin 'TraceFamiliesDisplayDefinition' + DisplayFamiliesType='DisplayAll' + $end 'TraceFamiliesDisplayDefinition' + $begin 'PointsetDefinition' + $begin 'SubsweepDefParamsContainer' + $begin '0' + SubsweepType='Regular' + SubsweepChoiceType='All' + SweepVariableName='Freq' + AllowSelecteValues=true + SweepHasConsistentValues=true + $end '0' + $begin '1' + SubsweepType='Regular' + SubsweepChoiceType='Selected' + SweepVariableName='fc' + AllowSelecteValues=true + SweepHasConsistentValues=true + ColumnValues(30000000000) + ParameterType='DoubleParam' + Units='GHz' + $end '1' + $begin '2' + SubsweepType='Regular' + SubsweepChoiceType='Selected' + SweepVariableName='w' + AllowSelecteValues=true + SweepHasConsistentValues=true + ColumnValues(0.02) + ParameterType='DoubleParam' + Units='' + $end '2' + $begin '3' + SubsweepType='Regular' + SubsweepChoiceType='Selected' + SweepVariableName='rA' + AllowSelecteValues=true + SweepHasConsistentValues=true + ColumnValues(0.51) + ParameterType='DoubleParam' + Units='' + $end '3' + $begin '4' + SubsweepType='Regular' + SubsweepChoiceType='Selected' + SweepVariableName='hA' + AllowSelecteValues=true + SweepHasConsistentValues=true + ColumnValues(0.5) + ParameterType='DoubleParam' + Units='' + $end '4' + $begin '5' + SubsweepType='Regular' + SubsweepChoiceType='Selected' + SweepVariableName='rB' + AllowSelecteValues=true + SweepHasConsistentValues=true + ColumnValues(0.687) + ParameterType='DoubleParam' + Units='' + $end '5' + $begin '6' + SubsweepType='Regular' + SubsweepChoiceType='Selected' + SweepVariableName='hB' + AllowSelecteValues=true + SweepHasConsistentValues=true + ColumnValues(0.585) + ParameterType='DoubleParam' + Units='' + $end '6' + $begin '7' + SubsweepType='Regular' + SubsweepChoiceType='Selected' + SweepVariableName='rC' + AllowSelecteValues=true + SweepHasConsistentValues=true + ColumnValues(0.805) + ParameterType='DoubleParam' + Units='' + $end '7' + $begin '8' + SubsweepType='Regular' + SubsweepChoiceType='Selected' + SweepVariableName='hC' + AllowSelecteValues=true + SweepHasConsistentValues=true + ColumnValues(0.661) + ParameterType='DoubleParam' + Units='' + $end '8' + $begin '9' + SubsweepType='Regular' + SubsweepChoiceType='Selected' + SweepVariableName='rD' + AllowSelecteValues=true + SweepHasConsistentValues=true + ColumnValues(0.822) + ParameterType='DoubleParam' + Units='' + $end '9' + $begin '10' + SubsweepType='Regular' + SubsweepChoiceType='Selected' + SweepVariableName='hD' + AllowSelecteValues=true + SweepHasConsistentValues=true + ColumnValues(2.3) + ParameterType='DoubleParam' + Units='' + $end '10' + $begin '11' + SubsweepType='Specifiable' + SubsweepChoiceType='Selected' + SweepVariableName='PC' + AllowSelecteValues=true + SweepHasConsistentValues=true + ColumnValues(-0.073) + ParameterType='DoubleParam' + Units='' + $end '11' + $end 'SubsweepDefParamsContainer' + FamilyBlock() + $end 'PointsetDefinition' + DesignInstanceID=5 + $end 'TraceDef' + $end 'Plot_6LKGC0' + $end 'Reports' + $begin 'AllTracesInterpreter' + $begin 'PerTraceInterpreter' + $begin 'TraceInterpreter' + ID=116 + $begin 'ReportMgrTraceInterpreter' + SolutionName='Setup1 : LastAdaptive' + SolutionID=5484 + $end 'ReportMgrTraceInterpreter' + $begin 'ProductTraceInterpreterBlock' + EMContext='3D' + $end 'ProductTraceInterpreterBlock' + $end 'TraceInterpreter' + $end 'PerTraceInterpreter' + $end 'AllTracesInterpreter' +$end 'ReportDefinitions' diff --git a/_unittest/example_models/T21/butter.asc b/_unittest/example_models/T21/butter.asc new file mode 100644 index 00000000000..429740dad7a --- /dev/null +++ b/_unittest/example_models/T21/butter.asc @@ -0,0 +1,360 @@ +Version 4 +SHEET 1 2536 1712 +WIRE 1040 512 896 512 +WIRE 1168 512 1040 512 +WIRE 1296 512 1168 512 +WIRE 1376 512 1296 512 +WIRE 1456 512 1440 512 +WIRE 1600 512 1536 512 +WIRE 1712 512 1600 512 +WIRE 1792 512 1712 512 +WIRE 1872 512 1856 512 +WIRE 1984 512 1952 512 +WIRE 2096 512 1984 512 +WIRE 2240 512 2096 512 +WIRE 896 528 896 512 +WIRE 1040 528 1040 512 +WIRE 1168 528 1168 512 +WIRE 1296 528 1296 512 +WIRE 1600 528 1600 512 +WIRE 1712 528 1712 512 +WIRE 1984 528 1984 512 +WIRE 2096 528 2096 512 +WIRE 2240 528 2240 512 +WIRE 896 624 896 608 +WIRE 1040 624 1040 608 +WIRE 1168 624 1168 592 +WIRE 1296 624 1296 608 +WIRE 1600 624 1600 592 +WIRE 1712 624 1712 608 +WIRE 1984 624 1984 592 +WIRE 2096 624 2096 608 +WIRE 2240 624 2240 608 +WIRE 1040 720 896 720 +WIRE 1168 720 1040 720 +WIRE 1296 720 1168 720 +WIRE 1376 720 1296 720 +WIRE 1456 720 1440 720 +WIRE 1600 720 1536 720 +WIRE 1712 720 1600 720 +WIRE 1792 720 1712 720 +WIRE 1872 720 1856 720 +WIRE 1984 720 1952 720 +WIRE 2096 720 1984 720 +WIRE 2240 720 2096 720 +WIRE 896 736 896 720 +WIRE 1040 736 1040 720 +WIRE 1168 736 1168 720 +WIRE 1296 736 1296 720 +WIRE 1600 736 1600 720 +WIRE 1712 736 1712 720 +WIRE 1984 736 1984 720 +WIRE 2096 736 2096 720 +WIRE 2240 736 2240 720 +WIRE 896 832 896 816 +WIRE 1040 832 1040 816 +WIRE 1168 832 1168 800 +WIRE 1296 832 1296 816 +WIRE 1600 832 1600 800 +WIRE 1712 832 1712 816 +WIRE 1984 832 1984 800 +WIRE 2096 832 2096 816 +WIRE 2240 832 2240 816 +WIRE 1040 928 896 928 +WIRE 1168 928 1040 928 +WIRE 1296 928 1168 928 +WIRE 1376 928 1296 928 +WIRE 1456 928 1440 928 +WIRE 1600 928 1536 928 +WIRE 1712 928 1600 928 +WIRE 1792 928 1712 928 +WIRE 1872 928 1856 928 +WIRE 1984 928 1952 928 +WIRE 2096 928 1984 928 +WIRE 2240 928 2096 928 +WIRE 896 944 896 928 +WIRE 1040 944 1040 928 +WIRE 1168 944 1168 928 +WIRE 1296 944 1296 928 +WIRE 1600 944 1600 928 +WIRE 1712 944 1712 928 +WIRE 1984 944 1984 928 +WIRE 2096 944 2096 928 +WIRE 2240 944 2240 928 +WIRE 896 1040 896 1024 +WIRE 1040 1040 1040 1024 +WIRE 1168 1040 1168 1008 +WIRE 1296 1040 1296 1024 +WIRE 1600 1040 1600 1008 +WIRE 1712 1040 1712 1024 +WIRE 1984 1040 1984 1008 +WIRE 2096 1040 2096 1024 +WIRE 2240 1040 2240 1024 +WIRE 1040 1120 896 1120 +WIRE 1168 1120 1040 1120 +WIRE 1296 1120 1168 1120 +WIRE 1376 1120 1296 1120 +WIRE 1456 1120 1440 1120 +WIRE 1600 1120 1536 1120 +WIRE 1712 1120 1600 1120 +WIRE 1792 1120 1712 1120 +WIRE 1872 1120 1856 1120 +WIRE 1984 1120 1952 1120 +WIRE 2096 1120 1984 1120 +WIRE 2240 1120 2096 1120 +WIRE 896 1136 896 1120 +WIRE 1040 1136 1040 1120 +WIRE 1168 1136 1168 1120 +WIRE 1296 1136 1296 1120 +WIRE 1600 1136 1600 1120 +WIRE 1712 1136 1712 1120 +WIRE 1984 1136 1984 1120 +WIRE 2096 1136 2096 1120 +WIRE 2240 1136 2240 1120 +WIRE 896 1232 896 1216 +WIRE 1040 1232 1040 1216 +WIRE 1168 1232 1168 1200 +WIRE 1296 1232 1296 1216 +WIRE 1600 1232 1600 1200 +WIRE 1712 1232 1712 1216 +WIRE 1984 1232 1984 1200 +WIRE 2096 1232 2096 1216 +WIRE 2240 1232 2240 1216 +FLAG 2240 1120 OUT4 +FLAG 2240 928 OUT3 +FLAG 2240 720 OUT2 +FLAG 2240 512 OUT1 +FLAG 896 624 0 +FLAG 1040 624 0 +FLAG 1168 624 0 +FLAG 1296 624 0 +FLAG 1600 624 0 +FLAG 1712 624 0 +FLAG 1984 624 0 +FLAG 2096 624 0 +FLAG 2240 624 0 +FLAG 896 832 0 +FLAG 1040 832 0 +FLAG 1168 832 0 +FLAG 1296 832 0 +FLAG 1600 832 0 +FLAG 1712 832 0 +FLAG 1984 832 0 +FLAG 2096 832 0 +FLAG 2240 832 0 +FLAG 896 1040 0 +FLAG 1040 1040 0 +FLAG 1168 1040 0 +FLAG 1296 1040 0 +FLAG 1600 1040 0 +FLAG 1712 1040 0 +FLAG 1984 1040 0 +FLAG 2096 1040 0 +FLAG 2240 1040 0 +FLAG 896 1232 0 +FLAG 1040 1232 0 +FLAG 1168 1232 0 +FLAG 1296 1232 0 +FLAG 1600 1232 0 +FLAG 1712 1232 0 +FLAG 1984 1232 0 +FLAG 2096 1232 0 +FLAG 2240 1232 0 +SYMBOL CURRENT 896 608 M180 +WINDOW 0 33 77 Left 2 +WINDOW 3 24 0 Left 2 +SYMATTR InstName I1 +SYMATTR Value AC 1. +SYMBOL CURRENT 896 816 M180 +WINDOW 0 31 75 Left 2 +WINDOW 3 24 0 Left 2 +SYMATTR InstName I2 +SYMATTR Value AC 1. +SYMBOL CURRENT 896 1024 M180 +WINDOW 0 24 78 Left 2 +WINDOW 3 24 0 Left 2 +SYMATTR InstName I3 +SYMATTR Value AC 1. +SYMBOL CURRENT 896 1216 M180 +WINDOW 0 24 77 Left 2 +WINDOW 3 24 0 Left 2 +SYMATTR InstName I4 +SYMATTR Value AC 1. +SYMBOL RES 1024 512 R0 +SYMATTR InstName R1 +SYMATTR Value 1 +SYMBOL CAP 1856 496 R90 +WINDOW 0 0 32 VBottom 2 +WINDOW 3 32 32 VTop 2 +SYMATTR InstName C4 +SYMATTR Value 1.125 +SYMBOL CAP 1968 528 R0 +SYMATTR InstName C5 +SYMATTR Value .3396 +SYMBOL CAP 1440 496 R90 +WINDOW 0 0 32 VBottom 2 +WINDOW 3 32 32 VTop 2 +SYMATTR InstName C2 +SYMATTR Value 1.125 +SYMBOL CAP 1584 528 R0 +SYMATTR InstName C3 +SYMATTR Value 1.099 +SYMBOL CAP 1152 528 R0 +SYMATTR InstName C1 +SYMATTR Value .3396 +SYMBOL CAP 1856 704 R90 +WINDOW 0 0 32 VBottom 2 +WINDOW 3 32 32 VTop 2 +SYMATTR InstName C9 +SYMATTR Value .9785 +SYMBOL CAP 1968 736 R0 +SYMATTR InstName C10 +SYMATTR Value .3903 +SYMBOL CAP 1440 704 R90 +WINDOW 0 0 32 VBottom 2 +WINDOW 3 32 32 VTop 2 +SYMATTR InstName C7 +SYMATTR Value .9785 +SYMBOL CAP 1584 736 R0 +SYMATTR InstName C8 +SYMATTR Value 1.263 +SYMBOL CAP 1152 736 R0 +SYMATTR InstName C6 +SYMATTR Value .3903 +SYMBOL CAP 1856 912 R90 +WINDOW 0 0 32 VBottom 2 +WINDOW 3 32 32 VTop 2 +SYMATTR InstName C14 +SYMATTR Value .8103 +SYMBOL CAP 1968 944 R0 +SYMATTR InstName C15 +SYMATTR Value .4714 +SYMBOL CAP 1440 912 R90 +WINDOW 0 0 32 VBottom 2 +WINDOW 3 32 32 VTop 2 +SYMATTR InstName C12 +SYMATTR Value .8103 +SYMBOL CAP 1584 944 R0 +SYMATTR InstName C13 +SYMATTR Value 1.526 +SYMBOL CAP 1152 944 R0 +SYMATTR InstName C11 +SYMATTR Value .4714 +SYMBOL CAP 1856 1104 R90 +WINDOW 0 0 32 VBottom 2 +WINDOW 3 32 32 VTop 2 +SYMATTR InstName C19 +SYMATTR Value .6177 +SYMBOL CAP 1968 1136 R0 +SYMATTR InstName C20 +SYMATTR Value .6177 +SYMBOL CAP 1440 1104 R90 +WINDOW 0 0 32 VBottom 2 +WINDOW 3 32 32 VTop 2 +SYMATTR InstName C17 +SYMATTR Value .6177 +SYMBOL CAP 1584 1136 R0 +SYMATTR InstName C18 +SYMATTR Value 2 +SYMBOL CAP 1152 1136 R0 +SYMATTR InstName C16 +SYMATTR Value .6177 +SYMBOL IND2 1856 528 R270 +WINDOW 0 32 56 VTop 2 +WINDOW 3 4 56 VBottom 2 +SYMATTR InstName L4 +SYMATTR Value .8890 +SYMBOL IND2 2080 512 R0 +SYMATTR InstName L5 +SYMATTR Value 2.945 +SYMBOL IND2 1440 528 R270 +WINDOW 0 32 56 VTop 2 +WINDOW 3 4 56 VBottom 2 +SYMATTR InstName L2 +SYMATTR Value .8890 +SYMBOL IND2 1696 512 R0 +SYMATTR InstName L3 +SYMATTR Value .9099 +SYMBOL IND2 1280 512 R0 +SYMATTR InstName L1 +SYMATTR Value 2.945 +SYMBOL IND2 1856 736 R270 +WINDOW 0 32 56 VTop 2 +WINDOW 3 4 56 VBottom 2 +SYMATTR InstName L9 +SYMATTR Value 1.022 +SYMBOL IND2 2080 720 R0 +SYMATTR InstName L10 +SYMATTR Value 2.562 +SYMBOL IND2 1440 736 R270 +WINDOW 0 32 56 VTop 2 +WINDOW 3 4 56 VBottom 2 +SYMATTR InstName L7 +SYMATTR Value 1.022 +SYMBOL IND2 1696 720 R0 +SYMATTR InstName L8 +SYMATTR Value .7918 +SYMBOL IND2 1280 720 R0 +SYMATTR InstName L6 +SYMATTR Value 2.562 +SYMBOL IND2 1856 944 R270 +WINDOW 0 32 56 VTop 2 +WINDOW 3 4 56 VBottom 2 +SYMATTR InstName L14 +SYMATTR Value 1.234 +SYMBOL IND2 2080 928 R0 +SYMATTR InstName L15 +SYMATTR Value 2.1213 +SYMBOL IND2 1440 944 R270 +WINDOW 0 32 56 VTop 2 +WINDOW 3 4 56 VBottom 2 +SYMATTR InstName L12 +SYMATTR Value 1.234 +SYMBOL IND2 1696 928 R0 +SYMATTR InstName L13 +SYMATTR Value .6553 +SYMBOL IND2 1280 928 R0 +SYMATTR InstName L11 +SYMATTR Value 2.1213 +SYMBOL IND2 1856 1136 R270 +WINDOW 0 32 56 VTop 2 +WINDOW 3 4 56 VBottom 2 +SYMATTR InstName L19 +SYMATTR Value 1.617 +SYMBOL IND2 2080 1120 R0 +SYMATTR InstName L20 +SYMATTR Value 1.617 +SYMBOL IND2 1440 1136 R270 +WINDOW 0 32 56 VTop 2 +WINDOW 3 4 56 VBottom 2 +SYMATTR InstName L17 +SYMATTR Value 1.617 +SYMBOL IND2 1696 1120 R0 +SYMATTR InstName L18 +SYMATTR Value .5 +SYMBOL IND2 1280 1120 R0 +SYMATTR InstName L16 +SYMATTR Value 1.617 +SYMBOL RES 2224 512 R0 +SYMATTR InstName R2 +SYMATTR Value 1 +SYMBOL RES 1024 720 R0 +SYMATTR InstName R3 +SYMATTR Value 1 +SYMBOL RES 2224 720 R0 +SYMATTR InstName R4 +SYMATTR Value 1 +SYMBOL RES 1024 928 R0 +SYMATTR InstName R5 +SYMATTR Value 1 +SYMBOL RES 2224 928 R0 +SYMATTR InstName R6 +SYMATTR Value 1 +SYMBOL RES 1024 1120 R0 +SYMATTR InstName R7 +SYMATTR Value 1 +SYMBOL RES 2224 1120 R0 +SYMATTR InstName R8 +SYMATTR Value 1 +TEXT 1024 1296 Left 2 !.ac oct 50 .01 3 +TEXT 1568 1320 Top 1 ;This example schematic is supplied for informational/educational purposes only. diff --git a/_unittest/example_models/T21/colpits.asc b/_unittest/example_models/T21/colpits.asc new file mode 100644 index 00000000000..461d91e9581 --- /dev/null +++ b/_unittest/example_models/T21/colpits.asc @@ -0,0 +1,64 @@ +Version 4 +SHEET 1 2740 2480 +WIRE 2208 1872 2064 1872 +WIRE 2064 1888 2064 1872 +WIRE 2208 1888 2208 1872 +WIRE 2064 1984 2064 1968 +WIRE 2208 1984 2208 1968 +WIRE 1920 2048 1776 2048 +WIRE 2032 2048 1920 2048 +WIRE 2160 2048 2032 2048 +WIRE 2032 2064 2032 2048 +WIRE 1776 2080 1776 2048 +WIRE 1920 2080 1920 2048 +WIRE 2032 2144 2032 2128 +WIRE 2112 2144 2032 2144 +WIRE 2208 2144 2208 2080 +WIRE 2208 2144 2176 2144 +WIRE 2032 2160 2032 2144 +WIRE 2208 2160 2208 2144 +WIRE 1776 2192 1776 2144 +WIRE 1920 2192 1920 2160 +WIRE 2032 2256 2032 2224 +WIRE 2208 2256 2208 2240 +FLAG 1920 2192 0 +FLAG 2032 2256 0 +FLAG 2208 2256 0 +FLAG 2064 1984 0 +FLAG 1776 2192 0 +SYMBOL NJF 2160 1984 R0 +SYMATTR InstName Q1 +SYMATTR Value 2N5484 +SYMBOL RES 2192 1872 R0 +SYMATTR InstName R1 +SYMATTR Value 500 +SYMBOL res 2192 2144 R0 +SYMATTR InstName R2 +SYMATTR Value 1K +SYMBOL ind 1936 2176 R180 +WINDOW 0 3 79 Right 2 +WINDOW 3 1 40 Right 2 +SYMATTR InstName L1 +SYMATTR Value 100µ +SYMBOL cap 2016 2064 R0 +SYMATTR InstName C1 +SYMATTR Value 500p +SYMBOL cap 2016 2160 R0 +SYMATTR InstName C2 +SYMATTR Value 500p +SYMBOL cap 2176 2128 R90 +WINDOW 0 0 32 VBottom 2 +WINDOW 3 32 32 VTop 2 +SYMATTR InstName C3 +SYMATTR Value 200p +SYMBOL VOLTAGE 2064 1872 R0 +SYMATTR InstName V1 +SYMATTR Value 7. +SYMBOL diode 1760 2144 M180 +WINDOW 0 24 72 Left 2 +WINDOW 3 24 0 Left 2 +SYMATTR InstName D1 +SYMATTR Value 1N4148 +TEXT 1832 2288 Left 2 !.tran 500µ startup +TEXT 1832 2320 Left 2 !.options method=trap +TEXT 2048 2344 Top 1 ;This example schematic is supplied for informational/educational purposes only. diff --git a/_unittest/example_models/T47/netlist.aedtz b/_unittest/example_models/T47/netlist.aedtz new file mode 100644 index 00000000000..2023910630d Binary files /dev/null and b/_unittest/example_models/T47/netlist.aedtz differ diff --git a/_unittest/test_12_PostProcessing.py b/_unittest/test_12_PostProcessing.py index ecd36d9445d..7e6a6a88754 100644 --- a/_unittest/test_12_PostProcessing.py +++ b/_unittest/test_12_PostProcessing.py @@ -195,6 +195,18 @@ def test_09_manipulate_report_B(self, field_test): new_report4.report_type = "Data Table" assert new_report4.create() + local_path = os.path.dirname(os.path.realpath(__file__)) + template = os.path.join(local_path, "example_models", test_subfolder, "template.rpt") + if not config["NonGraphical"]: + assert new_report4.apply_report_template(template) + template2 = os.path.join(local_path, "example_models", test_subfolder, "template_invented.rpt") + assert not new_report4.apply_report_template(template2) + template3 = os.path.join(local_path, "example_models", test_subfolder, "template.csv") + assert not new_report4.apply_report_template(template3) + assert not new_report4.apply_report_template(template3, property_type="Dummy") + + assert field_test.post.create_report_from_configuration(template) + def test_09_manipulate_report_C(self, field_test): variations = field_test.available_variations.nominal_w_values_dict variations["Theta"] = ["All"] diff --git a/_unittest/test_21_Circuit.py b/_unittest/test_21_Circuit.py index 0bd2c9552d4..ad17cce8253 100644 --- a/_unittest/test_21_Circuit.py +++ b/_unittest/test_21_Circuit.py @@ -447,7 +447,7 @@ def test_32_push_down(self): active_project = self.aedtapp.oproject.GetActiveDesign() if is_linux and config["desktopVersion"] == "2024.1": time.sleep(1) - self.aedtapp._desktop.CloseAllWindows() + self.aedtapp.desktop_class.close_windows() active_project_name_1 = active_project.GetName() self.aedtapp.pop_up() subcircuit_2 = self.aedtapp.modeler.schematic.create_subcircuit( @@ -456,7 +456,7 @@ def test_32_push_down(self): active_project = self.aedtapp.oproject.GetActiveDesign() if is_linux and config["desktopVersion"] == "2024.1": time.sleep(1) - self.aedtapp._desktop.CloseAllWindows() + self.aedtapp.desktop_class.close_windows() active_project_name_3 = active_project.GetName() assert active_project_name_1 == active_project_name_3 assert subcircuit_2.component_info["RefDes"] == "U2" @@ -468,14 +468,14 @@ def test_33_pop_up(self): active_project = self.aedtapp.oproject.GetActiveDesign() if is_linux and config["desktopVersion"] == "2024.1": time.sleep(1) - self.aedtapp._desktop.CloseAllWindows() + self.aedtapp.desktop_class.close_windows() active_project_name_1 = active_project.GetName() self.aedtapp.modeler.schematic.create_subcircuit(location=[0.0, 0.0]) assert self.aedtapp.pop_up() active_project = self.aedtapp.oproject.GetActiveDesign() if is_linux and config["desktopVersion"] == "2024.1": time.sleep(1) - self.aedtapp._desktop.CloseAllWindows() + self.aedtapp.desktop_class.close_windows() active_project_name_2 = active_project.GetName() assert active_project_name_1 == active_project_name_2 @@ -975,3 +975,8 @@ def test_51_change_symbol_pin_location(self): assert ts_component.change_symbol_pin_locations(pin_locations) pin_locations = {"left": [pins[0].name, pins[1].name, pins[2].name], "right": [pins[5].name]} assert not ts_component.change_symbol_pin_locations(pin_locations) + + def test_51_import_asc(self): + self.aedtapp.insert_design("ASC") + asc_file = os.path.join(local_path, "example_models", test_subfolder, "butter.asc") + assert self.aedtapp.create_schematic_from_asc_file(asc_file) diff --git a/_unittest/test_27_Maxwell2D.py b/_unittest/test_27_Maxwell2D.py index c154266883b..74b1741de59 100644 --- a/_unittest/test_27_Maxwell2D.py +++ b/_unittest/test_27_Maxwell2D.py @@ -450,7 +450,7 @@ def test_28_set_variable(self): assert "var_test" in self.aedtapp.variable_manager.design_variable_names assert self.aedtapp.variable_manager.design_variables["var_test"].expression == "234" - def test_31_cylindrical_gap(self): + def test_31a_cylindrical_gap(self): assert not self.aedtapp.mesh.assign_cylindrical_gap("Band") [ x.delete() @@ -467,6 +467,22 @@ def test_31_cylindrical_gap(self): ] assert self.aedtapp.mesh.assign_cylindrical_gap("Band", name="cyl_gap_test", band_mapping_angle=2) + def test_31b_skin_depth(self): + edge = self.aedtapp.modeler["Rotor"].edges[0] + mesh = self.aedtapp.mesh.assign_skin_depth(assignment=edge, skin_depth="0.3mm", layers_number=3) + assert mesh + assert mesh.type == "SkinDepthBased" + assert mesh.props["Edges"][0] == edge.id + assert mesh.props["SkinDepth"] == "0.3mm" + assert mesh.props["NumLayers"] == 3 + edge1 = self.aedtapp.modeler["Rotor"].edges[1] + mesh = self.aedtapp.mesh.assign_skin_depth(assignment=edge1.id, skin_depth="0.3mm", layers_number=3) + assert mesh + assert mesh.type == "SkinDepthBased" + assert mesh.props["Edges"][0] == edge1.id + assert mesh.props["SkinDepth"] == "0.3mm" + assert mesh.props["NumLayers"] == 3 + def test_32_control_program(self): user_ctl_path = "user.ctl" ctrl_prg_path = os.path.join(local_path, "example_models", test_subfolder, ctrl_prg_file) diff --git a/_unittest/test_28_Maxwell3D.py b/_unittest/test_28_Maxwell3D.py index 449cb5a5543..e1667a35407 100644 --- a/_unittest/test_28_Maxwell3D.py +++ b/_unittest/test_28_Maxwell3D.py @@ -282,7 +282,17 @@ def test_22_create_length_mesh(self): assert self.aedtapp.mesh.assign_length_mesh(["Plate"]) def test_23_create_skin_depth(self): - assert self.aedtapp.mesh.assign_skin_depth(["Plate"], "1mm") + mesh = self.aedtapp.mesh.assign_skin_depth(["Plate"], "1mm") + assert mesh + mesh.delete() + mesh = self.aedtapp.mesh.assign_skin_depth(["Plate"], "1mm", 1000) + assert mesh + mesh.delete() + mesh = self.aedtapp.mesh.assign_skin_depth(self.aedtapp.modeler["Plate"].faces[0].id, "1mm") + assert mesh + mesh.delete() + mesh = self.aedtapp.mesh.assign_skin_depth(self.aedtapp.modeler["Plate"], "1mm") + assert mesh def test_24_create_curvilinear(self): assert self.aedtapp.mesh.assign_curvilinear_elements(["Coil"], "1mm") diff --git a/_unittest/test_41_3dlayout_modeler.py b/_unittest/test_41_3dlayout_modeler.py index 07da92d854a..df072e9f2be 100644 --- a/_unittest/test_41_3dlayout_modeler.py +++ b/_unittest/test_41_3dlayout_modeler.py @@ -526,20 +526,30 @@ def test_19d_export_to_hfss(self): self.aedtapp.save_project() filename = "export_to_hfss_test" filename2 = "export_to_hfss_test2" + filename3 = "export_to_hfss_test_non_unite" file_fullname = os.path.join(self.local_scratch.path, filename) file_fullname2 = os.path.join(self.local_scratch.path, filename2) + file_fullname3 = os.path.join(self.local_scratch.path, filename3) setup = self.aedtapp.get_setup(self.aedtapp.existing_analysis_setups[0]) assert setup.export_to_hfss(output_file=file_fullname) if not is_linux: # TODO: EDB failing in Linux assert setup.export_to_hfss(output_file=file_fullname2, keep_net_name=True) + assert setup.export_to_hfss(output_file=file_fullname3, keep_net_name=True, unite=False) + def test_19e_export_to_q3d(self): filename = "export_to_q3d_test" file_fullname = os.path.join(self.local_scratch.path, filename) setup = self.aedtapp.get_setup(self.aedtapp.existing_analysis_setups[0]) assert setup.export_to_q3d(file_fullname) + def test_19f_export_to_q3d(self): + filename = "export_to_q3d_non_unite_test" + file_fullname = os.path.join(self.local_scratch.path, filename) + setup = self.aedtapp.get_setup(self.aedtapp.existing_analysis_setups[0]) + assert setup.export_to_q3d(file_fullname, keep_net_name=True, unite=False) + def test_21_variables(self): assert isinstance(self.aedtapp.available_variations.nominal_w_values_dict, dict) assert isinstance(self.aedtapp.available_variations.nominal_w_values, list) diff --git a/_unittest/test_47_CircuitNetlist.py b/_unittest/test_47_CircuitNetlist.py new file mode 100644 index 00000000000..0ff8c8452ef --- /dev/null +++ b/_unittest/test_47_CircuitNetlist.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2021 - 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# 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 + +from _unittest.conftest import config +from ansys.aedt.core import CircuitNetlist +from ansys.aedt.core.generic.general_methods import is_linux +import pytest + +netlist = "netlist" +test_subfolder = "T47" + + +@pytest.fixture(scope="class") +def netlist_test(add_app): + app = add_app(project_name=netlist, subfolder=test_subfolder, application=CircuitNetlist) + return app + + +class TestClass: + @pytest.fixture(autouse=True) + def init(self, local_scratch): + self.local_scratch = local_scratch + + def test_01_post(self, netlist_test): + if config["NonGraphical"]: + assert len(netlist_test.post.plots) == 0 + else: + assert len(netlist_test.post.plots) == 1 + + def test_02_browse_log_file(self, netlist_test, local_scratch): + assert not netlist_test.browse_log_file() + netlist_test.analyze() + assert netlist_test.browse_log_file() + if not is_linux: + netlist_test.save_project() + assert not netlist_test.browse_log_file(os.path.join(netlist_test.working_directory, "logfiles")) + assert netlist_test.browse_log_file(netlist_test.working_directory) diff --git a/_unittest/test_launch_desktop.py b/_unittest/test_launch_desktop.py index f06cf37f3e5..6460e265860 100644 --- a/_unittest/test_launch_desktop.py +++ b/_unittest/test_launch_desktop.py @@ -24,6 +24,7 @@ from _unittest.conftest import config from ansys.aedt.core import Circuit +from ansys.aedt.core import CircuitNetlist from ansys.aedt.core import Hfss from ansys.aedt.core import Hfss3dLayout from ansys.aedt.core import Icepak @@ -97,3 +98,8 @@ def test_run_desktop_maxwell3d(self): aedtapp = Maxwell3d() assert aedtapp.design_type == "Maxwell 3D" assert aedtapp.solution_type == "Magnetostatic" + + def test_run_desktop_circuit_netlist(self): + aedtapp = CircuitNetlist() + assert aedtapp.design_type == "Circuit Netlist" + assert aedtapp.solution_type == "" diff --git a/_unittest/test_utils.py b/_unittest/test_utils.py index 06d4252eae6..269eb6bbf3c 100644 --- a/_unittest/test_utils.py +++ b/_unittest/test_utils.py @@ -30,6 +30,7 @@ from unittest.mock import patch from ansys.aedt.core.generic.general_methods import pyaedt_function_handler +from ansys.aedt.core.generic.settings import Settings from ansys.aedt.core.generic.settings import settings import pytest @@ -93,3 +94,48 @@ def test_handler_deprecation_log_warning(caplog): foo(trigger_exception=False) assert len(caplog.records) == 1 + + +def test_settings_load_yaml(tmp_path): + """Test loading a configure file with correct input.""" + default_settings = Settings() + + # Create temporary YAML configuration file + yaml_path = tmp_path / "pyaedt_settings.yaml" + yaml_path.write_text( + """ + log: + global_log_file_name: 'dummy' + lsf: + lsf_num_cores: 12 + general: + desktop_launch_timeout: 12 + """ + ) + + default_settings.load_yaml_configuration(str(yaml_path)) + + assert default_settings.global_log_file_name == "dummy" + assert default_settings.lsf_num_cores == 12 + assert default_settings.desktop_launch_timeout == 12 + + +def test_settings_load_yaml_with_non_allowed_key(tmp_path): + """Test loading a configuration file with invalid key.""" + default_settings = Settings() + + # Create temporary YAML configuration file + yaml_path = tmp_path / "pyaedt_settings.yaml" + yaml_path.write_text( + """ + general: + dummy: 12.0 + """ + ) + + default_settings.load_yaml_configuration(str(yaml_path), raise_on_wrong_key=False) + assert not hasattr(default_settings, "dummy") + + with pytest.raises(KeyError) as excinfo: + default_settings.load_yaml_configuration(str(yaml_path), raise_on_wrong_key=True) + assert str(excinfo) in "Key 'dummy' is not part of the allowed keys" diff --git a/_unittest/test_warnings.py b/_unittest/test_warnings.py index da8285a2f88..6fe7d14590d 100644 --- a/_unittest/test_warnings.py +++ b/_unittest/test_warnings.py @@ -30,6 +30,7 @@ from ansys.aedt.core import PYTHON_VERSION_WARNING from ansys.aedt.core import deprecation_warning from pyaedt import ALIAS_WARNING +import pytest VALID_PYTHON_VERSION = (LATEST_DEPRECATED_PYTHON_VERSION[0], LATEST_DEPRECATED_PYTHON_VERSION[1] + 1) @@ -60,11 +61,5 @@ def test_alias_deprecation_warning(): import pyaedt - # Ensure that the warning will be triggered again - del pyaedt.__warningregistry__ - - importlib.reload(pyaedt) - - # Hardcoded test where 28 is the line number associated to the warning call - # TODO: See if pytest.warns can be 'fixed' to work with module reload - assert (ALIAS_WARNING, FutureWarning, 28) in pyaedt.__warningregistry__ + with pytest.warns(FutureWarning, match=ALIAS_WARNING): + importlib.reload(pyaedt) diff --git a/_unittest_solvers/conftest.py b/_unittest_solvers/conftest.py index 7203f913b22..8f3fe42264e 100644 --- a/_unittest_solvers/conftest.py +++ b/_unittest_solvers/conftest.py @@ -36,6 +36,7 @@ settings.enable_desktop_logs = False settings.desktop_launch_timeout = 180 settings.release_on_exception = False +settings.wait_for_license = True from ansys.aedt.core.aedt_logger import pyaedt_logger from ansys.aedt.core.generic.general_methods import generate_unique_name diff --git a/_unittest_solvers/example_models/T31/coupling.aedtz b/_unittest_solvers/example_models/T31/coupling.aedtz new file mode 100644 index 00000000000..3ed36917d7f Binary files /dev/null and b/_unittest_solvers/example_models/T31/coupling.aedtz differ diff --git a/_unittest_solvers/test_00_analyze.py b/_unittest_solvers/test_00_analyze.py index a3f0d2991f2..032ebd83917 100644 --- a/_unittest_solvers/test_00_analyze.py +++ b/_unittest_solvers/test_00_analyze.py @@ -217,6 +217,9 @@ def test_02_hfss_export_results(self, hfss_app): intrinsics=[]) assert not os.path.exists(fld_file2) + hfss_app.variable_manager.set_variable(name="dummy", expression=1, is_post_processing=True) + sweep = hfss_app.parametrics.add(variable="dummy", start_point=0, end_point=1, step=2) + assert hfss_app.analyze_setup(name=sweep.name, cores=4) def test_03a_icepak_analyze_and_export_summary(self): self.icepak_app.solution_type = self.icepak_app.SOLUTIONS.Icepak.SteadyFlowOnly @@ -548,6 +551,7 @@ def test_09c_compute_com(self, local_scratch): com_param.export_spisim_cfg(str(Path(local_scratch.path) / "test.cfg")) com_0, com_1 = spisim.compute_com(0, Path(local_scratch.path) / "test.cfg") assert com_0 and com_1 + def test_10_export_to_maxwell(self, add_app): app = add_app("assm_test", application=Rmxprt, subfolder="T00") app.analyze(cores=1) diff --git a/_unittest_solvers/test_31_Q3D.py b/_unittest_solvers/test_31_Q3D.py index 31963963e3a..1b0a4ecb8d5 100644 --- a/_unittest_solvers/test_31_Q3D.py +++ b/_unittest_solvers/test_31_Q3D.py @@ -15,6 +15,8 @@ bondwire_project_name = "bondwireq3d.aedt" q2d_q3d = "q2d_q3d" +mutual_coupling = "coupling" + test_subfolder = "T31" @@ -24,6 +26,12 @@ def aedtapp(add_app): return app +@pytest.fixture(scope="class") +def coupling(add_app): + app = add_app(application=Q3d, project_name=mutual_coupling, subfolder=test_subfolder) + return app + + @pytest.fixture(scope="class", autouse=True) def examples(local_scratch): example_project = os.path.join(local_path, "../_unittest_solvers/example_models", test_subfolder, @@ -450,3 +458,18 @@ def test_20_auto_identify_no_metal(self, add_app): q3d.modeler.create_box([0, 0, 0], [10, 20, 30], material="vacuum") assert q3d.auto_identify_nets() assert not q3d.nets + + def test_21_mutual_coupling(self, coupling): + data1 = coupling.get_mutual_coupling("a1", "a2", "b2", "b1", setup_sweep_name="Setup1 : Sweep1") + assert data1 + assert len(coupling.matrices) == 3 + data2 = coupling.get_mutual_coupling("a2", "a1", "b2", "b3") + assert data2 + assert len(coupling.matrices) == 4 + + data3 = coupling.get_mutual_coupling("a2", "a1", "a3", "a1") + assert data3 + assert len(coupling.matrices) == 5 + + assert not coupling.get_mutual_coupling("ac2", "a1", "a3", "a1") + assert not coupling.get_mutual_coupling("a1", "a2", "b2", "b1", calculation="ACL2") \ No newline at end of file diff --git a/_unittest_solvers/test_45_workflows.py b/_unittest_solvers/test_45_workflows.py index cab0231a861..e6c5bf61f10 100644 --- a/_unittest_solvers/test_45_workflows.py +++ b/_unittest_solvers/test_45_workflows.py @@ -376,3 +376,11 @@ def test_14_power_map_creation_ipk(self, local_scratch, add_app): assert main({"is_test": True, "file_path": file_path}) assert len(aedtapp.modeler.object_list) == 3 aedtapp.close_project() + + 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 + assert main({"is_test": True, "asc_file": file_path}) + aedtapp.close_project() + diff --git a/doc/source/Getting_started/Installation.rst b/doc/source/Getting_started/Installation.rst index 16e491ee992..f1480f7873e 100644 --- a/doc/source/Getting_started/Installation.rst +++ b/doc/source/Getting_started/Installation.rst @@ -71,7 +71,7 @@ see `Extensions `_ +Wheelhouses for CPython 3.8, 3.9, 3.10, 3.11, and 3.12 are available in the releases for both Windows and Linux. From the `Releases `_ page in the PyAEDT repository, you can find the wheelhouses for a particular release in its assets and download the wheelhouse specific to your setup. You can then install PyAEDT and all of its dependencies from one single entry point that can be shared internally, which eases the security review of the PyAEDT package content. -For example, on Windows with Python 3.7, install PyAEDT and all its dependencies from a wheelhouse with code like this: +For example, on Windows with Python 3.10, install PyAEDT and all its dependencies from a wheelhouse with code like this: .. code:: - pip install --no-cache-dir --no-index --find-links=file:////PyAEDT-v-wheelhouse-Windows-3.7 pyaedt - - -Use IronPython in AEDT -~~~~~~~~~~~~~~~~~~~~~~ -PyAEDT is designed to work in CPython 3.7+ and supports many advanced processing packages like -``matplotlib``, ``numpy``, and ``pyvista``. A user can still use PyAEDT in the IronPython -environment available in AEDT with many limitations. - -To use IronPython in AEDT: - -1. Download the PyAEDT package from ``https://pypi.org/project/pyaedt/#files``. -2. Extract the files. -3. Install PyAEDT into AEDT, specifying the full paths to ``ipy64`` and ``setup-distutils.py`` as needed: - -.. code:: - - ipy64 setup-distutils.py install --user + pip install --no-cache-dir --no-index --find-links=file:////PyAEDT-v-wheelhouse-Windows-3.10 pyaedt Install PyAEDT in Conda virtual environment diff --git a/doc/source/Getting_started/Troubleshooting.rst b/doc/source/Getting_started/Troubleshooting.rst index 33e105cb44b..1d62085640f 100644 --- a/doc/source/Getting_started/Troubleshooting.rst +++ b/doc/source/Getting_started/Troubleshooting.rst @@ -14,11 +14,11 @@ In this case, you can use the Python interpreter available in the AEDT installat Python 3.7 is available in AEDT 2023 R1 and earlier. Python 3.10 is available in AEDT 2023 R2. -Here is the path to the Python 3.7 interpreter for the 2023 R1 installation: +Here is the path to the Python 3.10 interpreter for the 2024 R2 installation: .. code:: python - path\to\AnsysEM\v231\commonfiles\CPython\3_7\winx64\Release\python" + path\to\AnsysEM\v242\commonfiles\CPython\3_10\winx64\Release\python" Error installing PyAEDT using pip diff --git a/doc/source/Resources/pyaedt_settings.yaml b/doc/source/Resources/pyaedt_settings.yaml new file mode 100644 index 00000000000..13b4b214f72 --- /dev/null +++ b/doc/source/Resources/pyaedt_settings.yaml @@ -0,0 +1,122 @@ +# This file contains the settings used by default to set the PyAEDT and AEDT including logging, +# LSF, environment variables and general settings. If you want to have a different behavior +# you can modify this file and save it. To be used in PyAEDT, the path to the configuration file +# should be specified with the environment variable ``PYAEDT_LOCAL_SETTINGS_PATH``. If no +# environment variable is set, PyAEDT looks for the configuration file ``pyaedt_settings.yaml`` +# in the user's ``APPDATA`` folder for Windows and ``HOME`` folder for Linux. + +# Settings related to logging +log: + # Enable or disable the logging of EDB API methods + enable_debug_edb_logger: false + # Enable or disable the logging of the geometry operators + enable_debug_geometry_operator_logger: false + # Enable or disable the logging of the gRPC API calls + enable_debug_grpc_api_logger: false + # Enable or disable the logging of internal methods + enable_debug_internal_methods_logger: false + # Enable or disable the logging at debug level + enable_debug_logger: false + # Enable or disable the logging of methods' arguments at debug level + enable_debug_methods_argument_logger: false + # Enable or disable the logging to the AEDT message window + enable_desktop_logs: true + # Enable or disable the logging to a file + enable_file_logs: true + # Enable or disable the global PyAEDT log file located in the global temp folder + enable_global_log_file: true + # Enable or disable the local PyAEDT log file located in the ``projectname.pyaedt`` project folder + enable_local_log_file: false + # Enable or disable the logging overall + enable_logger: true + # Enable or disable the logging to STDOUT + enable_screen_logs: true + # Global PyAEDT log file path + global_log_file_name: null + # Global PyAEDT log file size in MB + global_log_file_size: 10 + # Date format of the log entries + logger_datefmt: '%Y/%m/%d %H.%M.%S' + # PyAEDT log file path + logger_file_path: null + # Message format of the log entries + logger_formatter: '%(asctime)s:%(destination)s:%(extra)s%(levelname)-8s:%(message)s' + # Path to the AEDT log file + aedt_log_file: null + +# Settings related to Linux systems running LSF scheduler +lsf: + # Command to launch in the LSF Scheduler + custom_lsf_command: null + # Command to launch the task in the LSF Scheduler + lsf_aedt_command: 'ansysedt' + # Number of LSF cores + lsf_num_cores: 2 + # Operating system string + lsf_osrel: null + # LSF queue name + lsf_queue: null + # RAM allocated for the LSF job + lsf_ram: 1000 + # Timeout in seconds for trying to start the interactive session + lsf_timeout: 3600 + # Value passed in the LSF 'select' string to the ui resource + lsf_ui: null + # Enable or disable use LSF Scheduler + use_lsf_scheduler: false + +# Settings related to environment variables thare are set before launching a new AEDT session +# This includes those that enable the beta features ! +aedt_env_var: + ANSYSEM_FEATURE_F335896_MECHANICAL_STRUCTURAL_SOLN_TYPE_ENABLE: '1' + ANSYSEM_FEATURE_F395486_RIGID_FLEX_BENDING_ENABLE: '1' + ANSYSEM_FEATURE_F538630_MECH_TRANSIENT_THERMAL_ENABLE: '1' + ANSYSEM_FEATURE_F545177_ECAD_INTEGRATION_WITH_APHI_ENABLE: '1' + ANSYSEM_FEATURE_F650636_MECH_LAYOUT_COMPONENT_ENABLE: '1' + ANSYSEM_FEATURE_S432616_LAYOUT_COMPONENT_IN_3D_ENABLE: '1' + ANSYSEM_FEATURE_SF159726_SCRIPTOBJECT_ENABLE: '1' + ANSYSEM_FEATURE_SF222134_CABLE_MODELING_ENHANCEMENTS_ENABLE: '1' + ANSYSEM_FEATURE_SF6694_NON_GRAPHICAL_COMMAND_EXECUTION_ENABLE: '1' + ANS_MESHER_PROC_DUMP_PREPOST_BEND_SM3: '1' + +general: + # Enable or disable the lazy load + lazy_load: true + # Enable or disable the lazy load dedicated to objects associated to the modeler + objects_lazy_load: true + # AEDT installation path + aedt_install_dir: null + # AEDT version in the form ``"2023.x"`` + aedt_version: null + # Timeout in seconds for trying to launch AEDT + desktop_launch_timeout: 120 + # Enable or disable bounding box evaluation by exporting a SAT file + disable_bounding_box_sat: false + # Optional path for the EDB DLL file + edb_dll_path: null + # Enable or disable the internal PyAEDT error handling + enable_error_handler: true + # Enable or disable the use of Pandas to export dictionaries and lists + enable_pandas_output: false + # Enable or disable the check of the project path + force_error_on_missing_project: false + # Number of gRPC API retries + number_of_grpc_api_retries: 6 + # Enable or disable the release of AEDT on exception + release_on_exception: true + # Time interval between the retries by the ``_retry_n_times`` inner method + retry_n_times_time_interval: 0.1 + # Enable or disable the use of the gRPC API or legacy COM object + use_grpc_api: null + # Enable or disable the use of multiple desktop sessions in the same Python script + use_multi_desktop: false + # Enable or disable the use of the flag `-waitforlicense` when launching AEDT + wait_for_license: false + # State whether the remote API is used or not + remote_api: false + # Specify the port the RPyC server is to listen to + remote_rpc_service_manager_port: 17878 + # Specify the path to AEDT in the server + pyaedt_server_path: '' + # Remote temp folder + remote_rpc_session_temp_folder: '' diff --git a/doc/source/User_guide/index.rst b/doc/source/User_guide/index.rst index f687c34ea27..1c1ae73d09a 100644 --- a/doc/source/User_guide/index.rst +++ b/doc/source/User_guide/index.rst @@ -39,6 +39,13 @@ For end-to-end examples, see `Examples ` + +.. note:: + Not all settings from class ``Settings`` can be modified through this file + as some of them expect Python objects or values obtained from code execution. + For example, that is the case for ``formatter`` which expects an object of type + ``Formatter`` and ``time_tick`` which expects a time value, in seconds, since the + `epoch `_ as a floating-point number. + + +.. code-block:: yaml + + # Settings related to logging + log: + # Enable or disable the logging of EDB API methods + enable_debug_edb_logger: false + # Enable or disable the logging of the geometry operators + enable_debug_geometry_operator_logger: false + # Enable or disable the logging of the gRPC API calls + enable_debug_grpc_api_logger: false + # Enable or disable the logging of internal methods + enable_debug_internal_methods_logger: false + # Enable or disable the logging at debug level + enable_debug_logger: false + # Enable or disable the logging of methods' arguments at debug level + enable_debug_methods_argument_logger: false + # Enable or disable the logging to the AEDT message window + enable_desktop_logs: true + # Enable or disable the logging to a file + enable_file_logs: true + # Enable or disable the global PyAEDT log file located in the global temp folder + enable_global_log_file: true + # Enable or disable the local PyAEDT log file located in the ``projectname.pyaedt`` project folder + enable_local_log_file: false + # Enable or disable the logging overall + enable_logger: true + # Enable or disable the logging to STDOUT + enable_screen_logs: true + # Global PyAEDT log file path + global_log_file_name: null + # Global PyAEDT log file size in MB + global_log_file_size: 10 + # Date format of the log entries + logger_datefmt: '%Y/%m/%d %H.%M.%S' + # PyAEDT log file path + logger_file_path: null + # Message format of the log entries + logger_formatter: '%(asctime)s:%(destination)s:%(extra)s%(levelname)-8s:%(message)s' + + # Settings related to Linux systems running LSF scheduler + lsf: + # Command to launch in the LSF Scheduler + custom_lsf_command: null + # Command to launch the task in the LSF Scheduler + lsf_aedt_command: 'ansysedt' + # Number of LSF cores + lsf_num_cores: 2 + # Operating system string + lsf_osrel: null + # LSF queue name + lsf_queue: null + # RAM allocated for the LSF job + lsf_ram: 1000 + # Timeout in seconds for trying to start the interactive session + lsf_timeout: 3600 + # Value passed in the LSF 'select' string to the ui resource + lsf_ui: null + # Enable or disable use LSF Scheduler + use_lsf_scheduler: false + + # Settings related to environment variables thare are set before launching a new AEDT session + # This includes those that enable the beta features ! + aedt_env_var: + ANSYSEM_FEATURE_F335896_MECHANICAL_STRUCTURAL_SOLN_TYPE_ENABLE: '1' + ANSYSEM_FEATURE_F395486_RIGID_FLEX_BENDING_ENABLE: '1' + ANSYSEM_FEATURE_F538630_MECH_TRANSIENT_THERMAL_ENABLE: '1' + ANSYSEM_FEATURE_F545177_ECAD_INTEGRATION_WITH_APHI_ENABLE: '1' + ANSYSEM_FEATURE_F650636_MECH_LAYOUT_COMPONENT_ENABLE: '1' + ANSYSEM_FEATURE_S432616_LAYOUT_COMPONENT_IN_3D_ENABLE: '1' + ANSYSEM_FEATURE_SF159726_SCRIPTOBJECT_ENABLE: '1' + ANSYSEM_FEATURE_SF222134_CABLE_MODELING_ENHANCEMENTS_ENABLE: '1' + ANSYSEM_FEATURE_SF6694_NON_GRAPHICAL_COMMAND_EXECUTION_ENABLE: '1' + ANS_MESHER_PROC_DUMP_PREPOST_BEND_SM3: '1' + + general: + # Enable or disable the lazy load + lazy_load: true + # Enable or disable the lazy load dedicated to objects associated to the modeler + objects_lazy_load: true + # AEDT installation path + aedt_install_dir: null + # AEDT version in the form ``"2023.x"`` + aedt_version: null + # Timeout in seconds for trying to launch AEDT + desktop_launch_timeout: 120 + # Enable or disable bounding box evaluation by exporting a SAT file + disable_bounding_box_sat: false + # Optional path for the EDB DLL file + edb_dll_path: null + # Enable or disable the internal PyAEDT error handling + enable_error_handler: true + # Enable or disable the use of Pandas to export dictionaries and lists + enable_pandas_output: false + # Enable or disable the check of the project path + force_error_on_missing_project: false + # Number of gRPC API retries + number_of_grpc_api_retries: 6 + # Enable or disable the release of AEDT on exception + release_on_exception: true + # Time interval between the retries by the ``_retry_n_times`` inner method + retry_n_times_time_interval: 0.1 + # Enable or disable the use of the gRPC API or legacy COM object + use_grpc_api: null + # Enable or disable the use of multiple desktop sessions in the same Python script + use_multi_desktop: false + # Enable or disable the use of the flag `-waitforlicense` when launching Electronic Desktop + wait_for_license: false + # State whether the remote API is used or not + remote_api: false + # Specify the port the RPyC server is to listen to + remote_rpc_service_manager_port: 17878 + # Specify the path to AEDT in the server + pyaedt_server_path: '' + # Remote temp folder + remote_rpc_session_temp_folder: '' diff --git a/doc/source/conf.py b/doc/source/conf.py index 6fc8c127c04..91697a923be 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -10,7 +10,7 @@ import numpy as np from sphinx_gallery.sorting import FileNameSortKey from ansys_sphinx_theme import (ansys_favicon, - get_version_match, pyansys_logo_black, + get_version_match, watermark, ansys_logo_white, ansys_logo_white_cropped, latex) @@ -313,7 +313,6 @@ def setup(app): # -- Options for HTML output ------------------------------------------------- html_short_title = html_title = "PyAEDT" html_theme = "ansys_sphinx_theme" -html_logo = pyansys_logo_black html_context = { "github_user": "ansys", "github_repo": "pyaedt", @@ -323,6 +322,7 @@ def setup(app): # specify the location of your github repo html_theme_options = { + "logo": "pyansys", "github_url": "https://github.com/ansys/pyaedt", "navigation_with_keys": False, "show_prev_next": False, diff --git a/doc/source/release_1_0.rst b/doc/source/release_1_0.rst index c46b1819b8d..0563eabe3c7 100644 --- a/doc/source/release_1_0.rst +++ b/doc/source/release_1_0.rst @@ -65,7 +65,7 @@ accordingly. An example of migration is shown below: .. code-block:: python - from ansys.aedt import Circuit + from ansys.aedt.core import Circuit Python files renaming --------------------- diff --git a/doc/styles/config/vocabularies/ANSYS/accept.txt b/doc/styles/config/vocabularies/ANSYS/accept.txt index 4125d4a4f2c..f9d92aaea61 100644 --- a/doc/styles/config/vocabularies/ANSYS/accept.txt +++ b/doc/styles/config/vocabularies/ANSYS/accept.txt @@ -39,6 +39,7 @@ Icepak IronPython [Ll]ayout limitilines +LSF matplotlib Maxwell 2D Maxwell 3D diff --git a/examples/04-Icepak/Sherlock_Example.py b/examples/04-Icepak/Sherlock_Example.py index 70406929952..a55c2eead1e 100644 --- a/examples/04-Icepak/Sherlock_Example.py +++ b/examples/04-Icepak/Sherlock_Example.py @@ -44,13 +44,11 @@ component_step = "TutorialBoard.stp" aedt_odb_project = "SherlockTutorial.aedt" aedt_odb_design_name = "PCB" -stackup_thickness = 2.11836 -outline_polygon_name = "poly_14188" ############################################################################### # Launch AEDT # ~~~~~~~~~~~ -# Launch AEDT 2023 R2 in graphical mode. +# Launch AEDT 2024 R2 in graphical mode. d = ansys.aedt.core.launch_desktop(version=aedt_version, non_graphical=non_graphical, new_desktop=True) @@ -60,22 +58,21 @@ validation = os.path.join(project_folder, "validation.log") file_path = os.path.join(input_dir, component_step) project_name = os.path.join(project_folder, component_step[:-3] + "aedt") +component_name = "from_ODB" ############################################################################### # Create Icepak project # ~~~~~~~~~~~~~~~~~~~~~ # Create an Icepak project. -ipk = ansys.aedt.core.Icepak(project_name) +ipk = ansys.aedt.core.Icepak(project=project_name) ############################################################################### -# Delete region to speed up import +# Disable autosave to speed up import # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# Delete the region and disable autosave to speed up the import. +# Disable autosave to speed up the import. d.disable_autosave() -ipk.modeler.delete("Region") -component_name = "from_ODB" ############################################################################### # Import PCB from AEDB file @@ -92,49 +89,35 @@ # Create an offset coordinate system to match ODB++ with the # Sherlock STEP file. -ipk.modeler.create_coordinate_system(origin=[0, 0, stackup_thickness / 2], mode="view", view="XY") +bb = ipk.modeler.user_defined_components[component_name+"1"].bounding_box +stackup_thickness = bb[-1] - bb[2] +ipk.modeler.create_coordinate_system( + origin=[0, 0, stackup_thickness / 2], mode="view", view="XY" +) ############################################################################### # Import CAD file # ~~~~~~~~~~~~~~~ -# Import a CAD file. +# Import a CAD file and delete the CAD "pcb" object as the ECAD is already in the design. ipk.modeler.import_3d_cad(file_path, refresh_all_ids=False) - -############################################################################### -# Save CAD file -# ~~~~~~~~~~~~~ -# Save the CAD file and refresh the properties from the parsing of the AEDT file. - -ipk.save_project(refresh_ids=True) - -############################################################################### -# Plot model -# ~~~~~~~~~~ -# Plot the model. - -ipk.plot(show=False, output_file=os.path.join(project_folder, "Sherlock_Example.jpg"), plot_air_objects=False) - -############################################################################### -# Delete PCB objects -# ~~~~~~~~~~~~~~~~~~ -# Delete the PCB objects. - ipk.modeler.delete_objects_containing("pcb", False) ############################################################################### -# Create region +# Modify air region # ~~~~~~~~~~~~~ -# Create an air region. +# Modify air region dimensions. -ipk.modeler.create_air_region(*[20, 20, 300, 20, 20, 300]) +ipk.mesh.global_mesh_region.global_region.padding_values = [20, 20, 20, 20, 300, 300] ############################################################################### # Assign materials # ~~~~~~~~~~~~~~~~ # Assign materials from Sherlock file. -ipk.assignmaterial_from_sherlock_files(component_list, material_list) +ipk.assignmaterial_from_sherlock_files( + component_file=component_list, material_file=material_list +) ############################################################################### # Delete objects with no material assignments @@ -159,42 +142,42 @@ total_power = ipk.assign_block_from_sherlock_file(csv_name=component_list) +############################################################################### +# Assign openings +# ~~~~~~~~~~~~~~~~~~~ +# Assign opening boundary condition to all the faces of the region. +ipk.assign_openings(ipk.modeler.get_object_faces("Region")) + ############################################################################### # Plot model # ~~~~~~~~~~ # Plot the model again now that materials are assigned. -ipk.plot(show=False, output_file=os.path.join(project_folder, "Sherlock_Example.jpg"), plot_air_objects=False) +ipk.plot( + show=False, + output_file=os.path.join(project_folder, "Sherlock_Example.jpg"), + plot_air_objects=False, + plot_as_separate_objects=False +) ############################################################################### -# Set up boundaries +# Set up mesh settings # ~~~~~~~~~~~~~~~~~ -# Set up boundaries. - # Mesh settings that is tailored for PCB -# Max iterations is set to 20 for quick demonstration, please increase to at least 100 for better accuracy. ipk.globalMeshSettings(3, gap_min_elements='1', noOgrids=True, MLM_en=True, MLM_Type='2D', edge_min_elements='2', object='Region') +############################################################################### +# Numerical settings +# ~~~~~~~~~~~~~~~~~ + setup1 = ipk.create_setup() setup1.props["Solution Initialization - Y Velocity"] = "1m_per_sec" setup1.props["Radiation Model"] = "Discrete Ordinates Model" setup1.props["Include Gravity"] = True setup1.props["Secondary Gradient"] = True -setup1.props["Convergence Criteria - Max Iterations"] = 10 -ipk.assign_openings(ipk.modeler.get_object_faces("Region")) - -############################################################################### -# Create point monitor -# ~~~~~~~~~~~~~~~~~~~~ - -point1 = ipk.assign_point_monitor(ipk.modeler["COMP_U10"].top_face_z.center, monitor_name="Point1") -ipk.modeler.set_working_coordinate_system("Global") -line = ipk.modeler.create_polyline( - [ipk.modeler["COMP_U10"].top_face_z.vertices[0].position, ipk.modeler["COMP_U10"].top_face_z.vertices[2].position], - non_model=True) -ipk.post.create_report(expressions="Point1.Temperature", primary_sweep_variable="X") +setup1.props["Convergence Criteria - Max Iterations"] = 100 ############################################################################### # Check for intersections @@ -208,24 +191,9 @@ # Compute power budget # ~~~~~~~~~~~~~~~~~~~~ -power_budget, total = ipk.post.power_budget("W" ) +power_budget, total = ipk.post.power_budget("W") print(total) -############################################################################### -# Analyze the model -# ~~~~~~~~~~~~~~~~~ - -# ipk.analyze(cores=4, tasks=4) -ipk.save_project() - -############################################################################### -# Get solution data and plots -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -plot1 = ipk.post.create_fieldplot_surface(ipk.modeler["COMP_U10"].faces, "SurfTemperature") -# ipk.post.plot_field("SurfPressure", ipk.modeler["COMP_U10"].faces, show=False, export_path=ipk.working_directory) - - ############################################################################### # Save project and release AEDT # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -237,5 +205,3 @@ print("Elapsed time: {}".format(datetime.timedelta(seconds=end))) print("Project Saved in {} ".format(ipk.project_file)) ipk.release_desktop() - - diff --git a/pyproject.toml b/pyproject.toml index 8862669cecb..300d7f76f59 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,8 +38,9 @@ dependencies = [ "psutil", "pyedb>=0.4.0; python_version == '3.7'", "pyedb>=0.24.0; python_version > '3.7'", - "pytomlpp", + "pytomlpp; python_version < '3.12'", "rpyc>=6.0.0,<6.1", + "pyyaml", ] [project.optional-dependencies] @@ -70,7 +71,7 @@ dotnet = [ "pywin32>=303; platform_system=='Windows'", ] doc = [ - "ansys-sphinx-theme>=0.10.0,<0.17", + "ansys-sphinx-theme>=0.10.0,<1.1", "ipython>=8.13.0,<8.27", "joblib>=1.3.0,<1.5", "jupyterlab>=4.0.0,<4.3", @@ -84,12 +85,12 @@ doc = [ "pyvista[io]>=0.38.0,<0.45", "recommonmark", "scikit-rf>=0.30.0,<1.3", - "Sphinx>=7.1.0,<7.4", + "Sphinx>=7.1.0,<8.1", "sphinx-autobuild==2021.3.14; python_version == '3.8'", "sphinx-autobuild==2024.4.16; python_version > '3.8'", #"sphinx-autodoc-typehints", "sphinx-copybutton>=0.5.0,<0.6", - "sphinx-gallery>=0.14.0,<0.17", + "sphinx-gallery>=0.14.0,<0.18", #"sphinx-notfound-page", "sphinx_design>=0.4.0,<0.7", #"sphinxcontrib-websupport", @@ -97,15 +98,15 @@ doc = [ "utm", ] doc-no-examples = [ - "ansys-sphinx-theme>=0.10.0,<0.17", + "ansys-sphinx-theme>=0.10.0,<1.1", "numpydoc>=1.5.0,<1.9", "recommonmark", - "Sphinx>=7.1.0,<7.4", + "Sphinx>=7.1.0,<8.1", "sphinx-autobuild==2021.3.14; python_version == '3.8'", "sphinx-autobuild==2024.4.16; python_version > '3.8'", #"sphinx-autodoc-typehints", "sphinx-copybutton>=0.5.0,<0.6", - "sphinx-gallery>=0.14.0,<0.17", + "sphinx-gallery>=0.14.0,<0.18", #"sphinx-notfound-page", #"sphinxcontrib-websupport", "sphinx_design>=0.4.0,<0.7", diff --git a/src/ansys/aedt/core/__init__.py b/src/ansys/aedt/core/__init__.py index c2c63bd78e4..09ea77b8a1a 100644 --- a/src/ansys/aedt/core/__init__.py +++ b/src/ansys/aedt/core/__init__.py @@ -67,7 +67,13 @@ def custom_show_warning(message, category, filename, lineno, file=None, line=Non __version__ = "0.10.1" version = __version__ -# +# isort: off +# Settings have to be imported before importing other PyAEDT modules +from ansys.aedt.core.generic.general_methods import settings +from ansys.aedt.core.generic.general_methods import inner_project_settings + +# isort: on + if not ("IronPython" in sys.version or ".NETFramework" in sys.version): # pragma: no cover import ansys.aedt.core.downloads as downloads from ansys.aedt.core.edb import Edb # nosec @@ -75,6 +81,7 @@ def custom_show_warning(message, category, filename, lineno, file=None, line=Non from ansys.aedt.core.generic import constants import ansys.aedt.core.generic.data_handlers as data_handler from ansys.aedt.core.generic.design_types import Circuit +from ansys.aedt.core.generic.design_types import CircuitNetlist from ansys.aedt.core.generic.design_types import Desktop from ansys.aedt.core.generic.design_types import Emit from ansys.aedt.core.generic.design_types import FilterSolutions @@ -103,7 +110,6 @@ def custom_show_warning(message, category, filename, lineno, file=None, line=Non from ansys.aedt.core.generic.general_methods import is_windows from ansys.aedt.core.generic.general_methods import online_help from ansys.aedt.core.generic.general_methods import pyaedt_function_handler -from ansys.aedt.core.generic.general_methods import settings from ansys.aedt.core.misc import current_student_version from ansys.aedt.core.misc import current_version from ansys.aedt.core.misc import installed_versions diff --git a/src/ansys/aedt/core/application/aedt_objects.py b/src/ansys/aedt/core/application/aedt_objects.py index c5988969fd1..3805cd9ce8b 100644 --- a/src/ansys/aedt/core/application/aedt_objects.py +++ b/src/ansys/aedt/core/application/aedt_objects.py @@ -139,7 +139,7 @@ def design_type(self): def oboundary(self): """Boundary Object.""" if not self._oboundary: - if self.design_type in ["Twin Builder", "RMxprt", "RMxprtSolution", "Circuit Design"]: + if self.design_type in ["Twin Builder", "RMxprt", "RMxprtSolution", "Circuit Design", "Circuit Netlist"]: return if self.design_type in ["HFSS 3D Layout Design", "HFSS3DLayout"]: self._oboundary = self.get_module("Excitations") @@ -169,7 +169,7 @@ def ooptimetrics(self): >>> oDesign.GetModule("Optimetrics") """ - if not self._ooptimetrics and self.design_type not in ["Maxwell Circuit", "EMIT"]: + if not self._ooptimetrics and self.design_type not in ["Circuit Netlist", "Maxwell Circuit", "EMIT"]: self._ooptimetrics = self.get_module("Optimetrics") return self._ooptimetrics @@ -182,7 +182,7 @@ def ooutput_variable(self): >>> oDesign.GetModule("OutputVariable") """ - if not self._ooutput_variable and self.design_type not in ["EMIT", "Maxwell Circuit"]: + if not self._ooutput_variable and self.design_type not in ["EMIT", "Maxwell Circuit", "Circuit Netlist"]: self._ooutput_variable = self.get_module("OutputVariable") return self._ooutput_variable @@ -201,7 +201,7 @@ def oanalysis(self): return self._oanalysis if "HFSS 3D Layout Design" in self.design_type: self._oanalysis = self.get_module("SolveSetups") - elif "EMIT" in self.design_type or "Maxwell Circuit" in self.design_type: + elif self.design_type in ["EMIT", "Circuit Netlist", "Maxwell Circuit"]: self._oanalysis = None elif "Circuit Design" in self.design_type or "Twin Builder" in self.design_type: self._oanalysis = self.get_module("SimSetup") @@ -289,7 +289,14 @@ def osolution(self): >>> oModule = oDesign.GetModule("Solutions") """ if not self._osolution: - if self.design_type in ["RMxprt", "RMxprtSolution", "Twin Builder", "Circuit Design", "Maxwell Circuit"]: + if self.design_type in [ + "RMxprt", + "RMxprtSolution", + "Twin Builder", + "Circuit Design", + "Maxwell Circuit", + "Circuit Netlist", + ]: return if self.design_type in ["HFSS 3D Layout Design", "HFSS3DLayout"]: self._osolution = self.get_module("SolveSetups") @@ -337,6 +344,7 @@ def ofieldsreporter(self): """ if self.design_type in [ "Circuit Design", + "Circuit Netlist", "Twin Builder", "Maxwell Circuit", "EMIT", @@ -400,13 +408,15 @@ def oeditor(self): if not self._oeditor and self._odesign: if self.design_type in ["Circuit Design", "Twin Builder", "Maxwell Circuit", "EMIT"]: self._oeditor = self._odesign.SetActiveEditor("SchematicEditor") - if is_linux and settings.aedt_version == "2024.1": + if is_linux and settings.aedt_version == "2024.1": # pragma: no cover time.sleep(1) - self._odesktop.CloseAllWindows() + self.desktop_class.close_windows() elif self.design_type in ["HFSS 3D Layout Design", "HFSS3DLayout"]: self._oeditor = self._odesign.SetActiveEditor("Layout") elif self.design_type in ["RMxprt", "RMxprtSolution"]: self._oeditor = self._odesign.SetActiveEditor("Machine") + elif self.design_type in ["Circuit Netlist"]: + self._oeditor = None else: self._oeditor = self._odesign.SetActiveEditor("3D Modeler") return self._oeditor diff --git a/src/ansys/aedt/core/application/analysis.py b/src/ansys/aedt/core/application/analysis.py index 2a8bb4f07a5..ddafff07312 100644 --- a/src/ansys/aedt/core/application/analysis.py +++ b/src/ansys/aedt/core/application/analysis.py @@ -239,7 +239,7 @@ def setups(self): """ if not self._setups: - if self.design_type != "Maxwell Circuit": + if self.design_type not in ["Maxwell Circuit", "Circuit Netlist"]: self._setups = [self.get_setup(setup_name) for setup_name in self.setup_names] return self._setups @@ -427,7 +427,9 @@ def existing_analysis_setups(self): >>> oModule.GetSetups """ - setups = self.oanalysis.GetSetups() + setups = [] + if self.oanalysis and "GetSetups" in self.oanalysis.__dir__(): + setups = self.oanalysis.GetSetups() if setups: return list(setups) return [] @@ -446,7 +448,10 @@ def setup_names(self): >>> oModule.GetSetups """ - return self.oanalysis.GetSetups() + setup_names = [] + if self.oanalysis and "GetSetups" in self.oanalysis.__dir__(): + setup_names = self.oanalysis.GetSetups() + return setup_names @property def SimulationSetupTypes(self): @@ -1919,7 +1924,7 @@ def analyze_setup( else: try: self.logger.info("Solving Optimetrics") - self.ooptimetrics.solve_setup(name) + self.ooptimetrics.SolveSetup(name) except Exception: # pragma: no cover if set_custom_dso and active_config: self.set_registry_key(r"Desktop/ActiveDSOConfigurations/" + self.design_type, active_config) diff --git a/src/ansys/aedt/core/application/analysis_circuit_netlist.py b/src/ansys/aedt/core/application/analysis_circuit_netlist.py new file mode 100644 index 00000000000..2003d59f0ef --- /dev/null +++ b/src/ansys/aedt/core/application/analysis_circuit_netlist.py @@ -0,0 +1,126 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2021 - 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# 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. + +from ansys.aedt.core.application.analysis import Analysis +from ansys.aedt.core.generic.settings import settings + + +class AnalysisCircuitNetlist(Analysis, object): + """Provides the Circuit Netlist (CircuitNetlist) interface. + Circuit Netlist Editor has no setup, solution, analysis or postprocessor + It is automatically initialized by Application call. + Refer to Application function for inputs definition + + Parameters + ---------- + project : str, optional + Name of the project to select or the full path to the project + or AEDTZ archive to open. The default is ``None``, in which + case an attempt is made to get an active project. If no + projects are present, an empty project is created. + design : str, optional + Name of the design to select. The default is ``None``, in + which case an attempt is made to get an active design. If no + designs are present, an empty design is created. + version : str, int, float, optional + Version of AEDT to use. The default is ``None``. If ``None``, + the active setup is used or the latest installed version is + used. + non_graphical : bool, optional + Whether to launch AEDT in the non-graphical mode. The default + is ``False``, in which case AEDT is launched in the graphical mode. + new_desktop : bool, optional + Whether to launch an instance of AEDT in a new thread, even if + another instance of the ``specified_version`` is active on the + machine. The default is ``False``. + close_on_exit : bool, optional + Whether to release AEDT on exit. The default is ``False``. + student_version : bool, optional + Whether to open the AEDT student version. The default is ``False``. + aedt_process_id : int, optional + Only used when ``new_desktop = False``, specifies by process ID which instance + of Electronics Desktop to point PyAEDT at. + remove_lock : bool, optional + Whether to remove lock to project before opening it or not. + The default is ``False``, which means to not unlock + the existing project if needed and raise an exception. + """ + + def __init__( + self, + project, + design, + version, + non_graphical, + new_desktop, + close_on_exit, + student_version, + machine, + port, + aedt_process_id, + remove_lock, + ): + Analysis.__init__( + self, + "Circuit Netlist", + project, + design, + None, + None, + version, + non_graphical, + new_desktop, + close_on_exit, + student_version, + machine, + port, + aedt_process_id, + remove_lock=remove_lock, + ) + self._modeler = None + self._post = None + if not settings.lazy_load: + self._post = self.post + + @property + def post(self): + """PostProcessor. + + Returns + ------- + :class:`ansys.aedt.core.modules.advanced_post_processing.CircuitPostProcessor` + PostProcessor object. + """ + if self._post is None and self._odesign: + self.logger.reset_timer() + from ansys.aedt.core.modules.advanced_post_processing import PostProcessor + + self._post = PostProcessor(self) + self.logger.info_timer("Post class has been initialized!") + return self._post + + @property + def modeler(self): + """Modeler object.""" + return self._modeler diff --git a/src/ansys/aedt/core/application/analysis_maxwell_circuit.py b/src/ansys/aedt/core/application/analysis_maxwell_circuit.py index d3cfa7687a8..4a58ff6879b 100644 --- a/src/ansys/aedt/core/application/analysis_maxwell_circuit.py +++ b/src/ansys/aedt/core/application/analysis_maxwell_circuit.py @@ -34,12 +34,12 @@ class AnalysisMaxwellCircuit(Analysis): Parameters ---------- - projectname : str, optional + project : str, optional Name of the project to select or the full path to the project or AEDTZ archive to open. The default is ``None``, in which case an attempt is made to get an active project. If no projects are present, an empty project is created. - designname : str, optional + design : str, optional Name of the design to select. The default is ``None``, in which case an attempt is made to get an active design. If no designs are present, an empty design is created. @@ -47,7 +47,7 @@ class AnalysisMaxwellCircuit(Analysis): Version of AEDT to use. The default is ``None``. If ``None``, the active setup is used or the latest installed version is used. - NG : bool, optional + non_graphical : bool, optional Whether to launch AEDT in the non-graphical mode. The default is ``False``, in which case AEDT is launched in the graphical mode. new_desktop : bool, optional @@ -70,8 +70,8 @@ class AnalysisMaxwellCircuit(Analysis): def __init__( self, application, - projectname, - designname, + project, + design, version=None, non_graphical=False, new_desktop=False, @@ -85,8 +85,8 @@ def __init__( Analysis.__init__( self, application, - projectname, - designname, + project, + design, None, None, version, diff --git a/src/ansys/aedt/core/application/design.py b/src/ansys/aedt/core/application/design.py index 4d579fcbe6f..d3897399eeb 100644 --- a/src/ansys/aedt/core/application/design.py +++ b/src/ansys/aedt/core/application/design.py @@ -66,6 +66,7 @@ from ansys.aedt.core.generic.general_methods import GrpcApiError from ansys.aedt.core.generic.general_methods import check_and_download_file from ansys.aedt.core.generic.general_methods import generate_unique_name +from ansys.aedt.core.generic.general_methods import inner_project_settings from ansys.aedt.core.generic.general_methods import is_ironpython from ansys.aedt.core.generic.general_methods import is_project_locked from ansys.aedt.core.generic.general_methods import is_windows @@ -88,8 +89,8 @@ def load_aedt_thread(project_path): pp = load_entire_aedt_file(project_path) - settings._project_properties[os.path.normpath(project_path)] = pp - settings._project_time_stamp = os.path.getmtime(project_path) + inner_project_settings.properties[os.path.normpath(project_path)] = pp + inner_project_settings.time_stamp = os.path.getmtime(project_path) class Design(AedtObjects): @@ -306,7 +307,7 @@ def __init__( self._variable_manager = VariableManager(self) self._project_datasets = [] self._design_datasets = [] - if not self._design_type == "Maxwell Circuit": + if self._design_type not in ["Maxwell Circuit", "Circuit Netlist"]: self.design_settings = DesignSettings(self) @property @@ -352,28 +353,36 @@ def boundaries(self): List of :class:`ansys.aedt.core.modules.boundary.BoundaryObject` """ bb = [] - if "GetBoundaries" in self.oboundary.__dir__(): + if self.oboundary and "GetBoundaries" in self.oboundary.__dir__(): bb = list(self.oboundary.GetBoundaries()) - elif "GetAllBoundariesList" in self.oboundary.__dir__() and self.design_type == "HFSS 3D Layout Design": + elif ( + self.oboundary + and "GetAllBoundariesList" in self.oboundary.__dir__() + and self.design_type == "HFSS 3D Layout Design" + ): bb = list(self.oboundary.GetAllBoundariesList()) bb = [elem for sublist in zip(bb, ["Port"] * len(bb)) for elem in sublist] elif "Boundaries" in self.get_oo_name(self.odesign): bb = self.get_oo_name(self.odesign, "Boundaries") bb = list(bb) - if "GetHybridRegions" in self.oboundary.__dir__(): + if self.oboundary and "GetHybridRegions" in self.oboundary.__dir__(): hybrid_regions = self.oboundary.GetHybridRegions() for region in hybrid_regions: bb.append(region) bb.append("FE-BI") current_excitations = [] current_excitation_types = [] - if "GetExcitations" in self.oboundary.__dir__(): + if self.oboundary and "GetExcitations" in self.oboundary.__dir__(): ee = list(self.oboundary.GetExcitations()) current_excitations = [i.split(":")[0] for i in ee[::2]] current_excitation_types = ee[1::2] ff = [i.split(":")[0] for i in ee] bb.extend(ff) - elif "Excitations" in self.get_oo_name(self.odesign) and self.design_type == "HFSS 3D Layout Design": + elif ( + self.oboundary + and "Excitations" in self.get_oo_name(self.odesign) + and self.design_type == "HFSS 3D Layout Design" + ): ee = self.get_oo_name(self.odesign, "Excitations") ee = [elem for sublist in zip(ee, ["Port"] * len(ee)) for elem in sublist] current_excitations = ee[::2] @@ -415,12 +424,13 @@ def boundaries(self): current_boundaries = bb[::2] current_types = bb[1::2] - check_boundaries = list(current_boundaries[:]) + list(self.ports[:]) + self.excitations[:] - if "nets" in dir(self): - check_boundaries += self.nets - for k in list(self._boundaries.keys())[:]: - if k not in check_boundaries: - del self._boundaries[k] + if hasattr(self, "excitations"): + check_boundaries = list(current_boundaries[:]) + list(self.ports[:]) + self.excitations[:] + if "nets" in dir(self): + check_boundaries += self.nets + for k in list(self._boundaries.keys())[:]: + if k not in check_boundaries: + del self._boundaries[k] for boundary, boundarytype in zip(current_boundaries, current_types): if boundary in self._boundaries: continue @@ -473,7 +483,7 @@ def ports(self): """ design_excitations = [] - if "GetExcitations" in self.oboundary.__dir__(): + if self.oboundary and "GetExcitations" in self.oboundary.__dir__(): ee = list(self.oboundary.GetExcitations()) current_types = ee[1::2] for i in set(current_types): @@ -485,7 +495,11 @@ def ports(self): current_types = current_types + [i] * len(new_port) return design_excitations - elif "GetAllPortsList" in self.oboundary.__dir__() and self.design_type in ["HFSS 3D Layout Design"]: + elif ( + self.oboundary + and "GetAllPortsList" in self.oboundary.__dir__() + and self.design_type in ["HFSS 3D Layout Design"] + ): return self.oboundary.GetAllPortsList() return [] @@ -550,24 +564,28 @@ def project_properties(self): start = time.time() if self.project_timestamp_changed or ( os.path.exists(self.project_file) - and os.path.normpath(self.project_file) not in settings._project_properties + and os.path.normpath(self.project_file) not in inner_project_settings.properties ): - settings._project_properties[os.path.normpath(self.project_file)] = load_entire_aedt_file(self.project_file) + inner_project_settings.properties[os.path.normpath(self.project_file)] = load_entire_aedt_file( + self.project_file + ) self._logger.info("aedt file load time {}".format(time.time() - start)) elif ( - os.path.normpath(self.project_file) not in settings._project_properties + os.path.normpath(self.project_file) not in inner_project_settings.properties and settings.remote_rpc_session and settings.remote_rpc_session.filemanager.pathexists(self.project_file) ): file_path = check_and_download_file(self.project_file) try: - settings._project_properties[os.path.normpath(self.project_file)] = load_entire_aedt_file(file_path) + inner_project_settings.properties[os.path.normpath(self.project_file)] = load_entire_aedt_file( + file_path + ) except Exception: self._logger.info("Failed to load AEDT file.") else: self._logger.info("Time to load AEDT file: {}.".format(time.time() - start)) - if os.path.normpath(self.project_file) in settings._project_properties: - return settings._project_properties[os.path.normpath(self.project_file)] + if os.path.normpath(self.project_file) in inner_project_settings.properties: + return inner_project_settings.properties[os.path.normpath(self.project_file)] return {} @property @@ -694,7 +712,7 @@ def design_list(self): def design_type(self): """Design type. - Options are ``"Circuit Design"``, ``"Emit"``, ``"HFSS"``, + Options are ``"Circuit Design"``, ``"Circuit Netlist"``, ``"Emit"``, ``"HFSS"``, ``"HFSS 3D Layout Design"``, ``"Icepak"``, ``"Maxwell 2D"``, ``"Maxwell 3D"``, ``"Maxwell Circuit"``, ``"Mechanical"``, ``"ModelCreation"``, ``"Q2D Extractor"``, ``"Q3D Extractor"``, ``"RMxprtSolution"``, @@ -769,15 +787,15 @@ def project_path(self): def project_time_stamp(self): """Return Project time stamp.""" if os.path.exists(self.project_file): - settings._project_time_stamp = os.path.getmtime(self.project_file) + inner_project_settings.time_stamp = os.path.getmtime(self.project_file) else: - settings._project_time_stamp = 0 - return settings._project_time_stamp + inner_project_settings.time_stamp = 0 + return inner_project_settings.time_stamp @property def project_timestamp_changed(self): """Return a bool if time stamp changed or not.""" - old_time = settings._project_time_stamp + old_time = inner_project_settings.time_stamp return old_time != self.project_time_stamp @property @@ -1233,7 +1251,7 @@ def oproject(self, proj_name=None): self._oproject = self.odesktop.OpenProject(proj_name) if not is_windows and settings.aedt_version: time.sleep(1) - self.odesktop.CloseAllWindows() + self.desktop_class.close_windows() self._add_handler() self.logger.info("Project %s has been opened.", self._oproject.GetName()) time.sleep(0.5) @@ -3297,8 +3315,8 @@ def close_project(self, name=None, save=True): i += 0.2 time.sleep(0.2) - if os.path.normpath(proj_file) in settings._project_properties: - del settings._project_properties[os.path.normpath(proj_file)] + if os.path.normpath(proj_file) in inner_project_settings.properties: + del inner_project_settings.properties[os.path.normpath(proj_file)] return True @pyaedt_function_handler() @@ -3464,7 +3482,7 @@ def _insert_design(self, design_type, design_name=None): ) if not is_windows and settings.aedt_version and self.design_type == "Circuit Design": time.sleep(1) - self.odesktop.CloseAllWindows() + self.desktop_class.close_windows() if new_design is None: # pragma: no cover new_design = self.desktop_class.active_design(self.oproject, unique_design_name, self.design_type) if new_design is None: diff --git a/src/ansys/aedt/core/application/design_solutions.py b/src/ansys/aedt/core/application/design_solutions.py index 328256aa913..8531a688e7b 100644 --- a/src/ansys/aedt/core/application/design_solutions.py +++ b/src/ansys/aedt/core/application/design_solutions.py @@ -32,6 +32,7 @@ "Maxwell 3D": "Magnetostatic", "Twin Builder": "TR", "Circuit Design": "NexximLNA", + "Circuit Netlist": "", "Maxwell Circuit": "", "2D Extractor": "Open", "Q3D Extractor": "Q3D Extractor", @@ -548,6 +549,7 @@ }, # Maxwell Circuit has no solution type "Maxwell Circuit": {}, + "Circuit Netlist": {}, } model_names = { @@ -555,6 +557,7 @@ "Maxwell 3D": "Maxwell3DModel", "Twin Builder": "SimplorerCircuit", "Circuit Design": "NexximCircuit", + "Circuit Netlist": "NexximNetlist", "Maxwell Circuit": "MaxCirCircuit", "2D Extractor": "2DExtractorModel", "Q3D Extractor": "Q3DModel", diff --git a/src/ansys/aedt/core/application/variables.py b/src/ansys/aedt/core/application/variables.py index c8004fa08fc..7091243ab02 100644 --- a/src/ansys/aedt/core/application/variables.py +++ b/src/ansys/aedt/core/application/variables.py @@ -1372,7 +1372,10 @@ def delete_unused_variables(self): @pyaedt_function_handler() def _get_var_list_from_aedt(self, desktop_object): var_list = [] - if self._app._is_object_oriented_enabled() and self._app.design_type != "Maxwell Circuit": + if self._app._is_object_oriented_enabled() and self._app.design_type not in [ + "Maxwell Circuit", + "Circuit Netlist", + ]: # To retrieve local variables try: v = list(self._app.get_oo_object(self._app.odesign, "LocalVariables").GetPropNames()) @@ -1390,7 +1393,9 @@ def _get_var_list_from_aedt(self, desktop_object): except AttributeError: v = [] var_list += v - var_list += [i for i in list(desktop_object.GetVariables()) if i not in var_list] + + if "GetVariables" in desktop_object.__dir__(): + var_list += [i for i in list(desktop_object.GetVariables()) if i not in var_list] var_list += [i for i in list(self._app.oproject.GetArrayVariables()) if i not in var_list] return var_list diff --git a/src/ansys/aedt/core/circuit.py b/src/ansys/aedt/core/circuit.py index 1482ece983f..89ef67149f6 100644 --- a/src/ansys/aedt/core/circuit.py +++ b/src/ansys/aedt/core/circuit.py @@ -43,6 +43,7 @@ from ansys.aedt.core.generic.general_methods import is_linux from ansys.aedt.core.generic.general_methods import open_file from ansys.aedt.core.generic.general_methods import pyaedt_function_handler +from ansys.aedt.core.generic.general_methods import read_configuration_file from ansys.aedt.core.generic.settings import settings from ansys.aedt.core.hfss3dlayout import Hfss3dLayout from ansys.aedt.core.modules.boundary import CurrentSinSource @@ -115,8 +116,8 @@ class Circuit(FieldAnalysisCircuit, ScatteringMethods): Examples -------- - Create an instance of Circuit and connect to an existing HFSS - design or create a new HFSS design if one does not exist. + Create an instance of Circuit and connect to an existing Circuit + design or create a new Circuit design if one does not exist. >>> from ansys.aedt.core import Circuit >>> aedtapp = Circuit() @@ -235,7 +236,7 @@ def create_schematic_from_netlist(self, input_file): delta = 0.0508 use_instance = True model = [] - self._desktop.CloseAllWindows() + self.desktop_class.close_windows() autosave = False if self._desktop.GetAutoSaveEnabled() == 1: self._desktop.EnableAutoSave(False) @@ -688,9 +689,9 @@ def get_source_pin_names( self._desktop.OpenProject(source_project_path) oSrcProject = self._desktop.SetActiveProject(source_project_name) oDesign = oSrcProject.SetActiveDesign(source_design_name) - if is_linux and settings.aedt_version == "2024.1": + if is_linux and settings.aedt_version == "2024.1": # pragma: no cover time.sleep(1) - self._desktop.CloseAllWindows() + self.desktop_class.close_windows() tmp_oModule = oDesign.GetModule("BoundarySetup") port = None if port_selector == 1: @@ -2361,3 +2362,256 @@ def create_ibis_schematic_from_pins( if analyze: setup_ibis.analyze() return True, tx_eye_names, rx_eye_names + + @pyaedt_function_handler() + def _parse_asc_file(self, input_file, l_scale=2.54e-3 / 16, c_scale=2.54e-3 / 16, offset_angle=-90): + with open(input_file, "r") as fid: + asc_data = fid.read() + + wire_xy = [i.split()[1:] for i in asc_data.split("\n") if "WIRE" in i] + wire_xy = [ + [float(i[0]) * l_scale, -float(i[1]) * l_scale, float(i[2]) * l_scale, -float(i[3]) * l_scale] + for i in wire_xy + ] + + flag = [i.split()[1:] for i in asc_data.split("\n") if "FLAG" in i] + flag = [[float(i[0]) * l_scale, -float(i[1]) * l_scale, i[2]] for i in flag] + + for j, i in enumerate(flag): + for k in wire_xy: + if i[:2] in [[k[0], k[1]], [k[2], k[3]]]: + if k[0] - k[2]: + flag[j] += ["x"] + elif k[1] - k[3]: + flag[j] += ["y"] + + symbol = [i.split("\n")[0].split() for i in asc_data.split("SYMBOL")[1:]] + for j, i in enumerate(asc_data.split("SYMBOL")[1:]): + tmp = i.split("\n")[0].split() + tmp[0] = tmp[0].lower() + val = [k for k in i.split("\n") if "SYMATTR Value" in k] + if val: + if "(" in val[0].split("Value ")[-1]: + value = val[0].split("Value ")[-1] + else: + value = re.findall(r"[a-zA-Z]+|\d+", val[0].split("Value ")[-1]) + else: + value = [0] + unit_dict = {"f": 1e-15, "p": 1e-12, "n": 1e-9, "u": 1e-6, "m": 1e-3, "k": 1e3, "meg": 1e6} + if len(value) > 1: + try: + val = float(".".join(value[:-1])) * unit_dict[value[-1].lower()] + except Exception: + try: + val = float(".".join(value)) + except Exception: + if tmp[0] not in ["voltage", "current"]: + val = 0 + elif "PULSE" in value: + tmp[0] = "{}_pulse".format(tmp[0]) + val = value + else: + val = value + + else: + try: + val = float(".".join(value)) + except Exception: + if tmp[0] not in ["voltage", "current"]: + val = 0 + elif "PULSE" in value: + tmp[0] = "{}_pulse".format(tmp[0]) + val = value + else: + val = value + if isinstance(val, list): + val = " ".join(val) + tmp[1] = (float(tmp[1])) * c_scale + tmp[2] = (-float(tmp[2])) * c_scale + tmp.append([]) + tmp.append([]) + tmp.append([]) + if "R" in tmp[3]: + tmp[3] = int(tmp[3].replace("R", "")) - 90 + tmp[5] = "R" + elif "M" in tmp[3]: + tmp[3] = int(tmp[3].replace("M", "")) - 90 + tmp[5] = "M" + else: + tmp[3] = offset_angle + + cname = [k for k in i.split("\n") if "SYMATTR InstName" in k][0].split(" ")[-1] + tmp[4] = cname + if val: + tmp[6] = val + else: + tmp[6] = None + symbol[j] = tmp + self.logger.info("LTSpice file parsed correctly") + return flag, wire_xy, symbol + + @pyaedt_function_handler() + def create_schematic_from_asc_file(self, input_file, config_file=None): + """Import an asc schematic and convert to Circuit Schematic. Only passives and sources will be imported. + + Parameters + ---------- + input_file : str + Path to asc file. + config_file : str, optional + Path to configuration file to map components. Default is None which uses internal mapping. + + Returns + ------- + bool + ``True`` if successful. + """ + factor = 2 + + scale = 2.54e-3 / (16 / factor) + + flag, wire_xy, symbol = self._parse_asc_file(input_file=input_file, l_scale=scale, c_scale=scale) + for i in flag: + if i[2] == "0": + angle = 0 + if len(i) > 3: + if i[3] == "x": + i[0] -= 0.002540 * 0 + i[1] -= 0.002540 * 1 + angle = 0 + self.modeler.schematic.create_gnd([i[0], i[1]], angle) + + else: + self.modeler.schematic.create_interface_port(name=i[2], location=[i[0], i[1]]) + + if not config_file: + configuration = read_configuration_file( + os.path.join(os.path.dirname(__file__), "misc", "asc_circuit_mapping.json") + ) + else: + configuration = read_configuration_file(config_file) + + mils_to_meter = 0.00254 / 100 + + for j in symbol: + component = j[0].lower() + if component in configuration: + rotation = j[3] + rotation_type = j[5] + + offsetx = configuration[component]["xoffset"] * mils_to_meter + offsety = configuration[component]["yoffset"] * mils_to_meter + half_comp_size = configuration[component]["component_size"] * mils_to_meter / 2 + size_change = configuration[component].get("size_change", 0) * mils_to_meter + orientation = 1 if configuration[component]["orientation"] == "+x" else -1 + pts = [] + if rotation_type == "R": + if rotation == -90: + offsetx = configuration[component]["xoffset"] * mils_to_meter + offsety = configuration[component]["yoffset"] * mils_to_meter + pts = [ + [j[1] + offsetx, j[2] + offsety - half_comp_size * orientation], + [j[1] + offsetx, j[2] + offsety - ((half_comp_size + size_change) * orientation)], + ] + elif rotation == 0: + offsetx = configuration[component]["yoffset"] * mils_to_meter + offsety = -configuration[component]["xoffset"] * mils_to_meter + pts = [ + [j[1] + offsetx - half_comp_size * orientation, j[2] + offsety], + [j[1] + offsetx - ((half_comp_size + size_change) * orientation), j[2] + offsety], + ] + + elif rotation == 90: + offsetx = -configuration[component]["xoffset"] * mils_to_meter + offsety = -configuration[component]["yoffset"] * mils_to_meter + pts = [ + [j[1] + offsetx, j[2] + offsety + half_comp_size * orientation], + [j[1] + offsetx, j[2] + offsety + ((half_comp_size + size_change) * orientation)], + ] + + else: + offsetx = -configuration[component]["yoffset"] * mils_to_meter + offsety = configuration[component]["xoffset"] * mils_to_meter + pts = [ + [j[1] + offsetx + half_comp_size * orientation, j[2] + offsety], + [j[1] + offsetx + ((half_comp_size + size_change) * orientation), j[2] + offsety], + ] + + elif rotation_type == "M": + if rotation == -90: + offsetx = -configuration[component]["xoffset"] * mils_to_meter + offsety = configuration[component]["yoffset"] * mils_to_meter + pts = [ + [j[1] + offsetx, j[2] + offsety - half_comp_size * orientation], + [j[1] + offsetx, j[2] + offsety - ((half_comp_size + size_change) * orientation)], + ] + + elif rotation == 0: + offsetx = -configuration[component]["yoffset"] * mils_to_meter + offsety = -configuration[component]["xoffset"] * mils_to_meter + pts = [ + [j[1] + offsetx - half_comp_size * orientation, j[2] + offsety], + [j[1] + offsetx - ((half_comp_size + size_change) * orientation), j[2] + offsety], + ] + + elif rotation == 90: + offsetx = configuration[component]["xoffset"] * mils_to_meter + offsety = -configuration[component]["yoffset"] * mils_to_meter + pts = [ + [j[1] + offsetx, j[2] + offsety + half_comp_size * orientation], + [j[1] + offsetx, j[2] + offsety + ((half_comp_size + size_change) * orientation)], + ] + + else: + offsetx = configuration[component]["yoffset"] * mils_to_meter + offsety = configuration[component]["xoffset"] * mils_to_meter + pts = [ + [j[1] + offsetx + half_comp_size * orientation, j[2] + offsety], + [j[1] + offsetx + ((half_comp_size + size_change) * orientation), j[2] + offsety], + ] + + location = [j[1] + offsetx, j[2] + offsety] + name = j[4] + value = j[6] + angle_to_apply = (360 - (rotation + configuration[component]["rotation_offset"])) % 360 + if component == "res": + self.modeler.schematic.create_resistor( + value=value if value else 0, location=location, angle=angle_to_apply, name=name + ) + elif component == "cap": + self.modeler.schematic.create_capacitor( + value=value if value else 0, location=location, angle=angle_to_apply, name=name + ) + elif component in ["ind", "ind2"]: + self.modeler.schematic.create_inductor( + value=value if value else 0, location=location, angle=angle_to_apply, name=name + ) + else: + comp = self.modeler.schematic.create_component( + component_library=configuration[component]["Component Library"], + component_name=configuration[component]["Component Name"], + location=location, + angle=angle_to_apply, + name=name, + ) + if component in ["voltage_pulse", "current_pulse"]: + value = value.replace("PULSE(", "").replace(")", "").split(" ") + els = ["V1", "V2", "TD", "TR", "TF", "PW", "PER"] + for el, val in enumerate(value): + comp.set_property(els[el], val) + elif component in ["voltage", "current"]: + try: + if isinstance(value, str) and value.startswith("AC"): + comp.set_property("ACMAG", value.split(" ")[-1]) + elif isinstance(value, (int, float)): + comp.set_property("DC", value) + except: + self.logger.info("Failed to set DC Value or unnkown source type {}".format(component)) + pass + + if size_change != 0: + self.modeler.schematic.create_wire(points=pts) + + for i, j in enumerate(wire_xy): + self.modeler.schematic.create_wire([[j[0], j[1]], [j[2], j[3]]]) + return True diff --git a/src/ansys/aedt/core/circuit_netlist.py b/src/ansys/aedt/core/circuit_netlist.py new file mode 100644 index 00000000000..b5b4579ba1b --- /dev/null +++ b/src/ansys/aedt/core/circuit_netlist.py @@ -0,0 +1,197 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2021 - 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# 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. + +"""This module contains the ``CircuitNetlist`` class.""" + +from __future__ import absolute_import # noreorder + +import os +import shutil + +from ansys.aedt.core.application.analysis_circuit_netlist import AnalysisCircuitNetlist +from ansys.aedt.core.generic.filesystem import search_files +from ansys.aedt.core.generic.general_methods import pyaedt_function_handler + + +class CircuitNetlist(AnalysisCircuitNetlist, object): + """Provides the Circuit Netlist application interface. + + Parameters + ---------- + project : str, optional + Name of the project to select or the full path to the project + or AEDTZ archive to open. The default is ``None``, in which + case an attempt is made to get an active project. If no + projects are present, an empty project is created. + design : str, optional + Name of the design to select. The default is ``None``, in + which case an attempt is made to get an active design. If no + designs are present, an empty design is created. + version : str, int, float, optional + Version of AEDT to use. The default is ``None``, in which case + the active version or latest installed version is used. + This parameter is ignored when Script is launched within AEDT. + Examples of input values are ``232``, ``23.2``,``2023.2``,``"2023.2"``. + non_graphical : bool, optional + Whether to run AEDT in non-graphical mode. The default + is ``False``, in which case AEDT is launched in graphical mode. + This parameter is ignored when a script is launched within AEDT. + new_desktop : bool, optional + Whether to launch an instance of AEDT in a new thread, even if + another instance of the ``specified_version`` is active on the + machine. The default is ``False``. This parameter is ignored when + a script is launched within AEDT. + close_on_exit : bool, optional + Whether to release AEDT on exit. The default is ``False``. + student_version : bool, optional + Whether to open the AEDT student version. The default is ``False``. + This parameter is ignored when Script is launched within AEDT. + machine : str, optional + Machine name to which connect the oDesktop Session. Works only in 2022 R2 + or later. The remote server must be up and running with the command + `"ansysedt.exe -grpcsrv portnum"`. If a machine is `"localhost"`, the + server also starts if not present. + port : int, optional + Port number on which to start the oDesktop communication on an already existing server. + This parameter is ignored when creating a new server. It works only in 2022 R2 or + later. The remote server must be up and running with the command + `"ansysedt.exe -grpcsrv portnum"`. + aedt_process_id : int, optional + Process ID for the instance of AEDT to point PyAEDT at. The default is + ``None``. This parameter is only used when ``new_desktop = False``. + remove_lock : bool, optional + Whether to remove lock to project before opening it or not. + The default is ``False``, which means to not unlock + the existing project if needed and raise an exception. + + Examples + -------- + Create an instance of CircuitNetlist and connect to an existing CircuitNetlist + design or create a new HFSS design if one does not exist. + + >>> from ansys.aedt.core import CircuitNetlist + >>> aedtapp = CircuitNetlist() + + Create an instance of Circuit and link to a project named + ``"projectname"``. If this project does not exist, create one with + this name. + + >>> aedtapp = CircuitNetlist(projectname) + + Create an instance of Circuit and link to a design named + ``"designname"`` in a project named ``"projectname"``. + + >>> aedtapp = CircuitNetlist(projectname,designame) + + Create an instance of Circuit and open the specified project, + which is ``"myfie.aedt"``. + + >>> aedtapp = CircuitNetlist("myfile.aedt") + + Create an instance of Circuit using the 2023 R2 version and + open the specified project, which is ``"myfile.aedt"``. + + >>> aedtapp = CircuitNetlist(version=2023.2, project="myfile.aedt") + + Create an instance of Circuit using the 2023 R2 student version and open + the specified project, which is named ``"myfile.aedt"``. + + >>> hfss = CircuitNetlist(version="2023.2", project="myfile.aedt", student_version=True) + + """ + + @pyaedt_function_handler() + def __init__( + self, + project=None, + design=None, + version=None, + non_graphical=False, + new_desktop=False, + close_on_exit=False, + student_version=False, + machine="", + port=0, + aedt_process_id=None, + remove_lock=False, + ): + AnalysisCircuitNetlist.__init__( + self, + project, + design, + version, + non_graphical, + new_desktop, + close_on_exit, + student_version, + machine, + port, + aedt_process_id, + remove_lock, + ) + + def _init_from_design(self, *args, **kwargs): # pragma: no cover + self.__init__(*args, **kwargs) + + @pyaedt_function_handler(filepath="input_file") + def browse_log_file(self, input_file=None): # pragma: no cover + """Save the most recent log file in a new directory. + + Parameters + ---------- + input_file : str, optional + File path to save the new log file to. The default is the ``pyaedt`` folder. + + Returns + ------- + str + File Path. + """ + if input_file and not os.path.exists(os.path.normpath(input_file)): + self.logger.error("Path does not exist.") + return None + elif not input_file: + input_file = os.path.join(os.path.normpath(self.working_directory), "logfile") + if not os.path.exists(input_file): + os.mkdir(input_file) + + results_path = os.path.join(os.path.normpath(self.results_directory), self.design_name) + results_temp_path = os.path.join(results_path, "temp") + + # Check if .log exist in temp folder + if os.path.exists(results_temp_path) and search_files(results_temp_path, "*.log"): + # Check the most recent + files = search_files(results_temp_path, "*.log") + latest_file = max(files, key=os.path.getctime) + elif os.path.exists(results_path) and search_files(results_path, "*.log"): + # Check the most recent + files = search_files(results_path, "*.log") + latest_file = max(files, key=os.path.getctime) + else: + self.logger.error("Design not solved") + return None + + shutil.copy(latest_file, input_file) + filename = os.path.basename(latest_file) + return os.path.join(input_file, filename) diff --git a/src/ansys/aedt/core/desktop.py b/src/ansys/aedt/core/desktop.py index 9a0e9419947..b2be9ff0a46 100644 --- a/src/ansys/aedt/core/desktop.py +++ b/src/ansys/aedt/core/desktop.py @@ -94,6 +94,8 @@ def launch_desktop_on_port(): command.append("-ng") if settings.wait_for_license: command.append("-waitforlicense") + if settings.aedt_log_file: + command.extend(["-Logfile", settings.aedt_log_file]) my_env = os.environ.copy() for env, val in settings.aedt_environment_variables.items(): my_env[env] = val @@ -174,6 +176,8 @@ def launch_aedt_in_lsf(non_graphical, port): # pragma: no cover command.append("-ng") if settings.wait_for_license: command.append("-waitforlicense") + if settings.aedt_log_file: + command.extend(["-Logfile", settings.aedt_log_file]) else: # pragma: no cover command = settings.custom_lsf_command.split(" ") command.append("-grpcsrv") @@ -794,9 +798,9 @@ def active_design(self, project_object=None, name=None, design_type=None): active_design = project_object.GetActiveDesign() else: active_design = project_object.SetActiveDesign(name) - if is_linux and settings.aedt_version == "2024.1" and design_type == "Circuit Design": + if is_linux and settings.aedt_version == "2024.1" and design_type == "Circuit Design": # pragma: no cover time.sleep(1) - self.odesktop.CloseAllWindows() + self.close_windows() return active_design @pyaedt_function_handler() @@ -819,9 +823,9 @@ def active_project(self, name=None): active_project = self.odesktop.GetActiveProject() else: active_project = self.odesktop.SetActiveProject(name) - if is_linux and settings.aedt_version == "2024.1": + if is_linux and settings.aedt_version == "2024.1": # pragma: no cover time.sleep(1) - self.odesktop.CloseAllWindows() + self.close_windows() return active_project @property @@ -833,6 +837,23 @@ def install_path(self): except Exception: # pragma: no cover return installed_versions()[version_key + "CL"] + @pyaedt_function_handler() + def close_windows(self): + """Close all windows. + + Returns + ------- + bool + ``True`` when successful, ``False`` when failed. + + References + ---------- + + >>> oDesktop.CloseAllWindows + """ + self.odesktop.CloseAllWindows() + return True + @property def current_version(self): """Current AEDT version.""" diff --git a/src/ansys/aedt/core/generic/design_types.py b/src/ansys/aedt/core/generic/design_types.py index f68bed8e0ca..cc3b4453225 100644 --- a/src/ansys/aedt/core/generic/design_types.py +++ b/src/ansys/aedt/core/generic/design_types.py @@ -27,6 +27,7 @@ import time from ansys.aedt.core.circuit import Circuit +from ansys.aedt.core.circuit_netlist import CircuitNetlist from ansys.aedt.core.desktop import Desktop Emit = None @@ -173,6 +174,7 @@ def launch_desktop( "Maxwell Circuit": MaxwellCircuit, "Twin Builder": TwinBuilder, "Circuit Design": Circuit, + "Circuit Netlist": CircuitNetlist, "2D Extractor": Q2d, "Q3D Extractor": Q3d, "HFSS": Hfss, @@ -227,7 +229,7 @@ def get_pyaedt_app(project_name=None, design_name=None, desktop=None): oProject = odesktop.GetActiveProject() else: oProject = odesktop.SetActiveProject(project_name) - if is_linux and settings.aedt_version == "2024.1": + if is_linux and settings.aedt_version == "2024.1": # pragma: no cover time.sleep(1) odesktop.CloseAllWindows() if not oProject: @@ -243,7 +245,7 @@ def get_pyaedt_app(project_name=None, design_name=None, desktop=None): oDesign = oProject.GetActiveDesign() else: oDesign = oProject.SetActiveDesign(design_name) - if is_linux and settings.aedt_version == "2024.1": + if is_linux and settings.aedt_version == "2024.1": # pragma: no cover time.sleep(1) odesktop.CloseAllWindows() if not oDesign: diff --git a/src/ansys/aedt/core/generic/general_methods.py b/src/ansys/aedt/core/generic/general_methods.py index 0d9d96bc973..eabcc54dcd8 100644 --- a/src/ansys/aedt/core/generic/general_methods.py +++ b/src/ansys/aedt/core/generic/general_methods.py @@ -47,6 +47,7 @@ from ansys.aedt.core.aedt_logger import pyaedt_logger from ansys.aedt.core.generic.constants import CSS4_COLORS +from ansys.aedt.core.generic.settings import inner_project_settings # noqa: F401 from ansys.aedt.core.generic.settings import settings from ansys.aedt.core.misc.misc import installed_versions @@ -510,7 +511,7 @@ def read_json(fn): @pyaedt_function_handler() -def read_toml(file_path): +def read_toml(file_path): # pragma: no cover """Read a TOML file and return as a dictionary. Parameters @@ -523,7 +524,11 @@ def read_toml(file_path): dict Parsed TOML file as a dictionary. """ - import pytomlpp as tomllib + current_version = sys.version_info[:2] + if current_version < (3, 12): + import pytomlpp as tomllib + else: + import tomllib with open_file(file_path, "rb") as fb: return tomllib.load(fb) @@ -1319,7 +1324,11 @@ def is_digit(c): @pyaedt_function_handler() def _create_toml_file(input_dict, full_toml_path): - import pytomlpp as tomllib + current_version = sys.version_info[:2] + if current_version < (3, 12): + import pytomlpp as tomllib + else: + import tomllib if not os.path.exists(os.path.dirname(full_toml_path)): os.makedirs(os.path.dirname(full_toml_path)) diff --git a/src/ansys/aedt/core/generic/settings.py b/src/ansys/aedt/core/generic/settings.py index fbedec918fd..2dddf658537 100644 --- a/src/ansys/aedt/core/generic/settings.py +++ b/src/ansys/aedt/core/generic/settings.py @@ -22,12 +22,98 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +"""This module contains the ``Settings`` and ``_InnerProjectSettings`` classes. + +The first class encapsulates the settings associated with PyAEDT and AEDT including logging, +LSF, environment variables and general settings. Most of the default values used can be modified +using a YAML configuration file. An example of such file can be found in the documentation, see +`Settings YAML file `_. +The path to the configuration file should be specified with the environment variable +``PYAEDT_LOCAL_SETTINGS_PATH``. If no environment variable is set, the class will look for the +configuration file ``pyaedt_settings.yaml`` in the user's ``APPDATA`` folder for Windows and +``HOME`` folder for Linux. + +The second class is intended for internal use only and shouldn't be modified by users. +""" + +import logging import os import time +from typing import Any +from typing import List +from typing import Optional +from typing import Union import uuid is_linux = os.name == "posix" +# Settings allowed to be updated using a YAML configuration file. +ALLOWED_LOG_SETTINGS = [ + "enable_debug_edb_logger", + "enable_debug_geometry_operator_logger", + "enable_debug_grpc_api_logger", + "enable_debug_internal_methods_logger", + "enable_debug_logger", + "enable_debug_methods_argument_logger", + "enable_desktop_logs", + "enable_file_logs", + "enable_global_log_file", + "enable_local_log_file", + "enable_logger", + "enable_screen_logs", + "global_log_file_name", + "global_log_file_size", + "logger_datefmt", + "logger_file_path", + "logger_formatter", + "aedt_log_file", +] +ALLOWED_LSF_SETTINGS = [ + "custom_lsf_command", + "lsf_aedt_command", + "lsf_num_cores", + "lsf_osrel", + "lsf_queue", + "lsf_ram", + "lsf_timeout", + "lsf_ui", + "use_lsf_scheduler", +] +ALLOWED_GENERAL_SETTINGS = [ + "lazy_load", + "objects_lazy_load", + "aedt_install_dir", + "aedt_version", + "desktop_launch_timeout", + "disable_bounding_box_sat", + "edb_dll_path", + "enable_error_handler", + "enable_pandas_output", + "force_error_on_missing_project", + "number_of_grpc_api_retries", + "release_on_exception", + "retry_n_times_time_interval", + "use_grpc_api", + "use_multi_desktop", + "wait_for_license", + "remote_api", + "remote_rpc_service_manager_port", + "pyaedt_server_path", + "remote_rpc_session_temp_folder", +] +ALLOWED_AEDT_ENV_VAR_SETTINGS = [ + "ANSYSEM_FEATURE_F335896_MECHANICAL_STRUCTURAL_SOLN_TYPE_ENABLE", + "ANSYSEM_FEATURE_F395486_RIGID_FLEX_BENDING_ENABLE", + "ANSYSEM_FEATURE_F538630_MECH_TRANSIENT_THERMAL_ENABLE", + "ANSYSEM_FEATURE_F545177_ECAD_INTEGRATION_WITH_APHI_ENABLE", + "ANSYSEM_FEATURE_F650636_MECH_LAYOUT_COMPONENT_ENABLE", + "ANSYSEM_FEATURE_S432616_LAYOUT_COMPONENT_IN_3D_ENABLE", + "ANSYSEM_FEATURE_SF159726_SCRIPTOBJECT_ENABLE", + "ANSYSEM_FEATURE_SF222134_CABLE_MODELING_ENHANCEMENTS_ENABLE", + "ANSYSEM_FEATURE_SF6694_NON_GRAPHICAL_COMMAND_EXECUTION_ENABLE", + "ANS_MESHER_PROC_DUMP_PREPOST_BEND_SM3", +] + def generate_log_filename(): """Generate a log filename.""" @@ -37,58 +123,54 @@ def generate_log_filename(): return "{}_{}_{}.log".format(base, username, unique_id) +class _InnerProjectSettings: # pragma: no cover + """Global inner project settings. + + This class is intended for internal use only. + """ + + properties: dict = {} + time_stamp: Union[int, float] = 0 + + class Settings(object): # pragma: no cover """Manages all PyAEDT environment variables and global settings.""" def __init__(self): - self._logger = None - self._enable_logger = True - self._enable_desktop_logs = True - self._enable_screen_logs = True - self._enable_file_logs = True - self.pyaedt_server_path = "" - self._logger_file_path = None - self._logger_formatter = "%(asctime)s:%(destination)s:%(extra)s%(levelname)-8s:%(message)s" - self._logger_datefmt = "%Y/%m/%d %H.%M.%S" - self._enable_debug_edb_logger = False - self._enable_debug_grpc_api_logger = False - self._enable_debug_methods_argument_logger = False - self._enable_debug_geometry_operator_logger = False - self._enable_debug_internal_methods_logger = False - self._enable_debug_logger = False - self._enable_error_handler = True - self._release_on_exception = True - self._aedt_version = None - self._aedt_install_dir = None - self._use_multi_desktop = False - self.remote_api = False - self._use_grpc_api = None - self.formatter = None - self.remote_rpc_session = None - self.remote_rpc_session_temp_folder = "" - self.remote_rpc_service_manager_port = 17878 - self._project_properties = {} - self._project_time_stamp = 0 - self._disable_bounding_box_sat = False - self._force_error_on_missing_project = False - self._enable_pandas_output = False - self.time_tick = time.time() - self._global_log_file_name = generate_log_filename() - self._enable_global_log_file = True - self._enable_local_log_file = False - self._global_log_file_size = 10 - self._edb_dll_path = None - self._lsf_num_cores = 2 - self._lsf_ram = 1000 - self._use_lsf_scheduler = False - self._lsf_osrel = None - self._lsf_ui = None - self._lsf_aedt_command = "ansysedt" - self._lsf_timeout = 3600 - self._lsf_queue = None - self._custom_lsf_command = None - self._aedt_environment_variables = { - "ANS_MESHER_PROC_DUMP_PREPOST_BEND_SM3": "1", + # Setup default values then load values from PersoalLib' settings_config.yaml if it exists. + # Settings related to logging + self.__logger: Optional[logging.Logger] = None + self.__enable_logger: bool = True + self.__enable_desktop_logs: bool = True + self.__enable_screen_logs: bool = True + self.__enable_file_logs: bool = True + self.__logger_file_path: Optional[str] = None + self.__logger_formatter: str = "%(asctime)s:%(destination)s:%(extra)s%(levelname)-8s:%(message)s" + self.__logger_datefmt: str = "%Y/%m/%d %H.%M.%S" + self.__enable_debug_edb_logger: bool = False + self.__enable_debug_grpc_api_logger: bool = False + self.__enable_debug_methods_argument_logger: bool = False + self.__enable_debug_geometry_operator_logger: bool = False + self.__enable_debug_internal_methods_logger: bool = False + self.__enable_debug_logger: bool = False + self.__global_log_file_name: str = generate_log_filename() + self.__enable_global_log_file: bool = True + self.__enable_local_log_file: bool = False + self.__global_log_file_size: int = 10 + self.__aedt_log_file: Optional[str] = None + # Settings related to Linux systems running LSF scheduler + self.__lsf_num_cores: int = 2 + self.__lsf_ram: int = 1000 + self.__use_lsf_scheduler: bool = False + self.__lsf_osrel: Optional[str] = None + self.__lsf_ui: Optional[int] = None + self.__lsf_aedt_command: str = "ansysedt" + self.__lsf_timeout: int = 3600 + self.__lsf_queue: Optional[str] = None + self.__custom_lsf_command: Optional[str] = None + # Settings related to environment variables that are set before launching a new AEDT session + # This includes those that enable the beta features ! + self.__aedt_environment_variables: dict[str, str] = { "ANSYSEM_FEATURE_SF6694_NON_GRAPHICAL_COMMAND_EXECUTION_ENABLE": "1", "ANSYSEM_FEATURE_SF159726_SCRIPTOBJECT_ENABLE": "1", "ANSYSEM_FEATURE_SF222134_CABLE_MODELING_ENHANCEMENTS_ENABLE": "1", @@ -98,220 +180,470 @@ def __init__(self): "ANSYSEM_FEATURE_F650636_MECH_LAYOUT_COMPONENT_ENABLE": "1", "ANSYSEM_FEATURE_F538630_MECH_TRANSIENT_THERMAL_ENABLE": "1", "ANSYSEM_FEATURE_F335896_MECHANICAL_STRUCTURAL_SOLN_TYPE_ENABLE": "1", + "ANS_MESHER_PROC_DUMP_PREPOST_BEND_SM3": "1", } if is_linux: - self._aedt_environment_variables["ANS_NODEPCHECK"] = "1" - self._desktop_launch_timeout = 120 - self._number_of_grpc_api_retries = 6 - self._retry_n_times_time_interval = 0.1 - self._wait_for_license = False - self.__lazy_load = True - self.__objects_lazy_load = True + self.__aedt_environment_variables["ANS_NODEPCHECK"] = "1" + # General settings + self.__enable_error_handler: bool = True + self.__release_on_exception: bool = True + self.__aedt_version: Optional[str] = None + self.__aedt_install_dir: Optional[str] = None + self.__use_multi_desktop: bool = False + self.__use_grpc_api: Optional[bool] = None + self.__disable_bounding_box_sat = False + self.__force_error_on_missing_project = False + self.__enable_pandas_output = False + self.__edb_dll_path: Optional[str] = None + self.__desktop_launch_timeout: int = 120 + self.__number_of_grpc_api_retries: int = 6 + self.__retry_n_times_time_interval: float = 0.1 + self.__wait_for_license: bool = False + self.__lazy_load: bool = True + self.__objects_lazy_load: bool = True + # Previously 'public' attributes + self.__formatter: Optional[logging.Formatter] = None + self.__remote_rpc_session: Any = None + self.__remote_rpc_session_temp_folder: str = "" + self.__remote_rpc_service_manager_port: int = 17878 + self.__remote_api: bool = False + self.__time_tick = time.time() + self.__pyaedt_server_path = "" + + # Load local settings if YAML configuration file exists. + pyaedt_settings_path = os.environ.get("PYAEDT_LOCAL_SETTINGS_PATH", "") + if not pyaedt_settings_path: + if os.name == "posix": + pyaedt_settings_path = os.path.join(os.environ["HOME"], "pyaedt_settings.yaml") + else: + pyaedt_settings_path = os.path.join(os.environ["APPDATA"], "pyaedt_settings.yaml") + self.load_yaml_configuration(pyaedt_settings_path) + + # ########################## Logging properties ########################## @property - def release_on_exception(self): - """ + def logger(self): + """Active logger.""" + return self.__logger - Returns - ------- + @logger.setter + def logger(self, val): + self.__logger = val - """ - return self._release_on_exception + @property + def enable_desktop_logs(self): + """Enable or disable the logging to the AEDT message window.""" + return self.__enable_desktop_logs - @release_on_exception.setter - def release_on_exception(self, value): - self._release_on_exception = value + @enable_desktop_logs.setter + def enable_desktop_logs(self, val): + self.__enable_desktop_logs = val @property - def objects_lazy_load(self): - """Flag for enabling and disabling the lazy load. - The default is ``True``. + def global_log_file_size(self): + """Global PyAEDT log file size in MB. The default value is ``10``.""" + return self.__global_log_file_size - Returns - ------- - bool - """ - return self.__objects_lazy_load + @global_log_file_size.setter + def global_log_file_size(self, value): + self.__global_log_file_size = value - @objects_lazy_load.setter - def objects_lazy_load(self, value): - self.__objects_lazy_load = value + @property + def enable_global_log_file(self): + """Enable or disable the global PyAEDT log file located in the global temp folder. + The default is ``True``.""" + return self.__enable_global_log_file + + @enable_global_log_file.setter + def enable_global_log_file(self, value): + self.__enable_global_log_file = value @property - def lazy_load(self): - """Flag for enabling and disabling the lazy load. - The default is ``True``. + def enable_local_log_file(self): + """Enable or disable the local PyAEDT log file located in the ``projectname.pyaedt`` project folder. + The default is ``True``.""" + return self.__enable_local_log_file - Returns - ------- - bool - """ - return self.__lazy_load + @enable_local_log_file.setter + def enable_local_log_file(self, value): + self.__enable_local_log_file = value - @lazy_load.setter - def lazy_load(self, value): - self.__lazy_load = value + @property + def global_log_file_name(self): + """Global PyAEDT log file path. The default is ``pyaedt_username.log``.""" + return self.__global_log_file_name + + @global_log_file_name.setter + def global_log_file_name(self, value): + self.__global_log_file_name = value @property - def wait_for_license(self): - """Whether if Electronics Desktop has to be launched with ``-waitforlicense`` flag enabled or not. - Default is ``False``. + def enable_debug_methods_argument_logger(self): + """Flag for whether to write out the method's arguments in the debug logger. + The default is ``False``.""" + return self.__enable_debug_methods_argument_logger - Returns - ------- - bool - """ - return self._wait_for_license + @enable_debug_methods_argument_logger.setter + def enable_debug_methods_argument_logger(self, val): + self.__enable_debug_methods_argument_logger = val - @wait_for_license.setter - def wait_for_license(self, value): - self._wait_for_license = value + @property + def enable_screen_logs(self): + """Enable or disable the logging to STDOUT.""" + return self.__enable_screen_logs + + @enable_screen_logs.setter + def enable_screen_logs(self, val): + self.__enable_screen_logs = val @property - def retry_n_times_time_interval(self): - """Time interval between the retries by the ``_retry_n_times`` method.""" - return self._retry_n_times_time_interval + def enable_file_logs(self): + """Enable or disable the logging to a file.""" + return self.__enable_file_logs - @retry_n_times_time_interval.setter - def retry_n_times_time_interval(self, value): - self._retry_n_times_time_interval = float(value) + @enable_file_logs.setter + def enable_file_logs(self, val): + self.__enable_file_logs = val @property - def number_of_grpc_api_retries(self): - """Number of gRPC API retries. The default is ``3``.""" - return self._number_of_grpc_api_retries + def enable_logger(self): + """Enable or disable the logging overall.""" + return self.__enable_logger - @number_of_grpc_api_retries.setter - def number_of_grpc_api_retries(self, value): - self._number_of_grpc_api_retries = int(value) + @enable_logger.setter + def enable_logger(self, val): + self.__enable_logger = val @property - def desktop_launch_timeout(self): - """Timeout in seconds for trying to launch AEDT. The default is ``90`` seconds.""" - return self._desktop_launch_timeout + def logger_file_path(self): + """PyAEDT log file path.""" + return self.__logger_file_path - @desktop_launch_timeout.setter - def desktop_launch_timeout(self, value): - self._desktop_launch_timeout = int(value) + @logger_file_path.setter + def logger_file_path(self, val): + self.__logger_file_path = val @property - def aedt_environment_variables(self): - """Environment variables that are set before launching a new AEDT session, - including those that enable the beta features.""" - return self._aedt_environment_variables + def logger_formatter(self): + """Message format of the log entries. + The default is ``'%(asctime)s:%(destination)s:%(extra)s%(levelname)-8s:%(message)s'``""" + return self.__logger_formatter - @aedt_environment_variables.setter - def aedt_environment_variables(self, value): - self._aedt_environment_variables = value + @logger_formatter.setter + def logger_formatter(self, val): + self.__logger_formatter = val + + @property + def logger_datefmt(self): + """Date format of the log entries. + The default is ``'%Y/%m/%d %H.%M.%S'``""" + return self.__logger_datefmt + + @logger_datefmt.setter + def logger_datefmt(self, val): + self.__logger_datefmt = val + + @property + def enable_debug_edb_logger(self): + """Enable or disable the logger for any EDB API methods.""" + return self.__enable_debug_edb_logger + + @enable_debug_edb_logger.setter + def enable_debug_edb_logger(self, val): + self.__enable_debug_edb_logger = val + + @property + def enable_debug_grpc_api_logger(self): + """Enable or disable the logging for the gRPC API calls.""" + return self.__enable_debug_grpc_api_logger + + @enable_debug_grpc_api_logger.setter + def enable_debug_grpc_api_logger(self, val): + self.__enable_debug_grpc_api_logger = val + + @property + def enable_debug_geometry_operator_logger(self): + """Enable or disable the logging for the geometry operators. + This setting is useful for debug purposes.""" + return self.__enable_debug_geometry_operator_logger + + @enable_debug_geometry_operator_logger.setter + def enable_debug_geometry_operator_logger(self, val): + self.__enable_debug_geometry_operator_logger = val + + @property + def enable_debug_internal_methods_logger(self): + """Enable or disable the logging for internal methods. + This setting is useful for debug purposes.""" + return self.__enable_debug_internal_methods_logger + + @enable_debug_internal_methods_logger.setter + def enable_debug_internal_methods_logger(self, val): + self.__enable_debug_internal_methods_logger = val + + @property + def enable_debug_logger(self): + """Enable or disable the debug level logger.""" + return self.__enable_debug_logger + + @enable_debug_logger.setter + def enable_debug_logger(self, val): + self.__enable_debug_logger = val + + @property + def aedt_log_file(self): + """Path to the AEDT log file. + + Used to specify that Electronics Desktop has to be launched with ``-Logfile`` option. + """ + return self.__aedt_log_file + + @aedt_log_file.setter + def aedt_log_file(self, value: str): + self.__aedt_log_file = value + + # ############################# LSF properties ############################ @property def lsf_queue(self): """LSF queue name. This attribute is valid only on Linux systems running LSF Scheduler.""" - return self._lsf_queue + return self.__lsf_queue @lsf_queue.setter def lsf_queue(self, value): - self._lsf_queue = value + self.__lsf_queue = value @property def use_lsf_scheduler(self): """Whether to use LSF Scheduler. This attribute is valid only on Linux systems running LSF Scheduler.""" - return self._use_lsf_scheduler + return self.__use_lsf_scheduler @use_lsf_scheduler.setter def use_lsf_scheduler(self, value): - self._use_lsf_scheduler = value + self.__use_lsf_scheduler = value @property def lsf_aedt_command(self): """Command to launch the task in the LSF Scheduler. The default is ``"ansysedt"``. This attribute is valid only on Linux systems running LSF Scheduler.""" - return self._lsf_aedt_command + return self.__lsf_aedt_command @lsf_aedt_command.setter def lsf_aedt_command(self, value): - self._lsf_aedt_command = value + self.__lsf_aedt_command = value @property def lsf_num_cores(self): """Number of LSF cores. This attribute is valid only on Linux systems running LSF Scheduler.""" - return self._lsf_num_cores + return self.__lsf_num_cores @lsf_num_cores.setter def lsf_num_cores(self, value): - self._lsf_num_cores = int(value) + self.__lsf_num_cores = int(value) @property def lsf_ram(self): """RAM allocated for the LSF job. This attribute is valid only on Linux systems running LSF Scheduler.""" - return self._lsf_ram + return self.__lsf_ram @lsf_ram.setter def lsf_ram(self, value): - self._lsf_ram = int(value) + self.__lsf_ram = int(value) @property def lsf_ui(self): """Value passed in the LSF 'select' string to the ui resource.""" - return self._lsf_ui + return self.__lsf_ui @lsf_ui.setter def lsf_ui(self, value): - self._lsf_ui = int(value) + self.__lsf_ui = int(value) @property def lsf_timeout(self): """Timeout in seconds for trying to start the interactive session. The default is ``3600`` seconds.""" - return self._lsf_timeout + return self.__lsf_timeout @lsf_timeout.setter def lsf_timeout(self, value): - self._lsf_timeout = int(value) + self.__lsf_timeout = int(value) @property def lsf_osrel(self): """Operating system string. This attribute is valid only on Linux systems running LSF Scheduler.""" - return self._lsf_osrel + return self.__lsf_osrel @lsf_osrel.setter def lsf_osrel(self, value): - self._lsf_osrel = value + self.__lsf_osrel = value @property def custom_lsf_command(self): """Command to launch in the LSF Scheduler. The default is ``None``. This attribute is valid only on Linux systems running LSF Scheduler.""" - return self._custom_lsf_command + return self.__custom_lsf_command @custom_lsf_command.setter def custom_lsf_command(self, value): - self._custom_lsf_command = value + self.__custom_lsf_command = value + + # ############################## Environment variable properties ############################## + + @property + def aedt_environment_variables(self): + """Environment variables that are set before launching a new AEDT session, + including those that enable the beta features.""" + return self.__aedt_environment_variables + + @aedt_environment_variables.setter + def aedt_environment_variables(self, value): + self._aedt_environment_variables = value + + # ##################################### General properties #################################### + + @property + def remote_api(self): + """State whether remote API is used or not.""" + return self.__remote_api + + @remote_api.setter + def remote_api(self, value: bool): + self.__remote_api = value + + @property + def formatter(self): + """Get the formatter.""" + return self.__formatter + + @formatter.setter + def formatter(self, value: logging.Formatter): + self.__formatter = value + + @property + def remote_rpc_session(self): + """Get the RPyC connection.""" + return self.__remote_rpc_session + + @remote_rpc_session.setter + def remote_rpc_session(self, value: Any): + self.__remote_rpc_session = value + + @property + def remote_rpc_session_temp_folder(self): + """Get the remote RPyC session temp folder.""" + return self.__remote_rpc_session_temp_folder + + @remote_rpc_session_temp_folder.setter + def remote_rpc_session_temp_folder(self, value: str): + self.__remote_rpc_session_temp_folder = value + + @property + def remote_rpc_service_manager_port(self): + """Get the remote RPyC service manager port.""" + return self.__remote_rpc_service_manager_port + + @remote_rpc_service_manager_port.setter + def remote_rpc_service_manager_port(self, value: int): + self.__remote_rpc_service_manager_port = value + + @property + def time_tick(self): + """Time in seconds since the 'epoch' as a floating-point number.""" + return self.__time_tick + + @time_tick.setter + def time_tick(self, value: float): + self.__time_tick = value + + @property + def release_on_exception(self): + """Enable or disable the release of AEDT on exception.""" + return self.__release_on_exception + + @release_on_exception.setter + def release_on_exception(self, value): + self.__release_on_exception = value + + @property + def objects_lazy_load(self): + """Flag for enabling and disabling the lazy load. The default value is ``True``.""" + return self.__objects_lazy_load + + @objects_lazy_load.setter + def objects_lazy_load(self, value): + self.__objects_lazy_load = value + + @property + def lazy_load(self): + """Flag for enabling and disabling the lazy load. The default value is ``True``.""" + return self.__lazy_load + + @lazy_load.setter + def lazy_load(self, value): + self.__lazy_load = value + + @property + def wait_for_license(self): + """Enable or disable the use of the flag `-waitforlicense` when launching Electronic Desktop. + The default value is ``False``.""" + return self.__wait_for_license + + @wait_for_license.setter + def wait_for_license(self, value): + self.__wait_for_license = value + + @property + def retry_n_times_time_interval(self): + """Time interval between the retries by the ``_retry_n_times`` method.""" + return self.__retry_n_times_time_interval + + @retry_n_times_time_interval.setter + def retry_n_times_time_interval(self, value): + self.__retry_n_times_time_interval = float(value) + + @property + def number_of_grpc_api_retries(self): + """Number of gRPC API retries. The default is ``3``.""" + return self.__number_of_grpc_api_retries + + @number_of_grpc_api_retries.setter + def number_of_grpc_api_retries(self, value): + self.__number_of_grpc_api_retries = int(value) + + @property + def desktop_launch_timeout(self): + """Timeout in seconds for trying to launch AEDT. The default is ``120`` seconds.""" + return self.__desktop_launch_timeout + + @desktop_launch_timeout.setter + def desktop_launch_timeout(self, value): + self.__desktop_launch_timeout = int(value) @property def aedt_version(self): """AEDT version in the form ``"2023.x"``. In AEDT 2022 R2 and later, evaluating a bounding box by exporting a SAT file is disabled.""" - return self._aedt_version + return self.__aedt_version @aedt_version.setter def aedt_version(self, value): - self._aedt_version = value - if self._aedt_version >= "2023.1": + self.__aedt_version = value + if self.__aedt_version >= "2023.1": self.disable_bounding_box_sat = True @property def aedt_install_dir(self): """AEDT installation path.""" - return self._aedt_install_dir + return self.__aedt_install_dir @aedt_install_dir.setter def aedt_install_dir(self, value): - self._aedt_install_dir = value + self.__aedt_install_dir = value @property def use_multi_desktop(self): @@ -323,248 +655,123 @@ def use_multi_desktop(self): Enabling multiple desktop sessions is a beta feature.""" - return self._use_multi_desktop + return self.__use_multi_desktop @use_multi_desktop.setter def use_multi_desktop(self, value): - self._use_multi_desktop = value + self.__use_multi_desktop = value @property def edb_dll_path(self): """Optional path for the EDB DLL file.""" - return self._edb_dll_path + return self.__edb_dll_path @edb_dll_path.setter def edb_dll_path(self, value): if os.path.exists(value): - self._edb_dll_path = value - - @property - def global_log_file_size(self): - """Global PyAEDT log file size in MB. The default value is ``10``.""" - return self._global_log_file_size - - @global_log_file_size.setter - def global_log_file_size(self, value): - self._global_log_file_size = value - - @property - def enable_global_log_file(self): - """Flag for enabling and disabling the global PyAEDT log file located in the global temp folder. - The default is ``True``.""" - return self._enable_global_log_file - - @enable_global_log_file.setter - def enable_global_log_file(self, value): - self._enable_global_log_file = value - - @property - def enable_local_log_file(self): - """Flag for enabling and disabling the local PyAEDT log file located - in the ``projectname.pyaedt`` project folder. The default is ``True``.""" - return self._enable_local_log_file - - @enable_local_log_file.setter - def enable_local_log_file(self, value): - self._enable_local_log_file = value - - @property - def global_log_file_name(self): - """Global PyAEDT log file path. The default is ``pyaedt_username.log``.""" - return self._global_log_file_name - - @global_log_file_name.setter - def global_log_file_name(self, value): - self._global_log_file_name = value + self.__edb_dll_path = value @property def enable_pandas_output(self): """Flag for whether Pandas is being used to export dictionaries and lists. This attribute applies to Solution data output. The default is ``False``. If ``True``, the property or method returns a Pandas object. This property is valid only in the CPython environment.""" - return self._enable_pandas_output + return self.__enable_pandas_output @enable_pandas_output.setter def enable_pandas_output(self, val): - self._enable_pandas_output = val - - @property - def enable_debug_methods_argument_logger(self): - """Flag for whether to write out the method's arguments in the debug logger. - The default is ``False``.""" - return self._enable_debug_methods_argument_logger - - @enable_debug_methods_argument_logger.setter - def enable_debug_methods_argument_logger(self, val): - self._enable_debug_methods_argument_logger = val + self.__enable_pandas_output = val @property def force_error_on_missing_project(self): """Flag for whether to check the project path. The default is ``False``. If ``True``, when passing a project path, the project has to exist. Otherwise, an error is raised.""" - return self._force_error_on_missing_project + return self.__force_error_on_missing_project @force_error_on_missing_project.setter def force_error_on_missing_project(self, val): - self._force_error_on_missing_project = val + self.__force_error_on_missing_project = val @property def disable_bounding_box_sat(self): """Flag for enabling and disabling bounding box evaluation by exporting a SAT file.""" - return self._disable_bounding_box_sat + return self.__disable_bounding_box_sat @disable_bounding_box_sat.setter def disable_bounding_box_sat(self, val): - self._disable_bounding_box_sat = val + self.__disable_bounding_box_sat = val @property def use_grpc_api(self): """Flag for whether to use the gRPC API or legacy COM object.""" - return self._use_grpc_api + return self.__use_grpc_api @use_grpc_api.setter def use_grpc_api(self, val): - self._use_grpc_api = val - - @property - def logger(self): - """Active logger.""" - return self._logger - - @logger.setter - def logger(self, val): - self._logger = val + self.__use_grpc_api = val @property def enable_error_handler(self): """Flag for enabling and disabling the internal PyAEDT error handling.""" - return self._enable_error_handler + return self.__enable_error_handler @enable_error_handler.setter def enable_error_handler(self, val): - self._enable_error_handler = val - - @property - def enable_desktop_logs(self): - """Flag for enabling and disabling the logging to the AEDT message window.""" - return self._enable_desktop_logs - - @enable_desktop_logs.setter - def enable_desktop_logs(self, val): - self._enable_desktop_logs = val - - @property - def enable_screen_logs(self): - """Flag for enabling and disabling the logging to STDOUT.""" - return self._enable_screen_logs - - @enable_screen_logs.setter - def enable_screen_logs(self, val): - self._enable_screen_logs = val + self.__enable_error_handler = val @property def pyaedt_server_path(self): - """``PYAEDT_SERVER_AEDT_PATH`` environment variable.""" - return os.getenv("PYAEDT_SERVER_AEDT_PATH", "") + """Get ``PYAEDT_SERVER_AEDT_PATH`` environment variable.""" + self.__pyaedt_server_path = os.getenv("PYAEDT_SERVER_AEDT_PATH", "") + return self.__pyaedt_server_path + # NOTE: Convenient way to set the environment variable for RPyC @pyaedt_server_path.setter def pyaedt_server_path(self, val): os.environ["PYAEDT_SERVER_AEDT_PATH"] = str(val) - - @property - def enable_file_logs(self): - """Flag for enabling and disabling the logging to a file.""" - return self._enable_file_logs - - @enable_file_logs.setter - def enable_file_logs(self, val): - self._enable_file_logs = val - - @property - def enable_logger(self): - """Flag for enabling and disabling the logging overall.""" - return self._enable_logger - - @enable_logger.setter - def enable_logger(self, val): - self._enable_logger = val - - @property - def logger_file_path(self): - """PyAEDT log file path.""" - return self._logger_file_path - - @logger_file_path.setter - def logger_file_path(self, val): - self._logger_file_path = val - - @property - def logger_formatter(self): - """Message format of the log entries. - The default is ``'%(asctime)s:%(destination)s:%(extra)s%(levelname)-8s:%(message)s'``""" - return self._logger_formatter - - @logger_formatter.setter - def logger_formatter(self, val): - self._logger_formatter = val - - @property - def logger_datefmt(self): - """Date format of the log entries. - The default is ``'%Y/%m/%d %H.%M.%S'``""" - return self._logger_datefmt - - @logger_datefmt.setter - def logger_datefmt(self, val): - self._logger_datefmt = val - - @property - def enable_debug_edb_logger(self): - """Flag for enabling and disabling the logger for any EDB API methods.""" - return self._enable_debug_edb_logger - - @enable_debug_edb_logger.setter - def enable_debug_edb_logger(self, val): - self._enable_debug_edb_logger = val - - @property - def enable_debug_grpc_api_logger(self): - """Flag for enabling and disabling the logging for the gRPC API calls.""" - return self._enable_debug_grpc_api_logger - - @enable_debug_grpc_api_logger.setter - def enable_debug_grpc_api_logger(self, val): - self._enable_debug_grpc_api_logger = val - - @property - def enable_debug_geometry_operator_logger(self): - """Flag for enabling and disabling the logging for the geometry operators. - This setting is useful for debug purposes.""" - return self._enable_debug_geometry_operator_logger - - @enable_debug_geometry_operator_logger.setter - def enable_debug_geometry_operator_logger(self, val): - self._enable_debug_geometry_operator_logger = val - - @property - def enable_debug_internal_methods_logger(self): - """Flag for enabling and disabling the logging for internal methods. - This setting is useful for debug purposes.""" - return self._enable_debug_internal_methods_logger - - @enable_debug_internal_methods_logger.setter - def enable_debug_internal_methods_logger(self, val): - self._enable_debug_internal_methods_logger = val - - @property - def enable_debug_logger(self): - """Flag for enabling and disabling the debug level logger.""" - return self._enable_debug_logger - - @enable_debug_logger.setter - def enable_debug_logger(self, val): - self._enable_debug_logger = val + self.__pyaedt_server_path = os.environ["PYAEDT_SERVER_AEDT_PATH"] + + def load_yaml_configuration(self, path: str, raise_on_wrong_key: bool = False): + """Update default settings from a YAML configuration file.""" + import yaml + + def filter_settings(settings: dict, allowed_keys: List[str]): + """Filter the items of settings based on a list of allowed keys.""" + return filter(lambda item: item[0] in allowed_keys, settings.items()) + + def filter_settings_with_raise(settings: dict, allowed_keys: List[str]): + """Filter the items of settings based on a list of allowed keys.""" + for key, value in settings.items(): + if key not in allowed_keys: + raise KeyError(f"Key '{key}' is not part of the allowed keys {allowed_keys}") + yield key, value + + if os.path.exists(path): + with open(path, "r") as yaml_file: + local_settings = yaml.safe_load(yaml_file) + pairs = [ + ("log", ALLOWED_LOG_SETTINGS), + ("lsf", ALLOWED_LSF_SETTINGS), + ("aedt_env_var", ALLOWED_AEDT_ENV_VAR_SETTINGS), + ("general", ALLOWED_GENERAL_SETTINGS), + ] + for setting_type, allowed_settings_key in pairs: + settings = local_settings.get(setting_type, {}) + if raise_on_wrong_key: + for key, value in filter_settings_with_raise(settings, allowed_settings_key): + setattr(self, key, value) + else: + for key, value in filter_settings(settings, allowed_settings_key): + setattr(self, key, value) + + def writte_yaml_configuration(self, path: str): + """Write the current settings into a YAML configuration file.""" + import yaml + + if os.path.exists(path): + yaml.safe_dump(settings, path) settings = Settings() +inner_project_settings = _InnerProjectSettings() diff --git a/src/ansys/aedt/core/maxwell.py b/src/ansys/aedt/core/maxwell.py index f194feec815..cbf9efba2a8 100644 --- a/src/ansys/aedt/core/maxwell.py +++ b/src/ansys/aedt/core/maxwell.py @@ -2111,9 +2111,9 @@ def edit_external_circuit(self, netlist_file_path, schematic_design_name, parame return False odesign = self.desktop_class.active_design(self.oproject, schematic_design_name) oeditor = odesign.SetActiveEditor("SchematicEditor") - if is_linux and settings.aedt_version == "2024.1": + if is_linux and settings.aedt_version == "2024.1": # pragma: no cover time.sleep(1) - self.odesktop.CloseAllWindows() + self.desktop_class.close_windows() comps = oeditor.GetAllComponents() sources_array = [] sources_type_array = [] diff --git a/src/ansys/aedt/core/misc/asc_circuit_mapping.json b/src/ansys/aedt/core/misc/asc_circuit_mapping.json new file mode 100644 index 00000000000..f3d1bb25964 --- /dev/null +++ b/src/ansys/aedt/core/misc/asc_circuit_mapping.json @@ -0,0 +1,102 @@ +{"res":{ + "Component Library": "Resistors", + "Component Name": "RES_", + "xoffset": 200, + "yoffset": -400, + "rotation_offset": 0, + "component_size": 400, + "orientation": "+x", + "size_change": 600 +}, + "cap":{ + "Component Library": "Capacitors", + "Component Name": "CAP_", + "xoffset": 200, + "yoffset": -200, + "rotation_offset": 0, + "component_size": 400, + "orientation": "+x", + "size_change": 600 + }, + "ind":{ + "Component Library": "Inductors", + "Component Name": "IND_", + "xoffset": 200, + "yoffset": -400, + "rotation_offset": 0, + "component_size": 400, + "orientation": "+x", + "size_change": 600 + }, + "ind2":{ + "Component Library": "Inductors", + "Component Name": "IND_", + "xoffset": 200, + "yoffset": -400, + "rotation_offset": 0, + "component_size": 400, + "orientation": "+x", + "size_change": 600 + }, + "diode":{ + "Component Library": "Diodes", + "Component Name": "DIODE_Level1", + "xoffset": 200, + "yoffset": -600, + "rotation_offset": 180, + "component_size": 400, + "orientation": "-x", + "size_change": 400 + }, + "zener":{ + "Component Library": "Diodes", + "Component Name": "DIODE_Level1", + "xoffset": 200, + "yoffset": -600, + "rotation_offset": 180, + "component_size": 400, + "orientation": "-x", + "size_change": 400 + }, + "voltage_pulse": { + "Component Library": "Independent Sources", + "Component Name": "V_PULSE", + "xoffset": 0, + "yoffset": -1000, + "rotation_offset": 90, + "component_size": 400, + "orientation": "-x", + "size_change": 600 + }, + "voltage": { + "Component Library": "Independent Sources", + "Component Name": "V_DC", + "xoffset": 0, + "yoffset": -1000, + "rotation_offset": 90, + "component_size": 400, + "orientation": "-x", + "size_change": 600 + }, + "current_pulse": { + "Component Library": "Independent Sources", + "Component Name": "I_PULSE", + "xoffset": 0, + "yoffset": -800, + "rotation_offset": 90, + "component_size": 400, + "orientation": "x", + "size_change": 600 + }, + "current": { + "Component Library": "Independent Sources", + "Component Name": "I_DC", + "xoffset": 0, + "yoffset": -800, + "rotation_offset": 90, + "component_size": 400, + "orientation": "x", + "size_change": 600 + } +} + diff --git a/src/ansys/aedt/core/modeler/cad/components_3d.py b/src/ansys/aedt/core/modeler/cad/components_3d.py index bf1ea28b32e..3b802188ea2 100644 --- a/src/ansys/aedt/core/modeler/cad/components_3d.py +++ b/src/ansys/aedt/core/modeler/cad/components_3d.py @@ -280,7 +280,7 @@ def group_name(self, name): """ if "Group" in self._primitives.oeditor.GetChildObject(self.name).GetPropNames() and name not in list( - self._primitives.oeditor.GetChildNames("Groups") + list(self._primitives.oeditor.GetChildNames("Groups")) ): arg = [ "NAME:GroupParameter", diff --git a/src/ansys/aedt/core/modeler/cad/primitives.py b/src/ansys/aedt/core/modeler/cad/primitives.py index 98a0637a25e..ad48d2dad2e 100644 --- a/src/ansys/aedt/core/modeler/cad/primitives.py +++ b/src/ansys/aedt/core/modeler/cad/primitives.py @@ -5622,7 +5622,7 @@ def create_group(self, objects=None, components=None, groups=None, group_name=No if components is None and groups is None and objects is None: raise AttributeError("At least one between ``objects``, ``components``, ``groups`` has to be defined.") - all_objects = self.object_names[:] + self.oeditor.GetChildNames("Groups")[::] + all_objects = self.object_names[:] + list(self.oeditor.GetChildNames("Groups")) if objects: object_selection = self.convert_to_selections(objects, return_list=False) else: diff --git a/src/ansys/aedt/core/modeler/circuits/primitives_nexxim.py b/src/ansys/aedt/core/modeler/circuits/primitives_nexxim.py index 65bdb7282d1..4415d800c9c 100644 --- a/src/ansys/aedt/core/modeler/circuits/primitives_nexxim.py +++ b/src/ansys/aedt/core/modeler/circuits/primitives_nexxim.py @@ -206,16 +206,16 @@ def create_subcircuit(self, location=None, angle=None, name=None, nested_subcirc parent_name = "{}:{}".format(self._app.design_name.split("/")[0], ":U" + str(random.randint(1, 10000))) self._app.odesign.InsertDesign("Circuit Design", name, "", parent_name) - if is_linux and settings.aedt_version == "2024.1": + if is_linux and settings.aedt_version == "2024.1": # pragma: no cover time.sleep(1) - self._app.odesktop.CloseAllWindows() + self._app.desktop_class.close_windows() if nested_subcircuit_id: pname = "{}:{}".format(self._app.design_name.split("/")[0], nested_subcircuit_id) odes = self._app.desktop_class.active_design(self._app.oproject, pname) oed = odes.SetActiveEditor("SchematicEditor") - if is_linux and settings.aedt_version == "2024.1": + if is_linux and settings.aedt_version == "2024.1": # pragma: no cover time.sleep(1) - self._app.odesktop.CloseAllWindows() + self._app.desktop_class.close_windows() objs = oed.GetAllElements() match = [i for i in objs if name in i] o = CircuitComponent(self, tabname=self.tab_name, custom_editor=oed) diff --git a/src/ansys/aedt/core/modeler/modeler_3d.py b/src/ansys/aedt/core/modeler/modeler_3d.py index d7b912647cd..6ce22683626 100644 --- a/src/ansys/aedt/core/modeler/modeler_3d.py +++ b/src/ansys/aedt/core/modeler/modeler_3d.py @@ -1091,7 +1091,7 @@ def import_nastran( if save_only_stl: return output_stls - self._app.odesktop.CloseAllWindows() + self._app.desktop_class.close_windows() self.logger.info("Importing STL in 3D Modeler") if output_stls: for output_stl in output_stls: @@ -1108,7 +1108,7 @@ def import_nastran( aedt_objs = self.object_names[::] for assembly, _ in nas_to_dict["Assemblies"].items(): assembly_group_name = assembly - if assembly in self.oeditor.GetChildNames("Groups"): + if assembly in list(self.oeditor.GetChildNames("Groups")): assembly_group_name = generate_unique_name(assembly, n=2) new_group = [] for el in nas_to_dict["Assemblies"][assembly]["Solids"].keys(): @@ -1121,7 +1121,7 @@ def import_nastran( ] if obj_names: new_group.append(self.create_group(obj_names, group_name=str(el))) - if assembly_group_name in self.oeditor.GetChildNames("Groups"): + if assembly_group_name in list(self.oeditor.GetChildNames("Groups")): self.oeditor.MoveEntityToGroup( [ "Groups:=", @@ -1180,7 +1180,7 @@ def import_nastran( id += 1 if group_parts: pids = [i.name for i in polys] - if assembly_name in self.oeditor.GetChildNames("Groups"): + if assembly_name in list(self.oeditor.GetChildNames("Groups")): self.oeditor.MoveEntityToGroup( [ "Objects:=", diff --git a/src/ansys/aedt/core/modeler/schematic.py b/src/ansys/aedt/core/modeler/schematic.py index 2fc01dacce0..960ac95f4bd 100644 --- a/src/ansys/aedt/core/modeler/schematic.py +++ b/src/ansys/aedt/core/modeler/schematic.py @@ -553,9 +553,9 @@ def model_units(self): >>> oEditor.SetActiveUnits """ active_units = self.layouteditor.GetActiveUnits() - if is_linux and settings.aedt_version == "2024.1": + if is_linux and settings.aedt_version == "2024.1": # pragma: no cover time.sleep(1) - self._app.odesktop.CloseAllWindows() + self._app.desktop_class.close_windows() return active_units @property diff --git a/src/ansys/aedt/core/modules/mesh.py b/src/ansys/aedt/core/modules/mesh.py index fc94dd7d94f..cf1522607ae 100644 --- a/src/ansys/aedt/core/modules/mesh.py +++ b/src/ansys/aedt/core/modules/mesh.py @@ -1066,7 +1066,7 @@ def assign_skin_depth( Parameters ---------- assignment : list - List of the object names or face IDs. + List of the object names, face IDs or edges IDs for Maxwell 2D design. skin_depth : str, float, optional Skin depth value. It can be either provided as a float or as a string. @@ -1092,7 +1092,7 @@ def assign_skin_depth( """ assignment = self.modeler.convert_to_selections(assignment, True) - if self._app.design_type != "HFSS" and self._app.design_type != "Maxwell 3D": + if self._app.design_type not in ["HFSS", "Maxwell 3D", "Maxwell 2D"]: raise MethodNotSupportedError if name: for m in self.meshoperations: @@ -1101,35 +1101,38 @@ def assign_skin_depth( else: name = generate_unique_name("SkinDepth") - if maximum_elements is None: - restrictlength = False - maximum_elements = "1000" + if self._app.design_type == "Maxwell 2D": + props = OrderedDict( + { + "Edges": assignment, + "SkinDepth": skin_depth, + "NumLayers": layers_number, + } + ) else: - restrictlength = True - assignment = self._app.modeler.convert_to_selections(assignment, True) + if maximum_elements is None: + restrictlength = False + maximum_elements = "1000" + else: + restrictlength = True - if isinstance(assignment[0], int): - seltype = "Faces" - elif isinstance(assignment[0], str): - seltype = "Objects" - else: - seltype = None - if seltype is None: - self.logger.error("Error in Assignment") - return + if isinstance(assignment[0], int): + seltype = "Faces" + elif isinstance(assignment[0], str): + seltype = "Objects" - props = OrderedDict( - { - "Type": "SkinDepthBased", - "Enabled": True, - seltype: assignment, - "RestrictElem": restrictlength, - "NumMaxElem": str(maximum_elements), - "SkinDepth": skin_depth, - "SurfTriMaxLength": triangulation_max_length, - "NumLayers": layers_number, - } - ) + props = OrderedDict( + { + "Type": "SkinDepthBased", + "Enabled": True, + seltype: assignment, + "RestrictElem": restrictlength, + "NumMaxElem": str(maximum_elements), + "SkinDepth": skin_depth, + "SurfTriMaxLength": triangulation_max_length, + "NumLayers": layers_number, + } + ) mop = MeshOperation(self, name, props, "SkinDepthBased") mop.create() diff --git a/src/ansys/aedt/core/modules/post_processor.py b/src/ansys/aedt/core/modules/post_processor.py index d96a9251565..9de451e4188 100644 --- a/src/ansys/aedt/core/modules/post_processor.py +++ b/src/ansys/aedt/core/modules/post_processor.py @@ -875,7 +875,9 @@ class PostProcessorCommon(object): def __init__(self, app): self._app = app - self.oeditor = self.modeler.oeditor + self.oeditor = None + if self.modeler: + self.oeditor = self.modeler.oeditor self._scratch = self._app.working_directory self.plots = self._get_plot_inputs() self.reports_by_category = Reports(self, self._app.design_type) @@ -979,7 +981,7 @@ def available_quantities_categories( report_category = self.available_report_types[0] if not display_type: display_type = self.available_display_types(report_category)[0] - if not solution: + if not solution and hasattr(self._app, "nominal_adaptive"): solution = self._app.nominal_adaptive if is_siwave_dc: # pragma: no cover id_ = "0" @@ -1080,7 +1082,7 @@ def available_report_quantities( report_category = self.available_report_types[0] if not display_type: display_type = self.available_display_types(report_category)[0] - if not solution: + if not solution and hasattr(self._app, "nominal_adaptive"): solution = self._app.nominal_adaptive if is_siwave_dc: id = "0" @@ -1194,7 +1196,10 @@ def available_report_solutions(self, report_category=None): def _get_plot_inputs(self): names = self._app.get_oo_name(self.oreportsetup) plots = [] - if names: + skip_plot = False + if self._app.design_type == "Circuit Netlist" and self._app.desktop_class.non_graphical: + skip_plot = True + if names and not skip_plot: for name in names: obj = self._app.get_oo_object(self.oreportsetup, name) report_type = obj.GetPropValue("Report Type") @@ -1276,13 +1281,15 @@ def all_report_names(self): return list(self.oreportsetup.GetAllReportNames()) @pyaedt_function_handler(PlotName="plot_name") - def copy_report_data(self, plot_name): + def copy_report_data(self, plot_name, paste=True): """Copy report data as static data. Parameters ---------- plot_name : str Name of the report. + paste : bool, optional + Whether to paste the report. The default is ``True``. Returns ------- @@ -1296,6 +1303,23 @@ def copy_report_data(self, plot_name): >>> oModule.PasteReports """ self.oreportsetup.CopyReportsData([plot_name]) + if paste: + self.paste_report_data() + return True + + @pyaedt_function_handler() + def paste_report_data(self): + """Paste report data as static data. + + Returns + ------- + bool + ``True`` when successful, ``False`` when failed. + + References + ---------- + >>> oModule.PasteReports + """ self.oreportsetup.PasteReports() return True @@ -1585,8 +1609,8 @@ def export_report_to_csv( ) @pyaedt_function_handler(project_dir="project_path") - def export_report_to_jpg(self, project_path, plot_name, width=0, height=0): - """Export the SParameter plot to a JPG file. + def export_report_to_jpg(self, project_path, plot_name, width=0, height=0, image_format="jpg"): + """Export plot to an image file. Parameters ---------- @@ -1598,6 +1622,8 @@ def export_report_to_jpg(self, project_path, plot_name, width=0, height=0): Image width. Default is ``0`` which takes Desktop size or 1980 pixel in case of non-graphical mode. height : int, optional Image height. Default is ``0`` which takes Desktop size or 1020 pixel in case of non-graphical mode. + image_format : str, optional + Format of the image file. The default is ``"jpg"``. Returns ------- @@ -1609,9 +1635,7 @@ def export_report_to_jpg(self, project_path, plot_name, width=0, height=0): >>> oModule.ExportImageToFile """ - # path - npath = project_path - file_name = os.path.join(npath, plot_name + ".jpg") # name of the image file + file_name = os.path.join(project_path, plot_name + "." + image_format) # name of the image file if self._app.desktop_class.non_graphical: # pragma: no cover if width == 0: width = 1980 @@ -2253,12 +2277,12 @@ def get_solution_data( @pyaedt_function_handler(input_dict="report_settings") def create_report_from_configuration(self, input_file=None, report_settings=None, solution_name=None): - """Create a report based on a JSON file, TOML file, or dictionary of properties. + """Create a report based on a JSON file, TOML file, RPT file, or dictionary of properties. Parameters ---------- input_file : str, optional - Path to the JSON or TOML file containing report settings. + Path to the JSON, TOML, or RPT file containing report settings. report_settings : dict, optional Dictionary containing report settings. solution_name : str, optional @@ -2272,18 +2296,47 @@ def create_report_from_configuration(self, input_file=None, report_settings=None Examples -------- + Create report from JSON file. >>> from ansys.aedt.core import Hfss >>> hfss = Hfss() >>> hfss.post.create_report_from_configuration(r'C:\\temp\\my_report.json', - >>> solution_name="Setup1 : LastAdpative") + ... solution_name="Setup1 : LastAdpative") + + Create report from RPT file. + >>> from ansys.aedt.core import Hfss + >>> hfss = Hfss() + >>> hfss.post.create_report_from_configuration(r'C:\\temp\\my_report.rpt') + + Create report from dictionary. + >>> from ansys.aedt.core import Hfss + >>> from ansys.aedt.core.generic.general_methods import read_json + >>> hfss = Hfss() + >>> dict_vals = read_json("Report_Simple.json") + >>> hfss.post.create_report_from_configuration(report_settings=dict_vals) """ if not report_settings and not input_file: # pragma: no cover - self.logger.error("Either a JSON file or a dictionary must be passed as input.") + self.logger.error("Either a file or a dictionary must be passed as input.") return False if input_file: - props = read_configuration_file(input_file) + _, file_extension = os.path.splitext(input_file) + if file_extension == ".rpt": + old_expressions = self.all_report_names + self.oreportsetup.CreateReportFromTemplate(input_file) + new_expressions = [item for item in self.all_report_names if item not in old_expressions] + if new_expressions: + report_name = new_expressions[0] + self.plots = self._get_plot_inputs() + report = None + for plot in self.plots: + if plot.plot_name == report_name: + report = plot + break + return report + else: + props = read_configuration_file(input_file) else: props = report_settings + if ( isinstance(props.get("expressions", {}), list) and props["expressions"] @@ -2378,7 +2431,10 @@ def model_units(self): str Model units, such as ``"mm"``. """ - return self.oeditor.GetModelUnits() + model_units = None + if self.oeditor and "GetModelUnits" in self.oeditor.__dir__(): + model_units = self.oeditor.GetModelUnits() + return model_units @property def post_osolution(self): @@ -3288,7 +3344,7 @@ def _create_fieldplot( if not setup: setup = self._app.existing_analysis_sweeps[0] - self._desktop.CloseAllWindows() + self._app.desktop_class.close_windows() try: self._app.modeler.fit_all() except Exception: @@ -3348,7 +3404,7 @@ def _create_fieldplot_line_traces( for i in self._app.setups: if i.name == setup.split(" : ")[0]: intrinsics = i.default_intrinsics - self._desktop.CloseAllWindows() + self._app.desktop_class.close_windows() try: self._app._modeler.fit_all() except Exception: @@ -3788,6 +3844,7 @@ def create_fieldplot_layers_nets( lst = [] for layer in layers_nets: for el in layer[1:]: + el = "" if el == "no-net" else el get_ids = self._odesign.GetGeometryIdsForNetLayerCombination(el, layer[0], setup) if isinstance(get_ids, (tuple, list)) and len(get_ids) > 2: lst.extend([int(i) for i in get_ids[2:]]) @@ -4477,7 +4534,7 @@ def power_budget(self, units="W", temperature=22, output_type="component"): power_dict_obj = {} group_hierarchy = {} - groups = self._app.oeditor.GetChildNames("Groups") + groups = list(self._app.oeditor.GetChildNames("Groups")) self._app.modeler.add_new_user_defined_component() for g in groups: g1 = self._app.oeditor.GetChildObject(g) diff --git a/src/ansys/aedt/core/modules/report_templates.py b/src/ansys/aedt/core/modules/report_templates.py index 5eae5560eca..985abbdc448 100644 --- a/src/ansys/aedt/core/modules/report_templates.py +++ b/src/ansys/aedt/core/modules/report_templates.py @@ -366,8 +366,9 @@ def __init__(self, app, report_category, setup_name, expressions=None): self.props["context"]["primary_sweep_range"] = ["All"] self.props["context"]["secondary_sweep_range"] = ["All"] self.props["context"]["variations"] = {"Freq": ["All"]} - for el, k in self._post._app.available_variations.nominal_w_values_dict.items(): - self.props["context"]["variations"][el] = k + if hasattr(self._post._app, "available_variations") and self._post._app.available_variations: + for el, k in self._post._app.available_variations.nominal_w_values_dict.items(): + self.props["context"]["variations"][el] = k self.props["expressions"] = None self.props["plot_name"] = None if expressions: @@ -2062,6 +2063,51 @@ def update_trace_in_report(self, traces, setup_name=None, variations=None, conte finally: self.expressions = expr + @pyaedt_function_handler() + def apply_report_template(self, input_file, property_type="Graphical"): # pragma: no cover + """Apply report template. + + .. note:: + This method works in only in graphical mode. + + Parameters + ---------- + input_file : str + Path for the file to import. The extension supported is ``".rpt"``. + property_type : str, optional + Property types to apply. Options are ``"Graphical"``, ``"Data"``, and ``"All"``. + + Returns + ------- + bool + ``True`` when successful, ``False`` when failed. + + References + ---------- + + >>> oModule.ApplyReportTemplate + """ + if not os.path.exists(input_file): # pragma: no cover + msg = "File does not exist." + self._post.logger.error(msg) + return False + + split_path = os.path.splitext(input_file) + extension = split_path[1] + + supported_ext = [".rpt"] + if extension not in supported_ext: # pragma: no cover + msg = "Extension {} is not supported.".format(extension) + self._post.logger.error(msg) + return False + + if property_type not in ["Graphical", "Data", "All"]: # pragma: no cover + msg = "Invalid value for `property_type`. The value must be 'Graphical', 'Data', or 'All'." + self._post.logger.error(msg) + return False + self._post.oreportsetup.ApplyReportTemplate(self.plot_name, input_file, property_type) + return True + class Standard(CommonReport): """Provides a reporting class that fits most of the app's standard reports.""" diff --git a/src/ansys/aedt/core/modules/solve_setup.py b/src/ansys/aedt/core/modules/solve_setup.py index bc853bea27a..c70979c3cfa 100644 --- a/src/ansys/aedt/core/modules/solve_setup.py +++ b/src/ansys/aedt/core/modules/solve_setup.py @@ -1902,7 +1902,7 @@ def disable(self): return True @pyaedt_function_handler(file_fullname="output_file") - def export_to_hfss(self, output_file, keep_net_name=False): + def export_to_hfss(self, output_file, keep_net_name=False, unite=True): """Export the HFSS 3D Layout design to an HFSS 3D design. This method is not supported with IronPython. @@ -1911,9 +1911,12 @@ def export_to_hfss(self, output_file, keep_net_name=False): ---------- output_file : str Full path and file name for exporting the project. - - keep_net_name : bool - Keep net name in 3D export when ``True`` or by default when ``False``. Default value is ``False``. + keep_net_name : bool, optional + Keep net name in 3D export. + The default is ``False``. + unite : bool, optional + Unite bodies which belong to the same net. + The default is ``True``. Returns ------- @@ -1938,13 +1941,14 @@ def export_to_hfss(self, output_file, keep_net_name=False): if not is_ironpython: from ansys.aedt.core import Hfss - self._get_net_names(Hfss, output_file) + self._get_net_names(Hfss, output_file, unite) else: self.p_app.logger.error("Exporting layout while keeping net name is not supported with IronPython") return succeeded @pyaedt_function_handler() - def _get_net_names(self, app, file_fullname): + def _get_net_names(self, app, file_fullname, unite): + """Identify nets and unite bodies that belong to the same net.""" primitives_3d_pts_per_nets = self._get_primitives_points_per_net() self.p_app.logger.info("Processing vias...") via_per_nets = self._get_via_position_per_net() @@ -1956,6 +1960,7 @@ def _get_net_names(self, app, file_fullname): aedtapp = app(project=file_fullname) units = aedtapp.modeler.model_units aedt_units = AEDT_UNITS["Length"][units] + object_list = aedtapp.modeler.object_names self._convert_edb_to_aedt_units(input_dict=primitives_3d_pts_per_nets, output_unit=aedt_units) self._convert_edb_to_aedt_units(input_dict=via_per_nets, output_unit=aedt_units) self._convert_edb_layer_elevation_to_aedt_units(input_dict=layers_elevation, output_units=aedt_units) @@ -1997,16 +2002,27 @@ def _get_net_names(self, app, file_fullname): self.p_app.logger.info("Renaming primitives for net {}...".format(net_name)) object_names = list(set(object_names)) if len(object_names) == 1: - object_p = aedtapp.modeler[object_names[0]] object_p.name = net_name object_p.color = [randrange(255), randrange(255), randrange(255)] # nosec elif len(object_names) > 1: - united_object = aedtapp.modeler.unite(object_names, purge=True) - obj_ind = aedtapp.modeler.objects[united_object].id - if obj_ind: - aedtapp.modeler.objects[obj_ind].name = net_name - aedtapp.modeler.objects[obj_ind].color = [randrange(255), randrange(255), randrange(255)] # nosec + if unite: + united_object = aedtapp.modeler.unite(object_names, purge=True) + obj_ind = aedtapp.modeler.objects[united_object].id + if obj_ind: + aedtapp.modeler.objects[obj_ind].name = net_name + aedtapp.modeler.objects[obj_ind].color = [ + randrange(255), + randrange(255), + randrange(255), + ] # nosec + else: + name_cont = 0 + for body in object_names: + body_name = net_name + f"_{name_cont}" + if body in object_list: + aedtapp.modeler.objects[body].name = body_name + name_cont += 1 if aedtapp.design_type == "Q3D Extractor": aedtapp.auto_identify_nets() @@ -2159,7 +2175,7 @@ def _check_export_log(self, info_messages, error_messages, file_fullname): return succeeded @pyaedt_function_handler(file_fullname="output_file") - def export_to_q3d(self, output_file, keep_net_name=False): + def export_to_q3d(self, output_file, keep_net_name=False, unite=True): """Export the HFSS 3D Layout design to a Q3D design. Parameters @@ -2168,6 +2184,9 @@ def export_to_q3d(self, output_file, keep_net_name=False): Full path and file name for exporting the project. keep_net_name : bool Whether to keep the net name in the 3D export, The default is ``False``. + unite : bool, optional + Unite bodies which belong to the same net. + The default is ``True``. Returns ------- @@ -2195,7 +2214,7 @@ def export_to_q3d(self, output_file, keep_net_name=False): if not is_ironpython: from ansys.aedt.core import Q3d - self._get_net_names(Q3d, output_file) + self._get_net_names(Q3d, output_file, unite) else: self.p_app.logger.error("Exporting layout while keeping net name is not supported with IronPython.") return succeeded diff --git a/src/ansys/aedt/core/q3d.py b/src/ansys/aedt/core/q3d.py index 3dd8db9cab9..5b15154ef98 100644 --- a/src/ansys/aedt/core/q3d.py +++ b/src/ansys/aedt/core/q3d.py @@ -2071,6 +2071,146 @@ def assign_thin_conductor(self, assignment, material="copper", thickness=1, name return bound return False # pragma: no cover + @pyaedt_function_handler() + def get_mutual_coupling( + self, source1, sink1, source2, sink2, calculation="ACL", setup_sweep_name=None, variations=None + ): + """Get mutual coupling between two terminals. + User has to provide the pair, source and sink of each terminal. If the provided sinks are not part of the + original matrix, a new matrix will be created. + + Parameters + ---------- + source1 : str + First element source. + sink1 : str + First element sink. + source2 : str + Second element source. + sink2 : str + Second element sink. + calculation : str, optional + Calculation type. + Available options are: ``"ACL"``, ``"ACR"``, ``"DCL"``, ``"DCR"``. + The default is ``"ACL"``. + setup_sweep_name : str, optional + Name of the setup. The default is ``None``, in which case the ``nominal_adaptive`` + setup is used. Be sure to build a setup string in the form of + ``"SetupName : SetupSweep"``, where ``SetupSweep`` is the sweep name to + use in the export or ``LastAdaptive``. + variations : dict, optional + Dictionary of all families including the primary sweep. + The default is ``None`` which uses all variations of the setup. + + Returns + ------- + :class:`pyaedt.modules.solutions.SolutionData` or bool + Solution Data object if successful, ``False`` otherwise. + + Examples + -------- + >>> from pyaedt import Q3d + >>> aedtapp = Q3d() + >>> data = aedtapp.modeler.get_mutual_coupling("a1", "a2", "b1", "b2", calculation="DCL") + """ + if setup_sweep_name is None: + setup_sweep_name = self.nominal_sweep + + if calculation not in ["ACL", "ACR", "DCL", "DCR"]: + self.logger.error("Calculation type not valid.") + return False + + if not variations: + variations = self.available_variations.all + + assignment = {} + + for net in self.nets: + source_name = "source_1" + sources = self.net_sources(net) + sinks = self.net_sinks(net) + assignment[net] = {} + + if source1 in sources or source1 in sinks: + assignment[net][source_name] = source1 + source_name = "source_2" + if sink1 in sources or sink1 in sinks: + assignment[net]["sink"] = sink1 + if source2 in sources or source2 in sinks: + assignment[net][source_name] = source2 + if sink2 in sources or sink2 in sinks: + assignment[net]["sink"] = sink2 + + move_sink = [] + is_new_matrix = True + matrix_name = "Original" + + sources = [] + sinks = [] + + expression = calculation + "(" + + for net_name, net_props in assignment.items(): + + expression += net_name + + if "source_1" not in net_props or "sink" not in net_props: + self.logger.error("Sources and sinks passed not valid.") + return False + + sources.append(self.net_sources(net_name)) + sinks.append(self.net_sinks(net_name)) + + source = net_props["source_1"] + sink = net_props["sink"] + + expression += ":" + expression += source + expression += "," + + if "Sink" not in [self.excitation_objects[source].type, self.excitation_objects[sink].type]: + move_sink.append(sink) + elif self.excitation_objects[sink].type == "Source": + move_sink.append(sink) + + if "source_2" in net_props: + # Both sources in the same net + source = net_props["source_2"] + expression += net_name + expression += ":" + expression += source + expression += "," + break + expression = expression[:-1] + expression += ")" + + if move_sink: + sources = [item for sublist in sources for item in sublist] + sinks = [item for sublist in sinks for item in sublist] + all_terminals = sources + sinks + for q3d_matrix in self.matrices: + matrix_available_sources = self.omatrix.ListReduceMatrixReducedSources(q3d_matrix.name, False) + matrix_source_list = [element.split(":")[1] for element in matrix_available_sources] + matrix_sink_list = list(set(all_terminals).symmetric_difference(set(matrix_source_list))) + for initial_sink in sinks: + if initial_sink in matrix_sink_list: + matrix_sink_list.remove(initial_sink) + if sorted(matrix_sink_list) == sorted(move_sink): + is_new_matrix = False + matrix_name = q3d_matrix.name + break + else: + is_new_matrix = False + + if is_new_matrix: + matrix = self.insert_reduced_matrix("MoveSink", move_sink) + matrix_name = matrix.name + + data = self.post.get_solution_data( + expressions=expression, context=matrix_name, variations=variations, setup_sweep_name=setup_sweep_name + ) + return data + class Q2d(QExtractor, object): """Provides the Q2D app interface. diff --git a/src/ansys/aedt/core/workflows/circuit/images/large/schematic.png b/src/ansys/aedt/core/workflows/circuit/images/large/schematic.png new file mode 100644 index 00000000000..6db0740db7b Binary files /dev/null and b/src/ansys/aedt/core/workflows/circuit/images/large/schematic.png differ diff --git a/src/ansys/aedt/core/workflows/circuit/import_schematic.py b/src/ansys/aedt/core/workflows/circuit/import_schematic.py new file mode 100644 index 00000000000..a084d4de468 --- /dev/null +++ b/src/ansys/aedt/core/workflows/circuit/import_schematic.py @@ -0,0 +1,134 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2021 - 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# 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 + +import ansys.aedt.core +from ansys.aedt.core import Circuit +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 +from ansys.aedt.core.workflows.misc import get_process_id +from ansys.aedt.core.workflows.misc import is_student + +port = get_port() +version = get_aedt_version() +aedt_process_id = get_process_id() +is_student = is_student() + +# Extension batch arguments +extension_arguments = {"asc_file": ""} +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 + + master = tkinter.Tk() + + master.geometry("750x250") + + master.title(extension_description) + + # Load the logo for the main window + icon_path = os.path.join(ansys.aedt.core.workflows.__path__[0], "images", "large", "logo.png") + im = PIL.Image.open(icon_path) + photo = PIL.ImageTk.PhotoImage(im) + + # 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)) + + 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) + + 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 "/", + 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.grid(row=0, column=2, pady=10) + + def callback(): + master.asc_path_ui = text.get("1.0", tkinter.END).strip() + master.destroy() + + b3 = tkinter.Button(master, text="Ok", width=40, command=callback) + 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, + } + 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) + if not extension_args["is_test"]: # pragma: no cover + cir.release_desktop(False, False) + return True + + +if __name__ == "__main__": # pragma: no cover + args = get_arguments(extension_arguments, extension_description) + + # Open UI + if not args["is_batch"]: # pragma: no cover + output = frontend() + if output: + for output_name, output_value in output.items(): + if output_name in extension_arguments: + args[output_name] = output_value + + main(args) diff --git a/src/ansys/aedt/core/workflows/circuit/toolkits_catalog.toml b/src/ansys/aedt/core/workflows/circuit/toolkits_catalog.toml new file mode 100644 index 00000000000..df49448e0ab --- /dev/null +++ b/src/ansys/aedt/core/workflows/circuit/toolkits_catalog.toml @@ -0,0 +1,6 @@ +[SchematicImporter] +name = "Schematic Importer" +script = "import_schematic.py" +icon = "images/large/schematic.png" +template = "run_pyedb_toolkit_script" +pip = "" diff --git a/src/pyaedt/__init__.py b/src/pyaedt/__init__.py index 13da1ede8a2..f43843f5806 100644 --- a/src/pyaedt/__init__.py +++ b/src/pyaedt/__init__.py @@ -4,6 +4,7 @@ __version__ = __version__ import warnings +import os ALIAS_WARNING = ( "Module 'pyaedt' has become an alias to the new package structure. " \ @@ -22,6 +23,9 @@ def alias_deprecation_warning(): # pragma: no cover def custom_show_warning(message, category, filename, lineno, file=None, line=None): """Custom warning used to remove :loc: pattern.""" print("{}: {}".format(category.__name__, message)) + # NOTE: This line is added to ensure that pytest handle the warning correctly. + if "PYTEST_CURRENT_TEST" in os.environ: + existing_showwarning(message, category, filename, lineno, file=file, line=line) warnings.showwarning = custom_show_warning