diff --git a/examples/links/thermal_twinbuilder.py b/examples/links/thermal_twinbuilder.py.disabled similarity index 100% rename from examples/links/thermal_twinbuilder.py rename to examples/links/thermal_twinbuilder.py.disabled diff --git a/pyproject.toml b/pyproject.toml index bef9eb778..e7f440150 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "flit_core.buildapi" [project] name="ansys-motorcad-core" -version = "0.6.dev0" +version = "0.7.dev0" description = "Pythonic interface to Ansys Motor-CAD." readme = "README.rst" requires-python = ">=3.8" @@ -43,7 +43,7 @@ tests = [ doc = [ "Sphinx==8.0.2", "numpydoc==1.8.0", - "ansys-sphinx-theme==1.0.9", + "ansys-sphinx-theme==1.1.2", "sphinx-copybutton==0.5.2", "sphinx-gallery==0.17.1", "matplotlib", diff --git a/src/ansys/motorcad/core/geometry.py b/src/ansys/motorcad/core/geometry.py index 9d4e73fcb..5a76f3d29 100644 --- a/src/ansys/motorcad/core/geometry.py +++ b/src/ansys/motorcad/core/geometry.py @@ -46,11 +46,10 @@ def __init__(self, motorcad_instance=None): self._child_names = [] self._motorcad_instance = motorcad_instance self._region_type = RegionType.adaptive + self.mesh_length = 0 self._linked_region = None self._singular = False - # expect other properties to be implemented here including number duplications, material etc - def __eq__(self, other): """Override the default equals implementation for Region.""" return ( @@ -189,6 +188,9 @@ def _from_json(cls, json, motorcad_instance=None): new_region.parent_name = json["parent_name"] new_region._child_names = json["child_names"] + if "mesh_length" in json: + new_region.mesh_length = json["mesh_length"] + if "singular" in json: new_region._singular = json["singular"] @@ -214,6 +216,7 @@ def _to_json(self): "entities": _convert_entities_to_json(self.entities), "parent_name": self.parent_name, "region_type": self._region_type.value, + "mesh_length": self.mesh_length, "on_boundary": False if self._linked_region is None else True, "singular": self._singular, } @@ -259,8 +262,7 @@ def parent_name(self, name): @property def linked_region(self): - """Get linked duplication/unite region - """ + """Get linked duplication/unite region.""" return self._linked_region @linked_region.setter @@ -270,8 +272,7 @@ def linked_region(self, region): @property def singular(self): - """Get linked duplication/unite region - """ + """Get linked duplication/unite region.""" return self._singular @singular.setter diff --git a/src/ansys/motorcad/core/geometry_drawing.py b/src/ansys/motorcad/core/geometry_drawing.py index c5a25a4cb..e4b1dee70 100644 --- a/src/ansys/motorcad/core/geometry_drawing.py +++ b/src/ansys/motorcad/core/geometry_drawing.py @@ -22,6 +22,7 @@ """Unit containing region drawing functions.""" from copy import deepcopy +import warnings from warnings import warn from ansys.motorcad.core.geometry import Arc, Coordinate, Entity, Line, Region @@ -35,6 +36,8 @@ except ImportError: MATPLOTLIB_AVAILABLE = False +_MAX_RECURSION = 100 + class _RegionDrawing: def __init__(self, ax, stored_coords): @@ -46,11 +49,25 @@ def _get_plot_range(self): x_min, x_max = self.ax.get_xlim() return x_max - x_min - def _find_coord_no_overlap(self, entity_coord): + def _find_coord_no_overlap(self, entity_coord, tried_coords, modifier): # adjust depending on text size # 0.04 good compromise overlap_tol = 0.04 result = deepcopy(entity_coord) + + recursion_depth = len(tried_coords) + + if recursion_depth > _MAX_RECURSION: + return None + + if entity_coord in tried_coords: + # Already tried this coordinate + # Might be flip-flopping between 2 points + # Add a small amount to how far we move the coord to try and force out of pattern + modifier += 0.01 + + tried_coords += [entity_coord] + for stored_coord in self.stored_coords: difference = (entity_coord - stored_coord) / self._get_plot_range() @@ -62,25 +79,35 @@ def _find_coord_no_overlap(self, entity_coord): unit_vector = difference / abs(difference) if abs(difference) < overlap_tol: - result += unit_vector * overlap_tol * self._get_plot_range() * 1.1 - result = self._find_coord_no_overlap(result) + result += unit_vector * overlap_tol * self._get_plot_range() * (1.1 + modifier) + result = self._find_coord_no_overlap(result, tried_coords, modifier) break return result def _plot_text_no_overlap(self, point, text, colour): - new_coord = self._find_coord_no_overlap(point) - self.stored_coords += [new_coord] - self.ax.annotate( - text, - xy=(point.x, point.y), - xytext=( - new_coord.x + 0.04 * self._get_plot_range(), - new_coord.y + 0.04 * self._get_plot_range(), - ), - ha="right", - arrowprops=dict(arrowstyle="->", shrinkA=0, color=colour, alpha=0.5), - color=colour, - ) + # Reset params for recursive function + tried_coords = [] + modifier = 0 + + new_coord = self._find_coord_no_overlap(point, tried_coords, modifier) + + if new_coord is None: + warning_str = "Failed to plot all labels on graph" + warnings.warn(warning_str) + self.ax.set_title("Warning : " + warning_str, color="red") + else: + self.stored_coords += [new_coord] + self.ax.annotate( + text, + xy=(point.x, point.y), + xytext=( + new_coord.x + 0.04 * self._get_plot_range(), + new_coord.y + 0.04 * self._get_plot_range(), + ), + ha="right", + arrowprops=dict(arrowstyle="->", shrinkA=0, color=colour, alpha=0.5), + color=colour, + ) def draw_region(self, region, colour): """Draw a region.""" diff --git a/src/ansys/motorcad/core/methods/adaptive_geometry.py b/src/ansys/motorcad/core/methods/adaptive_geometry.py index acb87f0f7..8abde8f8b 100644 --- a/src/ansys/motorcad/core/methods/adaptive_geometry.py +++ b/src/ansys/motorcad/core/methods/adaptive_geometry.py @@ -21,7 +21,9 @@ # SOFTWARE. """Methods for adaptive geometry.""" -from ansys.motorcad.core.geometry import Region +from warnings import warn + +from ansys.motorcad.core.geometry import Region, RegionMagnet from ansys.motorcad.core.rpc_client_core import is_running_in_internal_scripting @@ -118,6 +120,17 @@ def set_region(self, region): Motor-CAD region object. """ self.connection.ensure_version_at_least("2024.0") + + if isinstance(region, RegionMagnet): + if (region._br_multiplier != 0.0) or (region._magnet_angle != 0.0): + # User has changed magnet properties that do not exist in older Motor-CAD API + if not self.connection.check_version_at_least("2024.2"): + warn("Setting magnet properties is only available in Motor-CAD 2024R2 or later") + + if region.mesh_length != 0: + if not self.connection.check_version_at_least("2025"): + warn("Setting region mesh length is only available in Motor-CAD 2025R1 or later") + raw_region = region._to_json() method = "SetRegion" diff --git a/tests/test_geometry.py b/tests/test_geometry.py index 8dc2789ab..592162b82 100644 --- a/tests/test_geometry.py +++ b/tests/test_geometry.py @@ -42,6 +42,7 @@ _orientation_of_three_points, rt_to_xy, ) +import ansys.motorcad.core.rpc_client_core as rpc_client_core from ansys.motorcad.core.rpc_client_core import DEFAULT_INSTANCE, set_default_instance @@ -351,6 +352,7 @@ def test_region_from_json(): "parent_name": "Insulation", "child_names": ["Duct", "Duct_1"], "region type": "Adaptive Region", + "mesh_length": 0.035, "singular": False, } @@ -365,7 +367,8 @@ def test_region_from_json(): test_region.entities = [] test_region.parent_name = "Insulation" test_region._child_names = ["Duct", "Duct_1"] - test_region.singular = False, + test_region.mesh_length = (0.035,) + test_region.singular = (False,) region = geometry.Region._from_json(raw_region) @@ -384,6 +387,7 @@ def test_region_to_json(): "entities": [], "parent_name": "Insulation", "region_type": "Adaptive Region", + "mesh_length": 0.035, "singular": True, "on_boundary": False, } @@ -398,6 +402,7 @@ def test_region_to_json(): test_region.duplications = 10 test_region.entities = [] test_region.parent_name = "Insulation" + test_region.mesh_length = 0.035 test_region.singular = True test_region.linked_region = None @@ -1912,3 +1917,18 @@ def test_get_set_region_magnet(mc): assert magnet.br_value == 1.31 assert magnet.br_used == 1.31 * 2 assert magnet.region_type == RegionType.magnet + + +def test_get_set_region_compatibility(mc, monkeypatch): + monkeypatch.setattr(mc.connection, "program_version", "2024.1") + monkeypatch.setattr(rpc_client_core, "DONT_CHECK_MOTORCAD_VERSION", False) + test_region = RegionMagnet() + test_region.br_multiplier = 2 + with pytest.warns(UserWarning): + mc.set_region(test_region) + + test_region = Region() + test_region.mesh_length = 0.1 + + with pytest.warns(UserWarning): + mc.set_region(test_region) diff --git a/tests/test_geometry_drawing.py b/tests/test_geometry_drawing.py index f03f025b8..4de6e3ef8 100644 --- a/tests/test_geometry_drawing.py +++ b/tests/test_geometry_drawing.py @@ -19,12 +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 matplotlib import pyplot as plt +import pytest + +from ansys.motorcad.core.geometry import Coordinate, Line, Region +import ansys.motorcad.core.geometry_drawing +from ansys.motorcad.core.geometry_drawing import draw_objects -# from matplotlib import pyplot as plt -# import pytest -# -# from ansys.motorcad.core.geometry import Arc, Coordinate, Line -# from ansys.motorcad.core.geometry_drawing import draw_objects, draw_objects_debug # from ansys.motorcad.core.rpc_client_core import DEFAULT_INSTANCE, set_default_instance drawing_flag = False @@ -35,6 +36,36 @@ def set_drawing_flag(*args, **kwargs): drawing_flag = True +def create_triangle_reg(bottom_left_coord): + region = Region() + region.name = "region " + str(bottom_left_coord) + c1 = bottom_left_coord + c2 = bottom_left_coord + Coordinate(10, 0) + c3 = bottom_left_coord + Coordinate(5, 5) + region.add_entity(Line(c1, c2)) + region.add_entity(Line(c2, c3)) + region.add_entity(Line(c3, c1)) + return region + + +def test_label_recursion(monkeypatch): + # Stop plt.show() blocking tests + global drawing_flag + drawing_flag = False + monkeypatch.setattr(plt, "show", set_drawing_flag) + + # add your geometry template here using PyMotorCAD + r1 = create_triangle_reg(Coordinate(0, 0)) + r2 = create_triangle_reg(Coordinate(0, 0.2)) + r3 = create_triangle_reg(Coordinate(0, 0.1)) + + draw_objects([r1, r2, r3]) + + monkeypatch.setattr(ansys.motorcad.core.geometry_drawing, "_MAX_RECURSION", 1) + with pytest.warns(): + draw_objects([r1, r2, r3]) + + # def test_draw_objects_debug(mc, monkeypatch): # # Just check it runs for now # # Stop plt.show() blocking tests diff --git a/tests/test_graphs.py b/tests/test_graphs.py index 7fa5d2d03..6850e3c61 100644 --- a/tests/test_graphs.py +++ b/tests/test_graphs.py @@ -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, 181, 0) + assert almost_equal(y, 182, 0) def test_get_temperature_graph_point(mc):