diff --git a/.github/workflows/ci_cd.yml b/.github/workflows/ci_cd.yml index 887e63d0f..7d4002be0 100644 --- a/.github/workflows/ci_cd.yml +++ b/.github/workflows/ci_cd.yml @@ -26,7 +26,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Ansys code style checks - uses: ansys/actions/code-style@v6 + uses: ansys/actions/code-style@v8 with: python-version: ${{ env.MAIN_PYTHON_VERSION }} @@ -35,7 +35,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Ansys documentation style checks - uses: ansys/actions/doc-style@v6 + uses: ansys/actions/doc-style@v8 with: token: ${{ secrets.GITHUB_TOKEN }} @@ -50,7 +50,7 @@ jobs: python-version: ['3.8', '3.9', '3.10', '3.11'] steps: - name: Build wheelhouse and perform smoke test - uses: ansys/actions/build-wheelhouse@v6 + uses: ansys/actions/build-wheelhouse@v8 with: library-name: ${{ env.LIBRARY_NAME }} library-namespace: ${{ env.LIBRARY_NAMESPACE }} @@ -116,10 +116,12 @@ jobs: needs: package steps: - name: "Deploy development documentation" - uses: ansys/actions/doc-deploy-dev@v6 + uses: ansys/actions/doc-deploy-dev@v8 with: cname: ${{ env.DOCUMENTATION_CNAME }} token: ${{ secrets.GITHUB_TOKEN }} + bot-user: ${{ secrets.PYANSYS_CI_BOT_USERNAME }} + bot-email: ${{ secrets.PYANSYS_CI_BOT_EMAIL }} doc-deploy-stable: name: Doc stable version deploy @@ -128,11 +130,13 @@ jobs: needs: release steps: - name: "Deploy stable documentation" - uses: ansys/actions/doc-deploy-stable@v6 + uses: ansys/actions/doc-deploy-stable@v8 with: cname: ${{ env.DOCUMENTATION_CNAME }} token: ${{ secrets.GITHUB_TOKEN }} python-version: ${{ env.MAIN_PYTHON_VERSION }} + bot-user: ${{ secrets.PYANSYS_CI_BOT_USERNAME }} + bot-email: ${{ secrets.PYANSYS_CI_BOT_EMAIL }} package: name: Package library @@ -140,7 +144,7 @@ jobs: needs: [doc-build, tests] steps: - name: Build library source and wheel artifacts - uses: ansys/actions/build-library@v6 + uses: ansys/actions/build-library@v8 with: library-name: ${{ env.LIBRARY_NAME }} python-version: ${{ env.MAIN_PYTHON_VERSION }} @@ -152,7 +156,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Release to PyPI repository - uses: ansys/actions/release-pypi-public@v6 + uses: ansys/actions/release-pypi-public@v8 with: library-name: ${{ env.LIBRARY_NAME }} twine-username: __token__ diff --git a/doc/source/methods/geometry_drawing.rst b/doc/source/methods/geometry_drawing.rst new file mode 100644 index 000000000..bbb302d37 --- /dev/null +++ b/doc/source/methods/geometry_drawing.rst @@ -0,0 +1,14 @@ +.. _ref_geometry_drawing: +.. currentmodule:: ansys.motorcad.core.geometry_drawing +Geometry drawing +================ +Geometry drawing functions are used to visualise Motor-CAD +geometry objects using PyMotorCAD. +More information on Adaptive Templates is available +in the :ref:`ref_user_guide` under :ref:`ref_adaptive_templates_UG`. + +.. autosummary:: + :toctree: _autosummary_geometry_drawing + + draw_objects + draw_objects_debug diff --git a/doc/source/methods/geometry_functions.rst b/doc/source/methods/geometry_functions.rst index c2c62fd66..1906d0b81 100644 --- a/doc/source/methods/geometry_functions.rst +++ b/doc/source/methods/geometry_functions.rst @@ -29,9 +29,3 @@ Geometry functions get_entities_have_common_coordinate xy_to_rt rt_to_xy - -.. currentmodule:: ansys.motorcad.core.geometry_drawing -.. autosummary:: - :toctree: _autosummary_geometry_functions - - draw_regions diff --git a/doc/source/methods/index.rst b/doc/source/methods/index.rst index 4a74f36ea..7ff52ace8 100644 --- a/doc/source/methods/index.rst +++ b/doc/source/methods/index.rst @@ -32,6 +32,14 @@ Geometry objects and functions are used for defining and modifying Adaptive Templates geometries in Python. For descriptions of the objects and functions, see :ref:`ref_geometry_functions`. +Geometry drawing +------------------------------ +The ``ansys.motorcad.core.geometry_drawing`` library contains functions for drawing +geometry objects as static visualisations in Python. Geometry drawing is used for plotting +objects such as regions, lines, arcs and coordinates within the x-y plane. Drawing Motor-CAD +geometry objects can make it easier to test and create Adaptive Templates scripts. +For descriptions of the geometry drawing functions, see :ref:`ref_geometry_drawing`. + Geometry shapes ------------------------------ The ``ansys.motorcad.core.geometry_shapes`` library contains geometry functions @@ -47,4 +55,5 @@ For descriptions of the functions, see :ref:`ref_geometry_shapes`. MotorCAD_object MotorCADCompatibility_object geometry_functions + geometry_drawing geometry_shapes diff --git a/examples/links/thermal_twinbuilder.py.disabled b/examples/links/thermal_twinbuilder.py similarity index 97% rename from examples/links/thermal_twinbuilder.py.disabled rename to examples/links/thermal_twinbuilder.py index da6883ddb..ecf41f51b 100644 --- a/examples/links/thermal_twinbuilder.py.disabled +++ b/examples/links/thermal_twinbuilder.py @@ -354,6 +354,23 @@ def updateMotfile(self): ## TB model will not include this logic self.mcad.set_variable("BearingLossSource", 0) + # 9 Workaround for models created in Motor-CAD 2025R1 - ensure the old fluid heat flow + # method is used + try: + heatFlowMethod = self.mcad.get_variable("FluidHeatFlowMethod") + if heatFlowMethod == 1: + # Revert model to use the old fluid heat flow method + warnings.warn( + "The Improved Fluid Heat Flow Method setting in this .mot file is incompatible " + + "with the Twin Builder Thermal ROM. The setting has been changed from " + + "Improved to Original." + ) + self.mcad.set_variable("FluidHeatFlowMethod", 0) + except: + # variable does not exist due to using older version of Motor-CAD + # no need to perform any action + pass + # save the updated model so it is clear which Motor-CAD file can be used to validate # the Twin Builder Motor-CAD ROM component self.motFileName = Path(self.inputMotFilePath).stem + "_TwinModel" @@ -423,12 +440,10 @@ def generateCoolingSystemNetwork(self): connectedNodes = self.returnConnectedNodes( node, self.nodeNumbers_fluid, resistanceMatrix ) - if len(connectedNodes) > 0: - # non isolated node - if node not in graphNodes: - graphNodes.append(node) - for connectedNode in connectedNodes: - graphEdges.append([node, connectedNode]) + if node not in graphNodes: + graphNodes.append(node) + for connectedNode in connectedNodes: + graphEdges.append([node, connectedNode]) G = nx.DiGraph() G.add_nodes_from(graphNodes) diff --git a/pyproject.toml b/pyproject.toml index e7f440150..81ae583df 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,14 +41,14 @@ tests = [ "matplotlib>=3.6.3" ] doc = [ - "Sphinx==8.0.2", + "Sphinx==8.1.3", "numpydoc==1.8.0", - "ansys-sphinx-theme==1.1.2", + "ansys-sphinx-theme==1.1.6", "sphinx-copybutton==0.5.2", - "sphinx-gallery==0.17.1", + "sphinx-gallery==0.18.0", "matplotlib", "scipy", - "pypandoc==1.13", + "pypandoc==1.14", "bezier>=2023.7.28", "networkx" ] diff --git a/src/ansys/motorcad/core/methods/rpc_methods_lab.py b/src/ansys/motorcad/core/methods/rpc_methods_lab.py index 1274c757b..2ded49f3a 100644 --- a/src/ansys/motorcad/core/methods/rpc_methods_lab.py +++ b/src/ansys/motorcad/core/methods/rpc_methods_lab.py @@ -21,6 +21,7 @@ # SOFTWARE. """RPC methods for Motor-CAD Lab.""" + k_num_custom_losses_internal_lab = "NumCustomLossesInternal_Lab" k_custom_loss_name_internal_lab = "CustomLoss_name_internal_lab" k_custom_loss_function_internal_lab = "CustomLoss_Function_Internal_Lab" @@ -291,3 +292,15 @@ def _get_index_from_name(self, name, var_length_array, variable_name): else: raise NameError("Provided name is not listed") return index + + def export_lab_model(self, file_path): + """Export lab model. + + Parameters + ---------- + file_path : str + File path including lab model file name and file extension (.lab) + """ + method = "ExportLabModel" + params = [file_path] + return self.connection.send_and_receive(method, params) diff --git a/src/ansys/motorcad/core/methods/rpc_methods_thermal.py b/src/ansys/motorcad/core/methods/rpc_methods_thermal.py index dd648dd9c..80774c2dc 100644 --- a/src/ansys/motorcad/core/methods/rpc_methods_thermal.py +++ b/src/ansys/motorcad/core/methods/rpc_methods_thermal.py @@ -82,13 +82,21 @@ def save_external_circuit(self, circuit_file_name): return self.connection.send_and_receive(method, params) def save_transient_power_values(self, file_name): - """Save transient power results to a CSV file.""" + """Save transient power results to a text file. + + Text file separator defined using the + ``"ExportTextSeparator"`` parameter (default is semicolon). + """ method = "SaveTransientPowerValues" params = [file_name] return self.connection.send_and_receive(method, params) def save_transient_temperatures(self, file_name): - """Save transient temperature results to a CSV file.""" + """Save transient temperature results to a text file. + + Text file separator defined using the + ``"ExportTextSeparator"`` parameter (default is semicolon). + """ method = "SaveTransientTemperatures" params = [file_name] return self.connection.send_and_receive(method, params) diff --git a/src/ansys/motorcad/core/methods/rpc_methods_variables.py b/src/ansys/motorcad/core/methods/rpc_methods_variables.py index 5e16a315e..80b345e93 100644 --- a/src/ansys/motorcad/core/methods/rpc_methods_variables.py +++ b/src/ansys/motorcad/core/methods/rpc_methods_variables.py @@ -21,6 +21,7 @@ # SOFTWARE. """RPC methods for variables.""" +from warnings import warn class _RpcMethodsVariables: @@ -136,3 +137,18 @@ def set_array_variable(self, array_name, array_index, variable_value): method = "SetArrayVariable" params = [array_name, array_index, {"variant": variable_value}] return self.connection.send_and_receive(method, params) + + def get_file_name(self): + """Get current .mot file name and path. + + Returns + ------- + str + Current .mot file path and name + """ + method = "GetMotorCADFileName" + if self.connection.send_and_receive(method) == "": + warn("No file has been loaded in this MotorCAD instance") + return None + else: + return self.connection.send_and_receive(method) diff --git a/tests/RPC_Test_Common.py b/tests/RPC_Test_Common.py index e50e31254..5aa90043d 100644 --- a/tests/RPC_Test_Common.py +++ b/tests/RPC_Test_Common.py @@ -45,6 +45,10 @@ def almost_equal(a, b, decimal_places=1): return round(a - b, decimal_places) == 0 +def almost_equal_percentage(a, b, percentage): + return abs(a - b) < abs(a * (percentage / 100)) + + def almost_equal_fixed(a, b, allowed_difference=0): return abs(a - b) < +allowed_difference diff --git a/tests/test_geometry.py b/tests/test_geometry.py index 0e9a6f85a..a18b634d4 100644 --- a/tests/test_geometry.py +++ b/tests/test_geometry.py @@ -2277,3 +2277,12 @@ def test_get_set_region_compatibility(mc, monkeypatch): with pytest.warns(UserWarning): mc.set_region(test_region) + + +def test_region_material_assignment(mc): + rotor = mc.get_region("Rotor") + rotor.material = "M470-50A" + + mc.set_region(rotor) + + assert rotor == mc.get_region("Rotor") diff --git a/tests/test_graphs.py b/tests/test_graphs.py index 6850e3c61..0376c240f 100644 --- a/tests/test_graphs.py +++ b/tests/test_graphs.py @@ -22,7 +22,7 @@ # import pytest -from RPC_Test_Common import almost_equal, reset_to_default_file +from RPC_Test_Common import almost_equal, almost_equal_percentage, reset_to_default_file def test_get_magnetic_graph_point(mc): @@ -34,7 +34,7 @@ def test_get_magnetic_graph_point(mc): x, y = mc.get_magnetic_graph_point("TorqueVW", 3) assert almost_equal(x, 360) - assert almost_equal(y, 182, 0) + assert almost_equal_percentage(y, 182, 20) def test_get_temperature_graph_point(mc): diff --git a/tests/test_lab.py b/tests/test_lab.py index 1a92cbb93..021761015 100644 --- a/tests/test_lab.py +++ b/tests/test_lab.py @@ -19,9 +19,13 @@ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. + +from os import path, remove +import time + import pytest -from RPC_Test_Common import reset_to_default_file +from RPC_Test_Common import get_dir_path, reset_to_default_file def test_model_build_lab(mc): @@ -166,3 +170,50 @@ def test_external_custom_loss_functions(mc): mc.remove_external_custom_loss(removed_name) assert mc.get_variable("NumCustomLossesExternal_Lab") == no_external_losses + 1 + + +def test_lab_model_export(mc): + mc.set_variable("MessageDisplayState", 2) + file_path = get_dir_path() + r"\test_files\temp_files\lab_model_export.lab" + + mc.load_template("e3") + + if path.exists(file_path): + remove(file_path) + + assert path.exists(file_path) is False + + mc.export_lab_model(file_path) + + # Exporting the lab model takes a few seconds and so a delay is required before + # asserting the .lab file is present. + checks = 0 + + while checks < 20: + time.sleep(1) + if path.exists(file_path) is False: + checks += 1 + else: + break + + assert path.exists(file_path) is True + + remove(file_path) + + # Checks that a warning is raised if the model build speed has changed + mc.set_variable("LabModel_Saturation_StatorCurrent_Peak", 750) + + with pytest.raises(Exception) as stator_current_changed_error: + mc.export_lab_model(file_path) + + assert "maximum current has changed" in str(stator_current_changed_error) + + # Clears lab model and checks a warning has been raised + mc.clear_model_build_lab() + + with pytest.raises(Exception) as model_not_built_error: + mc.export_lab_model(file_path) + + assert "model has not been built" in str(model_not_built_error) + + reset_to_default_file(mc) diff --git a/tests/test_variables.py b/tests/test_variables.py index c3d32f76a..88f83f6fc 100644 --- a/tests/test_variables.py +++ b/tests/test_variables.py @@ -20,10 +20,12 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +from os import path, remove + import pytest -from RPC_Test_Common import reset_to_default_file -from ansys.motorcad.core import MotorCADError +from RPC_Test_Common import get_dir_path, reset_to_default_file +from ansys.motorcad.core import MotorCAD, MotorCADError def test_get_variable(mc): @@ -123,3 +125,21 @@ def test_restore_compatibility_settings(mc): mc.restore_compatibility_settings() assert mc.get_variable(test_compatibility_setting) == improved_method + + +def test_get_file_name(): + mc = MotorCAD() + + file_path = get_dir_path() + r"\test_files\temp_files\Get_File_Name.mot" + + if path.exists(file_path): + remove(file_path) + + assert path.exists(file_path) is False + + with pytest.warns(): + mc.get_file_name() + + mc.save_to_file(file_path) + assert mc.get_file_name() == file_path + remove(file_path)