diff --git a/.github/workflows/ci_cd.yml b/.github/workflows/ci_cd.yml index cb49cd6d5..5b716f60d 100644 --- a/.github/workflows/ci_cd.yml +++ b/.github/workflows/ci_cd.yml @@ -45,30 +45,30 @@ concurrency: permissions: {} jobs: - update-changelog: - name: "Update CHANGELOG for new tag" - if: github.event_name == 'push' && contains(github.ref, 'refs/tags') - runs-on: ubuntu-latest - permissions: - contents: write - pull-requests: write - steps: - - uses: ansys/actions/doc-deploy-changelog@21c9de9bee9692173780696d4a39964f20b9cfa3 # v10.1.5 - with: - token: ${{ secrets.PYANSYS_CI_BOT_TOKEN }} - bot-user: ${{ secrets.PYANSYS_CI_BOT_USERNAME }} - bot-email: ${{ secrets.PYANSYS_CI_BOT_EMAIL }} - - style: - name: Code style - runs-on: ubuntu-latest - permissions: - contents: read - steps: - - name: PyAnsys code style checks - uses: ansys/actions/code-style@21c9de9bee9692173780696d4a39964f20b9cfa3 # v10.1.5 - with: - python-version: ${{ env.MAIN_PYTHON_VERSION }} + # update-changelog: + # name: "Update CHANGELOG for new tag" + # if: github.event_name == 'push' && contains(github.ref, 'refs/tags') + # runs-on: ubuntu-latest + # permissions: + # contents: write + # pull-requests: write + # steps: + # - uses: ansys/actions/doc-deploy-changelog@21c9de9bee9692173780696d4a39964f20b9cfa3 # v10.1.5 + # with: + # token: ${{ secrets.PYANSYS_CI_BOT_TOKEN }} + # bot-user: ${{ secrets.PYANSYS_CI_BOT_USERNAME }} + # bot-email: ${{ secrets.PYANSYS_CI_BOT_EMAIL }} + + # style: + # name: Code style + # runs-on: ubuntu-latest + # permissions: + # contents: read + # steps: + # - name: PyAnsys code style checks + # uses: ansys/actions/code-style@21c9de9bee9692173780696d4a39964f20b9cfa3 # v10.1.5 + # with: + # python-version: ${{ env.MAIN_PYTHON_VERSION }} doc-style: name: Documentation Style Check @@ -81,65 +81,65 @@ jobs: with: token: ${{ secrets.GITHUB_TOKEN }} - check-vulnerabilities: - name: Check Vulnerabilities - runs-on: ubuntu-latest - permissions: - contents: read - steps: - - name: PyAnsys Vulnerability check (on main) - if: github.ref == 'refs/heads/main' - uses: ansys/actions/check-vulnerabilities@21c9de9bee9692173780696d4a39964f20b9cfa3 # v10.1.5 - with: - python-version: ${{ env.MAIN_PYTHON_VERSION }} - python-package-name: ${{ env.PACKAGE_NAME }} - token: ${{ secrets.PYANSYS_CI_BOT_TOKEN }} - - - name: PyAnsys Vulnerability check (on dev mode) - if: github.ref != 'refs/heads/main' - uses: ansys/actions/check-vulnerabilities@21c9de9bee9692173780696d4a39964f20b9cfa3 # v10.1.5 - with: - python-version: ${{ env.MAIN_PYTHON_VERSION }} - python-package-name: ${{ env.PACKAGE_NAME }} - token: ${{ secrets.PYANSYS_CI_BOT_TOKEN }} - dev-mode: true - - check-actions-security: - name: "Check Actions Security" - runs-on: ubuntu-latest - permissions: - contents: read - steps: - - uses: ansys/actions/check-actions-security@21c9de9bee9692173780696d4a39964f20b9cfa3 # v10.1.5 - with: - generate-summary: true - token: ${{ secrets.GITHUB_TOKEN }} - auditing-level: 'high' - trust-ansys-actions: true - - smoke-tests: - name: Build and Smoke tests - runs-on: ${{ matrix.os }} - needs: [style] - strategy: - fail-fast: false - matrix: - os: [ubuntu-latest, windows-latest, macos-latest] - python-version: ['3.10', '3.11', '3.12', '3.13'] - should-release: - - ${{ github.event_name == 'push' && contains(github.ref, 'refs/tags') }} - exclude: - - should-release: false - os: macos-latest - permissions: - contents: read - steps: - - name: Build wheelhouse and perform smoke test - uses: ansys/actions/build-wheelhouse@21c9de9bee9692173780696d4a39964f20b9cfa3 # v10.1.5 - with: - library-name: ${{ env.PACKAGE_NAME }} - operating-system: ${{ matrix.os }} - python-version: ${{ matrix.python-version }} + # check-vulnerabilities: + # name: Check Vulnerabilities + # runs-on: ubuntu-latest + # permissions: + # contents: read + # steps: + # - name: PyAnsys Vulnerability check (on main) + # if: github.ref == 'refs/heads/main' + # uses: ansys/actions/check-vulnerabilities@21c9de9bee9692173780696d4a39964f20b9cfa3 # v10.1.5 + # with: + # python-version: ${{ env.MAIN_PYTHON_VERSION }} + # python-package-name: ${{ env.PACKAGE_NAME }} + # token: ${{ secrets.PYANSYS_CI_BOT_TOKEN }} + + # - name: PyAnsys Vulnerability check (on dev mode) + # if: github.ref != 'refs/heads/main' + # uses: ansys/actions/check-vulnerabilities@21c9de9bee9692173780696d4a39964f20b9cfa3 # v10.1.5 + # with: + # python-version: ${{ env.MAIN_PYTHON_VERSION }} + # python-package-name: ${{ env.PACKAGE_NAME }} + # token: ${{ secrets.PYANSYS_CI_BOT_TOKEN }} + # dev-mode: true + + # check-actions-security: + # name: "Check Actions Security" + # runs-on: ubuntu-latest + # permissions: + # contents: read + # steps: + # - uses: ansys/actions/check-actions-security@21c9de9bee9692173780696d4a39964f20b9cfa3 # v10.1.5 + # with: + # generate-summary: true + # token: ${{ secrets.GITHUB_TOKEN }} + # auditing-level: 'high' + # trust-ansys-actions: true + + # smoke-tests: + # name: Build and Smoke tests + # runs-on: ${{ matrix.os }} + # needs: [style] + # strategy: + # fail-fast: false + # matrix: + # os: [ubuntu-latest, windows-latest, macos-latest] + # python-version: ['3.10', '3.11', '3.12', '3.13'] + # should-release: + # - ${{ github.event_name == 'push' && contains(github.ref, 'refs/tags') }} + # exclude: + # - should-release: false + # os: macos-latest + # permissions: + # contents: read + # steps: + # - name: Build wheelhouse and perform smoke test + # uses: ansys/actions/build-wheelhouse@21c9de9bee9692173780696d4a39964f20b9cfa3 # v10.1.5 + # with: + # library-name: ${{ env.PACKAGE_NAME }} + # operating-system: ${{ matrix.os }} + # python-version: ${{ matrix.python-version }} revn-variations: name: Save variations of revn @@ -269,384 +269,384 @@ jobs: echo "container_stable_exit=false" >> $GITHUB_OUTPUT fi - remote-connect: - name: Remote connect testing and coverage - Mechanical ${{ matrix.mechanical-version }} - runs-on: public-ubuntu-latest-16-cores - needs: [style, revn-variations, config-matrix] - continue-on-error: ${{ matrix.experimental }} - strategy: - fail-fast: false - matrix: ${{ fromJSON(needs.config-matrix.outputs.matrix) }} - permissions: - contents: read - packages: read - checks: write - steps: - - name: Login in Github Container registry - uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Pull, launch, and validate Mechanical service - env: - MECHANICAL_IMAGE: ${{ env.DOCKER_PACKAGE }}:${{ matrix.mechanical-version }} - # ANSYS_WORKBENCH_LOGGING_CONSOLE: 0 - # ANSYS_WORKBENCH_LOGGING_DIRECTORY: /log_file # workbench.log file - ANSYS_WORKBENCH_LOGGING_AUTO_FLUSH: 0 # turn off autoflush for faster performance - DOCKER_MECH_CONTAINER_NAME: ${{ env.DOCKER_MECH_CONTAINER_NAME }} - LICENSE_SERVER: ${{ env.LICENSE_SERVER }} - PYMECHANICAL_PORT: ${{ env.PYMECHANICAL_PORT }} - run: | - docker pull ${MECHANICAL_IMAGE} - - echo "Run docker in detached mode" - docker run -d --name ${DOCKER_MECH_CONTAINER_NAME} -e ANSYSLMD_LICENSE_FILE=1055@${LICENSE_SERVER} -p ${PYMECHANICAL_PORT}:10000 ${MECHANICAL_IMAGE} - - # Wait for Mechanical to initialize with intelligent polling - max_wait=300 # Maximum wait time in seconds - check_interval=10 # Check every 10 seconds - elapsed=0 - - echo "Waiting for Mechanical to initialize..." - while [ $elapsed -lt $max_wait ]; do - docker logs ${DOCKER_MECH_CONTAINER_NAME} > log.txt - if grep -q 'WB Initialize Done' log.txt 2>/dev/null; then - echo "Mechanical initialized successfully after ${elapsed} seconds" - break - fi - echo "Waiting for initialization... (${elapsed}/${max_wait}s)" - sleep $check_interval - elapsed=$((elapsed + check_interval)) - done - - # Final check - docker logs ${DOCKER_MECH_CONTAINER_NAME} > log.txt - if ! grep -q 'WB Initialize Done' log.txt 2>/dev/null; then - echo "ERROR: Mechanical failed to initialize within ${max_wait} seconds" - echo "=== Last 50 lines of log.txt ===" - tail -n 50 log.txt || echo "No log file found" - exit 1 - fi - - - name: Display info - if: github.event_name == 'schedule' - id: capture_info - env: - DOCKER_PACKAGE: ${{ env.DOCKER_PACKAGE }} - MECH_VERSION: ${{ matrix.mechanical-version }} - TEST_REVN: ${{ needs.revn-variations.outputs.test_revn }} - run: | - IMAGE_NAME=${DOCKER_PACKAGE}:${MECH_VERSION} - BUILD_DATE=$(docker run --rm --entrypoint head $IMAGE_NAME -n 1 /install/ansys_inc/v${TEST_REVN}/aisol/CommonFiles/builddate.txt) - PUSHED_AT=$(docker inspect --format='{{.Created}}' $IMAGE_NAME) - echo "docker_info=$IMAGE_NAME was pushed at: $PUSHED_AT" >> $GITHUB_OUTPUT - echo "::group::Docker Info" - echo "docker_info=$PUSHED_AT" >> $GITHUB_OUTPUT - echo "build_info=$BUILD_DATE" >> $GITHUB_OUTPUT - echo "$IMAGE_NAME pushed at $PUSHED_AT" - echo "Build date : $BUILD_DATE" - echo "::endgroup::" - - - name: Testing - uses: ansys/actions/tests-pytest@21c9de9bee9692173780696d4a39964f20b9cfa3 # v10.1.5 - with: - python-version: ${{ env.MAIN_PYTHON_VERSION }} - pytest-markers: '-m remote_session_connect' - pytest-extra-args: '-s --junitxml remote_results${{ env.MAIN_PYTHON_VERSION }}.xml' - - - name: Publish Test Report - uses: mikepenz/action-junit-report@e08919a3b1fb83a78393dfb775a9c37f17d8eea6 # v6.0.1 - if: always() - with: - report_paths: '**/remote_results*.xml' - check_name: Remote Connect Test Report ${{ matrix.python-version }} - detailed_summary: true - include_passed: true - fail_on_failure: true - - - name: Upload coverage results - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 - if: matrix.mechanical-version == needs.revn-variations.outputs.test_docker_image_version - with: - include-hidden-files: true - name: coverage-tests - path: .cov - retention-days: 7 - - - name: Upload coverage results (as .coverage) - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 - if: matrix.mechanical-version == needs.revn-variations.outputs.test_docker_image_version - with: - include-hidden-files: true - name: coverage-file-tests - path: .coverage - retention-days: 7 - - - name: Get Mechanical container logs - if: always() - run: | # zizmor: ignore[template-injection] - docker logs ${{ env.DOCKER_MECH_CONTAINER_NAME }} > mechanical_tests_log-${{ matrix.mechanical-version }}.txt 2>&1 - echo CONTAINER LOGS OUTPUT - cat mechanical_tests_log-${{ matrix.mechanical-version }}.txt - echo CPU info - lscpu - - - name: Upload container logs - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 - with: - name: mechanical_tests_log-${{ matrix.mechanical-version }} - path: mechanical_tests_log-${{ matrix.mechanical-version }}.txt - retention-days: 7 - - embedding-tests: - name: Embedding testing and coverage - runs-on: public-ubuntu-latest-16-cores - timeout-minutes: 30 - needs: [revn-variations, test-container-info, container-stability-check, smoke-tests] - container: - image: ${{ needs.revn-variations.outputs.test_container }} # zizmor: ignore[unpinned-images] - options: --entrypoint /bin/bash - strategy: - fail-fast: false - matrix: - python-version: ['3.10', '3.11', '3.12', '3.13'] - permissions: - contents: read - packages: read - checks: write - steps: - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - with: - persist-credentials: false - - - name: Set up Python - shell: bash - env: - MAIN_PYTHON_VERSION: ${{ env.MAIN_PYTHON_VERSION }} - run: | - apt update - apt install lsb-release xvfb git curl make -y - curl -LsSf https://astral.sh/uv/install.sh | sh - export PATH="$HOME/.local/bin:$PATH" - uv python install python${MAIN_PYTHON_VERSION} - uv venv /env - - name: "Install packages for testing" - run: | - . /env/bin/activate - uv pip install --upgrade pip - uv pip install -e .[tests] - - - name: Unit Testing and coverage - env: - ANSYS_WORKBENCH_LOGGING_CONSOLE: 0 - ANSYS_WORKBENCH_LOGGING: 0 - ANSYS_WORKBENCH_LOGGING_FILTER_LEVEL: 2 - CONTAINER_STABLE_EXIT: ${{ needs.container-stability-check.outputs.container_stable_exit }} - MATRIX_PYTHON_VERSION: ${{ matrix.python-version }} - NUM_CORES: 1 - PYTHONUNBUFFERED: 1 - run: | - . /env/bin/activate - if [ "${CONTAINER_STABLE_EXIT}" = "true" ]; then - xvfb-run mechanical-env pytest -m embedding -s --junitxml test_results${MATRIX_PYTHON_VERSION}.xml - else - xvfb-run mechanical-env pytest -m embedding -s --junitxml test_results${MATRIX_PYTHON_VERSION}.xml || true - fi - - - name: Upload coverage results - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 - if: env.MAIN_PYTHON_VERSION == matrix.python-version - with: - include-hidden-files: true - name: coverage-tests-embedding - path: .cov - retention-days: 7 - - - name: Upload coverage results (as .coverage) - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 - if: env.MAIN_PYTHON_VERSION == matrix.python-version - with: - include-hidden-files: true - name: coverage-file-tests-embedding - path: .coverage - retention-days: 7 - - - name: Publish Test Report - uses: mikepenz/action-junit-report@e08919a3b1fb83a78393dfb775a9c37f17d8eea6 # v6.0.1 - if: always() - with: - report_paths: '**/test_results*.xml' - check_name: Test Report ${{ matrix.python-version }} - detailed_summary: true - include_passed: true - fail_on_failure: true - - embedding-scripts-tests: - name: Embedding scripts testing and coverage - runs-on: public-ubuntu-latest-16-cores - timeout-minutes: 30 - needs: [smoke-tests, revn-variations, test-container-info] - container: - image: ${{ needs.revn-variations.outputs.test_container }} # zizmor: ignore[unpinned-images] - options: --entrypoint /bin/bash - strategy: - fail-fast: false - matrix: - python-version: ['3.10', '3.11', '3.12', '3.13'] - permissions: - contents: read - packages: read - checks: write - steps: - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - with: - persist-credentials: false - - - name: Set up Python - shell: bash - env: - MAIN_PYTHON_VERSION: ${{ env.MAIN_PYTHON_VERSION }} - run: | - apt update - apt install lsb-release xvfb git curl make -y - curl -LsSf https://astral.sh/uv/install.sh | sh - export PATH="$HOME/.local/bin:$PATH" - uv python install python${MAIN_PYTHON_VERSION} - uv venv /env - - name: "Install packages for testing" - run: | - . /env/bin/activate - uv pip install --upgrade pip - uv pip install -e .[tests] - - - name: Embedding scripts unit testing and coverage - env: - ANSYS_WORKBENCH_LOGGING_CONSOLE: 0 - ANSYS_WORKBENCH_LOGGING: 0 - ANSYS_WORKBENCH_LOGGING_FILTER_LEVEL: 2 - MATRIX_PYTHON_VERSION: ${{ matrix.python-version }} - NUM_CORES: 1 - PYTHONUNBUFFERED: 1 - run: | - . /env/bin/activate - mechanical-env pytest -m embedding_scripts -s --junitxml test_results_embedding_scripts${MAIN_PYTHON_VERSION}.xml - pytest -m cli -s --junitxml test_results_cli_scripts${MAIN_PYTHON_VERSION}.xml - - - name: Upload coverage results - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 - if: env.MAIN_PYTHON_VERSION == matrix.python-version - with: - include-hidden-files: true - name: coverage-tests-embedding-scripts - path: .cov - retention-days: 7 - - - name: Upload coverage results (as .coverage) - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 - if: env.MAIN_PYTHON_VERSION == matrix.python-version - with: - include-hidden-files: true - name: coverage-file-tests-embedding-scripts - path: .coverage - retention-days: 7 - - - name: Publish Test Report - uses: mikepenz/action-junit-report@e08919a3b1fb83a78393dfb775a9c37f17d8eea6 # v6.0.1 - if: always() - with: - report_paths: '**/test_results*.xml' - check_name: Test Report ${{ matrix.python-version }} - detailed_summary: true - include_passed: true - fail_on_failure: true - - launch-tests: - name: Launch testing and coverage - runs-on: public-ubuntu-latest-16-cores - timeout-minutes: 30 - container: - image: ${{ needs.revn-variations.outputs.test_container }} # zizmor: ignore[unpinned-images] - options: --entrypoint /bin/bash - needs: [ style, revn-variations, container-stability-check, test-container-info] - strategy: - fail-fast: false - matrix: - python-version: ['3.10', '3.11', '3.12', '3.13'] - permissions: - contents: read - packages: read - checks: write - steps: - - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - with: - persist-credentials: false - - - name: Set up Python - shell: bash - env: - MAIN_PYTHON_VERSION: ${{ env.MAIN_PYTHON_VERSION }} - run: | - apt update - apt install lsb-release xvfb git curl make -y - curl -LsSf https://astral.sh/uv/install.sh | sh - export PATH="$HOME/.local/bin:$PATH" - uv python install python${MAIN_PYTHON_VERSION} - uv venv /env - - name: "Install packages for testing" - run: | - . /env/bin/activate - uv pip install --upgrade pip - uv pip install -e .[tests] - - - name: Set environment variable - env: - TEST_REVN: ${{ needs.revn-variations.outputs.test_revn }} - run: echo "ANSYSCL${TEST_REVN}_DIR=/install/ansys_inc/v${TEST_REVN}/licensingclient" >> $GITHUB_ENV - - - name: Unit Testing and coverage - env: - ANSYS_WORKBENCH_LOGGING_CONSOLE: 0 - CONTAINER_STABLE_EXIT: ${{ needs.container-stability-check.outputs.container_stable_exit }} - MATRIX_PYTHON_VERSION: ${{ matrix.python-version }} - run: | - unset PYMECHANICAL_PORT - unset PYMECHANICAL_START_INSTANCE - . /env/bin/activate - if [ "${CONTAINER_STABLE_EXIT}" = "true" ]; then - pytest -m remote_session_launch -s --junitxml launch_test_results${MATRIX_PYTHON_VERSION}.xml - else - pytest -m remote_session_launch -s --junitxml launch_test_results${MATRIX_PYTHON_VERSION}.xml || true - fi - - - name: Publish Launch Test Report - uses: mikepenz/action-junit-report@e08919a3b1fb83a78393dfb775a9c37f17d8eea6 # v6.0.1 - if: always() - with: - report_paths: '**/launch_test_results*.xml' - check_name: Launch Test Report ${{ matrix.python-version }} - detailed_summary: true - include_passed: true - fail_on_failure: true - - - name: Upload coverage results - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 - if: env.MAIN_PYTHON_VERSION == matrix.python-version - with: - include-hidden-files: true - name: coverage-tests-remote-session-launch - path: .cov - retention-days: 7 - - - name: Upload coverage results (as .coverage) - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 - if: env.MAIN_PYTHON_VERSION == matrix.python-version - with: - include-hidden-files: true - name: coverage-file-tests-remote-session-launch - path: .coverage - retention-days: 7 + # remote-connect: + # name: Remote connect testing and coverage - Mechanical ${{ matrix.mechanical-version }} + # runs-on: public-ubuntu-latest-16-cores + # needs: [style, revn-variations, config-matrix] + # continue-on-error: ${{ matrix.experimental }} + # strategy: + # fail-fast: false + # matrix: ${{ fromJSON(needs.config-matrix.outputs.matrix) }} + # permissions: + # contents: read + # packages: read + # checks: write + # steps: + # - name: Login in Github Container registry + # uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 + # with: + # registry: ghcr.io + # username: ${{ github.actor }} + # password: ${{ secrets.GITHUB_TOKEN }} + + # - name: Pull, launch, and validate Mechanical service + # env: + # MECHANICAL_IMAGE: ${{ env.DOCKER_PACKAGE }}:${{ matrix.mechanical-version }} + # # ANSYS_WORKBENCH_LOGGING_CONSOLE: 0 + # # ANSYS_WORKBENCH_LOGGING_DIRECTORY: /log_file # workbench.log file + # ANSYS_WORKBENCH_LOGGING_AUTO_FLUSH: 0 # turn off autoflush for faster performance + # DOCKER_MECH_CONTAINER_NAME: ${{ env.DOCKER_MECH_CONTAINER_NAME }} + # LICENSE_SERVER: ${{ env.LICENSE_SERVER }} + # PYMECHANICAL_PORT: ${{ env.PYMECHANICAL_PORT }} + # run: | + # docker pull ${MECHANICAL_IMAGE} + + # echo "Run docker in detached mode" + # docker run -d --name ${DOCKER_MECH_CONTAINER_NAME} -e ANSYSLMD_LICENSE_FILE=1055@${LICENSE_SERVER} -p ${PYMECHANICAL_PORT}:10000 ${MECHANICAL_IMAGE} + + # # Wait for Mechanical to initialize with intelligent polling + # max_wait=300 # Maximum wait time in seconds + # check_interval=10 # Check every 10 seconds + # elapsed=0 + + # echo "Waiting for Mechanical to initialize..." + # while [ $elapsed -lt $max_wait ]; do + # docker logs ${DOCKER_MECH_CONTAINER_NAME} > log.txt + # if grep -q 'WB Initialize Done' log.txt 2>/dev/null; then + # echo "Mechanical initialized successfully after ${elapsed} seconds" + # break + # fi + # echo "Waiting for initialization... (${elapsed}/${max_wait}s)" + # sleep $check_interval + # elapsed=$((elapsed + check_interval)) + # done + + # # Final check + # docker logs ${DOCKER_MECH_CONTAINER_NAME} > log.txt + # if ! grep -q 'WB Initialize Done' log.txt 2>/dev/null; then + # echo "ERROR: Mechanical failed to initialize within ${max_wait} seconds" + # echo "=== Last 50 lines of log.txt ===" + # tail -n 50 log.txt || echo "No log file found" + # exit 1 + # fi + + # - name: Display info + # if: github.event_name == 'schedule' + # id: capture_info + # env: + # DOCKER_PACKAGE: ${{ env.DOCKER_PACKAGE }} + # MECH_VERSION: ${{ matrix.mechanical-version }} + # TEST_REVN: ${{ needs.revn-variations.outputs.test_revn }} + # run: | + # IMAGE_NAME=${DOCKER_PACKAGE}:${MECH_VERSION} + # BUILD_DATE=$(docker run --rm --entrypoint head $IMAGE_NAME -n 1 /install/ansys_inc/v${TEST_REVN}/aisol/CommonFiles/builddate.txt) + # PUSHED_AT=$(docker inspect --format='{{.Created}}' $IMAGE_NAME) + # echo "docker_info=$IMAGE_NAME was pushed at: $PUSHED_AT" >> $GITHUB_OUTPUT + # echo "::group::Docker Info" + # echo "docker_info=$PUSHED_AT" >> $GITHUB_OUTPUT + # echo "build_info=$BUILD_DATE" >> $GITHUB_OUTPUT + # echo "$IMAGE_NAME pushed at $PUSHED_AT" + # echo "Build date : $BUILD_DATE" + # echo "::endgroup::" + + # - name: Testing + # uses: ansys/actions/tests-pytest@21c9de9bee9692173780696d4a39964f20b9cfa3 # v10.1.5 + # with: + # python-version: ${{ env.MAIN_PYTHON_VERSION }} + # pytest-markers: '-m remote_session_connect' + # pytest-extra-args: '-s --junitxml remote_results${{ env.MAIN_PYTHON_VERSION }}.xml' + + # - name: Publish Test Report + # uses: mikepenz/action-junit-report@e08919a3b1fb83a78393dfb775a9c37f17d8eea6 # v6.0.1 + # if: always() + # with: + # report_paths: '**/remote_results*.xml' + # check_name: Remote Connect Test Report ${{ matrix.python-version }} + # detailed_summary: true + # include_passed: true + # fail_on_failure: true + + # # - name: Upload coverage results + # # uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + # # if: matrix.mechanical-version == needs.revn-variations.outputs.test_docker_image_version + # # with: + # # include-hidden-files: true + # # name: coverage-tests + # # path: .cov + # # retention-days: 7 + + # # - name: Upload coverage results (as .coverage) + # # uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + # # if: matrix.mechanical-version == needs.revn-variations.outputs.test_docker_image_version + # # with: + # # include-hidden-files: true + # # name: coverage-file-tests + # # path: .coverage + # # retention-days: 7 + + # - name: Get Mechanical container logs + # if: always() + # run: | # zizmor: ignore[template-injection] + # docker logs ${{ env.DOCKER_MECH_CONTAINER_NAME }} > mechanical_tests_log-${{ matrix.mechanical-version }}.txt 2>&1 + # echo CONTAINER LOGS OUTPUT + # cat mechanical_tests_log-${{ matrix.mechanical-version }}.txt + # echo CPU info + # lscpu + + # # - name: Upload container logs + # # uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + # # with: + # # name: mechanical_tests_log-${{ matrix.mechanical-version }} + # # path: mechanical_tests_log-${{ matrix.mechanical-version }}.txt + # # retention-days: 7 + + # embedding-tests: + # name: Embedding testing and coverage + # runs-on: public-ubuntu-latest-16-cores + # timeout-minutes: 30 + # needs: [revn-variations, test-container-info, container-stability-check, smoke-tests] + # container: + # image: ${{ needs.revn-variations.outputs.test_container }} # zizmor: ignore[unpinned-images] + # options: --entrypoint /bin/bash + # strategy: + # fail-fast: false + # matrix: + # python-version: ['3.10', '3.11', '3.12', '3.13'] + # permissions: + # contents: read + # packages: read + # checks: write + # steps: + # - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + # with: + # persist-credentials: false + + # - name: Set up Python + # shell: bash + # env: + # MAIN_PYTHON_VERSION: ${{ env.MAIN_PYTHON_VERSION }} + # run: | + # apt update + # apt install lsb-release xvfb git curl make -y + # curl -LsSf https://astral.sh/uv/install.sh | sh + # export PATH="$HOME/.local/bin:$PATH" + # uv python install python${MAIN_PYTHON_VERSION} + # uv venv /env + # - name: "Install packages for testing" + # run: | + # . /env/bin/activate + # uv pip install --upgrade pip + # uv pip install -e .[tests] + + # - name: Unit Testing and coverage + # env: + # ANSYS_WORKBENCH_LOGGING_CONSOLE: 0 + # ANSYS_WORKBENCH_LOGGING: 0 + # ANSYS_WORKBENCH_LOGGING_FILTER_LEVEL: 2 + # CONTAINER_STABLE_EXIT: ${{ needs.container-stability-check.outputs.container_stable_exit }} + # MATRIX_PYTHON_VERSION: ${{ matrix.python-version }} + # NUM_CORES: 1 + # PYTHONUNBUFFERED: 1 + # run: | + # . /env/bin/activate + # if [ "${CONTAINER_STABLE_EXIT}" = "true" ]; then + # xvfb-run mechanical-env pytest -m embedding -s --junitxml test_results${MATRIX_PYTHON_VERSION}.xml + # else + # xvfb-run mechanical-env pytest -m embedding -s --junitxml test_results${MATRIX_PYTHON_VERSION}.xml || true + # fi + + # # - name: Upload coverage results + # # uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + # # if: env.MAIN_PYTHON_VERSION == matrix.python-version + # # with: + # # include-hidden-files: true + # # name: coverage-tests-embedding + # # path: .cov + # # retention-days: 7 + + # # - name: Upload coverage results (as .coverage) + # # uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + # # if: env.MAIN_PYTHON_VERSION == matrix.python-version + # # with: + # # include-hidden-files: true + # # name: coverage-file-tests-embedding + # # path: .coverage + # # retention-days: 7 + + # - name: Publish Test Report + # uses: mikepenz/action-junit-report@e08919a3b1fb83a78393dfb775a9c37f17d8eea6 # v6.0.1 + # if: always() + # with: + # report_paths: '**/test_results*.xml' + # check_name: Test Report ${{ matrix.python-version }} + # detailed_summary: true + # include_passed: true + # fail_on_failure: true + + # embedding-scripts-tests: + # name: Embedding scripts testing and coverage + # runs-on: public-ubuntu-latest-16-cores + # timeout-minutes: 30 + # needs: [smoke-tests, revn-variations, test-container-info] + # container: + # image: ${{ needs.revn-variations.outputs.test_container }} # zizmor: ignore[unpinned-images] + # options: --entrypoint /bin/bash + # strategy: + # fail-fast: false + # matrix: + # python-version: ['3.10', '3.11', '3.12', '3.13'] + # permissions: + # contents: read + # packages: read + # checks: write + # steps: + # - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + # with: + # persist-credentials: false + + # - name: Set up Python + # shell: bash + # env: + # MAIN_PYTHON_VERSION: ${{ env.MAIN_PYTHON_VERSION }} + # run: | + # apt update + # apt install lsb-release xvfb git curl make -y + # curl -LsSf https://astral.sh/uv/install.sh | sh + # export PATH="$HOME/.local/bin:$PATH" + # uv python install python${MAIN_PYTHON_VERSION} + # uv venv /env + # - name: "Install packages for testing" + # run: | + # . /env/bin/activate + # uv pip install --upgrade pip + # uv pip install -e .[tests] + + # - name: Embedding scripts unit testing and coverage + # env: + # ANSYS_WORKBENCH_LOGGING_CONSOLE: 0 + # ANSYS_WORKBENCH_LOGGING: 0 + # ANSYS_WORKBENCH_LOGGING_FILTER_LEVEL: 2 + # MATRIX_PYTHON_VERSION: ${{ matrix.python-version }} + # NUM_CORES: 1 + # PYTHONUNBUFFERED: 1 + # run: | + # . /env/bin/activate + # mechanical-env pytest -m embedding_scripts -s --junitxml test_results_embedding_scripts${MAIN_PYTHON_VERSION}.xml + # pytest -m cli -s --junitxml test_results_cli_scripts${MAIN_PYTHON_VERSION}.xml + + # # - name: Upload coverage results + # # uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + # # if: env.MAIN_PYTHON_VERSION == matrix.python-version + # # with: + # # include-hidden-files: true + # # name: coverage-tests-embedding-scripts + # # path: .cov + # # retention-days: 7 + + # # - name: Upload coverage results (as .coverage) + # # uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + # # if: env.MAIN_PYTHON_VERSION == matrix.python-version + # # with: + # # include-hidden-files: true + # # name: coverage-file-tests-embedding-scripts + # # path: .coverage + # # retention-days: 7 + + # - name: Publish Test Report + # uses: mikepenz/action-junit-report@e08919a3b1fb83a78393dfb775a9c37f17d8eea6 # v6.0.1 + # if: always() + # with: + # report_paths: '**/test_results*.xml' + # check_name: Test Report ${{ matrix.python-version }} + # detailed_summary: true + # include_passed: true + # fail_on_failure: true + + # launch-tests: + # name: Launch testing and coverage + # runs-on: public-ubuntu-latest-16-cores + # timeout-minutes: 30 + # container: + # image: ${{ needs.revn-variations.outputs.test_container }} # zizmor: ignore[unpinned-images] + # options: --entrypoint /bin/bash + # needs: [ style, revn-variations, container-stability-check, test-container-info] + # strategy: + # fail-fast: false + # matrix: + # python-version: ['3.10', '3.11', '3.12', '3.13'] + # permissions: + # contents: read + # packages: read + # checks: write + # steps: + # - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + # with: + # persist-credentials: false + + # - name: Set up Python + # shell: bash + # env: + # MAIN_PYTHON_VERSION: ${{ env.MAIN_PYTHON_VERSION }} + # run: | + # apt update + # apt install lsb-release xvfb git curl make -y + # curl -LsSf https://astral.sh/uv/install.sh | sh + # export PATH="$HOME/.local/bin:$PATH" + # uv python install python${MAIN_PYTHON_VERSION} + # uv venv /env + # - name: "Install packages for testing" + # run: | + # . /env/bin/activate + # uv pip install --upgrade pip + # uv pip install -e .[tests] + + # - name: Set environment variable + # env: + # TEST_REVN: ${{ needs.revn-variations.outputs.test_revn }} + # run: echo "ANSYSCL${TEST_REVN}_DIR=/install/ansys_inc/v${TEST_REVN}/licensingclient" >> $GITHUB_ENV + + # - name: Unit Testing and coverage + # env: + # ANSYS_WORKBENCH_LOGGING_CONSOLE: 0 + # CONTAINER_STABLE_EXIT: ${{ needs.container-stability-check.outputs.container_stable_exit }} + # MATRIX_PYTHON_VERSION: ${{ matrix.python-version }} + # run: | + # unset PYMECHANICAL_PORT + # unset PYMECHANICAL_START_INSTANCE + # . /env/bin/activate + # if [ "${CONTAINER_STABLE_EXIT}" = "true" ]; then + # pytest -m remote_session_launch -s --junitxml launch_test_results${MATRIX_PYTHON_VERSION}.xml + # else + # pytest -m remote_session_launch -s --junitxml launch_test_results${MATRIX_PYTHON_VERSION}.xml || true + # fi + + # - name: Publish Launch Test Report + # uses: mikepenz/action-junit-report@e08919a3b1fb83a78393dfb775a9c37f17d8eea6 # v6.0.1 + # if: always() + # with: + # report_paths: '**/launch_test_results*.xml' + # check_name: Launch Test Report ${{ matrix.python-version }} + # detailed_summary: true + # include_passed: true + # fail_on_failure: true + + # # - name: Upload coverage results + # # uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + # # if: env.MAIN_PYTHON_VERSION == matrix.python-version + # # with: + # # include-hidden-files: true + # # name: coverage-tests-remote-session-launch + # # path: .cov + # # retention-days: 7 + + # # - name: Upload coverage results (as .coverage) + # # uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + # # if: env.MAIN_PYTHON_VERSION == matrix.python-version + # # with: + # # include-hidden-files: true + # # name: coverage-file-tests-remote-session-launch + # # path: .coverage + # # retention-days: 7 doc-build: name: Documentation @@ -655,7 +655,7 @@ jobs: container: image: ${{ needs.revn-variations.outputs.test_container }} # zizmor: ignore[unpinned-images] options: --entrypoint /bin/bash - needs: [style, doc-style, revn-variations, container-stability-check, test-container-info] + needs: [doc-style, revn-variations, container-stability-check, test-container-info] # style permissions: contents: read packages: read @@ -751,256 +751,256 @@ jobs: path: doc/_build/html retention-days: 7 - doc-deploy-pr: - name: "Deploy PR documentation" - runs-on: ubuntu-latest - needs: [doc-build] - if: always() && (needs.doc-build.result == 'success' || needs.doc-build.result == 'skipped') - permissions: - contents: write - pull-requests: write - steps: - - uses: ansys/actions/doc-deploy-pr@21c9de9bee9692173780696d4a39964f20b9cfa3 # v10.1.5 - with: - cname: ${{ env.DOCUMENTATION_CNAME }} - token: ${{ secrets.GITHUB_TOKEN }} - bot-user: ${{ secrets.PYANSYS_CI_BOT_USERNAME }} - bot-email: ${{ secrets.PYANSYS_CI_BOT_EMAIL }} - maximum-pr-doc-deployments: 10 - - coverage: - name: Merging coverage - needs: [remote-connect, embedding-tests, embedding-scripts-tests, launch-tests] - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - with: - persist-credentials: false - - - name: Set up Python - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 - with: - python-version: ${{ env.MAIN_PYTHON_VERSION }} - - - name: Install coverage - run: | - rm -rf ./env - python -m pip install -U pip - pip install coverage - pip install -e . - - - name: Create common coverage directory - run: mkdir cov-dir - - - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 - with: - name: coverage-file-tests-embedding - path: cov-dir/embedding - - - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 - with: - name: coverage-file-tests-embedding-scripts - path: cov-dir/embedding-scripts - - - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 - with: - name: coverage-file-tests-remote-session-launch - path: cov-dir/launch - - - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 - with: - name: coverage-file-tests - path: cov-dir/normal - - - name: Display structure of downloaded files - run: ls -Ra - - - name: Move files to common location - run: | - mv cov-dir/embedding/.coverage .coverage.Embedding - mv cov-dir/embedding-scripts/.coverage .coverage.EmbeddingScripts - mv cov-dir/launch/.coverage .coverage.Launch - mv cov-dir/normal/.coverage .coverage.Normal - rm -rf cov-dir - - - name: Generate .coveragerc file - run: | - cat > .coveragerc << 'EOF' - - # .coveragerc to control coverage.py - [run] - relative_files = True - - [paths] - source = - src/ansys/mechanical - /opt/hostedtoolcache/**/ansys/mechanical - /usr/local/lib/**/ansys/mechanical - .venv/lib/**/ansys/mechanical - /env/lib/**/ansys/mechanical - EOF - - - name: Run coverage merge and show results - run: | - coverage combine --keep --debug=pathmap --rcfile=.coveragerc - coverage report - coverage html -d .coverage-combined/html - coverage xml -o .coverage-combined/xml - - - name: Upload combined coverage results - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 - with: - include-hidden-files: true - name: combined-coverage-results - path: .coverage-combined - retention-days: 7 - - - name: Upload coverage to Codecov - uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5.5.1 - env: - CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} - with: - files: .coverage-combined/xml - - - name: Upload coverage to Codacy - uses: codacy/codacy-coverage-reporter-action@89d6c85cfafaec52c72b6c5e8b2878d33104c699 # v1.3.0 - with: - project-token: ${{ secrets.CODACY_PROJECT_TOKEN }} - coverage-reports: '.coverage-combined/xml' - - package: - name: Package library - needs: [smoke-tests, remote-connect, embedding-tests, embedding-scripts-tests, doc-build] - runs-on: ubuntu-latest - permissions: - attestations: write - contents: read - id-token: write - steps: - - name: Build library source and wheel artifacts - uses: ansys/actions/build-library@21c9de9bee9692173780696d4a39964f20b9cfa3 # v10.1.5 - with: - library-name: ${{ env.PACKAGE_NAME }} - python-version: ${{ env.MAIN_PYTHON_VERSION }} - attest-provenance: true - - release: - name: Release project to GitHub - if: github.event_name == 'push' && contains(github.ref, 'refs/tags') - needs: [package, update-changelog] - runs-on: ubuntu-latest - permissions: - id-token: write - contents: write - steps: - - name: Release to GitHub - uses: ansys/actions/release-github@21c9de9bee9692173780696d4a39964f20b9cfa3 # v10.1.5 - with: - token: ${{ secrets.GITHUB_TOKEN }} - library-name: ${{ env.PACKAGE_NAME }} - only-code: true - - release-pypi: - name: Release project to PyPI - if: github.event_name == 'push' && contains(github.ref, 'refs/tags') - needs: [package, update-changelog] - runs-on: ubuntu-latest - permissions: - id-token: write - contents: write - steps: - - name: "Download the library artifacts from build-library step" - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v4.3.0 - with: - name: ${{ env.PACKAGE_NAME }}-artifacts - path: ${{ env.PACKAGE_NAME }}-artifacts - - - name: "Upload artifacts to PyPI using trusted publisher" - uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0 - with: - repository-url: "https://upload.pypi.org/legacy/" - print-hash: true - packages-dir: ${{ env.PACKAGE_NAME }}-artifacts - skip-existing: false - - upload_dev_docs: - name: Upload dev documentation - if: github.ref == 'refs/heads/main' - runs-on: ubuntu-latest - needs: [package] - permissions: - contents: write - steps: - - name: Deploy the latest documentation - uses: ansys/actions/doc-deploy-dev@21c9de9bee9692173780696d4a39964f20b9cfa3 # v10.1.5 - with: - cname: ${{ env.DOCUMENTATION_CNAME }} - token: ${{ secrets.PYANSYS_CI_BOT_TOKEN }} - bot-user: ${{ secrets.PYANSYS_CI_BOT_USERNAME }} - bot-email: ${{ secrets.PYANSYS_CI_BOT_EMAIL }} - - upload_docs_release: - name: Upload release documentation - if: github.event_name == 'push' && contains(github.ref, 'refs/tags') - runs-on: ubuntu-latest - needs: [release] - permissions: - contents: write - steps: - - name: Deploy the stable documentation - uses: ansys/actions/doc-deploy-stable@21c9de9bee9692173780696d4a39964f20b9cfa3 # v10.1.5 - with: - cname: ${{ env.DOCUMENTATION_CNAME }} - token: ${{ secrets.PYANSYS_CI_BOT_TOKEN }} - bot-user: ${{ secrets.PYANSYS_CI_BOT_USERNAME }} - bot-email: ${{ secrets.PYANSYS_CI_BOT_EMAIL }} - - get-date: - name: Get date - runs-on: ubuntu-latest - outputs: - date: ${{ steps.date.outputs.date }} - permissions: - contents: read - steps: - - name: Get current date - id: date - run: | - echo "date=$(date --iso-8601)" >> $GITHUB_OUTPUT - - notify-on-failure: - name: Notify on failure - needs: [get-date, embedding-tests, embedding-scripts-tests, launch-tests, remote-connect, doc-build] - if: github.event_name == 'schedule' && (failure() || cancelled()) - runs-on: ubuntu-latest - permissions: - id-token: write - contents: read - steps: - - name: Microsoft Teams Notification - uses: skitionek/notify-microsoft-teams@11e40c38c3a629ae65a985b582eca4897b01e79e # v1.0.9 - with: - webhook_url: ${{ secrets.MSTEAMS_WEBHOOK }} - # Message to send to Teams as a webhook notification in JSON Payload format - raw: >- - { - "type": "message", - "attachments": [ - { - "contentType": "application/vnd.microsoft.card.adaptive", - "content": { - "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", - "type": "AdaptiveCard", - "version": "1.0", - "body": [ - { - "type": "TextBlock", - "text": "**PyMechanical Nightly Run Failed on ${{ needs.get-date.outputs.date }}**\n\n[View details in GitHub Actions](https://github.com/ansys/pymechanical/actions/runs/${{ github.run_id }})", - "wrap": true - } - ] - } - } - ] - } \ No newline at end of file + # doc-deploy-pr: + # name: "Deploy PR documentation" + # runs-on: ubuntu-latest + # needs: [doc-build] + # if: always() && (needs.doc-build.result == 'success' || needs.doc-build.result == 'skipped') + # permissions: + # contents: write + # pull-requests: write + # steps: + # - uses: ansys/actions/doc-deploy-pr@21c9de9bee9692173780696d4a39964f20b9cfa3 # v10.1.5 + # with: + # cname: ${{ env.DOCUMENTATION_CNAME }} + # token: ${{ secrets.GITHUB_TOKEN }} + # bot-user: ${{ secrets.PYANSYS_CI_BOT_USERNAME }} + # bot-email: ${{ secrets.PYANSYS_CI_BOT_EMAIL }} + # maximum-pr-doc-deployments: 10 + + # coverage: + # name: Merging coverage + # needs: [remote-connect, embedding-tests, embedding-scripts-tests, launch-tests] + # runs-on: ubuntu-latest + # steps: + # - name: Checkout repository + # uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + # with: + # persist-credentials: false + + # # - name: Set up Python + # # uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 + # # with: + # # python-version: ${{ env.MAIN_PYTHON_VERSION }} + + # # - name: Install coverage + # # run: | + # # rm -rf ./env + # # python -m pip install -U pip + # # pip install coverage + # # pip install -e . + + # # - name: Create common coverage directory + # # run: mkdir cov-dir + + # - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 + # with: + # name: coverage-file-tests-embedding + # path: cov-dir/embedding + + # - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 + # with: + # name: coverage-file-tests-embedding-scripts + # path: cov-dir/embedding-scripts + + # - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 + # with: + # name: coverage-file-tests-remote-session-launch + # path: cov-dir/launch + + # - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 + # with: + # name: coverage-file-tests + # path: cov-dir/normal + + # # - name: Display structure of downloaded files + # # run: ls -Ra + + # - name: Move files to common location + # run: | + # mv cov-dir/embedding/.coverage .coverage.Embedding + # mv cov-dir/embedding-scripts/.coverage .coverage.EmbeddingScripts + # mv cov-dir/launch/.coverage .coverage.Launch + # mv cov-dir/normal/.coverage .coverage.Normal + # rm -rf cov-dir + + # # - name: Generate .coveragerc file + # # run: | + # # cat > .coveragerc << 'EOF' + + # # # .coveragerc to control coverage.py + # # [run] + # # relative_files = True + + # # [paths] + # # source = + # # src/ansys/mechanical + # # /opt/hostedtoolcache/**/ansys/mechanical + # # /usr/local/lib/**/ansys/mechanical + # # .venv/lib/**/ansys/mechanical + # # /env/lib/**/ansys/mechanical + # # EOF + + # # - name: Run coverage merge and show results + # # run: | + # # coverage combine --keep --debug=pathmap --rcfile=.coveragerc + # # coverage report + # # coverage html -d .coverage-combined/html + # # coverage xml -o .coverage-combined/xml + + # # - name: Upload combined coverage results + # # uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + # # with: + # # include-hidden-files: true + # # name: combined-coverage-results + # # path: .coverage-combined + # # retention-days: 7 + + # # - name: Upload coverage to Codecov + # # uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5.5.1 + # # env: + # # CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + # # with: + # # files: .coverage-combined/xml + + # # - name: Upload coverage to Codacy + # # uses: codacy/codacy-coverage-reporter-action@89d6c85cfafaec52c72b6c5e8b2878d33104c699 # v1.3.0 + # # with: + # # project-token: ${{ secrets.CODACY_PROJECT_TOKEN }} + # # coverage-reports: '.coverage-combined/xml' + + # package: + # name: Package library + # needs: [smoke-tests, remote-connect, embedding-tests, embedding-scripts-tests, doc-build] + # runs-on: ubuntu-latest + # permissions: + # attestations: write + # contents: read + # id-token: write + # steps: + # - name: Build library source and wheel artifacts + # uses: ansys/actions/build-library@21c9de9bee9692173780696d4a39964f20b9cfa3 # v10.1.5 + # with: + # library-name: ${{ env.PACKAGE_NAME }} + # python-version: ${{ env.MAIN_PYTHON_VERSION }} + # attest-provenance: true + + # release: + # name: Release project to GitHub + # if: github.event_name == 'push' && contains(github.ref, 'refs/tags') + # needs: [package, update-changelog] + # runs-on: ubuntu-latest + # permissions: + # id-token: write + # contents: write + # steps: + # - name: Release to GitHub + # uses: ansys/actions/release-github@21c9de9bee9692173780696d4a39964f20b9cfa3 # v10.1.5 + # with: + # token: ${{ secrets.GITHUB_TOKEN }} + # library-name: ${{ env.PACKAGE_NAME }} + # only-code: true + + # release-pypi: + # name: Release project to PyPI + # if: github.event_name == 'push' && contains(github.ref, 'refs/tags') + # needs: [package, update-changelog] + # runs-on: ubuntu-latest + # permissions: + # id-token: write + # contents: write + # steps: + # - name: "Download the library artifacts from build-library step" + # uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v4.3.0 + # with: + # name: ${{ env.PACKAGE_NAME }}-artifacts + # path: ${{ env.PACKAGE_NAME }}-artifacts + + # # - name: "Upload artifacts to PyPI using trusted publisher" + # # uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0 + # # with: + # # repository-url: "https://upload.pypi.org/legacy/" + # # print-hash: true + # # packages-dir: ${{ env.PACKAGE_NAME }}-artifacts + # # skip-existing: false + + # upload_dev_docs: + # name: Upload dev documentation + # if: github.ref == 'refs/heads/main' + # runs-on: ubuntu-latest + # needs: [package] + # permissions: + # contents: write + # steps: + # - name: Deploy the latest documentation + # uses: ansys/actions/doc-deploy-dev@21c9de9bee9692173780696d4a39964f20b9cfa3 # v10.1.5 + # with: + # cname: ${{ env.DOCUMENTATION_CNAME }} + # token: ${{ secrets.PYANSYS_CI_BOT_TOKEN }} + # bot-user: ${{ secrets.PYANSYS_CI_BOT_USERNAME }} + # bot-email: ${{ secrets.PYANSYS_CI_BOT_EMAIL }} + + # upload_docs_release: + # name: Upload release documentation + # if: github.event_name == 'push' && contains(github.ref, 'refs/tags') + # runs-on: ubuntu-latest + # needs: [release] + # permissions: + # contents: write + # steps: + # - name: Deploy the stable documentation + # uses: ansys/actions/doc-deploy-stable@21c9de9bee9692173780696d4a39964f20b9cfa3 # v10.1.5 + # with: + # cname: ${{ env.DOCUMENTATION_CNAME }} + # token: ${{ secrets.PYANSYS_CI_BOT_TOKEN }} + # bot-user: ${{ secrets.PYANSYS_CI_BOT_USERNAME }} + # bot-email: ${{ secrets.PYANSYS_CI_BOT_EMAIL }} + + # get-date: + # name: Get date + # runs-on: ubuntu-latest + # outputs: + # date: ${{ steps.date.outputs.date }} + # permissions: + # contents: read + # steps: + # - name: Get current date + # id: date + # run: | + # echo "date=$(date --iso-8601)" >> $GITHUB_OUTPUT + + # notify-on-failure: + # name: Notify on failure + # needs: [get-date, embedding-tests, embedding-scripts-tests, launch-tests, remote-connect, doc-build] + # if: github.event_name == 'schedule' && (failure() || cancelled()) + # runs-on: ubuntu-latest + # permissions: + # id-token: write + # contents: read + # steps: + # - name: Microsoft Teams Notification + # uses: skitionek/notify-microsoft-teams@11e40c38c3a629ae65a985b582eca4897b01e79e # v1.0.9 + # with: + # webhook_url: ${{ secrets.MSTEAMS_WEBHOOK }} + # # Message to send to Teams as a webhook notification in JSON Payload format + # raw: >- + # { + # "type": "message", + # "attachments": [ + # { + # "contentType": "application/vnd.microsoft.card.adaptive", + # "content": { + # "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + # "type": "AdaptiveCard", + # "version": "1.0", + # "body": [ + # { + # "type": "TextBlock", + # "text": "**PyMechanical Nightly Run Failed on ${{ needs.get-date.outputs.date }}**\n\n[View details in GitHub Actions](https://github.com/ansys/pymechanical/actions/runs/${{ github.run_id }})", + # "wrap": true + # } + # ] + # } + # } + # ] + # } diff --git a/.gitignore b/.gitignore index 45ebf2285..0cff3d997 100644 --- a/.gitignore +++ b/.gitignore @@ -200,6 +200,7 @@ doc/source/api/* # Examples files downloaded when building docs examples/01_basic/out/* +examples/00_setup/out/* # pymechanical-specific mylocal.ip diff --git a/doc/changelog.d/1287.added.md b/doc/changelog.d/1287.added.md new file mode 100644 index 000000000..4401a2e6a --- /dev/null +++ b/doc/changelog.d/1287.added.md @@ -0,0 +1 @@ +Update examples with code snippets diff --git a/doc/source/conf.py b/doc/source/conf.py index c0570d751..09db47c13 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -242,6 +242,7 @@ "sidebar_pages": ["changelog", "index"], }, "ansys_sphinx_theme_autoapi": {"project": project, "templates": "_templates/autoapi"}, + # "show_nav_level": 0, } if BUILD_CHEATSHEET: diff --git a/examples/00_setup/01_setup.py b/examples/00_setup/01_setup.py new file mode 100644 index 000000000..f8ff74f99 --- /dev/null +++ b/examples/00_setup/01_setup.py @@ -0,0 +1,113 @@ +# Copyright (C) 2022 - 2025 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. + +""".. _ref_setup_1: + +App +--- + +This section has helper scripts to start an embedded instance of Mechanical +Application and import/open a file. + +This is a prerequisite step for the other helper scripts in the following pages. + + +""" + +# %% +# Create an embedded instance and open an existing Mechanical File +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +from ansys.mechanical.core import App +from ansys.mechanical.core.examples import delete_downloads, download_file + +mechdat_path = download_file("cantilever.mechdat", "pymechanical", "embedding") + +# The following line creates an instance of the app, extracts the global API entry points, +# and merges them into your Python global variables. + +app = App(db_file=mechdat_path, globals=globals()) +print(app) + +# Alternatively, you can use the update_globals method of the App class to +# update the global variables.The second argument, if set to False updates +# globals without enums like "SelectionTypeEnum" or "LoadDefineBy" +# app = App() +# app.update_globals(globals(), False) + +# For a specific version , use ; +# app = App(version=241) + +# %% +# Import a Geometry File +# ~~~~~~~~~~~~~~~~~~~~~~ + +# sphinx_gallery_start_ignore +app.new() +# sphinx_gallery_end_ignore + +geom_file_path = download_file("example_06_bolt_pret_geom.agdb", "pymechanical", "00_basic") +geometry_import = Model.GeometryImportGroup.AddGeometryImport() +geometry_import_format = Ansys.Mechanical.DataModel.Enums.GeometryImportPreference.Format.Automatic +geometry_import_preferences = Ansys.ACT.Mechanical.Utilities.GeometryImportPreferences() +geometry_import_preferences.ProcessLines = True +geometry_import_preferences.NamedSelectionKey = "" +geometry_import_preferences.ProcessNamedSelections = True +geometry_import_preferences.ProcessMaterialProperties = True +geometry_import.Import(geom_file_path, geometry_import_format, geometry_import_preferences) +# %% +# Set Units +# ~~~~~~~~~~~~~~~~~~~~~~~ + +ExtAPI.Application.ActiveUnitSystem = MechanicalUnitSystem.StandardNMM +ExtAPI.Application.ActiveAngleUnit = AngleUnitType.Radian +ExtAPI.Application.ActiveAngularVelocityUnit = AngularVelocityUnitType.RadianPerSecond +# %% +# View messages in Mechanical using PyMechanical +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +app.messages.show() +# %% +# Plot and Print the Tree (To check model so far) +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +# Plot +app.plot() + +# Print the tree +app.print_tree() + +# %% +# Save the model +# ~~~~~~~~~~~~~~~ +from pathlib import Path + +output_path = Path.cwd() / "out" +test_mechdat_path = str(output_path / "test.mechdat") +app.save_as(test_mechdat_path, overwrite=True) + +# sphinx_gallery_start_ignore +# Close the app +app.close() +# Delete the downloaded files +delete_downloads() +# sphinx_gallery_end_ignore diff --git a/examples/00_setup/03_connections.py b/examples/00_setup/03_connections.py new file mode 100644 index 000000000..eac87ddb1 --- /dev/null +++ b/examples/00_setup/03_connections.py @@ -0,0 +1,207 @@ +# Copyright (C) 2022 - 2025 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. + +""".. _ref_connections: + +Connections +----------- + + +This section contains a few utility scripts for working with Connections. + +""" + +# sphinx_gallery_start_ignore +from ansys.mechanical.core import App +from ansys.mechanical.core.examples import delete_downloads, download_file + +app = App(globals=globals()) + +# Download the geometry file for the example +geom_file_path = download_file("example_06_bolt_pret_geom.agdb", "pymechanical", "00_basic") + +# Import the geometry into the Mechanical model +geometry_import = Model.GeometryImportGroup.AddGeometryImport() +geometry_import_format = Ansys.Mechanical.DataModel.Enums.GeometryImportPreference.Format.Automatic +geometry_import_preferences = Ansys.ACT.Mechanical.Utilities.GeometryImportPreferences() + +# Set preferences for geometry import +geometry_import_preferences.ProcessLines = True +geometry_import_preferences.NamedSelectionKey = "" +geometry_import_preferences.ProcessNamedSelections = True +geometry_import_preferences.ProcessMaterialProperties = True + +# Perform the geometry import +geometry_import.Import(geom_file_path, geometry_import_format, geometry_import_preferences) +# sphinx_gallery_end_ignore + +# Plot the imported geometry +app.plot() + +# Print the tree structure of the Mechanical model +app.print_tree() + +# %% +# Get information about all Contacts Defined +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Retrieve all contact regions defined in the model +all_contacts = Model.Connections.GetChildren(DataModelObjectCategory.ContactRegion, True) + +# Print count of all contact regions +numContacts = all_contacts.Count +print("There are %s contact regions" % (numContacts)) + +# Print details of each contact region +for contact in all_contacts: + print( + f"\n{contact.Parent.Name} > {contact.Name} : {contact.ContactType} : " + f"{contact.Suppressed} : {contact.ContactFormulation}" + ) + print("Contact: ", contact.ContactBodies, list(contact.SourceLocation.Ids)) + print("Target: ", contact.TargetBodies, list(contact.TargetLocation.Ids)) + + +# %% +# Create Automatic Connections on a chosen named selection +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Create a new connection group for automatic connections + +contactgroup = Model.Connections.AddConnectionGroup() +contactgroup.FaceFace = True +contactgroup.FaceEdge = contactgroup.FaceEdge.No +contactgroup.GroupBy = contactgroup.GroupBy.Faces +contactgroup.Priority = contactgroup.Priority.FaceOverEdge +contactgroup.InternalObject.DetectCylindricalFacesType = 1 + +# Retrieve a named selection for the connection group +NSall = Model.NamedSelections.GetChildren[Ansys.ACT.Automation.Mechanical.NamedSelection](True) +my_nsel = [i for i in NSall if i.Name == "bodies_5"][0] + +# Assign the named selection to the connection group and create automatic connections +contactgroup.Location = my_nsel +contactgroup.CreateAutomaticConnections() + +# Refresh the tree structure to reflect the changes +DataModel.Tree.Refresh() + +# %% +# Create a Contact region using face named selections +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Add a new contact region to the model +c = DataModel.Project.Model.Connections +c1 = c.AddContactRegion() + +# Retrieve named selections for the source and target locations +NSall = Model.NamedSelections.GetChildren[Ansys.ACT.Automation.Mechanical.NamedSelection](True) +a = [i for i in NSall if i.Name == "block1_washer_cont"][0] +c1.TargetLocation = a +a = [i for i in NSall if i.Name == "block1_washer_targ"][0] +c1.SourceLocation = a + + +# %% +# Insert a fixed body to ground joint +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +connections = Model.Connections +fixed_joint = connections.AddJoint() +fixed_joint.ConnectionType = JointScopingType.BodyToGround +fixed_joint.Type = JointType.Fixed +fixed_joint.MobileLocation = app.DataModel.GetObjectsByName("block1_washer_cont")[0] + + +# %% +# Insert a Joint using face IDs +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Define face IDs for the joint +face1 = 135 +face2 = 160 + + +from pathlib import Path # delete + +output_path = Path.cwd() / "out" # delete +test_mechdat_path = str(output_path / "temporarycheck.mechdat") # delete +app.save_as(test_mechdat_path, overwrite=True) # delete + +# Add a new joint to the model +j = Model.Connections.AddJoint() + +# Define the reference and mobile locations for the joint using face IDs +reference_scoping = ExtAPI.SelectionManager.CreateSelectionInfo(SelectionTypeEnum.GeometryEntities) +reference_scoping.Ids = [face1] +j.ReferenceLocation = reference_scoping + +mobile_scoping = ExtAPI.SelectionManager.CreateSelectionInfo(SelectionTypeEnum.GeometryEntities) +mobile_scoping.Ids = [face2] +j.MobileLocation = mobile_scoping + +# %% +# Define a ground to body spring with 1 N/m stiffness scoped to preexisting named selection +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +connections = Model.Connections +spring = connections.AddSpring() +spring.ConnectionType = JointScopingType.BodyToGround +spring.LongitudinalStiffness = Quantity(1, "N m^-1") +spring.MobileScopingMethod = GeometryDefineByType.Component +spring.MobileScopeLocation = app.DataModel.GetObjectsByName("block1_washer_cont")[0] + + +# %% +# Define a Bearing +# ~~~~~~~~~~~~~~~~ +# Add a new bearing connection to the model +brg = Model.Connections.AddBearing() + +# Set the reference rotation plane for the bearing +brg.ReferenceRotationPlane = RotationPlane.XY + +# Define stiffness values for the bearing +brg.StiffnessK11.Output.DiscreteValues = [Quantity("11 [N/m]")] +brg.StiffnessK22.Output.DiscreteValues = [Quantity("22 [N/m]")] +brg.StiffnessK21.Output.DiscreteValues = [Quantity("21 [N/m]")] +brg.StiffnessK12.Output.DiscreteValues = [Quantity("12 [N/m]")] + +# Define damping values for the bearing +brg.DampingC11.Output.DiscreteValues = [Quantity("111 [N sec m^-1]")] +brg.DampingC22.Output.DiscreteValues = [Quantity("122 [N sec m^-1]")] +brg.DampingC12.Output.DiscreteValues = [Quantity("112 [N sec m^-1]")] +brg.DampingC21.Output.DiscreteValues = [Quantity("121 [N sec m^-1]")] + +# Retrieve named selections for the reference and mobile locations +NSall = Model.NamedSelections.GetChildren[Ansys.ACT.Automation.Mechanical.NamedSelection](True) +brg.ReferenceLocation = [i for i in NSall if i.Name == "shank_surface"][0] +brg.MobileLocation = [i for i in NSall if i.Name == "shank_surface"][0] + + +# sphinx_gallery_start_ignore +# Save the Mechanical database file +from pathlib import Path + +output_path = Path.cwd() / "out" +test_mechdat_path = str(output_path / "test.mechdat") +# app.save_as(test_mechdat_path, overwrite=True) + + +# Close the application and delete downloaded files +app.close() +delete_downloads() +# sphinx_gallery_end_ignore diff --git a/examples/00_setup/06_loads.py b/examples/00_setup/06_loads.py new file mode 100644 index 000000000..6fcd70b9d --- /dev/null +++ b/examples/00_setup/06_loads.py @@ -0,0 +1,454 @@ +# Copyright (C) 2022 - 2025 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. + +""".. _ref_loads: + +Loads and BCs +------------- + +This script contains helper examples for applying loads and boundary +conditions in Ansys Mechanical. Analysis Settings too are covered here. +""" + +# sphinx_gallery_start_ignore + +from ansys.mechanical.core import App +from ansys.mechanical.core.examples import delete_downloads, download_file + +app = App(globals=globals()) +geom_file_path = download_file("example_06_bolt_pret_geom.agdb", "pymechanical", "00_basic") +geometry_import = Model.GeometryImportGroup.AddGeometryImport() +geometry_import_format = Ansys.Mechanical.DataModel.Enums.GeometryImportPreference.Format.Automatic +geometry_import_preferences = Ansys.ACT.Mechanical.Utilities.GeometryImportPreferences() +geometry_import_preferences.ProcessLines = True +geometry_import_preferences.NamedSelectionKey = "" +geometry_import_preferences.ProcessNamedSelections = True +geometry_import_preferences.ProcessMaterialProperties = True +geometry_import.Import(geom_file_path, geometry_import_format, geometry_import_preferences) + +# Generate mesh for the imported geometry +Model.Mesh.GenerateMesh() + +# Create named selections for fixed support and force application +selection_manager = ExtAPI.SelectionManager +selection = ExtAPI.SelectionManager.CreateSelectionInfo(SelectionTypeEnum.GeometryEntities) +selection.Ids = [30] + +ns2 = Model.AddNamedSelection() +ns2.Name = "fixed" +ns2.Location = selection +selection_manager.ClearSelection() + +selection_manager = ExtAPI.SelectionManager +selection = ExtAPI.SelectionManager.CreateSelectionInfo(SelectionTypeEnum.GeometryEntities) +selection.Ids = [94] + +ns2 = Model.AddNamedSelection() +ns2.Name = "force" +ns2.Location = selection +selection_manager.ClearSelection() + +# sphinx_gallery_end_ignore + +# Plot +app.plot() + +# Print the tree +app.print_tree() + +# %% +# Add an Analysis +# ~~~~~~~~~~~~~~~ +# Create a static structural analysis +analysis = Model.AddStaticStructuralAnalysis() + +# %% Apply Bolt Pretension by Face ID +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +analysis_settings = Model.Analyses[0].AnalysisSettings +analysis_settings.NumberOfSteps = 6 + +# Define coordinate system at face ID = 39 +selection = ExtAPI.SelectionManager.CreateSelectionInfo(SelectionTypeEnum.GeometryEntities) +selection.Ids = [39] +csys1 = Model.CoordinateSystems.AddCoordinateSystem() +csys1.OriginLocation = selection +csys1.Name = "cyl" + +# Apply bolt pretension load +pretension = Model.Analyses[0].AddBoltPretension() +pretension.Location = selection +pretension.CoordinateSystem = csys1 +pretension.SetDefineBy(1, BoltLoadDefineBy.Load) +pretension.Preload.Output.SetDiscreteValue(0, Quantity("1500[N]")) +# Lock the bolt for remaining steps +for i in range(2, analysis_settings.NumberOfSteps + 1): + pretension.SetDefineBy(int(i), BoltLoadDefineBy.Lock) + +# %% +# Apply a Fixed Support +# ~~~~~~~~~~~~~~~~~~~~~ +# Define a fixed support boundary condition on a specific geometry entity. + +support = Model.Analyses[0].AddFixedSupport() +support_scoping = ExtAPI.SelectionManager.CreateSelectionInfo(SelectionTypeEnum.GeometryEntities) +support_scoping.Ids = [30] +support.Location = support_scoping + +# %% +# Apply a Pressure on the First Face of the First Body +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +pressure = Model.Analyses[0].AddPressure() +part1 = Model.Geometry.Children[0] +body1 = part1.Children[0] +face1 = body1.GetGeoBody().Faces[0] # First face of first body +selection = ExtAPI.SelectionManager.CreateSelectionInfo(SelectionTypeEnum.GeometryEntities) +selection.Entities = [face1] +pressure.Location = selection +pressure.Magnitude.Inputs[0].DiscreteValues = [Quantity("0 [s]"), Quantity("1 [s]")] +pressure.Magnitude.Output.DiscreteValues = [Quantity("10 [Pa]"), Quantity("20 [Pa]")] + +# %% +# Apply a Pressure as a Formula +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +pressure = Model.Analyses[0].AddPressure() +pressure_scoping = ExtAPI.SelectionManager.CreateSelectionInfo(SelectionTypeEnum.GeometryEntities) +pressure_scoping.Ids = [95] +pressure.Location = pressure_scoping +pressure.Magnitude.Output.Formula = "10*time" + +# %% +# Apply a Force +# ~~~~~~~~~~~~~ +force = Model.Analyses[0].AddForce() +force_scoping = ExtAPI.SelectionManager.CreateSelectionInfo(SelectionTypeEnum.GeometryEntities) +force_scoping.Ids = [63] +force.Location = force_scoping +force.Magnitude.Output.DiscreteValues = [Quantity("11.3 [N]"), Quantity("12.85 [N]")] + +# %% +# Apply Force by Components +# ~~~~~~~~~~~~~~~~~~~~~~~~~ +force = Model.Analyses[0].AddForce() +force_scoping = ExtAPI.SelectionManager.CreateSelectionInfo(SelectionTypeEnum.GeometryEntities) +force_scoping.Ids = [63] +force.Location = force_scoping +force.DefineBy = LoadDefineBy.Components +force.ZComponent.Output.DiscreteValues = [Quantity("0 [N]"), Quantity("-9 [N]")] + +# %% +# Add a remote displacement with 6 degrees of freedom fixed +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +remote_disp = analysis.AddRemoteDisplacement() +remote_disp.XComponent.Output.DiscreteValues = [Quantity("0 [mm]")] +remote_disp.YComponent.Output.DiscreteValues = [Quantity("0 [mm]")] +remote_disp.ZComponent.Output.DiscreteValues = [Quantity("0 [mm]")] +remote_disp.RotationX.Output.DiscreteValues = [Quantity("0 [deg]")] +remote_disp.RotationY.Output.DiscreteValues = [Quantity("0 [deg]")] +remote_disp.RotationZ.Output.DiscreteValues = [Quantity("0 [deg]")] + + +# %% +# Apply Nodal Forces by Components +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +nodes_list = [16, 2329] +force_quantities_list = ["100 [N]", "-200 [N]"] + +# Loop through nodes and apply nodal force +for i in range(len(nodes_list)): + N1 = ExtAPI.SelectionManager.CreateSelectionInfo(SelectionTypeEnum.MeshNodes) + N1.Ids = [nodes_list[i]] + ExtAPI.SelectionManager.NewSelection(N1) + + NS = Model.AddNamedSelection() + NS.Name = "Node_" + str(nodes_list[i]) + + Force1 = Model.Analyses[0].AddNodalForce() + Force1.Location = NS + Force1.Name = "NodeAtNode_" + str(nodes_list[i]) + Force1.YComponent.Output.DiscreteValues = [Quantity(force_quantities_list[i])] + Force1.DivideLoadByNodes = False + +# %% +# Apply Force and Fixed Support using Named Selections +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Create named selections for force and fixed support +selection_manager = ExtAPI.SelectionManager +selection = ExtAPI.SelectionManager.CreateSelectionInfo(SelectionTypeEnum.GeometryEntities) +selection.Ids = [65] + +ns2 = Model.AddNamedSelection() +ns2.Name = "fixed" +ns2.Location = selection +selection_manager.ClearSelection() + +selection = ExtAPI.SelectionManager.CreateSelectionInfo(SelectionTypeEnum.GeometryEntities) +selection.Ids = [65] + +ns2 = Model.AddNamedSelection() +ns2.Name = "force" +ns2.Location = selection +selection_manager.ClearSelection() + +# Retrieve named selections +NSall = Model.NamedSelections.GetChildren[Ansys.ACT.Automation.Mechanical.NamedSelection](True) +for_fixed_supp = [i for i in NSall if i.Name.startswith("fixed")][0] +for_force = [i for i in NSall if i.Name.startswith("force")][0] + +# Apply load and support via named selections +f = Model.Analyses[0].AddForce() +f.Location = for_force +f.Name = "Force1" +f.Magnitude.Output.DiscreteValues = [Quantity("10 [N]")] + +fs = Model.Analyses[0].AddFixedSupport() +fs.Location = for_fixed_supp +fs.Name = "FixedSupport1" + +# %% +# Apply Radiation - Thermal Analysis +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +analysis2 = Model.AddSteadyStateThermalAnalysis() +radn = analysis2.AddRadiation() + +e = radn.Emissivity +e.Output.DiscreteValues = [Quantity("0.36")] + +t = radn.AmbientTemperature +t.Inputs[0].DiscreteValues = [Quantity("0 [sec]"), Quantity("1 [sec]")] +t.Output.DiscreteValues = [Quantity("22 [C]"), Quantity("2302 [C]")] + +# %% +# Add a temperature load applied to a named selection +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +temp_bc = analysis2.AddTemperature() +temp_bc.Location = app.DataModel.GetObjectsByName("fixed")[0] +temp_bc.Magnitude.Output.DiscreteValues = [Quantity("22[C]"), Quantity("60[C]")] +temp_bc.Magnitude.Inputs[0].DiscreteValues = [ + Quantity("0 [sec]"), + Quantity("1 [sec]"), + Quantity("2 [sec]"), +] +temp_bc.Magnitude.Output.DiscreteValues = [Quantity("22[C]"), Quantity("50[C]"), Quantity("80[C]")] + + +# %% +# # %% +# Create a convection load +# ~~~~~~~~~~~~~~~~~~~~~~~~ +# analysis = app.Model.AddSteadyStateThermalAnalysis() +try: + named_sel = app.Model.NamedSelections.Children[0] +except: + print("Named Selection not found") + +convection = Model.Analyses[0].AddConvection() +if named_sel != None: + convection.Location = named_sel + +convection.AmbientTemperature.Inputs[0].DiscreteValues = [ + Quantity("0 [s]"), + Quantity("1 [s]"), +] # Set the time values +convection.AmbientTemperature.Output.DiscreteValues = [ + Quantity("760 [C]"), + Quantity("800 [C]"), +] # Set the Ambient Temperature values +convection.FilmCoefficient.Inputs[0].DiscreteValues = [ + Quantity("0 [s]"), + Quantity("1 [s]"), +] # Set the time values +convection.FilmCoefficient.Output.DiscreteValues = [ + Quantity("100 [W m^-1 m^-1 K^-1]"), + Quantity("150 [W m^-1 m^-1 K^-1]"), +] # Set the HTC values + + +# %% +# Apply Tabular Pressure for 5 Load Steps +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +pressureLoad = Model.Analyses[0].AddPressure() +pressureLoad.Magnitude.Inputs[0].DiscreteValues = [ + Quantity("0 [sec]"), + Quantity("1 [sec]"), + Quantity("2 [sec]"), + Quantity("3 [sec]"), + Quantity("4 [sec]"), + Quantity("5 [sec]"), +] +pressureLoad.Magnitude.Output.DiscreteValues = [ + Quantity("0 [MPa]"), + Quantity("10 [MPa]"), + Quantity("30 [MPa]"), + Quantity("25 [MPa]"), + Quantity("-30 [MPa]"), + Quantity("100 [MPa]"), +] + +# %% +# Applying Direct FE Type Boundary Conditions +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Retrieve coordinate system named "cyl" and named selection "force" +CSall = Model.CoordinateSystems.GetChildren[Ansys.ACT.Automation.Mechanical.CoordinateSystem](True) +a = [i for i in CSall if i.Name == "cyl"][0] +NSall = Model.NamedSelections.GetChildren[Ansys.ACT.Automation.Mechanical.NamedSelection](True) +n = [i for i in NSall if i.Name == "force"][0] + +# Nodal Force +nf = Model.Analyses[0].AddNodalForce() +nf.Location = n +nf.YComponent.Inputs[0].DiscreteValues = [Quantity("0 [sec]"), Quantity("1 [sec]")] +nf.IndependentVariable = LoadVariableVariationType.YValue +nf.XYZFunctionCoordinateSystem = a +nf.YComponent.Output.DiscreteValues = [Quantity("0 [N]"), Quantity("100[N]")] + +# Nodal Displacement +nodal_displacement = Model.Analyses[0].AddNodalDisplacement() +nodal_displacement.Location = n +nodal_displacement.YComponent.Inputs[0].DiscreteValues = [Quantity("0 [sec]"), Quantity("1 [sec]")] +nodal_displacement.IndependentVariable = LoadVariableVariationType.YValue +nodal_displacement.XYZFunctionCoordinateSystem = a +nodal_displacement.YComponent.Output.DiscreteValues = [Quantity("0 [mm]"), Quantity("100[mm]")] + +# Nodal Pressure +nodal_pressure = Model.Analyses[0].AddNodalPressure() +nodal_pressure.Location = n +nodal_pressure.Magnitude.Inputs[0].DiscreteValues = [Quantity("0 [sec]"), Quantity("1 [sec]")] +nodal_pressure.IndependentVariable = LoadVariableVariationType.YValue +nodal_pressure.XYZFunctionCoordinateSystem = a +nodal_pressure.Magnitude.Output.DiscreteValues = [Quantity("0 [Pa]"), Quantity("100[Pa]")] + + +# %% +# Set Automatic Time Stepping setting for a specific Load Step +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +analysis_settings = Model.Analyses[0].AnalysisSettings +analysis_settings.CurrentStepNumber = 1 +print(analysis_settings.AutomaticTimeStepping) + + +# %% +# Set Step end time +# ~~~~~~~~~~~~~~~~~ +analysis_settings.CurrentStepNumber = 5 +analysis_settings.StepEndTime = Quantity("0.1 [sec]") + +# %% +# Define Load steps with end times +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +analysis_settings.NumberOfSteps = 3 + +analysis_settings.CurrentStepNumber = 1 +analysis_settings.StepEndTime = Quantity("1.0 [sec]") + +analysis_settings.CurrentStepNumber = 2 +analysis_settings.StepEndTime = Quantity("10.0 [sec]") + +analysis_settings.CurrentStepNumber = 3 +analysis_settings.StepEndTime = Quantity("100.0 [sec]") + + +# %% +# Define substep sizing using times +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +analysis_settings.CurrentStepNumber = 1 +analysis_settings.StepEndTime = Quantity("0.1 [sec]") +analysis_settings.AutomaticTimeStepping = analysis_settings.AutomaticTimeStepping.On +analysis_settings.DefineBy = analysis_settings.DefineBy.Time +analysis_settings.InitialTimeStep = Quantity("0.005 [s]") +analysis_settings.MaximumTimeStep = Quantity("0.5 [s]") +analysis_settings.MinimumTimeStep = Quantity("0.0005 [s]") + + +# %% +# Define substep sizing using steps +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +analysis_settings.CurrentStepNumber = 1 +analysis_settings.StepEndTime = Quantity("0.1 [sec]") +analysis_settings.AutomaticTimeStepping = analysis_settings.AutomaticTimeStepping.On +analysis_settings.DefineBy = analysis_settings.DefineBy.Substeps +analysis_settings.InitialSubsteps = 15 +analysis_settings.MinimumSubsteps = 5 +analysis_settings.MaximumSubsteps = 50 + +# %% +# Set Iterative solver type for solution +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +analysis_settings.SolverType = SolverType.Iterative + +# other options from dir(SolverType) : +# analysis_settings.SolverType = SolverType.Direct +# analysis_settings.SolverType = SolverType.FullDamped +# analysis_settings.SolverType=SolverType.ProgramControlled +# analysis_settings.SolverType=SolverType.ReducedDamped +# analysis_settings.SolverType=SolverType.Subspace +# analysis_settings.SolverType=SolverType.Supernode +# analysis_settings.SolverType=SolverType.Unsymmetric + + +# %% +# Change the solver unit system +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +analysis_settings.SolverUnits = SolverUnitsControlType.Manual +analysis_settings.SolverUnitSystem = WBUnitSystemType.ConsistentMKS + +# %% +# Get path to the Solver files directory +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +solve_dir = analysis_settings.ScratchSolverFilesDirectory +print(solve_dir) + +# To get path to the scratch Solver files directory for an unsaved file +# solve_dir = analysis_settings.SolverFilesDirectory + + +# %% +# Solve an analysis +# ~~~~~~~~~~~~~~~~~ +Model.Analyses[0].Solution.Activate() +Model.Analyses[0].Solution.Solve(True) + + +# %% +# Set the step end time and time steps in Transient structural analysis +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# analysis = app.Model.AddTransientStructuralAnalysis() +analysis_settings = analysis.AnalysisSettings +analysis_settings.SetStepEndTime(1, Quantity("0.4 [s]")) +analysis_settings.SetInitialTimeStep(1, Quantity("0.0001 [s]")) +analysis_settings.SetMinimumTimeStep(1, Quantity("0.0000001 [s]")) +analysis_settings.SetMaximumTimeStep(1, Quantity("0.01 [s]")) + + +# sphinx_gallery_start_ignore +# Save the project as a mechdat file (currently commented out) + +from pathlib import Path + +output_path = Path.cwd() / "out" +test_mechdat_path = str(output_path / "test_loads.mechdat") +# app.save_as(test_mechdat_path, overwrite=True) + +# Close Mechanical application +app.close() +# Delete downloaded files +delete_downloads() +# sphinx_gallery_end_ignore diff --git a/examples/00_setup/07_results.py b/examples/00_setup/07_results.py new file mode 100644 index 000000000..8b04c4aa2 --- /dev/null +++ b/examples/00_setup/07_results.py @@ -0,0 +1,192 @@ +# Copyright (C) 2022 - 2025 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. + +""".. _ref_results: + +Results +------- + +This section contains a few utility scripts for result objects. + +""" + +# sphinx_gallery_start_ignore +import os + +# Import Mechanical API core and example utilities +from ansys.mechanical.core import App +from ansys.mechanical.core.examples import delete_downloads, download_file + +# Download an example Mechanical database (.mechdat file) +mechdat_path = download_file("example_03_simple_bolt_new.mechdat", "pymechanical", "00_basic") + +# Launch Mechanical and open the example database +app = App(db_file=mechdat_path, globals=globals()) +print(app) + + +# sphinx_gallery_end_ignore + + +# Plot the geometry of the model +app.plot() + +# Print the Mechanical tree structure +app.print_tree() + +# %% +# Solve +# ~~~~~ +# Access the first analysis system (Static Structural) +static_struct = app.DataModel.AnalysisList[0] + +# Clear any previously generated solution data +static_struct.Solution.ClearGeneratedData() +print("Solution Status:", static_struct.Solution.Status) + +# Run the solver +static_struct.Solution.Solve() +print("Solution Status:", static_struct.Solution.Status) + + +# %% +# Results that are accessible for GetResult +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# List names of results already available in the solution +result_names = static_struct.GetResultsData().ResultNames +print("Available Results:", ", ".join(result_names)) + + +# %% +# List Result Objects that can be added +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Show all available result objects that can be inserted into the solution +all_results = [x for x in dir(static_struct) if str(x)[:3] == "Add"] +print(all_results) + +# %% +# Insert a Result +# ~~~~~~~~~~~~~~~ + +# Add a Total Deformation result and evaluate it +total_deformation = static_struct.Solution.AddTotalDeformation() +total_deformation.EvaluateAllResults() + + +# %% Access max and min of a result +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Retrieve the minimum and maximum values for total deformation +minimum_deformation = total_deformation.Minimum +maximum_deformation = total_deformation.Maximum +print(f"Minimum Deformation: {minimum_deformation}") +print(f"Maximum Deformation: {maximum_deformation}") + +# %% +# Get Results by Node Number +# ~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Extract displacement ("U") results for a specific node ID +node_number = 2000 +result_data = static_struct.GetResultsData() +node_values = result_data.GetResult("U").GetNodeValues(node_number) +print(f"Node {node_number} Values:", node_values) + +# %% +# Access other results +# ~~~~~~~~~~~~~~~~~~~~ + + +# Insert a command snippet into the solution (for custom APDL/commands) +cs = static_struct.Solution.AddCommandSnippet() + +# %% +# Fatigue Results +# ~~~~~~~~~~~~~~~ +# Add a Fatigue Tool to the solution + +solution = static_struct.Solution +fatigue_tool = solution.AddFatigueTool() + +# Insert a Safety Factor calculation under the fatigue tool +safety_factor = fatigue_tool.AddSafetyFactor() + +# Scope safety factor evaluation to specific mesh nodes +selection = ExtAPI.SelectionManager.CreateSelectionInfo(SelectionTypeEnum.MeshNodes) +selection.Ids = [1, 2] +safety_factor.Location = selection + +# Get the minimum safety factor value +minimum = safety_factor.Minimum +print("Safety Factor Minimum:", minimum) + +# Export safety factor results to a text file +fname = "safety_factor_results.txt" +safety_factor.ExportToTextFile(fname) + +# %% +# User-defined Result +# ~~~~~~~~~~~~~~~~~~~ +# Insert a User-Defined Result object +user_result = static_struct.Solution.AddUserDefinedResult() +print("User-defined Result Added:", user_result) + +# %% +# Get number of result sets +# ~~~~~~~~~~~~~~~~~~~~~~~~~ +# Retrieve the number of available result sets (time/frequency steps) + +reader = static_struct.GetResultsData() +result_set_count = reader.ListTimeFreq.Count +print("Number of Result Sets:", result_set_count) +reader.Dispose() + +# %% +# Export all results in the tree to PNG (2D image) files +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Iterate over all result objects and export each one to an image +results = DataModel.GetObjectsByType(DataModelObjectCategory.Result) +for result in results: + result.Activate() + Graphics.Camera.SetSpecificViewOrientation(ViewOrientationType.Front) + image_path = os.path.join(os.getcwd(), "out", result.Name + ".png") + Graphics.ExportImage( + str(image_path), + GraphicsImageExportFormat.PNG, + Ansys.Mechanical.Graphics.GraphicsImageExportSettings(), + ) +print("Done with Exporting Results") + + +# sphinx_gallery_start_ignore +# Save the project as a .mechdat file +from pathlib import Path + +output_path = Path.cwd() / "out" +test_mechdat_path = str(output_path / "test.mechdat") +# app.save_as(test_mechdat_path, overwrite=True) + + +# Close Mechanical application + +app.close() +# Delete the downloaded files +delete_downloads() +# sphinx_gallery_end_ignore diff --git a/examples/00_setup/08_tree_objects.py b/examples/00_setup/08_tree_objects.py new file mode 100644 index 000000000..92e337182 --- /dev/null +++ b/examples/00_setup/08_tree_objects.py @@ -0,0 +1,224 @@ +# Copyright (C) 2022 - 2025 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. + +""".. _ref_tree_objects: + +Tree Objects +------------ + +This section contains a few utility scripts for working with Tree Objects. + +""" + +# sphinx_gallery_start_ignore +from ansys.mechanical.core import App +from ansys.mechanical.core.examples import delete_downloads, download_file + +# Initialize the Mechanical application +app = App(globals=globals()) + +# Download and import the geometry file +geom_file_path = download_file("Valve.pmdb", "pymechanical", "embedding") +geometry_import = Model.GeometryImportGroup.AddGeometryImport() +geometry_import_format = Ansys.Mechanical.DataModel.Enums.GeometryImportPreference.Format.Automatic + +# Define geometry import format and preferences +geometry_import_preferences = Ansys.ACT.Mechanical.Utilities.GeometryImportPreferences() +geometry_import_preferences.ProcessLines = True +geometry_import_preferences.NamedSelectionKey = "" +geometry_import_preferences.ProcessNamedSelections = True +geometry_import_preferences.ProcessMaterialProperties = True + +# Import the geometry into the project +geometry_import.Import(geom_file_path, geometry_import_format, geometry_import_preferences) +# sphinx_gallery_end_ignore + + +# Plot the imported geometry +app.plot() + +# Print the Mechanical tree structure +app.print_tree() + + +# %% +# Accessing Geometry +# ~~~~~~~~~~~~~~~~~~ +# Access a specific geometry entity by its ID. + +body = DataModel.GeoData.GeoEntityById(312) + + +# %% +# Accessing Mesh Data +# ~~~~~~~~~~~~~~~~~~~ +# Access specific mesh data by name and ID. + +node = DataModel.MeshDataByName("Global").NodeById(555) +element = DataModel.MeshDataByName("Global").ElementById(444) + +# %% +# Accessing All Objects and Child Objects +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Examples of accessing all objects, bodies, named selections, and contact regions. + +# Retrieve all objects in the tree +AllObj = Model.GetChildren(DataModelObjectCategory.DataModelObject, True) + +# Retrieve all bodies +all_bodies = Model.GetChildren(DataModelObjectCategory.Body, True) + +# Retrieve all named selections +ns_all = DataModel.GetObjectsByType(DataModelObjectCategory.NamedSelections.NamedSelection) + +# Retrieve all contact regions +abc = DataModel.GetObjectsByType(DataModelObjectCategory.ContactRegion) +all_contacts = Model.Connections.GetChildren(DataModelObjectCategory.ContactRegion, True) + +# Access a specific contact region by name +my_contact = [contact for contact in all_contacts if contact.Name == "Contact Region"][0] + +# Retrieve all result objects of a specific type (e.g., Normal Stress) +all_norm_stress = DataModel.GetObjectsByType(DataModelObjectCategory.Result.NormalStress) + +# Retrieve other objects such as remote points and analyses +all_remote_points = DataModel.GetObjectsByType(DataModelObjectCategory.RemotePoint) +ana = DataModel.Tree.GetObjectsByType(DataModelObjectCategory.Analysis) + +# Using the ACT Automation API to retrieve specific objects +all_contacts2 = Model.Connections.GetChildren[ + Ansys.ACT.Automation.Mechanical.Connections.ContactRegion +](True) +all_remote_points2 = Model.GetChildren[Ansys.ACT.Automation.Mechanical.RemotePoint](True) +all_folders = Model.GetChildren[Ansys.ACT.Automation.Mechanical.TreeGroupingFolder](True) + +# %% +# Finding Duplicate Objects by Name +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Identify duplicate objects in the project tree based on their names. + +import collections + +# Retrieve all objects and their names +AllObj = Model.GetChildren(DataModelObjectCategory.DataModelObject, True) +AllObjNames = [x.Name for x in AllObj] + +# Find duplicate names +duplicates_by_name = [item for item, count in collections.Counter(AllObjNames).items() if count > 1] +print(duplicates_by_name) + + +# sphinx_gallery_start_ignore +# Add a static structural analysis and equivalent stress result for testing +static_struct = Model.AddStaticStructuralAnalysis() +static_struct.Solution.AddEquivalentStress() +# sphinx_gallery_end_ignore + + +# %% +# Using DataObjects and GetByName +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +# Access specific data objects by their names. +c1 = "Solution" +c2 = "Equivalent Stress" +c = DataModel.AnalysisList[0].DataObjects.GetByName(c1).DataObjects.GetByName(c2) + +# %% +# Using DataObjects, NamesByType, and GetByName +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Access and filter contact regions based on their properties. + +new_contact_list = [] +dataobjects = DataModel.AnalysisList[0].DataObjects +for group in dataobjects: + print(group.Type) + +# Retrieve names of all contact groups +names = dataobjects.NamesByType("ContactGroup") +for name in names: + connet_data_objects = dataobjects.GetByName(name).DataObjects + c_names = connet_data_objects.Names + for c_name in c_names: + type_c = connet_data_objects.GetByName(c_name).Type + if type_c == "ConnectionGroup": + contacts_list = connet_data_objects.GetByName(c_name).DataObjects.NamesByType( + "ContactRegion" + ) + for contact in contacts_list: + contact_type = ( + connet_data_objects.GetByName(c_name) + .DataObjects.GetByName(contact) + .PropertyValue("ContactType") + ) + contact_state = ( + connet_data_objects.GetByName(c_name) + .DataObjects.GetByName(contact) + .PropertyValue("Suppressed") + ) + if contact_state == 0 and contact_type == 1: + new_contact_list.append(contact) +print(new_contact_list) + +# %% +# Using GetObjectsByName +# ~~~~~~~~~~~~~~~~~~~~~~ +# Access a specific object by its name. +bb = DataModel.GetObjectsByName("Connector\Solid1")[0] + +# %% +# Accessing a Named Selection +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Access specific named selections by their names. + +NSall = Model.NamedSelections.GetChildren[Ansys.ACT.Automation.Mechanical.NamedSelection](True) +my_nsel = [i for i in NSall if i.Name.startswith("NSF")][0] +my_nsel2 = [i for i in NSall if i.Name == "NSInsideFaces"][0] + +# %% +# Get All Unsuppressed Bodies and Point Masses +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Example of getting all unsuppressed bodies and point masses. + +# Retrieve all unsuppressed bodies +all_bodies = Model.GetChildren(DataModelObjectCategory.Body, True) +all_bodies = [i for i in all_bodies if not i.Suppressed] +print(len(all_bodies)) + +# Retrieve all unsuppressed point masses +all_pm = Model.GetChildren(DataModelObjectCategory.PointMass, True) +all_pm = [i for i in all_pm if not i.Suppressed] + + +# sphinx_gallery_start_ignore +# Save the Mechanical database file +from pathlib import Path + +output_path = Path.cwd() / "out" +test_mechdat_path = str(output_path / "test.mechdat") +# app.save_as(test_mechdat_path, overwrite=True) + + +# Close the application and delete downloaded files +app.close() +delete_downloads() +# sphinx_gallery_end_ignore diff --git a/examples/00_setup/readme.txt b/examples/00_setup/readme.txt new file mode 100644 index 000000000..cc85d5066 --- /dev/null +++ b/examples/00_setup/readme.txt @@ -0,0 +1,6 @@ +Setup +----- + + +This directory contains a collection of helper scripts for Embedded Instance of PyMechanical.Each script focuses on a specific stage in the Ansys Mechanical workflow, providing utility and automation for common tasks. + diff --git a/examples_save/02_geometry.py b/examples_save/02_geometry.py new file mode 100644 index 000000000..c897341a7 --- /dev/null +++ b/examples_save/02_geometry.py @@ -0,0 +1,330 @@ +# Copyright (C) 2022 - 2025 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. + +""".. _ref_geometry: + +Geometry +-------- + +This section contains a few utility scripts for working with Geometry, +including importing, analyzing, and accessing geometric data, as well +as utilizing it for downstream preprocessing operations in Mechanical +simulations. Coordinate Systems too are covered here. +""" + +# %% +# Import Geometry +# ~~~~~~~~~~~~~~~ + +import logging + +from ansys.mechanical.core import App +from ansys.mechanical.core.embedding.logger import Configuration +from ansys.mechanical.core.examples import delete_downloads, download_file + +Configuration.configure(level=logging.DEBUG, to_stdout=True, base_directory=None) + +app = App(globals=globals()) + +# Download the geometry file for the example +geom_file_path = download_file("example_06_bolt_pret_geom.agdb", "pymechanical", "00_basic") +# Alternatively, you can specify a local file path +# or geom_file_path = r"C:\geometry.agdb" + +# Import the geometry into the Mechanical model +geometry_import = Model.GeometryImportGroup.AddGeometryImport() +geometry_import_format = Ansys.Mechanical.DataModel.Enums.GeometryImportPreference.Format.Automatic +geometry_import_preferences = Ansys.ACT.Mechanical.Utilities.GeometryImportPreferences() + +# Set preferences for geometry import +geometry_import_preferences.ProcessLines = True +geometry_import_preferences.NamedSelectionKey = "" +geometry_import_preferences.ProcessNamedSelections = True +geometry_import_preferences.ProcessMaterialProperties = True + +# Perform the geometry import +geometry_import.Import(geom_file_path, geometry_import_format, geometry_import_preferences) + +# Plot the imported geometry +app.plot() + +# Print the tree structure of the Mechanical model +app.print_tree() + +# %% +# Get all bodies +# ~~~~~~~~~~~~~~~~~ + +# Retrieve all body objects from the geometry +body_objects = Model.Geometry.GetChildren(DataModelObjectCategory.Body, True) +# Alternatively, use the following method: +# bodies_objects = Model.Geometry.GetChildren(Ansys.ACT.Automation.Mechanical.Body, True) + +# Extract geometric body wrappers for each body object +bodies = [body.GetGeoBody() for body in body_objects] # GeoBodyWrapper +# or +# import itertools +# nested_list = [x.Bodies for x in ExtAPI.DataModel.GeoData.Assemblies[0].AllParts] +# bodies = list(itertools.chain(*nested_list)) + +# Access details of the first body object and its geometric properties +bo = body_objects[0] # Access Object Details and RMB options +b = bodies[0] # Access Geometric Properties: 'Area', 'GeoData', 'Centroid', 'Faces', etc. + +# %% +# Find Body with Largest Volume +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Set the active unit system to Standard NMM +ExtAPI.Application.ActiveUnitSystem = MechanicalUnitSystem.StandardNMM + +# Create a list of body names, volumes, and IDs for unsuppressed bodies +body_names_volumes = [] +for body in body_objects: + if body.Suppressed == 0 and body.Volume: + body_names_volumes.append((body.Name, body.Volume, body.GetGeoBody().Id)) + +# Sort the list and retrieve the body with the largest volume +sorted_name_vol = sorted(body_names_volumes) +bodyname, volu, bodyid = sorted_name_vol.pop() + +# Print details of the largest body +print(f"Unit System is: {ExtAPI.Application.ActiveUnitSystem}") +print(f"Name of the Largest Body: '{bodyname}'") +print(f"Its Volume: {round(volu.Value, 2)} {volu.Unit}") +print(f"Its id: {bodyid}") + +# %% +# Find Body by its ID +# ~~~~~~~~~~~~~~~~~~~~~~ +# Retrieve a body object using its ID +b2 = DataModel.GeoData.GeoEntityById(bodyid) +print(f"Body Name: {b2.Name}, Body Id: {b2.Id}") + +# %% +# Find the Part that the body belongs to +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Retrieve the name of a body and the part it belongs to using its ID +body_name = DataModel.GeoData.GeoEntityById(59).Name +part_name = DataModel.GeoData.GeoEntityById(59).Part.Name +print(f"The Body named '{body_name}' belongs to the part named '{part_name}'") + +# %% +# Find Body by its ID AND print its Faces, Centroid, etc. +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Retrieve a body object and its faces, centroids, etc. +body2 = DataModel.GeoData.GeoEntityById(bodyid) + +# Get face IDs and centroids for each face +face_ids = [face.Id for face in body2.Faces] +centroids_of_each_face = [DataModel.GeoData.GeoEntityById(face_id).Centroid for face_id in face_ids] + +# Print face IDs and their centroids +for face_id, centroid in zip(face_ids, centroids_of_each_face): + print(f"Face ID: {face_id}", f"List: {list(centroid)}") + +# %% +# Get all Vertices +# ~~~~~~~~~~~~~~~~~~~ + +# Retrieve all vertex IDs from the geometry +vertices = [] +geo = DataModel.GeoData +for asm in geo.Assemblies: + for part in asm.Parts: + for body in part.Bodies: + for i in range(0, body.Vertices.Count): + vertices.append(body.Vertices[i].Id) +print(f"Vertices: {vertices}") + +# %% +# Get all edges of a given length +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Retrieve all edges with a specified length +ExtAPI.Application.ActiveUnitSystem = MechanicalUnitSystem.StandardNMM +use_length = 0.100 + +geo = DataModel.GeoData +edgelist = [] + +# Iterate through assemblies, parts, and bodies to find edges of the given length +for asm in geo.Assemblies: + for part in asm.Parts: + for body in part.Bodies: + for edge in body.Edges: + if abs(edge.Length - use_length) <= use_length * 0.01: + edgelist.append(edge.Id) +print(f"Edgelist: {edgelist}") + +# %% +# Get all circular edges of a given radius +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Retrieve all circular edges with a specified radius +import math + +radius = 0.018 # Target radius +circumference = 2 * math.pi * radius # Calculate circumference + +geo = DataModel.GeoData +circlelist = [] + +# Iterate through assemblies, parts, and bodies to find circular edges +for asm in geo.Assemblies: + for part in asm.Parts: + for body in part.Bodies: + for edge in body.Edges: + if ( + abs(edge.Length - circumference) <= circumference * 0.01 + and str(edge.CurveType) == "GeoCurveCircle" + ): + circlelist.append(edge.Id) +print(f"Circle list: {circlelist}") + +# %% +# Get Radius of a selected edge +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Retrieve the radius of a specific edge if it is circular +my_edge = DataModel.GeoData.GeoEntityById(27) +my_edge_radius = my_edge.Radius if str(my_edge.CurveType) == "GeoCurveCircle" else 0.0 +print(f"Edge radius is: {my_edge_radius}") + +# %% +# Create a Named Selection from a list of body Ids +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Create a named selection for a list of body IDs +mylist = [bodyid] + +selection_manager = ExtAPI.SelectionManager +selection = ExtAPI.SelectionManager.CreateSelectionInfo(SelectionTypeEnum.GeometryEntities) +selection.Ids = mylist +selection_manager.NewSelection(selection) + +ns2 = Model.AddNamedSelection() +ns2.Name = "bodies2" +ns2.Location = selection + +# %% +# Find a Named Selection with a prefix +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Retrieve a named selection whose name starts with a specific prefix +NSall = Model.NamedSelections.GetChildren[Ansys.ACT.Automation.Mechanical.NamedSelection](True) +my_nsel = [i for i in NSall if i.Name.startswith("b")][0] +print(f"Named selection name: {my_nsel.Name}") + +# %% +# Create a Named Selection of all bodies with a cylindrical face +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +geo = DataModel.GeoData +cyl_body_ids = [] + +for asm in geo.Assemblies: + for part in asm.Parts: + for body in part.Bodies: + countcyl = 0 + print(f"countcyl {countcyl} for body {body}") + for face in body.Faces: + if ( + face.SurfaceType + == Ansys.ACT.Interfaces.Geometry.GeoSurfaceTypeEnum.GeoSurfaceCylinder + ): + countcyl += 1 + if countcyl != 0: + cyl_body_ids.append(body.Id) + +print(f"Bodies with cylindrical face IDs: {cyl_body_ids}") + +selection_manager = ExtAPI.SelectionManager +selection = ExtAPI.SelectionManager.CreateSelectionInfo(SelectionTypeEnum.GeometryEntities) +selection.Ids = cyl_body_ids + +ns2 = Model.AddNamedSelection() +ns2.Name = "bodies_with_cyl_face" +ns2.Location = selection +selection_manager.ClearSelection() + +print("Cleared selection") + +# %% +# Modify material assignment +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Assign a specific material to all bodies in the model +allbodies = Model.GetChildren(DataModelObjectCategory.Body, True) +for body in allbodies: + body.Material = "Structural Steel" + +# %% +# Get all Coordinate Systems +# ~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Retrieve all coordinate systems in the model +tree_cs = Model.CoordinateSystems + +# %% +# Add a cylindrical coordinate system +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Create a new coordinate system +csys = Model.CoordinateSystems.AddCoordinateSystem() + +print("Added coordinate system") + +# place csys origin at arbitrary (0,25,50) location +csys.SetOriginLocation(Quantity(0, "in"), Quantity(25, "in"), Quantity(50, "in")) +# set primary X axis to arbitrary (1,2,3) direction +csys.PrimaryAxisDirection = Vector3D(1, 2, 3) +# %% +# Add a cartesian coordinate system at a location (0,25,50) inches +# with primary X axis towards an arbitrary (1,2,3) direction +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +csys = Model.CoordinateSystems.AddCoordinateSystem() +# place csys origin at arbitrary (0,25,50) location +csys.SetOriginLocation(Quantity(0, "in"), Quantity(25, "in"), Quantity(50, "in")) +# set primary X axis to arbitrary (1,2,3) direction +csys.PrimaryAxisDirection = Vector3D(1, 2, 3) + +# %% +# Find a coordinate system by name +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Create a new coordinate system +csys = app.Model.CoordinateSystems.AddCoordinateSystem() + +print("Added 2nd coordinate system") + +# place csys origin at arbitrary (0,25,50) location +csys.SetOriginLocation(Quantity(0, "in"), Quantity(25, "in"), Quantity(50, "in")) +# set primary X axis to arbitrary (1,2,3) direction +csys.PrimaryAxisDirection = Vector3D(1, 2, 3) + +# Save the Mechanical database file +from pathlib import Path + +output_path = Path.cwd() / "out" +test_mechdat_path = str(output_path / "test.mechdat") +print("Set mechdat path") + +app.save(test_mechdat_path) +print("Saved mechdat") +# sphinx_gallery_start_ignore +# Close the application and delete downloaded files +app.close() +print("Closed app") +delete_downloads() +print("Deleted downloads") +# sphinx_gallery_end_ignore diff --git a/examples_save/04_named_selections.py b/examples_save/04_named_selections.py new file mode 100644 index 000000000..ce45ce48b --- /dev/null +++ b/examples_save/04_named_selections.py @@ -0,0 +1,189 @@ +# Copyright (C) 2022 - 2025 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. + +""".. _ref_named_selections: + +Named Selections +---------------- + +This section contains a few utility scripts for creating , finding and using Named Selections. +""" + +# sphinx_gallery_start_ignore +from ansys.mechanical.core import App +from ansys.mechanical.core.examples import delete_downloads, download_file + +app = App(globals=globals()) +# Download the geometry file for the example +geom_file_path = download_file("example_05_td26_Rubber_Boot_Seal.agdb", "pymechanical", "00_basic") + +# Import the geometry into the Mechanical model +geometry_import = Model.GeometryImportGroup.AddGeometryImport() +geometry_import_format = Ansys.Mechanical.DataModel.Enums.GeometryImportPreference.Format.Automatic +geometry_import_preferences = Ansys.ACT.Mechanical.Utilities.GeometryImportPreferences() + +# Set preferences for geometry import +geometry_import_preferences.ProcessLines = True +geometry_import_preferences.NamedSelectionKey = "" +geometry_import_preferences.ProcessNamedSelections = True +geometry_import_preferences.ProcessMaterialProperties = True + +# Perform the geometry import +geometry_import.Import(geom_file_path, geometry_import_format, geometry_import_preferences) +# sphinx_gallery_end_ignore + +# Plot the imported geometry +app.plot() + +# Print the tree structure of the Mechanical model +app.print_tree() + + +# %% +# Fetch all Named Selections +# ~~~~~~~~~~~~~~~~~~~~~~~~~~ + +# Retrieve all named selections in the model +nsall = DataModel.GetObjectsByType(DataModelObjectCategory.NamedSelections.NamedSelection) + +# %% +# Delete a named selection +# ~~~~~~~~~~~~~~~~~~~~~~~~ +# Retrieve all named selections in the project +NSall = Model.NamedSelections.GetChildren[Ansys.ACT.Automation.Mechanical.NamedSelection](True) + +# Delete a named selection by its name +a = [i for i in NSall if i.Name == "Top_Face"][0] +a.Delete() + +# Alternative way to delete a named selection by name +b = DataModel.GetObjectsByName("Bottom_Face")[0] +b.Delete() + +# %% +# Create a named selection +# ~~~~~~~~~~~~~~~~~~~~~~~~ +# Create a new named selection for specific geometry entities +selection_manager = ExtAPI.SelectionManager +selection = ExtAPI.SelectionManager.CreateSelectionInfo(SelectionTypeEnum.GeometryEntities) +selection.Ids = [216, 221, 224] + +# Add the named selection to the model +ns2 = Model.AddNamedSelection() +ns2.Name = "faces" # Set the name of the named selection +ns2.Location = selection +selection_manager.ClearSelection() # Clear the selection after creation - delete + +# %% +# Create a Named Selection by Worksheet +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Create a named selection using worksheet criteria +NS1 = Model.AddNamedSelection() +NS1.ScopingMethod = GeometryDefineByType.Worksheet +GenerationCriteria = NS1.GenerationCriteria + +# Let us create a named selection "faces_for_support" +# using worksheet for all faces at y=0 or z=0 m + +# Add criteria to the worksheet for selecting entities +Criterion1 = Ansys.ACT.Automation.Mechanical.NamedSelectionCriterion() +Criterion1.Action = SelectionActionType.Add +Criterion1.EntityType = SelectionType.GeoFace +Criterion1.Criterion = SelectionCriterionType.LocationY +Criterion1.Operator = SelectionOperatorType.Equal +Criterion1.Value = Quantity("0 [m]") +GenerationCriteria.Add(Criterion1) + +Criterion2 = Ansys.ACT.Automation.Mechanical.NamedSelectionCriterion() +Criterion2.Action = SelectionActionType.Add +Criterion2.EntityType = SelectionType.GeoFace +Criterion2.Criterion = SelectionCriterionType.LocationZ +Criterion2.Operator = SelectionOperatorType.Equal +Criterion2.Value = Quantity("0 [m]") +GenerationCriteria.Add(Criterion2) + +# Generate the named selection based on the criteria +NS1.Name = "faces_for_support" +NS1.Generate() + + +# %% +# Extract all the details of a named selection worksheet named "faces_for_support" +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +NSall = app.Model.NamedSelections.GetChildren[Ansys.ACT.Automation.Mechanical.NamedSelection](True) +my_nsel = [i for i in NSall if i.Name == "faces_for_support"][0] + +worksheet = my_nsel.GenerationCriteria +for i in range(0, len(list(worksheet))): + print(worksheet[i].Action) + print(worksheet[i].EntityType) + print(worksheet[i].Criterion) + print(worksheet[i].Operator) + print(worksheet[i].Value) + print(worksheet[i].CoordinateSystem.Name) + + +# %% +# Find a Named Selection +# ~~~~~~~~~~~~~~~~~~~~~~ +# Retrieve all named selections in the project +NSall = Model.NamedSelections.GetChildren[Ansys.ACT.Automation.Mechanical.NamedSelection](True) + +# Find a specific named selection by its name +a = [i for i in NSall if i.Name == "Rubber_Bodies30"][0] + +# Access entities in the named selection +entities = a.Entities +print(entities[0].Volume) + +# %% +# Identify Named Selections based on Name and Type +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Retrieve all named selections in the project +NSall = Model.NamedSelections.GetChildren[Ansys.ACT.Automation.Mechanical.NamedSelection](True) + +# Filter named selections based on keywords in their names +keywords = ["Rubber_Bodies30", "Inner_Faces30", "Outer_Faces30"] +ns1 = [i for i in NSall if keywords[0] in i.Name] +ns2 = [i for i in NSall if keywords[1] in i.Name] +ns3 = [i for i in NSall if keywords[2] in i.Name] +filtered = ns1 + ns2 + ns3 + +# Further filter the named selections based on entity type +FaceNsels = [ + i for i in filtered if str(DataModel.GeoData.GeoEntityById(i.Ids[0]).Type) == "GeoFace" +] + + +# sphinx_gallery_start_ignore +# Save the mechdat +from pathlib import Path + +output_path = Path.cwd() / "out" +test_mechdat_path = str(output_path / "test.mechdat") +app.save_as(test_mechdat_path, overwrite=True) + + +# Close the application and delete downloaded files +app.close() +delete_downloads() +# sphinx_gallery_end_ignore diff --git a/examples_save/05_mesh.py b/examples_save/05_mesh.py new file mode 100644 index 000000000..492cf3c64 --- /dev/null +++ b/examples_save/05_mesh.py @@ -0,0 +1,161 @@ +# Copyright (C) 2022 - 2025 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. + +""".. _ref_mesh: + +Mesh +---- + +This section contains a few utility scripts for setting local and global Mesh Controls. + +""" + +# sphinx_gallery_start_ignore +from ansys.mechanical.core import App +from ansys.mechanical.core.examples import delete_downloads, download_file + +app = App(globals=globals()) + +# Download and import the geometry file +geom_file_path = download_file("example_06_bolt_pret_geom.agdb", "pymechanical", "00_basic") +geometry_import = Model.GeometryImportGroup.AddGeometryImport() + +# Define geometry import format and preferences +geometry_import_format = Ansys.Mechanical.DataModel.Enums.GeometryImportPreference.Format.Automatic +geometry_import_preferences = Ansys.ACT.Mechanical.Utilities.GeometryImportPreferences() +geometry_import_preferences.ProcessLines = True +geometry_import_preferences.NamedSelectionKey = "" +geometry_import_preferences.ProcessNamedSelections = True +geometry_import_preferences.ProcessMaterialProperties = True + +# Import the geometry into the project +geometry_import.Import(geom_file_path, geometry_import_format, geometry_import_preferences) +# sphinx_gallery_end_ignore + + +# Plot the imported geometry +app.plot() + +# Print the Mechanical tree structure +app.print_tree() + +# %% +# Set Global Mesh Settings +# ~~~~~~~~~~~~~~~~~~~~~~~~ +mesh = Model.Mesh +mesh.ElementSize = Quantity("37 [mm]") +mesh.ElementOrder = ElementOrder.Linear + + +# %% +# Insert a Local Meshing Control for a Named Selection +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Get all named selections and pick the one named "shank" +NSall = Model.NamedSelections.GetChildren[Ansys.ACT.Automation.Mechanical.NamedSelection](True) +use_nsel = [i for i in NSall if i.Name == "shank"][0] + +# Add an Automatic Method mesh control scoped to the named selection +ms = Model.Mesh.AddAutomaticMethod() +ms.Location = use_nsel +ms.Method = ms.Method.AllTriAllTet +ms.Algorithm = ms.Algorithm.PatchConforming + + +# %% +# Generate Mesh +# ~~~~~~~~~~~~~ +# Generate the mesh and print mesh object state +Model.Mesh.GenerateMesh() +print(Model.Mesh.ObjectState) + + +# %% +# Get Element Count of a meshed body +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Access mesh data +meshdata = DataModel.MeshDataByName("Global") +print(use_nsel.Ids[0]) + +# Retrieve body entity from geometry +geoBody = DataModel.GeoData.GeoEntityById(use_nsel.Ids[0]) +body = Model.Geometry.GetBody(geoBody) + +# Get mesh region corresponding to that body and print element count +meshregion = meshdata.MeshRegionById(geoBody.Id) +print(body.Name, meshregion.ElementCount) + +# %% +# Clear generated mesh +# ~~~~~~~~~~~~~~~~~~~~~~~~ +Model.Mesh.ClearGeneratedData() + + +# %% +# Insert a Sweep Method (Scoping Method: Named Selection) +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Get all named selections and pick the one named "bodies_5" +NSall = Model.NamedSelections.GetChildren[Ansys.ACT.Automation.Mechanical.NamedSelection](True) +use_nsel = [i for i in NSall if i.Name == "bodies_5"][0] + +# Add a sweep meshing method scoped to the named selection +mesh = Model.Mesh +mesh_method = mesh.AddAutomaticMethod() +mesh_method.Location = use_nsel +mesh_method.Method = MethodType.Sweep + +# %% +# Insert a Mesh Sizing Control (Scoping Method: Geometry Selection) +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Get all named selections and pick the one containing "bottom_surface" +NSall = Model.NamedSelections.GetChildren[Ansys.ACT.Automation.Mechanical.NamedSelection](True) +ns = [i for i in NSall if "bottom_surface" in i.Name][0] + +# Extract bottom face and its edges +bot_face = DataModel.GeoData.GeoEntityById(ns.Ids[0]) +body_ids = [edge.Id for edge in bot_face.Edges] + +# Create a geometry selection object from edges +sel = ExtAPI.SelectionManager.CreateSelectionInfo(SelectionTypeEnum.GeometryEntities) +sel.Ids = body_ids + +# Apply mesh sizing control to the selection +mesh = Model.Mesh +mesh_sizing = mesh.AddSizing() +mesh_sizing.Location = sel +mesh_sizing.Behavior = SizingBehavior.Hard + + +# sphinx_gallery_start_ignore +# Save the project as a .mechdat file (currently commented out) + +from pathlib import Path + +output_path = Path.cwd() / "out" +test_mechdat_path = str(output_path / "test5.mechdat") +# app.save_as(test_mechdat_path, overwrite=True) + + +# Close the Mechanical application +app.close() +# Delete any downloaded example files +delete_downloads() +# sphinx_gallery_end_ignore