From 6a9e830524f0ce1dc8411f6ef6aca1a5abbe0baa Mon Sep 17 00:00:00 2001 From: Jack Davies Date: Tue, 27 May 2025 15:54:27 +0100 Subject: [PATCH 01/29] initial testing --- src/ansys/motorcad/core/methods/adaptive_geometry.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/ansys/motorcad/core/methods/adaptive_geometry.py b/src/ansys/motorcad/core/methods/adaptive_geometry.py index db363aea9..f58e423d9 100644 --- a/src/ansys/motorcad/core/methods/adaptive_geometry.py +++ b/src/ansys/motorcad/core/methods/adaptive_geometry.py @@ -304,3 +304,14 @@ def reset_adaptive_geometry(self): # No need to do this if running internally if not is_running_in_internal_scripting(): return self.connection.send_and_receive(method) + + def get_geometry_tree(self): + """Do placeholder.""" + method = "GetGeometryTree" + return self.connection.send_and_receive(method) + + def set_geometry_tree(self, tree): + """Do placeholder.""" + params = [tree] + method = "SetTree" + return self.connection.send_and_receive(method, params) From ce5606c4f9643472eb7294e4f69b39c1cfba5e04 Mon Sep 17 00:00:00 2001 From: Aidan Crandell Date: Mon, 23 Jun 2025 09:09:17 +0100 Subject: [PATCH 02/29] Geometry and geometrynode outline --- src/ansys/motorcad/core/geometry.py | 8 +- .../core/methods/adaptive_geometry.py | 2 +- .../motorcad/core/methods/geometry_tree.py | 75 +++++++++++++++++++ 3 files changed, 82 insertions(+), 3 deletions(-) create mode 100644 src/ansys/motorcad/core/methods/geometry_tree.py diff --git a/src/ansys/motorcad/core/geometry.py b/src/ansys/motorcad/core/geometry.py index d338de8be..93cf4ea19 100644 --- a/src/ansys/motorcad/core/geometry.py +++ b/src/ansys/motorcad/core/geometry.py @@ -233,7 +233,7 @@ def replace(self, replacement_region): # method to receive region from Motor-CAD and create python object @classmethod - def _from_json(cls, json, motorcad_instance=None): + def _from_json(cls, json, motorcad_instance=None, cls_to_create=None): """Convert the class from a JSON object. Parameters @@ -243,10 +243,14 @@ def _from_json(cls, json, motorcad_instance=None): motorcad_instance : ansys.motorcad.core.MotorCAD Motor-CAD instance to connect to. The default is ``None``. """ + if cls_to_create is None: + cls_to_create = cls has_region_type = "region_type" in json is_magnet = has_region_type and (json["region_type"] == RegionType.magnet.value) if is_magnet: + if cls_to_create is Region: + cls_to_create = RegionMagnet new_region = RegionMagnet(motorcad_instance) new_region._magnet_angle = json["magnet_angle"] new_region._br_multiplier = json["magnet_magfactor"] @@ -285,7 +289,7 @@ def _from_json(cls, json, motorcad_instance=None): if "lamination_type" in json: new_region._lamination_type = json["lamination_type"] - return new_region + return cls_to_create(new_region) # method to convert python object to send to Motor-CAD def _to_json(self): diff --git a/src/ansys/motorcad/core/methods/adaptive_geometry.py b/src/ansys/motorcad/core/methods/adaptive_geometry.py index f58e423d9..3528815fd 100644 --- a/src/ansys/motorcad/core/methods/adaptive_geometry.py +++ b/src/ansys/motorcad/core/methods/adaptive_geometry.py @@ -313,5 +313,5 @@ def get_geometry_tree(self): def set_geometry_tree(self, tree): """Do placeholder.""" params = [tree] - method = "SetTree" + method = "SetGeometryTree" return self.connection.send_and_receive(method, params) diff --git a/src/ansys/motorcad/core/methods/geometry_tree.py b/src/ansys/motorcad/core/methods/geometry_tree.py new file mode 100644 index 000000000..60041467c --- /dev/null +++ b/src/ansys/motorcad/core/methods/geometry_tree.py @@ -0,0 +1,75 @@ +# 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. + +"""Methods for building geometry trees.""" +from ansys.motorcad.core.geometry import Region + + +class GeometryTree(dict): + """.""" + + def __init__(self, tree: dict): + """Initialize tree. + + Parameters + ---------- + tree + """ + self.root = GeometryNode() + self.tree_json = tree["regions"] + + def build_tree(self, region: dict, parent): + """Recursively builds tree. + + Parameters + ---------- + region + parent + """ + # base case + if region["parent_name"]: + pass + + +class GeometryNode(Region): + """.""" + + @classmethod + def _from_json(cls, json, parent): + new_region = super()._from_json(json, cls_to_create=cls) + new_region.parent = parent + return new_region + + @property + def parent(self): + """Get or set parent region. + + Returns + ------- + list of ansys.motorcad.core.geometry.Region + list of Motor-CAD region object + """ + return self._parent + + @parent.setter + def parent(self, parent): + self._parent = parent From 93979a8a076e543587d7857b3fdc24c0daeacc33 Mon Sep 17 00:00:00 2001 From: Aidan Crandell Date: Mon, 23 Jun 2025 15:16:14 +0100 Subject: [PATCH 03/29] Base functionality complete --- src/ansys/motorcad/core/geometry.py | 8 +- .../motorcad/core/methods/geometry_tree.py | 86 ++++++++++++++++--- 2 files changed, 77 insertions(+), 17 deletions(-) diff --git a/src/ansys/motorcad/core/geometry.py b/src/ansys/motorcad/core/geometry.py index 93cf4ea19..4e3c39f37 100644 --- a/src/ansys/motorcad/core/geometry.py +++ b/src/ansys/motorcad/core/geometry.py @@ -243,14 +243,10 @@ def _from_json(cls, json, motorcad_instance=None, cls_to_create=None): motorcad_instance : ansys.motorcad.core.MotorCAD Motor-CAD instance to connect to. The default is ``None``. """ - if cls_to_create is None: - cls_to_create = cls has_region_type = "region_type" in json is_magnet = has_region_type and (json["region_type"] == RegionType.magnet.value) if is_magnet: - if cls_to_create is Region: - cls_to_create = RegionMagnet new_region = RegionMagnet(motorcad_instance) new_region._magnet_angle = json["magnet_angle"] new_region._br_multiplier = json["magnet_magfactor"] @@ -289,7 +285,7 @@ def _from_json(cls, json, motorcad_instance=None, cls_to_create=None): if "lamination_type" in json: new_region._lamination_type = json["lamination_type"] - return cls_to_create(new_region) + return new_region # method to convert python object to send to Motor-CAD def _to_json(self): @@ -381,7 +377,7 @@ def child_names(self): Returns ------- - list of string + list of string\ list of child region names """ return self._child_names diff --git a/src/ansys/motorcad/core/methods/geometry_tree.py b/src/ansys/motorcad/core/methods/geometry_tree.py index 60041467c..f49ebd1a6 100644 --- a/src/ansys/motorcad/core/methods/geometry_tree.py +++ b/src/ansys/motorcad/core/methods/geometry_tree.py @@ -21,7 +21,7 @@ # SOFTWARE. """Methods for building geometry trees.""" -from ansys.motorcad.core.geometry import Region +from ansys.motorcad.core.geometry import Region, RegionType class GeometryTree(dict): @@ -34,29 +34,78 @@ def __init__(self, tree: dict): ---------- tree """ - self.root = GeometryNode() - self.tree_json = tree["regions"] + root = dict() + root["name_unique"] = "root" + root["parent_name"] = "" + root["child_names"] = list() + tree_json = tree["regions"] - def build_tree(self, region: dict, parent): + # properly connect the json file before tree is constructed + for region in tree_json.values(): + if region["parent_name"] == "": + region["parent_name"] = "root" + root["child_names"].append(region["name_unique"]) + + self.build_tree(tree_json, root) + + def build_tree(self, tree_json, node, parent=None): """Recursively builds tree. Parameters ---------- - region - parent + tree_json: dict + node: dict + parent: None or GeometryNode """ # base case - if region["parent_name"]: - pass + self[node["name_unique"]] = GeometryNode._from_json(self, node, parent) + + # recursive case + if node["child_names"] != []: + for child_name in node["child_names"]: + self.build_tree(tree_json, tree_json[child_name], self[node["name_unique"]]) + + def add_region(self, region, name=None, parent=None, children=[]): + """.""" + region.__class__ = GeometryNode + region.children = children + if parent is None: + region.parent = self["root"] + else: + region.parent = parent + if name is None: + self[region.name] = region + else: + self[name] = region + + def remove_region(self, region_name): + """.""" + for child in self[region_name].children: + child.parent = self[region_name].parent + child.parent_name = self[region_name].parent.name + self[region_name].parent.children.append(child) + self.pop(region_name) class GeometryNode(Region): """.""" @classmethod - def _from_json(cls, json, parent): - new_region = super()._from_json(json, cls_to_create=cls) - new_region.parent = parent + def _from_json(cls, tree, node_json, parent): + """Create node from dict.""" + if node_json["name_unique"] == "root": + new_region = GeometryNode(region_type=RegionType.airgap) + new_region.name = "root" + new_region.key = "root" + new_region.children = list() + + else: + new_region = super()._from_json(node_json) + new_region.__class__ = GeometryNode + new_region.parent = parent + new_region.children = list() + parent.children.append(new_region) + new_region.key = node_json["name_unique"] return new_region @property @@ -73,3 +122,18 @@ def parent(self): @parent.setter def parent(self, parent): self._parent = parent + + @property + def children(self): + """Get or set parent region. + + Returns + ------- + list of ansys.motorcad.core.geometry.Region + list of Motor-CAD region object + """ + return self._children + + @children.setter + def children(self, children): + self._children = children From 5af4bafbba710a1ddb892f2de2272ae7412d3e03 Mon Sep 17 00:00:00 2001 From: Aidan Crandell Date: Thu, 26 Jun 2025 15:55:53 +0100 Subject: [PATCH 04/29] Testing implemented, overall improvements --- src/ansys/motorcad/core/geometry.py | 4 +- .../motorcad/core/methods/geometry_tree.py | 266 +- tests/test_geometry_tree.py | 2491 +++++++++++++++++ 3 files changed, 2731 insertions(+), 30 deletions(-) create mode 100644 tests/test_geometry_tree.py diff --git a/src/ansys/motorcad/core/geometry.py b/src/ansys/motorcad/core/geometry.py index 4e3c39f37..6f3ac12cc 100644 --- a/src/ansys/motorcad/core/geometry.py +++ b/src/ansys/motorcad/core/geometry.py @@ -36,7 +36,7 @@ class RegionType(Enum): stator = "Stator" rotor = "Rotor" - slot_area_stator = "Stator Slot" + slot_area_stator = "Stator Slot Area" slot_area_rotor = "Rotor Slot" slot_split = "Split Slot" stator_liner = "Stator Liner" @@ -233,7 +233,7 @@ def replace(self, replacement_region): # method to receive region from Motor-CAD and create python object @classmethod - def _from_json(cls, json, motorcad_instance=None, cls_to_create=None): + def _from_json(cls, json, motorcad_instance=None): """Convert the class from a JSON object. Parameters diff --git a/src/ansys/motorcad/core/methods/geometry_tree.py b/src/ansys/motorcad/core/methods/geometry_tree.py index f49ebd1a6..51608a140 100644 --- a/src/ansys/motorcad/core/methods/geometry_tree.py +++ b/src/ansys/motorcad/core/methods/geometry_tree.py @@ -21,18 +21,77 @@ # SOFTWARE. """Methods for building geometry trees.""" -from ansys.motorcad.core.geometry import Region, RegionType + +from ansys.motorcad.core.geometry import Region, RegionMagnet, RegionType class GeometryTree(dict): - """.""" + """Class used to build geometry trees.""" + + def __init__(self, empty=False): + """Initialize the geometry tree. + + Parameters + ---------- + empty: bool + Return an empty geometry tree, mostly used for the purposes of debugging. + """ + if empty: + super().__init__() + else: + root = GeometryNode(region_type=RegionType.airgap) + root.parent = None + root.children = list() + root.name = "root" + root.key = "root" + pair = [("root", root)] + super().__init__(pair) + + def __eq__(self, other): + """Define equality operator. + + Equality for trees requires both trees have the same structure. + Also requires that each node with the same key is equal. + """ - def __init__(self, tree: dict): + def dive(key): + if len(self[key].children) != len(other[key].children): + return False + for child in self[key].children: + # Making sure each child is in the other corresponding node's children + if not child.key in other[key].child_keys: + return False + if not dive(child.key): + return False + # Actual equality check of nodes + if self[key] != other[key]: + return False + return True + + return dive("root") + + def __ne__(self, other): + """Define inequality.""" + return not self.__eq__(other) + + @classmethod + def from_json(cls, tree): + """Return a GeometryTree representation of the geometry defined within a JSON. + + Parameters + ---------- + tree: dict + JSON to create a tree from (generally, the output of get_geometry_tree()). + Returns + ------- + GeometryTree + """ + self = cls(empty=True) """Initialize tree. Parameters ---------- - tree + tree: dict """ root = dict() root["name_unique"] = "root" @@ -47,6 +106,26 @@ def __init__(self, tree: dict): root["child_names"].append(region["name_unique"]) self.build_tree(tree_json, root) + return self + + def to_json(self): + """Return a dict object used to set geometry.""" + regions = dict() + for key in self: + if key != "root": + if self[key].region_type == "Magnet": + regions[key] = RegionMagnet._to_json(self[key]) + else: + regions[key] = Region._to_json(self[key]) + return {"regions": regions} + + def get_node(self, key): + """Get a region from the tree (case-insensitive).""" + try: + node = self[key] + except KeyError: + node = self[key.capitalize()] + return node def build_tree(self, tree_json, node, parent=None): """Recursively builds tree. @@ -54,53 +133,139 @@ def build_tree(self, tree_json, node, parent=None): Parameters ---------- tree_json: dict + Dictionary containing region dicts node: dict + Information of current region parent: None or GeometryNode """ - # base case - self[node["name_unique"]] = GeometryNode._from_json(self, node, parent) + # Convert current node to GeometryNode and add it to tree + self[node["name_unique"]] = GeometryNode.from_json(self, node, parent) - # recursive case + # Recur for each child. if node["child_names"] != []: for child_name in node["child_names"]: - self.build_tree(tree_json, tree_json[child_name], self[node["name_unique"]]) + self.build_tree( + tree_json, tree_json[child_name], self.get_node(node["name_unique"]) + ) + + def add_node(self, region, key=None, parent=None, children=None): + """Add node to tree. + + Parameters + ---------- + region: ansys.motorcad.core.geometry.Region + Region to convert and add to tree + key: str + Key to be used for dict + parent: GeometryNode or str + Parent object or parent key (must be already within tree) + children: list of GeometryNode or str + Children objects or children keys (must be already within tree) - def add_region(self, region, name=None, parent=None, children=[]): - """.""" + """ region.__class__ = GeometryNode - region.children = children + if children is None: + region.children = list() + else: + if all(isinstance(child, Region) for child in children): + region.children = children + elif all(isinstance(child, str) for child in children): + direct_children = list(self.get_node(child) for child in children) + region.children = direct_children + else: + raise TypeError("Children must be a GeometryNode or str") + # Essentially, slotting the given node in between the given parent and children + for child in region.children: + child.parent.children.remove(child) + child.parent = region + if parent is None: region.parent = self["root"] else: - region.parent = parent - if name is None: + if isinstance(parent, GeometryNode): + region.parent = parent + parent.children.append(region) + elif isinstance(parent, str): + region.parent = self[parent] + self[parent].children.append(region) + else: + raise TypeError("Parent must be a GeometryNode or str") + + if key is None: self[region.name] = region + region.key = region.name else: - self[name] = region + self[key] = region + region.key = key + + def remove_node(self, node): + """Remove Node from tree, attach children of removed node to parent.""" + if type(node) is str: + node = self.get_node(node) + for child in node.children: + child.parent = node.parent + node.parent.children.append(child) + node.parent.children.remove(node) + self.pop(node.key) + + def remove_branch(self, node): + """Remove Node and all descendants from tree.""" + if type(node) == str: + node = self.get_node(node) - def remove_region(self, region_name): - """.""" - for child in self[region_name].children: - child.parent = self[region_name].parent - child.parent_name = self[region_name].parent.name - self[region_name].parent.children.append(child) - self.pop(region_name) + # Recursive inner function to find and remove all descendants + def dive(node): + for child in node.children: + dive(child) + self.pop(node.key) + + dive(node) + node.parent.children.remove(node) class GeometryNode(Region): - """.""" + """Subclass of Region used for entries in GeometryTree.""" + + def __init__(self, region_type=RegionType.adaptive, parent=None, child_nodes=None): + """Initialize the geometry node. + + Parameters + ---------- + region_type: RegionType + parent: GeometryNode + child_nodes: list of GeometryNode + """ + super().__init__(region_type=region_type) + if parent is not None: + self.parent = parent + parent.children.append(self) + else: + self.parent = None + if child_nodes is None: + child_nodes = list() + self.children = child_nodes @classmethod - def _from_json(cls, tree, node_json, parent): - """Create node from dict.""" + def from_json(cls, tree, node_json, parent): + """Create a GeometryNode from JSON data. + + Parameters + ---------- + tree: dict + node_json: dict + parent: GeometryNode + + Returns + ------- + GeometryNode + """ if node_json["name_unique"] == "root": new_region = GeometryNode(region_type=RegionType.airgap) new_region.name = "root" new_region.key = "root" - new_region.children = list() else: - new_region = super()._from_json(node_json) + new_region = Region._from_json(node_json) new_region.__class__ = GeometryNode new_region.parent = parent new_region.children = list() @@ -114,8 +279,7 @@ def parent(self): Returns ------- - list of ansys.motorcad.core.geometry.Region - list of Motor-CAD region object + ansys.motorcad.core.geometry.Region """ return self._parent @@ -137,3 +301,49 @@ def children(self): @children.setter def children(self, children): self._children = children + + @property + def child_keys(self): + """Get list of keys corresponding to child nodes. + + Returns + ------- + list of str + """ + return list(child.key for child in self.children) + + @property + def parent_key(self): + """Get key corresponding to parent node. + + Returns + ------- + str + """ + if self.parent is None: + return "" + else: + return self.parent.key + + @property + def child_names(self): + """Get list of names corresponding to child nodes. + + Returns + ------- + list of str + """ + return list(child.name for child in self.children) + + @property + def parent_name(self): + """Get name corresponding to parent node. + + Returns + ------- + str + """ + if self.parent is None: + return "" + else: + return self.parent.name diff --git a/tests/test_geometry_tree.py b/tests/test_geometry_tree.py new file mode 100644 index 000000000..5f34edd8b --- /dev/null +++ b/tests/test_geometry_tree.py @@ -0,0 +1,2491 @@ +# 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. + + +from copy import deepcopy + +import pytest + +from ansys.motorcad.core import MotorCAD +from ansys.motorcad.core.geometry import Coordinate, Line, Region, RegionType +from ansys.motorcad.core.methods.geometry_tree import GeometryNode, GeometryTree + +mc = MotorCAD(open_new_instance=False) + +mc.set_variable("MessageDisplayState", 2) +mc.set_visible(True) +mc.load_template("e9") +mc.display_screen("Geometry;Radial") + +sample_json1 = { + "regions": { + "ArmatureSlotL1": { + "adaptive_entities": False, + "area": 54.1336003515254, + "centroid": {"x": 77.6745925290051, "y": 3.69028322112561}, + "child_names": [], + "colour": {"b": 0, "g": 255, "r": 255}, + "duplications": 48, + "entities": [ + { + "end": {"x": 67.1737928392437, "y": 4.40280299311749}, + "start": {"x": 86.8137263217585, "y": 5.69007224302245}, + "type": "line", + }, + { + "end": {"x": 67.3148950021716, "y": 2.24999999999998}, + "start": {"x": 67.1737928392437, "y": 4.40280299311749}, + "type": "line", + }, + { + "end": {"x": 84.8936835106123, "y": 2.25000000000002}, + "start": {"x": 67.3148950021716, "y": 2.24999999999998}, + "type": "line", + }, + { + "centre": {"x": 84.8936835106122, "y": 4.25000000000002}, + "end": {"x": 86.8911819461561, "y": 4.35000000000002}, + "radius": 2.00000000000001, + "start": {"x": 84.8936835106123, "y": 2.25000000000002}, + "type": "arc", + }, + { + "centre": {"x": 0, "y": 0}, + "end": {"x": 86.8137263217585, "y": 5.69007224302245}, + "radius": 87, + "start": {"x": 86.8911819461561, "y": 4.35000000000002}, + "type": "arc", + }, + ], + "entities_internal": [], + "entities_polyline_type": 2, + "extrusion_blocks": [], + "incl_region_id": True, + "lamination_type": "", + "linked_regions": [], + "magnet_temp_coeff_method": 0, + "material": "Copper (Pure)", + "material_weight_component_type": 1, + "material_weight_field": "Weight_Total_Copper_Active", + "mesh_length": 0, + "name": "ArmatureSlotL1", + "name_unique": "ArmatureSlotL1", + "parent_name": "", + "region_coordinate": {"x": 82.6541179328448, "y": 3.97003612151121}, + "region_temperature": 20, + "region_type": "Split Slot", + "region_type_mapped": "No type", + "singular": True, + "thermal_loss": 0, + "use_in_weight_calculation": False, + "weight_reduction_factor": 1, + }, + "ArmatureSlotR1": { + "adaptive_entities": False, + "area": 54.1336003515217, + "centroid": {"x": 77.4917542392541, "y": 6.47985645845607}, + "child_names": [], + "colour": {"b": 0, "g": 215, "r": 255}, + "duplications": 48, + "entities": [ + { + "centre": {"x": 0, "y": 0}, + "end": {"x": 86.7156047753705, "y": 7.02878996995535}, + "radius": 87, + "start": {"x": 86.8137263217585, "y": 5.69007224302245}, + "type": "arc", + }, + { + "centre": {"x": 84.7221425966263, "y": 6.86720859133569}, + "end": {"x": 84.4610902121862, "y": 8.8500983140833}, + "radius": 2, + "start": {"x": 86.7156047753705, "y": 7.02878996995535}, + "type": "arc", + }, + { + "end": {"x": 67.0326906763158, "y": 6.55560598623458}, + "start": {"x": 84.4610902121862, "y": 8.8500983140833}, + "type": "line", + }, + { + "end": {"x": 67.1737928392437, "y": 4.40280299311749}, + "start": {"x": 67.0326906763158, "y": 6.55560598623458}, + "type": "line", + }, + { + "end": {"x": 86.8137263217585, "y": 5.69007224302245}, + "start": {"x": 67.1737928392437, "y": 4.40280299311749}, + "type": "line", + }, + ], + "entities_internal": [], + "entities_polyline_type": 2, + "extrusion_blocks": [], + "incl_region_id": True, + "lamination_type": "", + "linked_regions": [], + "magnet_temp_coeff_method": 0, + "material": "Copper (Pure)", + "material_weight_component_type": 1, + "material_weight_field": "Weight_Total_Copper_Active", + "mesh_length": 0, + "name": "ArmatureSlotR1", + "name_unique": "ArmatureSlotR1", + "parent_name": "", + "region_coordinate": {"x": 84.3322623694114, "y": 7.19305649200386}, + "region_temperature": 20, + "region_type": "Split Slot", + "region_type_mapped": "No type", + "singular": True, + "thermal_loss": 0, + "use_in_weight_calculation": False, + "weight_reduction_factor": 1, + }, + "Housing": { + "adaptive_entities": False, + "area": 188.495559215387, + "centroid": {"x": 119.725253764956, "y": 7.84720771818839}, + "child_names": [], + "colour": {"b": 253, "g": 231, "r": 229}, + "duplications": 48, + "entities": [ + {"end": {"x": 126, "y": 0}, "start": {"x": 114, "y": 0}, "type": "line"}, + { + "centre": {"x": 0, "y": 0}, + "end": {"x": 124.9220525331, "y": 16.4463002197265}, + "radius": 126, + "start": {"x": 126, "y": 0}, + "type": "arc", + }, + { + "end": {"x": 113.024714196614, "y": 14.8799859130859}, + "start": {"x": 124.9220525331, "y": 16.4463002197265}, + "type": "line", + }, + { + "centre": {"x": 0, "y": 0}, + "end": {"x": 114, "y": 0}, + "radius": -114, + "start": {"x": 113.024714196614, "y": 14.8799859130859}, + "type": "arc", + }, + ], + "entities_internal": [], + "entities_polyline_type": 2, + "extrusion_blocks": [], + "incl_region_id": True, + "lamination_type": "", + "linked_regions": [], + "magnet_temp_coeff_method": 0, + "material": "Aluminium (Alloy 195 Cast)", + "material_weight_component_type": 1, + "material_weight_field": "Weight_Total_Housing_Active", + "mesh_length": 0, + "name": "Housing", + "name_unique": "Housing", + "parent_name": "", + "region_coordinate": {"x": 119.725253764956, "y": 7.84720771818839}, + "region_temperature": 20, + "region_type": "Housing", + "region_type_mapped": "No type", + "singular": False, + "thermal_loss": 0, + "use_in_weight_calculation": True, + "weight_reduction_factor": 1, + }, + "Housing_1": { + "adaptive_entities": False, + "area": 72.9765793490131, + "centroid": {"x": 111.170714519021, "y": 7.28651359322089}, + "child_names": [], + "colour": {"b": 253, "g": 231, "r": 229}, + "duplications": 48, + "entities": [ + {"end": {"x": 114, "y": 0}, "start": {"x": 109, "y": 0}, "type": "line"}, + { + "centre": {"x": 0, "y": 0}, + "end": {"x": 113.024714196614, "y": 14.8799859130859}, + "radius": 114, + "start": {"x": 114, "y": 0}, + "type": "arc", + }, + { + "end": {"x": 108.067489889745, "y": 14.2273549519856}, + "start": {"x": 113.024714196614, "y": 14.8799859130859}, + "type": "line", + }, + { + "centre": {"x": 0, "y": 0}, + "end": {"x": 109, "y": 0}, + "radius": -109, + "start": {"x": 108.067489889745, "y": 14.2273549519856}, + "type": "arc", + }, + ], + "entities_internal": [], + "entities_polyline_type": 2, + "extrusion_blocks": [], + "incl_region_id": True, + "lamination_type": "", + "linked_regions": [], + "magnet_temp_coeff_method": 0, + "material": "Aluminium (Alloy 195 Cast)", + "material_weight_component_type": 1, + "material_weight_field": "Weight_Total_Housing_Active", + "mesh_length": 0, + "name": "Housing", + "name_unique": "Housing_1", + "parent_name": "", + "region_coordinate": {"x": 111.170714519021, "y": 7.28651359322089}, + "region_temperature": 20, + "region_type": "Housing", + "region_type_mapped": "No type", + "singular": False, + "thermal_loss": 0, + "use_in_weight_calculation": True, + "weight_reduction_factor": 1, + }, + "Housing_2": { + "adaptive_entities": False, + "area": 136.135681655558, + "centroid": {"x": 103.755368536621, "y": 6.80048613956137}, + "child_names": [], + "colour": {"b": 253, "g": 231, "r": 229}, + "duplications": 48, + "entities": [ + {"end": {"x": 109, "y": 0}, "start": {"x": 99, "y": 0}, "type": "line"}, + { + "centre": {"x": 0, "y": 0}, + "end": {"x": 108.067489889745, "y": 14.2273549519856}, + "radius": 109, + "start": {"x": 109, "y": 0}, + "type": "arc", + }, + { + "end": {"x": 98.1530412760072, "y": 12.9220930297851}, + "start": {"x": 108.067489889745, "y": 14.2273549519856}, + "type": "line", + }, + { + "centre": {"x": 0, "y": 0}, + "end": {"x": 99, "y": 0}, + "radius": -99, + "start": {"x": 98.1530412760072, "y": 12.9220930297851}, + "type": "arc", + }, + ], + "entities_internal": [], + "entities_polyline_type": 2, + "extrusion_blocks": [], + "incl_region_id": True, + "lamination_type": "", + "linked_regions": [], + "magnet_temp_coeff_method": 0, + "material": "Aluminium (Alloy 195 Cast)", + "material_weight_component_type": 1, + "material_weight_field": "Weight_Total_Housing_Active", + "mesh_length": 0, + "name": "Housing", + "name_unique": "Housing_2", + "parent_name": "", + "region_coordinate": {"x": 103.755368536621, "y": 6.80048613956137}, + "region_temperature": 20, + "region_type": "Housing", + "region_type_mapped": "No type", + "singular": False, + "thermal_loss": 0, + "use_in_weight_calculation": True, + "weight_reduction_factor": 1, + }, + "Impreg": { + "adaptive_entities": False, + "area": 97.2839971133977, + "centroid": {"x": 77.5396239396489, "y": 5.08221545839593}, + "child_names": [], + "colour": {"b": 48, "g": 224, "r": 48}, + "duplications": 48, + "entities": [ + { + "end": {"x": 67.2985091364678, "y": 2.5}, + "start": {"x": 67.0490765420196, "y": 6.30560598623492}, + "type": "line", + }, + { + "end": {"x": 84.8936835106123, "y": 2.5}, + "start": {"x": 67.2985091364678, "y": 2.5}, + "type": "line", + }, + { + "centre": {"x": 84.8936835106122, "y": 4.25}, + "end": {"x": 86.6414946417131, "y": 4.3375}, + "radius": 1.75, + "start": {"x": 84.8936835106123, "y": 2.5}, + "type": "arc", + }, + { + "centre": {"x": 0, "y": 0}, + "end": {"x": 86.5642615909489, "y": 5.67372146071491}, + "radius": 86.75, + "start": {"x": 86.6414946417131, "y": 4.3375}, + "type": "arc", + }, + { + "centre": {"x": 0, "y": 0}, + "end": {"x": 86.4664220030275, "y": 7.00859229762791}, + "radius": 86.75, + "start": {"x": 86.5642615909489, "y": 5.67372146071491}, + "type": "arc", + }, + { + "centre": {"x": 84.7221425966263, "y": 6.86720859133571}, + "end": {"x": 84.4937217602412, "y": 8.60223709873987}, + "radius": 1.75, + "start": {"x": 86.4664220030275, "y": 7.00859229762791}, + "type": "arc", + }, + { + "end": {"x": 67.0490765420196, "y": 6.30560598623492}, + "start": {"x": 84.4937217602412, "y": 8.60223709873987}, + "type": "line", + }, + ], + "entities_internal": [], + "entities_polyline_type": 2, + "extrusion_blocks": [], + "incl_region_id": True, + "lamination_type": "", + "linked_regions": [], + "magnet_temp_coeff_method": 0, + "material": "", + "material_weight_component_type": 1, + "material_weight_field": "Weight_Total_Impreg_Active", + "mesh_length": 0, + "name": "Impreg", + "name_unique": "Impreg", + "parent_name": "", + "region_coordinate": {"x": 82.3420980203589, "y": 5.55860429566785}, + "region_temperature": 20, + "region_type": "Stator Impreg", + "region_type_mapped": "No type", + "singular": False, + "thermal_loss": 0, + "use_in_weight_calculation": True, + "weight_reduction_factor": 0.8, + }, + "L1_1Magnet1": { + "adaptive_entities": False, + "area": 96.0000000000001, + "centroid": {"x": 56.040689756688, "y": 11.8668792573194}, + "child_names": [], + "colour": {"b": 0, "g": 212, "r": 0}, + "duplications": 8, + "entities": [ + { + "end": {"x": 63.2908412096315, "y": 7.34633682201565}, + "start": {"x": 58.5307211678841, "y": 3.69376824796331}, + "type": "line", + }, + { + "end": {"x": 53.550658345492, "y": 20.0399902666754}, + "start": {"x": 63.2908412096315, "y": 7.34633682201565}, + "type": "line", + }, + { + "end": {"x": 48.7905383037446, "y": 16.3874216926231}, + "start": {"x": 53.550658345492, "y": 20.0399902666754}, + "type": "line", + }, + { + "end": {"x": 58.5307211678841, "y": 3.69376824796331}, + "start": {"x": 48.7905383037446, "y": 16.3874216926231}, + "type": "line", + }, + ], + "entities_internal": [], + "entities_polyline_type": 2, + "extrusion_blocks": [ + { + "extrusion_block_angle_step": 0, + "extrusion_block_end": 63.3333333333333, + "extrusion_block_start": 55, + }, + { + "extrusion_block_angle_step": 0, + "extrusion_block_end": 71.6666666666667, + "extrusion_block_start": 63.3333333333333, + }, + { + "extrusion_block_angle_step": 0, + "extrusion_block_end": 80, + "extrusion_block_start": 71.6666666666667, + }, + { + "extrusion_block_angle_step": 0, + "extrusion_block_end": 88.3333333333333, + "extrusion_block_start": 80, + }, + { + "extrusion_block_angle_step": 0, + "extrusion_block_end": 96.6666666666667, + "extrusion_block_start": 88.3333333333333, + }, + { + "extrusion_block_angle_step": 0, + "extrusion_block_end": 105, + "extrusion_block_start": 96.6666666666667, + }, + { + "extrusion_block_angle_step": 0, + "extrusion_block_end": 113.333333333333, + "extrusion_block_start": 105, + }, + { + "extrusion_block_angle_step": 0, + "extrusion_block_end": 121.666666666667, + "extrusion_block_start": 113.333333333333, + }, + { + "extrusion_block_angle_step": 0, + "extrusion_block_end": 130, + "extrusion_block_start": 121.666666666667, + }, + { + "extrusion_block_angle_step": 0, + "extrusion_block_end": 138.333333333333, + "extrusion_block_start": 130, + }, + { + "extrusion_block_angle_step": 0, + "extrusion_block_end": 146.666666666667, + "extrusion_block_start": 138.333333333333, + }, + { + "extrusion_block_angle_step": 0, + "extrusion_block_end": 155, + "extrusion_block_start": 146.666666666667, + }, + { + "extrusion_block_angle_step": 0, + "extrusion_block_end": 163.333333333333, + "extrusion_block_start": 155, + }, + { + "extrusion_block_angle_step": 0, + "extrusion_block_end": 171.666666666667, + "extrusion_block_start": 163.333333333333, + }, + { + "extrusion_block_angle_step": 0, + "extrusion_block_end": 180, + "extrusion_block_start": 171.666666666667, + }, + { + "extrusion_block_angle_step": 0, + "extrusion_block_end": 188.333333333333, + "extrusion_block_start": 180, + }, + { + "extrusion_block_angle_step": 0, + "extrusion_block_end": 196.666666666667, + "extrusion_block_start": 188.333333333333, + }, + { + "extrusion_block_angle_step": 0, + "extrusion_block_end": 205, + "extrusion_block_start": 196.666666666667, + }, + ], + "incl_region_id": True, + "lamination_type": "", + "linked_regions": [], + "magnet_angle": 37.5, + "magnet_br_value": 1.06425, + "magnet_magfactor": 1, + "magnet_polarity": "N", + "magnet_temp_coeff_method": 0, + "material": "N30UH", + "material_weight_component_type": 1, + "material_weight_field": "Weight_Total_Magnet", + "mesh_length": 0, + "name": "L1_1Magnet1", + "name_unique": "L1_1Magnet1", + "parent_name": "Rotor", + "region_coordinate": {"x": 56.040689756688, "y": 11.8668792573194}, + "region_temperature": 65, + "region_type": "Magnet", + "region_type_mapped": "No type", + "singular": True, + "thermal_loss": 0, + "use_in_weight_calculation": True, + "weight_reduction_factor": 1, + }, + "L1_1Magnet2": { + "adaptive_entities": False, + "area": 96.0000000000001, + "centroid": {"x": 48.0179025436981, "y": 31.2356009549531}, + "child_names": [], + "colour": {"b": 0, "g": 212, "r": 0}, + "duplications": 8, + "entities": [ + { + "end": {"x": 43.9993584218163, "y": 38.7755812692834}, + "start": {"x": 49.9480275900591, "y": 39.5587384226037}, + "type": "line", + }, + { + "end": {"x": 46.0877774973371, "y": 22.9124634873025}, + "start": {"x": 43.9993584218163, "y": 38.7755812692834}, + "type": "line", + }, + { + "end": {"x": 52.03644666558, "y": 23.6956206406228}, + "start": {"x": 46.0877774973371, "y": 22.9124634873025}, + "type": "line", + }, + { + "end": {"x": 49.9480275900591, "y": 39.5587384226037}, + "start": {"x": 52.03644666558, "y": 23.6956206406228}, + "type": "line", + }, + ], + "entities_internal": [], + "entities_polyline_type": 2, + "extrusion_blocks": [ + { + "extrusion_block_angle_step": 0, + "extrusion_block_end": 63.3333333333333, + "extrusion_block_start": 55, + }, + { + "extrusion_block_angle_step": 0, + "extrusion_block_end": 71.6666666666667, + "extrusion_block_start": 63.3333333333333, + }, + { + "extrusion_block_angle_step": 0, + "extrusion_block_end": 80, + "extrusion_block_start": 71.6666666666667, + }, + { + "extrusion_block_angle_step": 0, + "extrusion_block_end": 88.3333333333333, + "extrusion_block_start": 80, + }, + { + "extrusion_block_angle_step": 0, + "extrusion_block_end": 96.6666666666667, + "extrusion_block_start": 88.3333333333333, + }, + { + "extrusion_block_angle_step": 0, + "extrusion_block_end": 105, + "extrusion_block_start": 96.6666666666667, + }, + { + "extrusion_block_angle_step": 0, + "extrusion_block_end": 113.333333333333, + "extrusion_block_start": 105, + }, + { + "extrusion_block_angle_step": 0, + "extrusion_block_end": 121.666666666667, + "extrusion_block_start": 113.333333333333, + }, + { + "extrusion_block_angle_step": 0, + "extrusion_block_end": 130, + "extrusion_block_start": 121.666666666667, + }, + { + "extrusion_block_angle_step": 0, + "extrusion_block_end": 138.333333333333, + "extrusion_block_start": 130, + }, + { + "extrusion_block_angle_step": 0, + "extrusion_block_end": 146.666666666667, + "extrusion_block_start": 138.333333333333, + }, + { + "extrusion_block_angle_step": 0, + "extrusion_block_end": 155, + "extrusion_block_start": 146.666666666667, + }, + { + "extrusion_block_angle_step": 0, + "extrusion_block_end": 163.333333333333, + "extrusion_block_start": 155, + }, + { + "extrusion_block_angle_step": 0, + "extrusion_block_end": 171.666666666667, + "extrusion_block_start": 163.333333333333, + }, + { + "extrusion_block_angle_step": 0, + "extrusion_block_end": 180, + "extrusion_block_start": 171.666666666667, + }, + { + "extrusion_block_angle_step": 0, + "extrusion_block_end": 188.333333333333, + "extrusion_block_start": 180, + }, + { + "extrusion_block_angle_step": 0, + "extrusion_block_end": 196.666666666667, + "extrusion_block_start": 188.333333333333, + }, + { + "extrusion_block_angle_step": 0, + "extrusion_block_end": 205, + "extrusion_block_start": 196.666666666667, + }, + ], + "incl_region_id": True, + "lamination_type": "", + "linked_regions": [], + "magnet_angle": 7.5, + "magnet_br_value": 1.06425, + "magnet_magfactor": 1, + "magnet_polarity": "N", + "magnet_temp_coeff_method": 0, + "material": "N30UH", + "material_weight_component_type": 1, + "material_weight_field": "Weight_Total_Magnet", + "mesh_length": 0, + "name": "L1_1Magnet2", + "name_unique": "L1_1Magnet2", + "parent_name": "Rotor", + "region_coordinate": {"x": 48.0179025436981, "y": 31.2356009549531}, + "region_temperature": 65, + "region_type": "Magnet", + "region_type_mapped": "No type", + "singular": True, + "thermal_loss": 0, + "use_in_weight_calculation": True, + "weight_reduction_factor": 1, + }, + "Liner": { + "adaptive_entities": False, + "area": 10.983203589653, + "centroid": {"x": 77.968913664417, "y": 5.11035259350818}, + "child_names": [], + "colour": {"b": 0, "g": 128, "r": 0}, + "duplications": 48, + "entities": [ + { + "end": {"x": 84.8936835106123, "y": 2.25}, + "start": {"x": 67.3148950021716, "y": 2.24999999999998}, + "type": "line", + }, + { + "centre": {"x": 84.8936835106123, "y": 4.25}, + "end": {"x": 86.8911819461561, "y": 4.35}, + "radius": 2, + "start": {"x": 84.8936835106123, "y": 2.25}, + "type": "arc", + }, + { + "centre": {"x": 0, "y": 0}, + "end": {"x": 86.8137263217585, "y": 5.69007224302245}, + "radius": 87, + "start": {"x": 86.8911819461561, "y": 4.35}, + "type": "arc", + }, + { + "centre": {"x": 0, "y": 0}, + "end": {"x": 86.7156047753705, "y": 7.02878996995537}, + "radius": 87, + "start": {"x": 86.8137263217585, "y": 5.69007224302245}, + "type": "arc", + }, + { + "centre": {"x": 84.7221425966263, "y": 6.86720859133571}, + "end": {"x": 84.4610902121862, "y": 8.85009831408333}, + "radius": 2, + "start": {"x": 86.7156047753705, "y": 7.02878996995537}, + "type": "arc", + }, + { + "end": {"x": 67.0326906763158, "y": 6.55560598623492}, + "start": {"x": 84.4610902121862, "y": 8.85009831408333}, + "type": "line", + }, + { + "end": {"x": 67.0490765420196, "y": 6.30560598623492}, + "start": {"x": 67.0326906763158, "y": 6.55560598623492}, + "type": "line", + }, + { + "end": {"x": 84.4937217602412, "y": 8.60223709873987}, + "start": {"x": 67.0490765420196, "y": 6.30560598623492}, + "type": "line", + }, + { + "centre": {"x": 84.7221425966263, "y": 6.86720859133571}, + "end": {"x": 86.4664220030275, "y": 7.00859229762791}, + "radius": -1.75, + "start": {"x": 84.4937217602412, "y": 8.60223709873987}, + "type": "arc", + }, + { + "centre": {"x": 0, "y": 0}, + "end": {"x": 86.5642615909489, "y": 5.67372146071491}, + "radius": -86.75, + "start": {"x": 86.4664220030275, "y": 7.00859229762791}, + "type": "arc", + }, + { + "centre": {"x": 0, "y": 0}, + "end": {"x": 86.6414946417131, "y": 4.3375}, + "radius": -86.75, + "start": {"x": 86.5642615909489, "y": 5.67372146071491}, + "type": "arc", + }, + { + "centre": {"x": 84.8936835106122, "y": 4.25}, + "end": {"x": 84.8936835106123, "y": 2.5}, + "radius": -1.75, + "start": {"x": 86.6414946417131, "y": 4.3375}, + "type": "arc", + }, + { + "end": {"x": 67.2985091364678, "y": 2.5}, + "start": {"x": 84.8936835106123, "y": 2.5}, + "type": "line", + }, + { + "end": {"x": 67.3148950021716, "y": 2.24999999999998}, + "start": {"x": 67.2985091364678, "y": 2.5}, + "type": "line", + }, + ], + "entities_internal": [], + "entities_polyline_type": 2, + "extrusion_blocks": [], + "incl_region_id": True, + "lamination_type": "", + "linked_regions": [], + "magnet_temp_coeff_method": 0, + "material": "", + "material_weight_component_type": 1, + "material_weight_field": "Weight_Total_Slot_Liner", + "mesh_length": 0, + "name": "Liner", + "name_unique": "Liner", + "parent_name": "", + "region_coordinate": {"x": 79.4399567850703, "y": 8.04005751741874}, + "region_temperature": 20, + "region_type": "Stator Liner", + "region_type_mapped": "No type", + "singular": False, + "thermal_loss": 0, + "use_in_weight_calculation": True, + "weight_reduction_factor": 1, + }, + "Rotor": { + "adaptive_entities": False, + "area": 1030.83508945915, + "centroid": {"x": 48.1444344595077, "y": 19.9420777059107}, + "child_names": ["Rotor Pocket", "L1_1Magnet1", "L1_1Magnet2", "Rotor Pocket_1"], + "colour": {"b": 240, "g": 240, "r": 0}, + "duplications": 8, + "entities": [ + {"end": {"x": 65, "y": 0}, "start": {"x": 40, "y": 0}, "type": "line"}, + { + "centre": {"x": 0, "y": 0}, + "end": {"x": 45.9619407771256, "y": 45.9619407771256}, + "radius": 65, + "start": {"x": 65, "y": 0}, + "type": "arc", + }, + { + "end": {"x": 28.2842712474619, "y": 28.2842712474619}, + "start": {"x": 45.9619407771256, "y": 45.9619407771256}, + "type": "line", + }, + { + "centre": {"x": 0, "y": 0}, + "end": {"x": 40, "y": 0}, + "radius": -40, + "start": {"x": 28.2842712474619, "y": 28.2842712474619}, + "type": "arc", + }, + ], + "entities_internal": [], + "entities_polyline_type": 2, + "extrusion_blocks": [ + { + "extrusion_block_angle_step": 0, + "extrusion_block_end": 205, + "extrusion_block_start": 55, + } + ], + "incl_region_id": True, + "lamination_type": "Laminated", + "linked_regions": [], + "magnet_temp_coeff_method": 0, + "material": "M250-35A", + "material_weight_component_type": 1, + "material_weight_field": "Weight_Total_Rotor_Lam_Back_Iron", + "mesh_length": 0, + "name": "Rotor", + "name_unique": "Rotor", + "parent_name": "", + "region_coordinate": {"x": 47.7895021472478, "y": 21.7999639468195}, + "region_temperature": 20, + "region_type": "Rotor", + "region_type_mapped": "No type", + "singular": False, + "thermal_loss": 0, + "use_in_weight_calculation": True, + "weight_reduction_factor": 0.97, + }, + "Rotor Pocket": { + "adaptive_entities": False, + "area": 27.2374304690439, + "centroid": {"x": 47.559936471459, "y": 31.1704313411946}, + "child_names": [], + "colour": {"b": 240, "g": 241, "r": 241}, + "duplications": 8, + "entities": [ + { + "end": {"x": 43.8010694495415, "y": 38.7494760308394}, + "start": {"x": 43.7358063534315, "y": 39.2451984615263}, + "type": "line", + }, + { + "end": {"x": 45.8894885250623, "y": 22.8863582488585}, + "start": {"x": 43.8010694495415, "y": 38.7494760308394}, + "type": "line", + }, + { + "end": {"x": 45.9547516211724, "y": 22.3906358181716}, + "start": {"x": 45.8894885250623, "y": 22.8863582488585}, + "type": "line", + }, + { + "centre": {"x": 48.8451677068426, "y": 24.1857684321305}, + "end": {"x": 52.10170976169, "y": 23.1998982099359}, + "radius": 3.4025, + "start": {"x": 45.9547516211724, "y": 22.3906358181716}, + "type": "arc", + }, + { + "end": {"x": 52.03644666558, "y": 23.6956206406228}, + "start": {"x": 52.10170976169, "y": 23.1998982099359}, + "type": "line", + }, + { + "end": {"x": 46.0877774973371, "y": 22.9124634873025}, + "start": {"x": 52.03644666558, "y": 23.6956206406228}, + "type": "line", + }, + { + "end": {"x": 43.9993584218163, "y": 38.7755812692834}, + "start": {"x": 46.0877774973371, "y": 22.9124634873025}, + "type": "line", + }, + { + "end": {"x": 49.9480275900591, "y": 39.5587384226037}, + "start": {"x": 43.9993584218163, "y": 38.7755812692834}, + "type": "line", + }, + { + "end": {"x": 49.8827644939491, "y": 40.0544608532907}, + "start": {"x": 49.9480275900591, "y": 39.5587384226037}, + "type": "line", + }, + { + "centre": {"x": 46.9923484082789, "y": 38.2593282393317}, + "end": {"x": 43.7358063534315, "y": 39.2451984615263}, + "radius": 3.4025, + "start": {"x": 49.8827644939491, "y": 40.0544608532907}, + "type": "arc", + }, + ], + "entities_internal": [], + "entities_polyline_type": 2, + "extrusion_blocks": [ + { + "extrusion_block_angle_step": 0, + "extrusion_block_end": 205, + "extrusion_block_start": 55, + } + ], + "incl_region_id": True, + "lamination_type": "", + "linked_regions": [], + "magnet_temp_coeff_method": 0, + "material": "Air (Motor-CAD model)", + "material_weight_component_type": 1, + "material_weight_field": "Weight_Total_RotorPocket", + "mesh_length": 0, + "name": "Rotor Pocket", + "name_unique": "Rotor Pocket", + "parent_name": "Rotor", + "region_coordinate": {"x": 45.8272822054961, "y": 39.6065511007121}, + "region_temperature": 20, + "region_type": "Rotor Pocket", + "region_type_mapped": "No type", + "singular": False, + "thermal_loss": 0, + "use_in_weight_calculation": True, + "weight_reduction_factor": 1, + }, + "Rotor Pocket_1": { + "adaptive_entities": False, + "area": 27.2374304690439, + "centroid": {"x": 55.6707769656374, "y": 11.5891302179014}, + "child_names": [], + "colour": {"b": 240, "g": 241, "r": 241}, + "duplications": 8, + "entities": [ + { + "end": {"x": 63.2908412096315, "y": 7.34633682201565}, + "start": {"x": 63.5952219241359, "y": 6.94966015187003}, + "type": "line", + }, + { + "end": {"x": 58.5307211678841, "y": 3.69376824796331}, + "start": {"x": 63.2908412096315, "y": 7.34633682201565}, + "type": "line", + }, + { + "end": {"x": 48.7905383037446, "y": 16.3874216926231}, + "start": {"x": 58.5307211678841, "y": 3.69376824796331}, + "type": "line", + }, + { + "end": {"x": 53.550658345492, "y": 20.0399902666754}, + "start": {"x": 48.7905383037446, "y": 16.3874216926231}, + "type": "line", + }, + { + "end": {"x": 53.2462776309876, "y": 20.436666936821}, + "start": {"x": 53.550658345492, "y": 20.0399902666754}, + "type": "line", + }, + { + "centre": {"x": 51.6406701802695, "y": 17.4368284471355}, + "end": {"x": 48.327486921182, "y": 16.662346076967}, + "radius": 3.4025, + "start": {"x": 53.2462776309876, "y": 20.436666936821}, + "type": "arc", + }, + { + "end": {"x": 48.6318676356863, "y": 16.2656694068213}, + "start": {"x": 48.327486921182, "y": 16.662346076967}, + "type": "line", + }, + { + "end": {"x": 58.3720504998259, "y": 3.57201596216157}, + "start": {"x": 48.6318676356863, "y": 16.2656694068213}, + "type": "line", + }, + { + "end": {"x": 58.6764312143302, "y": 3.17533929201596}, + "start": {"x": 58.3720504998259, "y": 3.57201596216157}, + "type": "line", + }, + { + "centre": {"x": 60.2820386650483, "y": 6.17517778170145}, + "end": {"x": 63.5952219241359, "y": 6.94966015187003}, + "radius": 3.4025, + "start": {"x": 58.6764312143302, "y": 3.17533929201596}, + "type": "arc", + }, + ], + "entities_internal": [], + "entities_polyline_type": 2, + "extrusion_blocks": [ + { + "extrusion_block_angle_step": 0, + "extrusion_block_end": 205, + "extrusion_block_start": 55, + } + ], + "incl_region_id": True, + "lamination_type": "", + "linked_regions": [], + "magnet_temp_coeff_method": 0, + "material": "Air (Motor-CAD model)", + "material_weight_component_type": 1, + "material_weight_field": "Weight_Total_RotorPocket", + "mesh_length": 0, + "name": "Rotor Pocket", + "name_unique": "Rotor Pocket_1", + "parent_name": "Rotor", + "region_coordinate": {"x": 61.753742604451, "y": 4.70347384229879}, + "region_temperature": 20, + "region_type": "Rotor Pocket", + "region_type_mapped": "No type", + "singular": False, + "thermal_loss": 0, + "use_in_weight_calculation": True, + "weight_reduction_factor": 1, + }, + "Shaft": { + "adaptive_entities": False, + "area": 628.318530717959, + "centroid": {"x": 24.0007863180929, "y": 9.94145120057268}, + "child_names": [], + "colour": {"b": 160, "g": 160, "r": 160}, + "duplications": 8, + "entities": [ + { + "centre": {"x": 0, "y": 0}, + "end": {"x": 28.2842712474619, "y": 28.2842712474619}, + "radius": 40, + "start": {"x": 40, "y": 0}, + "type": "arc", + }, + { + "end": {"x": 0, "y": 0}, + "start": {"x": 28.2842712474619, "y": 28.2842712474619}, + "type": "line", + }, + {"end": {"x": 40, "y": 0}, "start": {"x": 0, "y": 0}, "type": "line"}, + ], + "entities_internal": [], + "entities_polyline_type": 2, + "extrusion_blocks": [ + { + "extrusion_block_angle_step": 0, + "extrusion_block_end": 205, + "extrusion_block_start": 55, + } + ], + "incl_region_id": True, + "lamination_type": "", + "linked_regions": [], + "magnet_temp_coeff_method": 0, + "material": "", + "material_weight_component_type": 1, + "material_weight_field": "Weight_Total_Shaft_Active", + "mesh_length": 0, + "name": "Shaft", + "name_unique": "Shaft", + "parent_name": "", + "region_coordinate": {"x": 24.7487373415292, "y": 10.6066017177982}, + "region_temperature": 20, + "region_type": "Shaft", + "region_type_mapped": "No type", + "singular": False, + "thermal_loss": 0, + "use_in_weight_calculation": True, + "weight_reduction_factor": 1, + }, + "Stator": { + "adaptive_entities": False, + "area": 243.916173195236, + "centroid": {"x": 86.1706272589501, "y": 5.64792130351275}, + "child_names": [], + "colour": {"b": 16, "g": 0, "r": 240}, + "duplications": 48, + "entities": [ + { + "centre": {"x": 0, "y": 0}, + "end": {"x": 98.1530412760072, "y": 12.9220930297851}, + "radius": 99, + "start": {"x": 99, "y": 0}, + "type": "arc", + }, + { + "end": {"x": 65.4353608506715, "y": 8.6147286865234}, + "start": {"x": 98.1530412760072, "y": 12.9220930297851}, + "type": "line", + }, + { + "centre": {"x": 0, "y": 0}, + "end": {"x": 65.7435730840115, "y": 5.81227994398376}, + "radius": -66, + "start": {"x": 65.4353608506715, "y": 8.6147286865234}, + "type": "arc", + }, + { + "end": {"x": 66.7414320072501, "y": 5.87768307321394}, + "start": {"x": 65.7435730840115, "y": 5.81227994398376}, + "type": "line", + }, + { + "end": {"x": 67.0326906763158, "y": 6.555605986235}, + "start": {"x": 66.7414320072501, "y": 5.87768307321394}, + "type": "line", + }, + { + "end": {"x": 84.4610902121862, "y": 8.8500983140833}, + "start": {"x": 67.0326906763158, "y": 6.555605986235}, + "type": "line", + }, + { + "centre": {"x": 84.7221425966263, "y": 6.86720859133569}, + "end": {"x": 86.7156047753705, "y": 7.02878996995535}, + "radius": -2, + "start": {"x": 84.4610902121862, "y": 8.8500983140833}, + "type": "arc", + }, + { + "centre": {"x": 0, "y": 0}, + "end": {"x": 86.8137263217585, "y": 5.69007224302245}, + "radius": -87, + "start": {"x": 86.7156047753705, "y": 7.02878996995535}, + "type": "arc", + }, + { + "centre": {"x": 0, "y": 0}, + "end": {"x": 86.8911819461561, "y": 4.35000000000002}, + "radius": -87, + "start": {"x": 86.8137263217585, "y": 5.69007224302245}, + "type": "arc", + }, + { + "centre": {"x": 84.8936835106122, "y": 4.25000000000002}, + "end": {"x": 84.8936835106123, "y": 2.25000000000002}, + "radius": -2.00000000000001, + "start": {"x": 86.8911819461561, "y": 4.35000000000002}, + "type": "arc", + }, + { + "end": {"x": 67.3148950021716, "y": 2.24999999999998}, + "start": {"x": 84.8936835106123, "y": 2.25000000000002}, + "type": "line", + }, + { + "end": {"x": 66.9376413949405, "y": 2.88410630349807}, + "start": {"x": 67.3148950021716, "y": 2.24999999999998}, + "type": "line", + }, + { + "end": {"x": 65.939782471702, "y": 2.81870317426794}, + "start": {"x": 66.9376413949405, "y": 2.88410630349807}, + "type": "line", + }, + { + "centre": {"x": 0, "y": 0}, + "end": {"x": 66, "y": 0}, + "radius": -66, + "start": {"x": 65.939782471702, "y": 2.81870317426794}, + "type": "arc", + }, + {"end": {"x": 99, "y": 0}, "start": {"x": 66, "y": 0}, "type": "line"}, + ], + "entities_internal": [], + "entities_polyline_type": 2, + "extrusion_blocks": [ + { + "extrusion_block_angle_step": 0, + "extrusion_block_end": 210, + "extrusion_block_start": 50, + } + ], + "incl_region_id": True, + "lamination_type": "Laminated", + "linked_regions": [], + "magnet_temp_coeff_method": 0, + "material": "M250-35A", + "material_weight_component_type": 1, + "material_weight_field": "Weight_Total_Stator_Lam_Back_Iron", + "mesh_length": 0, + "name": "Stator", + "name_unique": "Stator", + "parent_name": "", + "region_coordinate": {"x": 97.7405934251342, "y": 6.46104651489255}, + "region_temperature": 20, + "region_type": "Stator", + "region_type_mapped": "No type", + "singular": False, + "thermal_loss": 0, + "use_in_weight_calculation": True, + "weight_reduction_factor": 0.97, + }, + "StatorAir": { + "adaptive_entities": False, + "area": 2.96590380676454, + "centroid": {"x": 66.4303884745974, "y": 4.35407769678662}, + "child_names": [], + "colour": {"b": 240, "g": 240, "r": 239}, + "duplications": 48, + "entities": [ + { + "end": {"x": 66.9376413949405, "y": 2.88410630349813}, + "start": {"x": 65.939782471702, "y": 2.81870317426794}, + "type": "line", + }, + { + "end": {"x": 66.7414320072501, "y": 5.87768307321392}, + "start": {"x": 66.9376413949405, "y": 2.88410630349813}, + "type": "line", + }, + { + "end": {"x": 65.7435730840115, "y": 5.81227994398376}, + "start": {"x": 66.7414320072501, "y": 5.87768307321392}, + "type": "line", + }, + { + "centre": {"x": 0, "y": 0}, + "end": {"x": 65.939782471702, "y": 2.81870317426794}, + "radius": -66, + "start": {"x": 65.7435730840115, "y": 5.81227994398376}, + "type": "arc", + }, + ], + "entities_internal": [], + "entities_polyline_type": 2, + "extrusion_blocks": [ + { + "extrusion_block_angle_step": 0, + "extrusion_block_end": 210, + "extrusion_block_start": 50, + } + ], + "incl_region_id": True, + "lamination_type": "", + "linked_regions": [], + "magnet_temp_coeff_method": 0, + "material": "Air", + "material_weight_component_type": 1, + "material_weight_field": "Weight_Total_Air_TSupply", + "mesh_length": 0, + "name": "StatorAir", + "name_unique": "StatorAir", + "parent_name": "", + "region_coordinate": {"x": 66.340607239476, "y": 4.60980564066148}, + "region_temperature": 20, + "region_type": "Stator Air", + "region_type_mapped": "No type", + "singular": False, + "thermal_loss": 0, + "use_in_weight_calculation": False, + "weight_reduction_factor": 1, + }, + "StatorSlot": { + "adaptive_entities": False, + "area": 108.267200703049, + "centroid": {"x": 77.5831733841295, "y": 5.08506983979084}, + "child_names": [], + "colour": {"b": 16, "g": 240, "r": 240}, + "duplications": 48, + "entities": [ + { + "end": {"x": 84.8936835106123, "y": 2.25000000000002}, + "start": {"x": 67.3148950021716, "y": 2.24999999999998}, + "type": "line", + }, + { + "centre": {"x": 84.8936835106122, "y": 4.25000000000002}, + "end": {"x": 86.8911819461561, "y": 4.35000000000002}, + "radius": 2.00000000000001, + "start": {"x": 84.8936835106123, "y": 2.25000000000002}, + "type": "arc", + }, + { + "centre": {"x": 0, "y": 0}, + "end": {"x": 86.8137263217585, "y": 5.69007224302245}, + "radius": 87, + "start": {"x": 86.8911819461561, "y": 4.35000000000002}, + "type": "arc", + }, + { + "centre": {"x": 0, "y": 0}, + "end": {"x": 86.7156047753705, "y": 7.02878996995535}, + "radius": 87, + "start": {"x": 86.8137263217585, "y": 5.69007224302245}, + "type": "arc", + }, + { + "centre": {"x": 84.7221425966263, "y": 6.86720859133569}, + "end": {"x": 84.4610902121862, "y": 8.8500983140833}, + "radius": 2, + "start": {"x": 86.7156047753705, "y": 7.02878996995535}, + "type": "arc", + }, + { + "end": {"x": 67.0326906763158, "y": 6.55560598623481}, + "start": {"x": 84.4610902121862, "y": 8.8500983140833}, + "type": "line", + }, + { + "end": {"x": 67.3148950021716, "y": 2.24999999999998}, + "start": {"x": 67.0326906763158, "y": 6.55560598623481}, + "type": "line", + }, + ], + "entities_internal": [], + "entities_polyline_type": 2, + "extrusion_blocks": [], + "incl_region_id": True, + "lamination_type": "", + "linked_regions": [], + "magnet_temp_coeff_method": 0, + "material": "Copper (Pure)", + "material_weight_component_type": 1, + "material_weight_field": "Weight_Total_Copper_Active", + "mesh_length": 0, + "name": "StatorSlot", + "name_unique": "StatorSlot", + "parent_name": "", + "region_coordinate": {"x": 83.5757121546551, "y": 5.55860429566783}, + "region_temperature": 65, + "region_type": "Stator Slot Area", + "region_type_mapped": "No type", + "singular": False, + "thermal_loss": 0, + "use_in_weight_calculation": False, + "weight_reduction_factor": 1, + }, + "StatorWedge": { + "adaptive_entities": False, + "area": 1.22513893654097, + "centroid": {"x": 67.0166785354928, "y": 4.39250517759192}, + "child_names": [], + "colour": {"b": 192, "g": 192, "r": 160}, + "duplications": 48, + "entities": [ + { + "end": {"x": 67.3148950021716, "y": 2.25}, + "start": {"x": 66.9376413949405, "y": 2.88410630349807}, + "type": "line", + }, + { + "end": {"x": 67.0326906763158, "y": 6.55560598623498}, + "start": {"x": 67.3148950021716, "y": 2.25}, + "type": "line", + }, + { + "end": {"x": 66.7414320072501, "y": 5.87768307321414}, + "start": {"x": 67.0326906763158, "y": 6.55560598623498}, + "type": "line", + }, + { + "end": {"x": 66.9376413949405, "y": 2.88410630349807}, + "start": {"x": 66.7414320072501, "y": 5.87768307321414}, + "type": "line", + }, + ], + "entities_internal": [], + "entities_polyline_type": 2, + "extrusion_blocks": [ + { + "extrusion_block_angle_step": 0, + "extrusion_block_end": 210, + "extrusion_block_start": 50, + } + ], + "incl_region_id": True, + "lamination_type": "", + "linked_regions": [], + "magnet_temp_coeff_method": 0, + "material": "", + "material_weight_component_type": 1, + "material_weight_field": "Weight_Total_SlotWedge", + "mesh_length": 0, + "name": "StatorWedge", + "name_unique": "StatorWedge", + "parent_name": "", + "region_coordinate": {"x": 67.0166785354928, "y": 4.39250517759192}, + "region_temperature": 20, + "region_type": "Wedge", + "region_type_mapped": "No type", + "singular": False, + "thermal_loss": 0, + "use_in_weight_calculation": True, + "weight_reduction_factor": 1, + }, + } +} + +simple_json = { + "regions": { + "Triangle": { + "area": 0.5, + "child_names": [], + "name_unique": "Triangle", + "centroid": {"x": 0.333333333333333, "y": 0.333333333333333}, + "colour": {"b": 0, "g": 0, "r": 0}, + "duplications": 1, + "entities": [ + {"end": {"x": 1, "y": 0}, "start": {"x": 0, "y": 0}, "type": "line"}, + {"end": {"x": 0, "y": 1}, "start": {"x": 1, "y": 0}, "type": "line"}, + {"end": {"x": 0, "y": 0}, "start": {"x": 0, "y": 1}, "type": "line"}, + ], + "lamination_type": "", + "material": "air", + "mesh_length": 0, + "name": "Triangle", + "on_boundary": False, + "parent_name": "", + "region_coordinate": {"x": 0.25, "y": 0.25}, + "region_type": "Airgap", + "singular": False, + } + } +} +sample_json2 = { + "regions": { + "ArmatureSlotL1": { + "area": 54.1336003515254, + "centroid": {"x": 77.6745925290051, "y": 3.69028322112561}, + "colour": {"b": 0, "g": 255, "r": 255}, + "duplications": 48, + "entities": [ + { + "end": {"x": 67.1737928392437, "y": 4.40280299311749}, + "start": {"x": 86.8137263217585, "y": 5.69007224302245}, + "type": "line", + }, + { + "end": {"x": 67.3148950021716, "y": 2.24999999999998}, + "start": {"x": 67.1737928392437, "y": 4.40280299311749}, + "type": "line", + }, + { + "end": {"x": 84.8936835106123, "y": 2.25000000000002}, + "start": {"x": 67.3148950021716, "y": 2.24999999999998}, + "type": "line", + }, + { + "centre": {"x": 84.89368351061226, "y": 4.25000000000003}, + "end": {"x": 86.8911819461561, "y": 4.35000000000002}, + "radius": 2.00000000000001, + "start": {"x": 84.8936835106123, "y": 2.25000000000002}, + "type": "arc", + }, + { + "centre": {"x": 1.1368683772161603e-13, "y": -1.8181012251261564e-12}, + "end": {"x": 86.8137263217585, "y": 5.69007224302245}, + "radius": 87, + "start": {"x": 86.8911819461561, "y": 4.35000000000002}, + "type": "arc", + }, + ], + "lamination_type": "", + "material": "Copper (Pure)", + "mesh_length": 0, + "name": "ArmatureSlotL1", + "on_boundary": False, + "parent_name": "root", + "region_coordinate": {"x": 82.6541179328448, "y": 3.97003612151121}, + "region_type": "Split Slot", + "singular": True, + }, + "ArmatureSlotR1": { + "area": 54.1336003515217, + "centroid": {"x": 77.4917542392541, "y": 6.47985645845607}, + "colour": {"b": 0, "g": 215, "r": 255}, + "duplications": 48, + "entities": [ + { + "centre": {"x": -1.2789769243681803e-13, "y": 1.7390533457728452e-12}, + "end": {"x": 86.7156047753705, "y": 7.02878996995535}, + "radius": 87, + "start": {"x": 86.8137263217585, "y": 5.69007224302245}, + "type": "arc", + }, + { + "centre": {"x": 84.72214259662634, "y": 6.867208591335685}, + "end": {"x": 84.4610902121862, "y": 8.8500983140833}, + "radius": 2, + "start": {"x": 86.7156047753705, "y": 7.02878996995535}, + "type": "arc", + }, + { + "end": {"x": 67.0326906763158, "y": 6.55560598623458}, + "start": {"x": 84.4610902121862, "y": 8.8500983140833}, + "type": "line", + }, + { + "end": {"x": 67.1737928392437, "y": 4.40280299311749}, + "start": {"x": 67.0326906763158, "y": 6.55560598623458}, + "type": "line", + }, + { + "end": {"x": 86.8137263217585, "y": 5.69007224302245}, + "start": {"x": 67.1737928392437, "y": 4.40280299311749}, + "type": "line", + }, + ], + "lamination_type": "", + "material": "Copper (Pure)", + "mesh_length": 0, + "name": "ArmatureSlotR1", + "on_boundary": False, + "parent_name": "root", + "region_coordinate": {"x": 84.3322623694114, "y": 7.19305649200386}, + "region_type": "Split Slot", + "singular": True, + }, + "Housing": { + "area": 188.495559215387, + "centroid": {"x": 119.725253764956, "y": 7.84720771818839}, + "colour": {"b": 253, "g": 231, "r": 229}, + "duplications": 48, + "entities": [ + {"end": {"x": 126, "y": 0}, "start": {"x": 114, "y": 0}, "type": "line"}, + { + "centre": {"x": 0.0, "y": -8.668621376273222e-13}, + "end": {"x": 124.9220525331, "y": 16.4463002197265}, + "radius": 126, + "start": {"x": 126, "y": 0}, + "type": "arc", + }, + { + "end": {"x": 113.024714196614, "y": 14.8799859130859}, + "start": {"x": 124.9220525331, "y": 16.4463002197265}, + "type": "line", + }, + { + "centre": {"x": 0.0, "y": -2.9203306439740118e-12}, + "end": {"x": 114, "y": 0}, + "radius": -114, + "start": {"x": 113.024714196614, "y": 14.8799859130859}, + "type": "arc", + }, + ], + "lamination_type": "", + "material": "Aluminium (Alloy 195 Cast)", + "mesh_length": 0, + "name": "Housing", + "on_boundary": False, + "parent_name": "root", + "region_coordinate": {"x": 119.725253764956, "y": 7.84720771818839}, + "region_type": "Housing", + "singular": False, + }, + "Housing_1": { + "area": 72.9765793490131, + "centroid": {"x": 111.170714519021, "y": 7.28651359322089}, + "colour": {"b": 253, "g": 231, "r": 229}, + "duplications": 48, + "entities": [ + {"end": {"x": 114, "y": 0}, "start": {"x": 109, "y": 0}, "type": "line"}, + { + "centre": {"x": 0.0, "y": -2.8919089345436078e-12}, + "end": {"x": 113.024714196614, "y": 14.8799859130859}, + "radius": 114, + "start": {"x": 114, "y": 0}, + "type": "arc", + }, + { + "end": {"x": 108.067489889745, "y": 14.2273549519856}, + "start": {"x": 113.024714196614, "y": 14.8799859130859}, + "type": "line", + }, + { + "centre": {"x": 0.0, "y": -2.5712765250318625e-12}, + "end": {"x": 109, "y": 0}, + "radius": -109, + "start": {"x": 108.067489889745, "y": 14.2273549519856}, + "type": "arc", + }, + ], + "lamination_type": "", + "material": "Aluminium (Alloy 195 Cast)", + "mesh_length": 0, + "name": "Housing", + "on_boundary": False, + "parent_name": "root", + "region_coordinate": {"x": 111.170714519021, "y": 7.28651359322089}, + "region_type": "Housing", + "singular": False, + }, + "Housing_2": { + "area": 136.135681655558, + "centroid": {"x": 103.755368536621, "y": 6.80048613956137}, + "colour": {"b": 253, "g": 231, "r": 229}, + "duplications": 48, + "entities": [ + {"end": {"x": 109, "y": 0}, "start": {"x": 99, "y": 0}, "type": "line"}, + { + "centre": {"x": -1.4210854715202004e-14, "y": -2.5446311724408588e-12}, + "end": {"x": 108.067489889745, "y": 14.2273549519856}, + "radius": 109, + "start": {"x": 109, "y": 0}, + "type": "arc", + }, + { + "end": {"x": 98.1530412760072, "y": 12.9220930297851}, + "start": {"x": 108.067489889745, "y": 14.2273549519856}, + "type": "line", + }, + { + "centre": {"x": 0.0, "y": -2.7000623958883807e-13}, + "end": {"x": 99, "y": 0}, + "radius": -99, + "start": {"x": 98.1530412760072, "y": 12.9220930297851}, + "type": "arc", + }, + ], + "lamination_type": "", + "material": "Aluminium (Alloy 195 Cast)", + "mesh_length": 0, + "name": "Housing", + "on_boundary": False, + "parent_name": "root", + "region_coordinate": {"x": 103.755368536621, "y": 6.80048613956137}, + "region_type": "Housing", + "singular": False, + }, + "Impreg": { + "area": 97.2839971133977, + "centroid": {"x": 77.5396239396489, "y": 5.08221545839593}, + "colour": {"b": 48, "g": 224, "r": 48}, + "duplications": 48, + "entities": [ + { + "end": {"x": 67.2985091364678, "y": 2.5}, + "start": {"x": 67.0490765420196, "y": 6.30560598623492}, + "type": "line", + }, + { + "end": {"x": 84.8936835106123, "y": 2.5}, + "start": {"x": 67.2985091364678, "y": 2.5}, + "type": "line", + }, + { + "centre": {"x": 84.89368351061225, "y": 4.25}, + "end": {"x": 86.6414946417131, "y": 4.3375}, + "radius": 1.75, + "start": {"x": 84.8936835106123, "y": 2.5}, + "type": "arc", + }, + { + "centre": {"x": -1.8474111129762605e-13, "y": 3.5207392556912964e-12}, + "end": {"x": 86.5642615909489, "y": 5.67372146071491}, + "radius": 86.75, + "start": {"x": 86.6414946417131, "y": 4.3375}, + "type": "arc", + }, + { + "centre": {"x": 4.263256414560601e-14, "y": -1.0391687510491465e-13}, + "end": {"x": 86.4664220030275, "y": 7.00859229762791}, + "radius": 86.75, + "start": {"x": 86.5642615909489, "y": 5.67372146071491}, + "type": "arc", + }, + { + "centre": {"x": 84.72214259662638, "y": 6.867208591335713}, + "end": {"x": 84.4937217602412, "y": 8.60223709873987}, + "radius": 1.75, + "start": {"x": 86.4664220030275, "y": 7.00859229762791}, + "type": "arc", + }, + { + "end": {"x": 67.0490765420196, "y": 6.30560598623492}, + "start": {"x": 84.4937217602412, "y": 8.60223709873987}, + "type": "line", + }, + ], + "lamination_type": "", + "material": "", + "mesh_length": 0, + "name": "Impreg", + "on_boundary": False, + "parent_name": "root", + "region_coordinate": {"x": 82.3420980203589, "y": 5.55860429566785}, + "region_type": "Stator Impreg", + "singular": False, + }, + "L1_1Magnet1": { + "area": 96.0000000000001, + "centroid": {"x": 56.040689756688, "y": 11.8668792573194}, + "colour": {"b": 0, "g": 212, "r": 0}, + "duplications": 8, + "entities": [ + { + "end": {"x": 63.2908412096315, "y": 7.34633682201565}, + "start": {"x": 58.5307211678841, "y": 3.69376824796331}, + "type": "line", + }, + { + "end": {"x": 53.550658345492, "y": 20.0399902666754}, + "start": {"x": 63.2908412096315, "y": 7.34633682201565}, + "type": "line", + }, + { + "end": {"x": 48.7905383037446, "y": 16.3874216926231}, + "start": {"x": 53.550658345492, "y": 20.0399902666754}, + "type": "line", + }, + { + "end": {"x": 58.5307211678841, "y": 3.69376824796331}, + "start": {"x": 48.7905383037446, "y": 16.3874216926231}, + "type": "line", + }, + ], + "lamination_type": "", + "material": "N30UH", + "mesh_length": 0, + "name": "L1_1Magnet1", + "on_boundary": False, + "parent_name": "Rotor", + "region_coordinate": {"x": 56.040689756688, "y": 11.8668792573194}, + "region_type": "Magnet", + "singular": True, + }, + "L1_1Magnet2": { + "area": 96.0000000000001, + "centroid": {"x": 48.0179025436981, "y": 31.2356009549531}, + "colour": {"b": 0, "g": 212, "r": 0}, + "duplications": 8, + "entities": [ + { + "end": {"x": 43.9993584218163, "y": 38.7755812692834}, + "start": {"x": 49.9480275900591, "y": 39.5587384226037}, + "type": "line", + }, + { + "end": {"x": 46.0877774973371, "y": 22.9124634873025}, + "start": {"x": 43.9993584218163, "y": 38.7755812692834}, + "type": "line", + }, + { + "end": {"x": 52.03644666558, "y": 23.6956206406228}, + "start": {"x": 46.0877774973371, "y": 22.9124634873025}, + "type": "line", + }, + { + "end": {"x": 49.9480275900591, "y": 39.5587384226037}, + "start": {"x": 52.03644666558, "y": 23.6956206406228}, + "type": "line", + }, + ], + "lamination_type": "", + "material": "N30UH", + "mesh_length": 0, + "name": "L1_1Magnet2", + "on_boundary": False, + "parent_name": "Rotor", + "region_coordinate": {"x": 48.0179025436981, "y": 31.2356009549531}, + "region_type": "Magnet", + "singular": True, + }, + "Liner": { + "area": 10.983203589653, + "centroid": {"x": 77.968913664417, "y": 5.11035259350818}, + "colour": {"b": 0, "g": 128, "r": 0}, + "duplications": 48, + "entities": [ + { + "end": {"x": 84.8936835106123, "y": 2.25}, + "start": {"x": 67.3148950021716, "y": 2.24999999999998}, + "type": "line", + }, + { + "centre": {"x": 84.89368351061228, "y": 4.25}, + "end": {"x": 86.8911819461561, "y": 4.35}, + "radius": 2, + "start": {"x": 84.8936835106123, "y": 2.25}, + "type": "arc", + }, + { + "centre": {"x": 1.1368683772161603e-13, "y": -1.7896795156957523e-12}, + "end": {"x": 86.8137263217585, "y": 5.69007224302245}, + "radius": 87, + "start": {"x": 86.8911819461561, "y": 4.35}, + "type": "arc", + }, + { + "centre": {"x": -1.2789769243681803e-13, "y": 1.865174681370263e-12}, + "end": {"x": 86.7156047753705, "y": 7.02878996995537}, + "radius": 87, + "start": {"x": 86.8137263217585, "y": 5.69007224302245}, + "type": "arc", + }, + { + "centre": {"x": 84.72214259662634, "y": 6.867208591335713}, + "end": {"x": 84.4610902121862, "y": 8.85009831408333}, + "radius": 2, + "start": {"x": 86.7156047753705, "y": 7.02878996995537}, + "type": "arc", + }, + { + "end": {"x": 67.0326906763158, "y": 6.55560598623492}, + "start": {"x": 84.4610902121862, "y": 8.85009831408333}, + "type": "line", + }, + { + "end": {"x": 67.0490765420196, "y": 6.30560598623492}, + "start": {"x": 67.0326906763158, "y": 6.55560598623492}, + "type": "line", + }, + { + "end": {"x": 84.4937217602412, "y": 8.60223709873987}, + "start": {"x": 67.0490765420196, "y": 6.30560598623492}, + "type": "line", + }, + { + "centre": {"x": 84.72214259662638, "y": 6.867208591335713}, + "end": {"x": 86.4664220030275, "y": 7.00859229762791}, + "radius": -1.75, + "start": {"x": 84.4937217602412, "y": 8.60223709873987}, + "type": "arc", + }, + { + "centre": {"x": 4.263256414560601e-14, "y": -1.2523315717771766e-13}, + "end": {"x": 86.5642615909489, "y": 5.67372146071491}, + "radius": -86.75, + "start": {"x": 86.4664220030275, "y": 7.00859229762791}, + "type": "arc", + }, + { + "centre": {"x": -1.8474111129762605e-13, "y": 3.4994229736184934e-12}, + "end": {"x": 86.6414946417131, "y": 4.3375}, + "radius": -86.75, + "start": {"x": 86.5642615909489, "y": 5.67372146071491}, + "type": "arc", + }, + { + "centre": {"x": 84.89368351061225, "y": 4.25}, + "end": {"x": 84.8936835106123, "y": 2.5}, + "radius": -1.75, + "start": {"x": 86.6414946417131, "y": 4.3375}, + "type": "arc", + }, + { + "end": {"x": 67.2985091364678, "y": 2.5}, + "start": {"x": 84.8936835106123, "y": 2.5}, + "type": "line", + }, + { + "end": {"x": 67.3148950021716, "y": 2.24999999999998}, + "start": {"x": 67.2985091364678, "y": 2.5}, + "type": "line", + }, + ], + "lamination_type": "", + "material": "", + "mesh_length": 0, + "name": "Liner", + "on_boundary": False, + "parent_name": "root", + "region_coordinate": {"x": 79.4399567850703, "y": 8.04005751741874}, + "region_type": "Stator Liner", + "singular": False, + }, + "Rotor": { + "area": 1030.83508945915, + "centroid": {"x": 48.1444344595077, "y": 19.9420777059107}, + "colour": {"b": 240, "g": 240, "r": 0}, + "duplications": 8, + "entities": [ + {"end": {"x": 65, "y": 0}, "start": {"x": 40, "y": 0}, "type": "line"}, + { + "centre": {"x": 0.0, "y": 1.0658141036401503e-14}, + "end": {"x": 45.9619407771256, "y": 45.9619407771256}, + "radius": 65, + "start": {"x": 65, "y": 0}, + "type": "arc", + }, + { + "end": {"x": 28.2842712474619, "y": 28.2842712474619}, + "start": {"x": 45.9619407771256, "y": 45.9619407771256}, + "type": "line", + }, + { + "centre": {"x": 0.0, "y": -5.329070518200751e-15}, + "end": {"x": 40, "y": 0}, + "radius": -40, + "start": {"x": 28.2842712474619, "y": 28.2842712474619}, + "type": "arc", + }, + ], + "lamination_type": "", + "material": "M250-35A", + "mesh_length": 0, + "name": "Rotor", + "on_boundary": False, + "parent_name": "root", + "region_coordinate": {"x": 47.7895021472478, "y": 21.7999639468195}, + "region_type": "Rotor", + "singular": False, + }, + "Rotor Pocket": { + "area": 27.2374304690439, + "centroid": {"x": 47.559936471459, "y": 31.1704313411946}, + "colour": {"b": 240, "g": 241, "r": 241}, + "duplications": 8, + "entities": [ + { + "end": {"x": 43.8010694495415, "y": 38.7494760308394}, + "start": {"x": 43.7358063534315, "y": 39.2451984615263}, + "type": "line", + }, + { + "end": {"x": 45.8894885250623, "y": 22.8863582488585}, + "start": {"x": 43.8010694495415, "y": 38.7494760308394}, + "type": "line", + }, + { + "end": {"x": 45.9547516211724, "y": 22.3906358181716}, + "start": {"x": 45.8894885250623, "y": 22.8863582488585}, + "type": "line", + }, + { + "centre": {"x": 48.84516770684258, "y": 24.185768432130544}, + "end": {"x": 52.10170976169, "y": 23.1998982099359}, + "radius": 3.4025, + "start": {"x": 45.9547516211724, "y": 22.3906358181716}, + "type": "arc", + }, + { + "end": {"x": 52.03644666558, "y": 23.6956206406228}, + "start": {"x": 52.10170976169, "y": 23.1998982099359}, + "type": "line", + }, + { + "end": {"x": 46.0877774973371, "y": 22.9124634873025}, + "start": {"x": 52.03644666558, "y": 23.6956206406228}, + "type": "line", + }, + { + "end": {"x": 43.9993584218163, "y": 38.7755812692834}, + "start": {"x": 46.0877774973371, "y": 22.9124634873025}, + "type": "line", + }, + { + "end": {"x": 49.9480275900591, "y": 39.5587384226037}, + "start": {"x": 43.9993584218163, "y": 38.7755812692834}, + "type": "line", + }, + { + "end": {"x": 49.8827644939491, "y": 40.0544608532907}, + "start": {"x": 49.9480275900591, "y": 39.5587384226037}, + "type": "line", + }, + { + "centre": {"x": 46.99234840827894, "y": 38.259328239331715}, + "end": {"x": 43.7358063534315, "y": 39.2451984615263}, + "radius": 3.4025, + "start": {"x": 49.8827644939491, "y": 40.0544608532907}, + "type": "arc", + }, + ], + "lamination_type": "", + "material": "Air (Motor-CAD model)", + "mesh_length": 0, + "name": "Rotor Pocket", + "on_boundary": False, + "parent_name": "Rotor", + "region_coordinate": {"x": 45.8272822054961, "y": 39.6065511007121}, + "region_type": "Rotor Pocket", + "singular": False, + }, + "Rotor Pocket_1": { + "area": 27.2374304690439, + "centroid": {"x": 55.6707769656374, "y": 11.5891302179014}, + "colour": {"b": 240, "g": 241, "r": 241}, + "duplications": 8, + "entities": [ + { + "end": {"x": 63.2908412096315, "y": 7.34633682201565}, + "start": {"x": 63.5952219241359, "y": 6.94966015187003}, + "type": "line", + }, + { + "end": {"x": 58.5307211678841, "y": 3.69376824796331}, + "start": {"x": 63.2908412096315, "y": 7.34633682201565}, + "type": "line", + }, + { + "end": {"x": 48.7905383037446, "y": 16.3874216926231}, + "start": {"x": 58.5307211678841, "y": 3.69376824796331}, + "type": "line", + }, + { + "end": {"x": 53.550658345492, "y": 20.0399902666754}, + "start": {"x": 48.7905383037446, "y": 16.3874216926231}, + "type": "line", + }, + { + "end": {"x": 53.2462776309876, "y": 20.436666936821}, + "start": {"x": 53.550658345492, "y": 20.0399902666754}, + "type": "line", + }, + { + "centre": {"x": 51.64067018026958, "y": 17.436828447135458}, + "end": {"x": 48.327486921182, "y": 16.662346076967}, + "radius": 3.4025, + "start": {"x": 53.2462776309876, "y": 20.436666936821}, + "type": "arc", + }, + { + "end": {"x": 48.6318676356863, "y": 16.2656694068213}, + "start": {"x": 48.327486921182, "y": 16.662346076967}, + "type": "line", + }, + { + "end": {"x": 58.3720504998259, "y": 3.57201596216157}, + "start": {"x": 48.6318676356863, "y": 16.2656694068213}, + "type": "line", + }, + { + "end": {"x": 58.6764312143302, "y": 3.17533929201596}, + "start": {"x": 58.3720504998259, "y": 3.57201596216157}, + "type": "line", + }, + { + "centre": {"x": 60.28203866504834, "y": 6.175177781701426}, + "end": {"x": 63.5952219241359, "y": 6.94966015187003}, + "radius": 3.4025, + "start": {"x": 58.6764312143302, "y": 3.17533929201596}, + "type": "arc", + }, + ], + "lamination_type": "", + "material": "Air (Motor-CAD model)", + "mesh_length": 0, + "name": "Rotor Pocket", + "on_boundary": False, + "parent_name": "Rotor", + "region_coordinate": {"x": 61.753742604451, "y": 4.70347384229879}, + "region_type": "Rotor Pocket", + "singular": False, + }, + "Shaft": { + "area": 628.318530717959, + "centroid": {"x": 24.0007863180929, "y": 9.94145120057268}, + "colour": {"b": 160, "g": 160, "r": 160}, + "duplications": 8, + "entities": [ + { + "centre": {"x": -7.105427357601002e-15, "y": 1.7763568394002505e-15}, + "end": {"x": 28.2842712474619, "y": 28.2842712474619}, + "radius": 40, + "start": {"x": 40, "y": 0}, + "type": "arc", + }, + { + "end": {"x": 0, "y": 0}, + "start": {"x": 28.2842712474619, "y": 28.2842712474619}, + "type": "line", + }, + {"end": {"x": 40, "y": 0}, "start": {"x": 0, "y": 0}, "type": "line"}, + ], + "lamination_type": "", + "material": "", + "mesh_length": 0, + "name": "Shaft", + "on_boundary": False, + "parent_name": "root", + "region_coordinate": {"x": 24.7487373415292, "y": 10.6066017177982}, + "region_type": "Shaft", + "singular": False, + }, + "Stator": { + "area": 243.916173195236, + "centroid": {"x": 86.1706272589501, "y": 5.64792130351275}, + "colour": {"b": 16, "g": 0, "r": 240}, + "duplications": 48, + "entities": [ + { + "centre": {"x": 0.0, "y": -2.469136006766348e-13}, + "end": {"x": 98.1530412760072, "y": 12.9220930297851}, + "radius": 99, + "start": {"x": 99, "y": 0}, + "type": "arc", + }, + { + "end": {"x": 65.4353608506715, "y": 8.6147286865234}, + "start": {"x": 98.1530412760072, "y": 12.9220930297851}, + "type": "line", + }, + { + "centre": {"x": -7.105427357601002e-14, "y": 7.123190925995004e-13}, + "end": {"x": 65.7435730840115, "y": 5.81227994398376}, + "radius": -66, + "start": {"x": 65.4353608506715, "y": 8.6147286865234}, + "type": "arc", + }, + { + "end": {"x": 66.7414320072501, "y": 5.87768307321394}, + "start": {"x": 65.7435730840115, "y": 5.81227994398376}, + "type": "line", + }, + { + "end": {"x": 67.0326906763158, "y": 6.555605986235}, + "start": {"x": 66.7414320072501, "y": 5.87768307321394}, + "type": "line", + }, + { + "end": {"x": 84.4610902121862, "y": 8.8500983140833}, + "start": {"x": 67.0326906763158, "y": 6.555605986235}, + "type": "line", + }, + { + "centre": {"x": 84.72214259662634, "y": 6.867208591335685}, + "end": {"x": 86.7156047753705, "y": 7.02878996995535}, + "radius": -2, + "start": {"x": 84.4610902121862, "y": 8.8500983140833}, + "type": "arc", + }, + { + "centre": {"x": -1.1368683772161603e-13, "y": 1.7177370637000422e-12}, + "end": {"x": 86.8137263217585, "y": 5.69007224302245}, + "radius": -87, + "start": {"x": 86.7156047753705, "y": 7.02878996995535}, + "type": "arc", + }, + { + "centre": {"x": 1.1368683772161603e-13, "y": -1.8394175071989594e-12}, + "end": {"x": 86.8911819461561, "y": 4.35000000000002}, + "radius": -87, + "start": {"x": 86.8137263217585, "y": 5.69007224302245}, + "type": "arc", + }, + { + "centre": {"x": 84.89368351061226, "y": 4.25000000000003}, + "end": {"x": 84.8936835106123, "y": 2.25000000000002}, + "radius": -2.00000000000001, + "start": {"x": 86.8911819461561, "y": 4.35000000000002}, + "type": "arc", + }, + { + "end": {"x": 67.3148950021716, "y": 2.24999999999998}, + "start": {"x": 84.8936835106123, "y": 2.25000000000002}, + "type": "line", + }, + { + "end": {"x": 66.9376413949405, "y": 2.88410630349807}, + "start": {"x": 67.3148950021716, "y": 2.24999999999998}, + "type": "line", + }, + { + "end": {"x": 65.939782471702, "y": 2.81870317426794}, + "start": {"x": 66.9376413949405, "y": 2.88410630349807}, + "type": "line", + }, + { + "centre": {"x": 1.4210854715202004e-14, "y": 1.2376766278521245e-12}, + "end": {"x": 66, "y": 0}, + "radius": -66, + "start": {"x": 65.939782471702, "y": 2.81870317426794}, + "type": "arc", + }, + {"end": {"x": 99, "y": 0}, "start": {"x": 66, "y": 0}, "type": "line"}, + ], + "lamination_type": "", + "material": "M250-35A", + "mesh_length": 0, + "name": "Stator", + "on_boundary": False, + "parent_name": "root", + "region_coordinate": {"x": 97.7405934251342, "y": 6.46104651489255}, + "region_type": "Stator", + "singular": False, + }, + "StatorAir": { + "area": 2.96590380676454, + "centroid": {"x": 66.4303884745974, "y": 4.35407769678662}, + "colour": {"b": 240, "g": 240, "r": 239}, + "duplications": 48, + "entities": [ + { + "end": {"x": 66.9376413949405, "y": 2.88410630349813}, + "start": {"x": 65.939782471702, "y": 2.81870317426794}, + "type": "line", + }, + { + "end": {"x": 66.7414320072501, "y": 5.87768307321392}, + "start": {"x": 66.9376413949405, "y": 2.88410630349813}, + "type": "line", + }, + { + "end": {"x": 65.7435730840115, "y": 5.81227994398376}, + "start": {"x": 66.7414320072501, "y": 5.87768307321392}, + "type": "line", + }, + { + "centre": {"x": 1.1368683772161603e-13, "y": -1.5827339439056232e-12}, + "end": {"x": 65.939782471702, "y": 2.81870317426794}, + "radius": -66, + "start": {"x": 65.7435730840115, "y": 5.81227994398376}, + "type": "arc", + }, + ], + "lamination_type": "", + "material": "Air", + "mesh_length": 0, + "name": "StatorAir", + "on_boundary": False, + "parent_name": "root", + "region_coordinate": {"x": 66.340607239476, "y": 4.60980564066148}, + "region_type": "Stator Air", + "singular": False, + }, + "StatorSlot": { + "area": 108.267200703049, + "centroid": {"x": 77.5831733841295, "y": 5.08506983979084}, + "colour": {"b": 16, "g": 240, "r": 240}, + "duplications": 48, + "entities": [ + { + "end": {"x": 84.8936835106123, "y": 2.25000000000002}, + "start": {"x": 67.3148950021716, "y": 2.24999999999998}, + "type": "line", + }, + { + "centre": {"x": 84.89368351061226, "y": 4.25000000000003}, + "end": {"x": 86.8911819461561, "y": 4.35000000000002}, + "radius": 2.00000000000001, + "start": {"x": 84.8936835106123, "y": 2.25000000000002}, + "type": "arc", + }, + { + "centre": {"x": 1.1368683772161603e-13, "y": -1.8181012251261564e-12}, + "end": {"x": 86.8137263217585, "y": 5.69007224302245}, + "radius": 87, + "start": {"x": 86.8911819461561, "y": 4.35000000000002}, + "type": "arc", + }, + { + "centre": {"x": -1.2789769243681803e-13, "y": 1.7390533457728452e-12}, + "end": {"x": 86.7156047753705, "y": 7.02878996995535}, + "radius": 87, + "start": {"x": 86.8137263217585, "y": 5.69007224302245}, + "type": "arc", + }, + { + "centre": {"x": 84.72214259662634, "y": 6.867208591335685}, + "end": {"x": 84.4610902121862, "y": 8.8500983140833}, + "radius": 2, + "start": {"x": 86.7156047753705, "y": 7.02878996995535}, + "type": "arc", + }, + { + "end": {"x": 67.0326906763158, "y": 6.55560598623481}, + "start": {"x": 84.4610902121862, "y": 8.8500983140833}, + "type": "line", + }, + { + "end": {"x": 67.3148950021716, "y": 2.24999999999998}, + "start": {"x": 67.0326906763158, "y": 6.55560598623481}, + "type": "line", + }, + ], + "lamination_type": "", + "material": "Copper (Pure)", + "mesh_length": 0, + "name": "StatorSlot", + "on_boundary": False, + "parent_name": "root", + "region_coordinate": {"x": 83.5757121546551, "y": 5.55860429566783}, + "region_type": "Stator Slot Area", + "singular": False, + }, + "StatorWedge": { + "area": 1.22513893654097, + "centroid": {"x": 67.0166785354928, "y": 4.39250517759192}, + "colour": {"b": 192, "g": 192, "r": 160}, + "duplications": 48, + "entities": [ + { + "end": {"x": 67.3148950021716, "y": 2.25}, + "start": {"x": 66.9376413949405, "y": 2.88410630349807}, + "type": "line", + }, + { + "end": {"x": 67.0326906763158, "y": 6.55560598623498}, + "start": {"x": 67.3148950021716, "y": 2.25}, + "type": "line", + }, + { + "end": {"x": 66.7414320072501, "y": 5.87768307321414}, + "start": {"x": 67.0326906763158, "y": 6.55560598623498}, + "type": "line", + }, + { + "end": {"x": 66.9376413949405, "y": 2.88410630349807}, + "start": {"x": 66.7414320072501, "y": 5.87768307321414}, + "type": "line", + }, + ], + "lamination_type": "", + "material": "", + "mesh_length": 0, + "name": "StatorWedge", + "on_boundary": False, + "parent_name": "root", + "region_coordinate": {"x": 67.0166785354928, "y": 4.39250517759192}, + "region_type": "Wedge", + "singular": False, + }, + } +} + +sample_tree = GeometryTree.from_json(mc.get_geometry_tree()) + + +def get_simple_tree(): + """Return a simple GeometryTree for the purposes of testing""" + p1 = Coordinate(0, 0) + p2 = Coordinate(1, 0) + p3 = Coordinate(0, 1) + line1 = Line(p1, p2) + line2 = Line(p2, p3) + line3 = Line(p3, p1) + triangle = Region(region_type=RegionType.airgap) + triangle.__class__ = GeometryNode + triangle.entities.append(line1) + triangle.entities.append(line2) + triangle.entities.append(line3) + triangle.name = "Triangle" + triangle.key = "Triangle" + triangle.children = [] + + tree = GeometryTree() + + triangle.parent = tree["root"] + tree["root"].children.append(triangle) + tree["Triangle"] = triangle + + return tree + + +def test_get_json(): + test_json = mc.get_geometry_tree() + assert test_json == sample_json1 + + +def test_from_json(): + test_tree = get_simple_tree() + assert test_tree == GeometryTree.from_json(simple_json) + + +def test_to_json(): + test_json = sample_tree.to_json() + assert test_json == sample_json2 + + +def test_get_node(): + assert sample_tree.get_node("rotor") == sample_tree["Rotor"] + + +def test_add_node(): + # Tests the basic functionality of adding a node + test_tree = deepcopy(get_simple_tree()) + new_node = GeometryNode(parent=test_tree["root"]) + new_node.name = "node" + new_node.key = "node" + test_tree["node"] = new_node + + function_tree = deepcopy(get_simple_tree()) + new_node2 = GeometryNode(parent=function_tree["root"]) + new_node2.name = "node" + function_tree.add_node(new_node2) + + assert test_tree == function_tree + + +def test_add_node_with_children(): + # Tests the parent and child reassignment performed when including those values + test_tree = deepcopy(get_simple_tree()) + new_node = GeometryNode() + new_node.parent = test_tree["root"] + new_node.children.append(test_tree["Triangle"]) + new_node.name = "node" + new_node.key = "node" + test_tree["root"].children.remove(test_tree["Triangle"]) + test_tree["root"].children.append(new_node) + test_tree["node"] = new_node + test_tree["Triangle"].parent = new_node + + function_tree = deepcopy(get_simple_tree()) + new_node2 = Region() + new_node2.name = "node" + function_tree.add_node(new_node2, parent="root", children=["Triangle"]) + + assert test_tree == function_tree + + +def test_add_node_with_children_2(): + # Same test as above, but testing different mode of function input + test_tree = deepcopy(get_simple_tree()) + new_node = GeometryNode() + new_node.parent = test_tree["root"] + new_node.children.append(test_tree["Triangle"]) + new_node.name = "node" + new_node.key = "node1" + test_tree["root"].children.remove(test_tree["Triangle"]) + test_tree["root"].children.append(new_node) + test_tree["node1"] = new_node + test_tree["Triangle"].parent = new_node + + function_tree = deepcopy(get_simple_tree()) + new_node2 = Region() + new_node2.name = "node" + function_tree.add_node( + new_node2, parent=function_tree["root"], children=[function_tree["Triangle"]], key="node1" + ) + + assert test_tree == function_tree + + +def test_add_node_errors(): + function_tree = deepcopy(get_simple_tree()) + new_node2 = Region() + new_node2.name = "node" + + with pytest.raises(TypeError, match="Parent must be a GeometryNode or str"): + function_tree.add_node(new_node2, parent=0) + + with pytest.raises(TypeError, match="Children must be a GeometryNode or str"): + function_tree.add_node(new_node2, children=[0, "root"]) + + +def test_remove_node(): + test_tree1 = deepcopy(sample_tree) + magnet = test_tree1["L1_1Magnet1"] + test_tree1["Rotor"].children.remove(magnet) + test_tree1.pop("L1_1Magnet1") + + test_tree_2 = deepcopy(sample_tree) + test_tree_2.remove_node("L1_1Magnet1") + assert test_tree1 == test_tree_2 + + +def test_remove_branch(): + test_tree1 = deepcopy(sample_tree) + test_tree1["root"].children.remove(test_tree1["Rotor"]) + for child_key in test_tree1["Rotor"].child_keys: + test_tree1.pop(child_key) + test_tree1.pop("Rotor") + + test_tree2 = deepcopy(sample_tree) + test_tree2.remove_branch("Rotor") + + assert test_tree1 == test_tree2 + + +def test_remove_branch2(): + # Same test, slightly different function input + test_tree1 = deepcopy(sample_tree) + test_tree1["root"].children.remove(test_tree1["Rotor"]) + for child_key in test_tree1["Rotor"].child_keys: + test_tree1.pop(child_key) + test_tree1.pop("Rotor") + + test_tree2 = deepcopy(sample_tree) + test_tree2.remove_branch(test_tree2["Rotor"]) + + assert test_tree1 == test_tree2 From 35bd62e62aea0d5607d27e41318809c1f71159ed Mon Sep 17 00:00:00 2001 From: Aidan Crandell Date: Fri, 27 Jun 2025 09:14:26 +0100 Subject: [PATCH 05/29] More testing --- tests/test_geometry_tree.py | 50 ++++++++++++++++++++++++++----------- 1 file changed, 35 insertions(+), 15 deletions(-) diff --git a/tests/test_geometry_tree.py b/tests/test_geometry_tree.py index 5f34edd8b..9bad57b6a 100644 --- a/tests/test_geometry_tree.py +++ b/tests/test_geometry_tree.py @@ -2454,27 +2454,27 @@ def test_add_node_errors(): def test_remove_node(): - test_tree1 = deepcopy(sample_tree) - magnet = test_tree1["L1_1Magnet1"] - test_tree1["Rotor"].children.remove(magnet) - test_tree1.pop("L1_1Magnet1") + test_tree = deepcopy(sample_tree) + magnet = test_tree["L1_1Magnet1"] + test_tree["Rotor"].children.remove(magnet) + test_tree.pop("L1_1Magnet1") - test_tree_2 = deepcopy(sample_tree) - test_tree_2.remove_node("L1_1Magnet1") - assert test_tree1 == test_tree_2 + function_tree = deepcopy(sample_tree) + function_tree.remove_node("L1_1Magnet1") + assert test_tree == function_tree def test_remove_branch(): - test_tree1 = deepcopy(sample_tree) - test_tree1["root"].children.remove(test_tree1["Rotor"]) - for child_key in test_tree1["Rotor"].child_keys: - test_tree1.pop(child_key) - test_tree1.pop("Rotor") + test_tree = deepcopy(sample_tree) + test_tree["root"].children.remove(test_tree["Rotor"]) + for child_key in test_tree["Rotor"].child_keys: + test_tree.pop(child_key) + test_tree.pop("Rotor") - test_tree2 = deepcopy(sample_tree) - test_tree2.remove_branch("Rotor") + function_tree = deepcopy(sample_tree) + function_tree.remove_branch("Rotor") - assert test_tree1 == test_tree2 + assert test_tree == function_tree def test_remove_branch2(): @@ -2489,3 +2489,23 @@ def test_remove_branch2(): test_tree2.remove_branch(test_tree2["Rotor"]) assert test_tree1 == test_tree2 + + +def test_get_parent(): + test_tree = get_simple_tree() + + assert test_tree["root"] == test_tree["Triangle"].parent + + assert test_tree["root"].key == test_tree["Triangle"].parent_key + + assert test_tree["root"].name == test_tree["Triangle"].parent_name + + +def test_get_children(): + test_tree = get_simple_tree() + + assert test_tree["root"].children == [test_tree["Triangle"]] + + assert test_tree["root"].child_names == ["Triangle"] + + assert test_tree["root"].child_keys == ["Triangle"] From be1f685f58f894008667dfaafad5bdac23ab648f Mon Sep 17 00:00:00 2001 From: Aidan Crandell Date: Fri, 27 Jun 2025 13:21:40 +0100 Subject: [PATCH 06/29] Modified testing, changed expected output of mc.get_geometry_tree --- .../core/methods/adaptive_geometry.py | 4 +- .../motorcad/core/methods/geometry_tree.py | 11 +- tests/test_geometry_tree.py | 2439 +---------------- 3 files changed, 80 insertions(+), 2374 deletions(-) diff --git a/src/ansys/motorcad/core/methods/adaptive_geometry.py b/src/ansys/motorcad/core/methods/adaptive_geometry.py index 3528815fd..836746bac 100644 --- a/src/ansys/motorcad/core/methods/adaptive_geometry.py +++ b/src/ansys/motorcad/core/methods/adaptive_geometry.py @@ -24,6 +24,7 @@ from warnings import warn from ansys.motorcad.core.geometry import Region, RegionMagnet +from ansys.motorcad.core.methods.geometry_tree import GeometryTree from ansys.motorcad.core.rpc_client_core import MotorCADError, is_running_in_internal_scripting @@ -308,7 +309,8 @@ def reset_adaptive_geometry(self): def get_geometry_tree(self): """Do placeholder.""" method = "GetGeometryTree" - return self.connection.send_and_receive(method) + json = self.connection.send_and_receive(method) + return GeometryTree._from_json(json) def set_geometry_tree(self, tree): """Do placeholder.""" diff --git a/src/ansys/motorcad/core/methods/geometry_tree.py b/src/ansys/motorcad/core/methods/geometry_tree.py index 51608a140..676902bd4 100644 --- a/src/ansys/motorcad/core/methods/geometry_tree.py +++ b/src/ansys/motorcad/core/methods/geometry_tree.py @@ -75,7 +75,7 @@ def __ne__(self, other): return not self.__eq__(other) @classmethod - def from_json(cls, tree): + def _from_json(cls, tree): """Return a GeometryTree representation of the geometry defined within a JSON. Parameters @@ -105,7 +105,7 @@ def from_json(cls, tree): region["parent_name"] = "root" root["child_names"].append(region["name_unique"]) - self.build_tree(tree_json, root) + self._build_tree(tree_json, root) return self def to_json(self): @@ -127,7 +127,7 @@ def get_node(self, key): node = self[key.capitalize()] return node - def build_tree(self, tree_json, node, parent=None): + def _build_tree(self, tree_json, node, parent=None): """Recursively builds tree. Parameters @@ -144,13 +144,16 @@ def build_tree(self, tree_json, node, parent=None): # Recur for each child. if node["child_names"] != []: for child_name in node["child_names"]: - self.build_tree( + self._build_tree( tree_json, tree_json[child_name], self.get_node(node["name_unique"]) ) def add_node(self, region, key=None, parent=None, children=None): """Add node to tree. + Note that any children specified will be 'reassigned' to the added node, with no + connection to their previous parent. + Parameters ---------- region: ansys.motorcad.core.geometry.Region diff --git a/tests/test_geometry_tree.py b/tests/test_geometry_tree.py index 9bad57b6a..c591b6be5 100644 --- a/tests/test_geometry_tree.py +++ b/tests/test_geometry_tree.py @@ -25,2318 +25,18 @@ import pytest -from ansys.motorcad.core import MotorCAD from ansys.motorcad.core.geometry import Coordinate, Line, Region, RegionType from ansys.motorcad.core.methods.geometry_tree import GeometryNode, GeometryTree -mc = MotorCAD(open_new_instance=False) -mc.set_variable("MessageDisplayState", 2) -mc.set_visible(True) -mc.load_template("e9") -mc.display_screen("Geometry;Radial") +@pytest.fixture(scope="session") +def sample_tree(mc): + mc.display_screen("Geometry;Radial") + return mc.get_geometry_tree() -sample_json1 = { - "regions": { - "ArmatureSlotL1": { - "adaptive_entities": False, - "area": 54.1336003515254, - "centroid": {"x": 77.6745925290051, "y": 3.69028322112561}, - "child_names": [], - "colour": {"b": 0, "g": 255, "r": 255}, - "duplications": 48, - "entities": [ - { - "end": {"x": 67.1737928392437, "y": 4.40280299311749}, - "start": {"x": 86.8137263217585, "y": 5.69007224302245}, - "type": "line", - }, - { - "end": {"x": 67.3148950021716, "y": 2.24999999999998}, - "start": {"x": 67.1737928392437, "y": 4.40280299311749}, - "type": "line", - }, - { - "end": {"x": 84.8936835106123, "y": 2.25000000000002}, - "start": {"x": 67.3148950021716, "y": 2.24999999999998}, - "type": "line", - }, - { - "centre": {"x": 84.8936835106122, "y": 4.25000000000002}, - "end": {"x": 86.8911819461561, "y": 4.35000000000002}, - "radius": 2.00000000000001, - "start": {"x": 84.8936835106123, "y": 2.25000000000002}, - "type": "arc", - }, - { - "centre": {"x": 0, "y": 0}, - "end": {"x": 86.8137263217585, "y": 5.69007224302245}, - "radius": 87, - "start": {"x": 86.8911819461561, "y": 4.35000000000002}, - "type": "arc", - }, - ], - "entities_internal": [], - "entities_polyline_type": 2, - "extrusion_blocks": [], - "incl_region_id": True, - "lamination_type": "", - "linked_regions": [], - "magnet_temp_coeff_method": 0, - "material": "Copper (Pure)", - "material_weight_component_type": 1, - "material_weight_field": "Weight_Total_Copper_Active", - "mesh_length": 0, - "name": "ArmatureSlotL1", - "name_unique": "ArmatureSlotL1", - "parent_name": "", - "region_coordinate": {"x": 82.6541179328448, "y": 3.97003612151121}, - "region_temperature": 20, - "region_type": "Split Slot", - "region_type_mapped": "No type", - "singular": True, - "thermal_loss": 0, - "use_in_weight_calculation": False, - "weight_reduction_factor": 1, - }, - "ArmatureSlotR1": { - "adaptive_entities": False, - "area": 54.1336003515217, - "centroid": {"x": 77.4917542392541, "y": 6.47985645845607}, - "child_names": [], - "colour": {"b": 0, "g": 215, "r": 255}, - "duplications": 48, - "entities": [ - { - "centre": {"x": 0, "y": 0}, - "end": {"x": 86.7156047753705, "y": 7.02878996995535}, - "radius": 87, - "start": {"x": 86.8137263217585, "y": 5.69007224302245}, - "type": "arc", - }, - { - "centre": {"x": 84.7221425966263, "y": 6.86720859133569}, - "end": {"x": 84.4610902121862, "y": 8.8500983140833}, - "radius": 2, - "start": {"x": 86.7156047753705, "y": 7.02878996995535}, - "type": "arc", - }, - { - "end": {"x": 67.0326906763158, "y": 6.55560598623458}, - "start": {"x": 84.4610902121862, "y": 8.8500983140833}, - "type": "line", - }, - { - "end": {"x": 67.1737928392437, "y": 4.40280299311749}, - "start": {"x": 67.0326906763158, "y": 6.55560598623458}, - "type": "line", - }, - { - "end": {"x": 86.8137263217585, "y": 5.69007224302245}, - "start": {"x": 67.1737928392437, "y": 4.40280299311749}, - "type": "line", - }, - ], - "entities_internal": [], - "entities_polyline_type": 2, - "extrusion_blocks": [], - "incl_region_id": True, - "lamination_type": "", - "linked_regions": [], - "magnet_temp_coeff_method": 0, - "material": "Copper (Pure)", - "material_weight_component_type": 1, - "material_weight_field": "Weight_Total_Copper_Active", - "mesh_length": 0, - "name": "ArmatureSlotR1", - "name_unique": "ArmatureSlotR1", - "parent_name": "", - "region_coordinate": {"x": 84.3322623694114, "y": 7.19305649200386}, - "region_temperature": 20, - "region_type": "Split Slot", - "region_type_mapped": "No type", - "singular": True, - "thermal_loss": 0, - "use_in_weight_calculation": False, - "weight_reduction_factor": 1, - }, - "Housing": { - "adaptive_entities": False, - "area": 188.495559215387, - "centroid": {"x": 119.725253764956, "y": 7.84720771818839}, - "child_names": [], - "colour": {"b": 253, "g": 231, "r": 229}, - "duplications": 48, - "entities": [ - {"end": {"x": 126, "y": 0}, "start": {"x": 114, "y": 0}, "type": "line"}, - { - "centre": {"x": 0, "y": 0}, - "end": {"x": 124.9220525331, "y": 16.4463002197265}, - "radius": 126, - "start": {"x": 126, "y": 0}, - "type": "arc", - }, - { - "end": {"x": 113.024714196614, "y": 14.8799859130859}, - "start": {"x": 124.9220525331, "y": 16.4463002197265}, - "type": "line", - }, - { - "centre": {"x": 0, "y": 0}, - "end": {"x": 114, "y": 0}, - "radius": -114, - "start": {"x": 113.024714196614, "y": 14.8799859130859}, - "type": "arc", - }, - ], - "entities_internal": [], - "entities_polyline_type": 2, - "extrusion_blocks": [], - "incl_region_id": True, - "lamination_type": "", - "linked_regions": [], - "magnet_temp_coeff_method": 0, - "material": "Aluminium (Alloy 195 Cast)", - "material_weight_component_type": 1, - "material_weight_field": "Weight_Total_Housing_Active", - "mesh_length": 0, - "name": "Housing", - "name_unique": "Housing", - "parent_name": "", - "region_coordinate": {"x": 119.725253764956, "y": 7.84720771818839}, - "region_temperature": 20, - "region_type": "Housing", - "region_type_mapped": "No type", - "singular": False, - "thermal_loss": 0, - "use_in_weight_calculation": True, - "weight_reduction_factor": 1, - }, - "Housing_1": { - "adaptive_entities": False, - "area": 72.9765793490131, - "centroid": {"x": 111.170714519021, "y": 7.28651359322089}, - "child_names": [], - "colour": {"b": 253, "g": 231, "r": 229}, - "duplications": 48, - "entities": [ - {"end": {"x": 114, "y": 0}, "start": {"x": 109, "y": 0}, "type": "line"}, - { - "centre": {"x": 0, "y": 0}, - "end": {"x": 113.024714196614, "y": 14.8799859130859}, - "radius": 114, - "start": {"x": 114, "y": 0}, - "type": "arc", - }, - { - "end": {"x": 108.067489889745, "y": 14.2273549519856}, - "start": {"x": 113.024714196614, "y": 14.8799859130859}, - "type": "line", - }, - { - "centre": {"x": 0, "y": 0}, - "end": {"x": 109, "y": 0}, - "radius": -109, - "start": {"x": 108.067489889745, "y": 14.2273549519856}, - "type": "arc", - }, - ], - "entities_internal": [], - "entities_polyline_type": 2, - "extrusion_blocks": [], - "incl_region_id": True, - "lamination_type": "", - "linked_regions": [], - "magnet_temp_coeff_method": 0, - "material": "Aluminium (Alloy 195 Cast)", - "material_weight_component_type": 1, - "material_weight_field": "Weight_Total_Housing_Active", - "mesh_length": 0, - "name": "Housing", - "name_unique": "Housing_1", - "parent_name": "", - "region_coordinate": {"x": 111.170714519021, "y": 7.28651359322089}, - "region_temperature": 20, - "region_type": "Housing", - "region_type_mapped": "No type", - "singular": False, - "thermal_loss": 0, - "use_in_weight_calculation": True, - "weight_reduction_factor": 1, - }, - "Housing_2": { - "adaptive_entities": False, - "area": 136.135681655558, - "centroid": {"x": 103.755368536621, "y": 6.80048613956137}, - "child_names": [], - "colour": {"b": 253, "g": 231, "r": 229}, - "duplications": 48, - "entities": [ - {"end": {"x": 109, "y": 0}, "start": {"x": 99, "y": 0}, "type": "line"}, - { - "centre": {"x": 0, "y": 0}, - "end": {"x": 108.067489889745, "y": 14.2273549519856}, - "radius": 109, - "start": {"x": 109, "y": 0}, - "type": "arc", - }, - { - "end": {"x": 98.1530412760072, "y": 12.9220930297851}, - "start": {"x": 108.067489889745, "y": 14.2273549519856}, - "type": "line", - }, - { - "centre": {"x": 0, "y": 0}, - "end": {"x": 99, "y": 0}, - "radius": -99, - "start": {"x": 98.1530412760072, "y": 12.9220930297851}, - "type": "arc", - }, - ], - "entities_internal": [], - "entities_polyline_type": 2, - "extrusion_blocks": [], - "incl_region_id": True, - "lamination_type": "", - "linked_regions": [], - "magnet_temp_coeff_method": 0, - "material": "Aluminium (Alloy 195 Cast)", - "material_weight_component_type": 1, - "material_weight_field": "Weight_Total_Housing_Active", - "mesh_length": 0, - "name": "Housing", - "name_unique": "Housing_2", - "parent_name": "", - "region_coordinate": {"x": 103.755368536621, "y": 6.80048613956137}, - "region_temperature": 20, - "region_type": "Housing", - "region_type_mapped": "No type", - "singular": False, - "thermal_loss": 0, - "use_in_weight_calculation": True, - "weight_reduction_factor": 1, - }, - "Impreg": { - "adaptive_entities": False, - "area": 97.2839971133977, - "centroid": {"x": 77.5396239396489, "y": 5.08221545839593}, - "child_names": [], - "colour": {"b": 48, "g": 224, "r": 48}, - "duplications": 48, - "entities": [ - { - "end": {"x": 67.2985091364678, "y": 2.5}, - "start": {"x": 67.0490765420196, "y": 6.30560598623492}, - "type": "line", - }, - { - "end": {"x": 84.8936835106123, "y": 2.5}, - "start": {"x": 67.2985091364678, "y": 2.5}, - "type": "line", - }, - { - "centre": {"x": 84.8936835106122, "y": 4.25}, - "end": {"x": 86.6414946417131, "y": 4.3375}, - "radius": 1.75, - "start": {"x": 84.8936835106123, "y": 2.5}, - "type": "arc", - }, - { - "centre": {"x": 0, "y": 0}, - "end": {"x": 86.5642615909489, "y": 5.67372146071491}, - "radius": 86.75, - "start": {"x": 86.6414946417131, "y": 4.3375}, - "type": "arc", - }, - { - "centre": {"x": 0, "y": 0}, - "end": {"x": 86.4664220030275, "y": 7.00859229762791}, - "radius": 86.75, - "start": {"x": 86.5642615909489, "y": 5.67372146071491}, - "type": "arc", - }, - { - "centre": {"x": 84.7221425966263, "y": 6.86720859133571}, - "end": {"x": 84.4937217602412, "y": 8.60223709873987}, - "radius": 1.75, - "start": {"x": 86.4664220030275, "y": 7.00859229762791}, - "type": "arc", - }, - { - "end": {"x": 67.0490765420196, "y": 6.30560598623492}, - "start": {"x": 84.4937217602412, "y": 8.60223709873987}, - "type": "line", - }, - ], - "entities_internal": [], - "entities_polyline_type": 2, - "extrusion_blocks": [], - "incl_region_id": True, - "lamination_type": "", - "linked_regions": [], - "magnet_temp_coeff_method": 0, - "material": "", - "material_weight_component_type": 1, - "material_weight_field": "Weight_Total_Impreg_Active", - "mesh_length": 0, - "name": "Impreg", - "name_unique": "Impreg", - "parent_name": "", - "region_coordinate": {"x": 82.3420980203589, "y": 5.55860429566785}, - "region_temperature": 20, - "region_type": "Stator Impreg", - "region_type_mapped": "No type", - "singular": False, - "thermal_loss": 0, - "use_in_weight_calculation": True, - "weight_reduction_factor": 0.8, - }, - "L1_1Magnet1": { - "adaptive_entities": False, - "area": 96.0000000000001, - "centroid": {"x": 56.040689756688, "y": 11.8668792573194}, - "child_names": [], - "colour": {"b": 0, "g": 212, "r": 0}, - "duplications": 8, - "entities": [ - { - "end": {"x": 63.2908412096315, "y": 7.34633682201565}, - "start": {"x": 58.5307211678841, "y": 3.69376824796331}, - "type": "line", - }, - { - "end": {"x": 53.550658345492, "y": 20.0399902666754}, - "start": {"x": 63.2908412096315, "y": 7.34633682201565}, - "type": "line", - }, - { - "end": {"x": 48.7905383037446, "y": 16.3874216926231}, - "start": {"x": 53.550658345492, "y": 20.0399902666754}, - "type": "line", - }, - { - "end": {"x": 58.5307211678841, "y": 3.69376824796331}, - "start": {"x": 48.7905383037446, "y": 16.3874216926231}, - "type": "line", - }, - ], - "entities_internal": [], - "entities_polyline_type": 2, - "extrusion_blocks": [ - { - "extrusion_block_angle_step": 0, - "extrusion_block_end": 63.3333333333333, - "extrusion_block_start": 55, - }, - { - "extrusion_block_angle_step": 0, - "extrusion_block_end": 71.6666666666667, - "extrusion_block_start": 63.3333333333333, - }, - { - "extrusion_block_angle_step": 0, - "extrusion_block_end": 80, - "extrusion_block_start": 71.6666666666667, - }, - { - "extrusion_block_angle_step": 0, - "extrusion_block_end": 88.3333333333333, - "extrusion_block_start": 80, - }, - { - "extrusion_block_angle_step": 0, - "extrusion_block_end": 96.6666666666667, - "extrusion_block_start": 88.3333333333333, - }, - { - "extrusion_block_angle_step": 0, - "extrusion_block_end": 105, - "extrusion_block_start": 96.6666666666667, - }, - { - "extrusion_block_angle_step": 0, - "extrusion_block_end": 113.333333333333, - "extrusion_block_start": 105, - }, - { - "extrusion_block_angle_step": 0, - "extrusion_block_end": 121.666666666667, - "extrusion_block_start": 113.333333333333, - }, - { - "extrusion_block_angle_step": 0, - "extrusion_block_end": 130, - "extrusion_block_start": 121.666666666667, - }, - { - "extrusion_block_angle_step": 0, - "extrusion_block_end": 138.333333333333, - "extrusion_block_start": 130, - }, - { - "extrusion_block_angle_step": 0, - "extrusion_block_end": 146.666666666667, - "extrusion_block_start": 138.333333333333, - }, - { - "extrusion_block_angle_step": 0, - "extrusion_block_end": 155, - "extrusion_block_start": 146.666666666667, - }, - { - "extrusion_block_angle_step": 0, - "extrusion_block_end": 163.333333333333, - "extrusion_block_start": 155, - }, - { - "extrusion_block_angle_step": 0, - "extrusion_block_end": 171.666666666667, - "extrusion_block_start": 163.333333333333, - }, - { - "extrusion_block_angle_step": 0, - "extrusion_block_end": 180, - "extrusion_block_start": 171.666666666667, - }, - { - "extrusion_block_angle_step": 0, - "extrusion_block_end": 188.333333333333, - "extrusion_block_start": 180, - }, - { - "extrusion_block_angle_step": 0, - "extrusion_block_end": 196.666666666667, - "extrusion_block_start": 188.333333333333, - }, - { - "extrusion_block_angle_step": 0, - "extrusion_block_end": 205, - "extrusion_block_start": 196.666666666667, - }, - ], - "incl_region_id": True, - "lamination_type": "", - "linked_regions": [], - "magnet_angle": 37.5, - "magnet_br_value": 1.06425, - "magnet_magfactor": 1, - "magnet_polarity": "N", - "magnet_temp_coeff_method": 0, - "material": "N30UH", - "material_weight_component_type": 1, - "material_weight_field": "Weight_Total_Magnet", - "mesh_length": 0, - "name": "L1_1Magnet1", - "name_unique": "L1_1Magnet1", - "parent_name": "Rotor", - "region_coordinate": {"x": 56.040689756688, "y": 11.8668792573194}, - "region_temperature": 65, - "region_type": "Magnet", - "region_type_mapped": "No type", - "singular": True, - "thermal_loss": 0, - "use_in_weight_calculation": True, - "weight_reduction_factor": 1, - }, - "L1_1Magnet2": { - "adaptive_entities": False, - "area": 96.0000000000001, - "centroid": {"x": 48.0179025436981, "y": 31.2356009549531}, - "child_names": [], - "colour": {"b": 0, "g": 212, "r": 0}, - "duplications": 8, - "entities": [ - { - "end": {"x": 43.9993584218163, "y": 38.7755812692834}, - "start": {"x": 49.9480275900591, "y": 39.5587384226037}, - "type": "line", - }, - { - "end": {"x": 46.0877774973371, "y": 22.9124634873025}, - "start": {"x": 43.9993584218163, "y": 38.7755812692834}, - "type": "line", - }, - { - "end": {"x": 52.03644666558, "y": 23.6956206406228}, - "start": {"x": 46.0877774973371, "y": 22.9124634873025}, - "type": "line", - }, - { - "end": {"x": 49.9480275900591, "y": 39.5587384226037}, - "start": {"x": 52.03644666558, "y": 23.6956206406228}, - "type": "line", - }, - ], - "entities_internal": [], - "entities_polyline_type": 2, - "extrusion_blocks": [ - { - "extrusion_block_angle_step": 0, - "extrusion_block_end": 63.3333333333333, - "extrusion_block_start": 55, - }, - { - "extrusion_block_angle_step": 0, - "extrusion_block_end": 71.6666666666667, - "extrusion_block_start": 63.3333333333333, - }, - { - "extrusion_block_angle_step": 0, - "extrusion_block_end": 80, - "extrusion_block_start": 71.6666666666667, - }, - { - "extrusion_block_angle_step": 0, - "extrusion_block_end": 88.3333333333333, - "extrusion_block_start": 80, - }, - { - "extrusion_block_angle_step": 0, - "extrusion_block_end": 96.6666666666667, - "extrusion_block_start": 88.3333333333333, - }, - { - "extrusion_block_angle_step": 0, - "extrusion_block_end": 105, - "extrusion_block_start": 96.6666666666667, - }, - { - "extrusion_block_angle_step": 0, - "extrusion_block_end": 113.333333333333, - "extrusion_block_start": 105, - }, - { - "extrusion_block_angle_step": 0, - "extrusion_block_end": 121.666666666667, - "extrusion_block_start": 113.333333333333, - }, - { - "extrusion_block_angle_step": 0, - "extrusion_block_end": 130, - "extrusion_block_start": 121.666666666667, - }, - { - "extrusion_block_angle_step": 0, - "extrusion_block_end": 138.333333333333, - "extrusion_block_start": 130, - }, - { - "extrusion_block_angle_step": 0, - "extrusion_block_end": 146.666666666667, - "extrusion_block_start": 138.333333333333, - }, - { - "extrusion_block_angle_step": 0, - "extrusion_block_end": 155, - "extrusion_block_start": 146.666666666667, - }, - { - "extrusion_block_angle_step": 0, - "extrusion_block_end": 163.333333333333, - "extrusion_block_start": 155, - }, - { - "extrusion_block_angle_step": 0, - "extrusion_block_end": 171.666666666667, - "extrusion_block_start": 163.333333333333, - }, - { - "extrusion_block_angle_step": 0, - "extrusion_block_end": 180, - "extrusion_block_start": 171.666666666667, - }, - { - "extrusion_block_angle_step": 0, - "extrusion_block_end": 188.333333333333, - "extrusion_block_start": 180, - }, - { - "extrusion_block_angle_step": 0, - "extrusion_block_end": 196.666666666667, - "extrusion_block_start": 188.333333333333, - }, - { - "extrusion_block_angle_step": 0, - "extrusion_block_end": 205, - "extrusion_block_start": 196.666666666667, - }, - ], - "incl_region_id": True, - "lamination_type": "", - "linked_regions": [], - "magnet_angle": 7.5, - "magnet_br_value": 1.06425, - "magnet_magfactor": 1, - "magnet_polarity": "N", - "magnet_temp_coeff_method": 0, - "material": "N30UH", - "material_weight_component_type": 1, - "material_weight_field": "Weight_Total_Magnet", - "mesh_length": 0, - "name": "L1_1Magnet2", - "name_unique": "L1_1Magnet2", - "parent_name": "Rotor", - "region_coordinate": {"x": 48.0179025436981, "y": 31.2356009549531}, - "region_temperature": 65, - "region_type": "Magnet", - "region_type_mapped": "No type", - "singular": True, - "thermal_loss": 0, - "use_in_weight_calculation": True, - "weight_reduction_factor": 1, - }, - "Liner": { - "adaptive_entities": False, - "area": 10.983203589653, - "centroid": {"x": 77.968913664417, "y": 5.11035259350818}, - "child_names": [], - "colour": {"b": 0, "g": 128, "r": 0}, - "duplications": 48, - "entities": [ - { - "end": {"x": 84.8936835106123, "y": 2.25}, - "start": {"x": 67.3148950021716, "y": 2.24999999999998}, - "type": "line", - }, - { - "centre": {"x": 84.8936835106123, "y": 4.25}, - "end": {"x": 86.8911819461561, "y": 4.35}, - "radius": 2, - "start": {"x": 84.8936835106123, "y": 2.25}, - "type": "arc", - }, - { - "centre": {"x": 0, "y": 0}, - "end": {"x": 86.8137263217585, "y": 5.69007224302245}, - "radius": 87, - "start": {"x": 86.8911819461561, "y": 4.35}, - "type": "arc", - }, - { - "centre": {"x": 0, "y": 0}, - "end": {"x": 86.7156047753705, "y": 7.02878996995537}, - "radius": 87, - "start": {"x": 86.8137263217585, "y": 5.69007224302245}, - "type": "arc", - }, - { - "centre": {"x": 84.7221425966263, "y": 6.86720859133571}, - "end": {"x": 84.4610902121862, "y": 8.85009831408333}, - "radius": 2, - "start": {"x": 86.7156047753705, "y": 7.02878996995537}, - "type": "arc", - }, - { - "end": {"x": 67.0326906763158, "y": 6.55560598623492}, - "start": {"x": 84.4610902121862, "y": 8.85009831408333}, - "type": "line", - }, - { - "end": {"x": 67.0490765420196, "y": 6.30560598623492}, - "start": {"x": 67.0326906763158, "y": 6.55560598623492}, - "type": "line", - }, - { - "end": {"x": 84.4937217602412, "y": 8.60223709873987}, - "start": {"x": 67.0490765420196, "y": 6.30560598623492}, - "type": "line", - }, - { - "centre": {"x": 84.7221425966263, "y": 6.86720859133571}, - "end": {"x": 86.4664220030275, "y": 7.00859229762791}, - "radius": -1.75, - "start": {"x": 84.4937217602412, "y": 8.60223709873987}, - "type": "arc", - }, - { - "centre": {"x": 0, "y": 0}, - "end": {"x": 86.5642615909489, "y": 5.67372146071491}, - "radius": -86.75, - "start": {"x": 86.4664220030275, "y": 7.00859229762791}, - "type": "arc", - }, - { - "centre": {"x": 0, "y": 0}, - "end": {"x": 86.6414946417131, "y": 4.3375}, - "radius": -86.75, - "start": {"x": 86.5642615909489, "y": 5.67372146071491}, - "type": "arc", - }, - { - "centre": {"x": 84.8936835106122, "y": 4.25}, - "end": {"x": 84.8936835106123, "y": 2.5}, - "radius": -1.75, - "start": {"x": 86.6414946417131, "y": 4.3375}, - "type": "arc", - }, - { - "end": {"x": 67.2985091364678, "y": 2.5}, - "start": {"x": 84.8936835106123, "y": 2.5}, - "type": "line", - }, - { - "end": {"x": 67.3148950021716, "y": 2.24999999999998}, - "start": {"x": 67.2985091364678, "y": 2.5}, - "type": "line", - }, - ], - "entities_internal": [], - "entities_polyline_type": 2, - "extrusion_blocks": [], - "incl_region_id": True, - "lamination_type": "", - "linked_regions": [], - "magnet_temp_coeff_method": 0, - "material": "", - "material_weight_component_type": 1, - "material_weight_field": "Weight_Total_Slot_Liner", - "mesh_length": 0, - "name": "Liner", - "name_unique": "Liner", - "parent_name": "", - "region_coordinate": {"x": 79.4399567850703, "y": 8.04005751741874}, - "region_temperature": 20, - "region_type": "Stator Liner", - "region_type_mapped": "No type", - "singular": False, - "thermal_loss": 0, - "use_in_weight_calculation": True, - "weight_reduction_factor": 1, - }, - "Rotor": { - "adaptive_entities": False, - "area": 1030.83508945915, - "centroid": {"x": 48.1444344595077, "y": 19.9420777059107}, - "child_names": ["Rotor Pocket", "L1_1Magnet1", "L1_1Magnet2", "Rotor Pocket_1"], - "colour": {"b": 240, "g": 240, "r": 0}, - "duplications": 8, - "entities": [ - {"end": {"x": 65, "y": 0}, "start": {"x": 40, "y": 0}, "type": "line"}, - { - "centre": {"x": 0, "y": 0}, - "end": {"x": 45.9619407771256, "y": 45.9619407771256}, - "radius": 65, - "start": {"x": 65, "y": 0}, - "type": "arc", - }, - { - "end": {"x": 28.2842712474619, "y": 28.2842712474619}, - "start": {"x": 45.9619407771256, "y": 45.9619407771256}, - "type": "line", - }, - { - "centre": {"x": 0, "y": 0}, - "end": {"x": 40, "y": 0}, - "radius": -40, - "start": {"x": 28.2842712474619, "y": 28.2842712474619}, - "type": "arc", - }, - ], - "entities_internal": [], - "entities_polyline_type": 2, - "extrusion_blocks": [ - { - "extrusion_block_angle_step": 0, - "extrusion_block_end": 205, - "extrusion_block_start": 55, - } - ], - "incl_region_id": True, - "lamination_type": "Laminated", - "linked_regions": [], - "magnet_temp_coeff_method": 0, - "material": "M250-35A", - "material_weight_component_type": 1, - "material_weight_field": "Weight_Total_Rotor_Lam_Back_Iron", - "mesh_length": 0, - "name": "Rotor", - "name_unique": "Rotor", - "parent_name": "", - "region_coordinate": {"x": 47.7895021472478, "y": 21.7999639468195}, - "region_temperature": 20, - "region_type": "Rotor", - "region_type_mapped": "No type", - "singular": False, - "thermal_loss": 0, - "use_in_weight_calculation": True, - "weight_reduction_factor": 0.97, - }, - "Rotor Pocket": { - "adaptive_entities": False, - "area": 27.2374304690439, - "centroid": {"x": 47.559936471459, "y": 31.1704313411946}, - "child_names": [], - "colour": {"b": 240, "g": 241, "r": 241}, - "duplications": 8, - "entities": [ - { - "end": {"x": 43.8010694495415, "y": 38.7494760308394}, - "start": {"x": 43.7358063534315, "y": 39.2451984615263}, - "type": "line", - }, - { - "end": {"x": 45.8894885250623, "y": 22.8863582488585}, - "start": {"x": 43.8010694495415, "y": 38.7494760308394}, - "type": "line", - }, - { - "end": {"x": 45.9547516211724, "y": 22.3906358181716}, - "start": {"x": 45.8894885250623, "y": 22.8863582488585}, - "type": "line", - }, - { - "centre": {"x": 48.8451677068426, "y": 24.1857684321305}, - "end": {"x": 52.10170976169, "y": 23.1998982099359}, - "radius": 3.4025, - "start": {"x": 45.9547516211724, "y": 22.3906358181716}, - "type": "arc", - }, - { - "end": {"x": 52.03644666558, "y": 23.6956206406228}, - "start": {"x": 52.10170976169, "y": 23.1998982099359}, - "type": "line", - }, - { - "end": {"x": 46.0877774973371, "y": 22.9124634873025}, - "start": {"x": 52.03644666558, "y": 23.6956206406228}, - "type": "line", - }, - { - "end": {"x": 43.9993584218163, "y": 38.7755812692834}, - "start": {"x": 46.0877774973371, "y": 22.9124634873025}, - "type": "line", - }, - { - "end": {"x": 49.9480275900591, "y": 39.5587384226037}, - "start": {"x": 43.9993584218163, "y": 38.7755812692834}, - "type": "line", - }, - { - "end": {"x": 49.8827644939491, "y": 40.0544608532907}, - "start": {"x": 49.9480275900591, "y": 39.5587384226037}, - "type": "line", - }, - { - "centre": {"x": 46.9923484082789, "y": 38.2593282393317}, - "end": {"x": 43.7358063534315, "y": 39.2451984615263}, - "radius": 3.4025, - "start": {"x": 49.8827644939491, "y": 40.0544608532907}, - "type": "arc", - }, - ], - "entities_internal": [], - "entities_polyline_type": 2, - "extrusion_blocks": [ - { - "extrusion_block_angle_step": 0, - "extrusion_block_end": 205, - "extrusion_block_start": 55, - } - ], - "incl_region_id": True, - "lamination_type": "", - "linked_regions": [], - "magnet_temp_coeff_method": 0, - "material": "Air (Motor-CAD model)", - "material_weight_component_type": 1, - "material_weight_field": "Weight_Total_RotorPocket", - "mesh_length": 0, - "name": "Rotor Pocket", - "name_unique": "Rotor Pocket", - "parent_name": "Rotor", - "region_coordinate": {"x": 45.8272822054961, "y": 39.6065511007121}, - "region_temperature": 20, - "region_type": "Rotor Pocket", - "region_type_mapped": "No type", - "singular": False, - "thermal_loss": 0, - "use_in_weight_calculation": True, - "weight_reduction_factor": 1, - }, - "Rotor Pocket_1": { - "adaptive_entities": False, - "area": 27.2374304690439, - "centroid": {"x": 55.6707769656374, "y": 11.5891302179014}, - "child_names": [], - "colour": {"b": 240, "g": 241, "r": 241}, - "duplications": 8, - "entities": [ - { - "end": {"x": 63.2908412096315, "y": 7.34633682201565}, - "start": {"x": 63.5952219241359, "y": 6.94966015187003}, - "type": "line", - }, - { - "end": {"x": 58.5307211678841, "y": 3.69376824796331}, - "start": {"x": 63.2908412096315, "y": 7.34633682201565}, - "type": "line", - }, - { - "end": {"x": 48.7905383037446, "y": 16.3874216926231}, - "start": {"x": 58.5307211678841, "y": 3.69376824796331}, - "type": "line", - }, - { - "end": {"x": 53.550658345492, "y": 20.0399902666754}, - "start": {"x": 48.7905383037446, "y": 16.3874216926231}, - "type": "line", - }, - { - "end": {"x": 53.2462776309876, "y": 20.436666936821}, - "start": {"x": 53.550658345492, "y": 20.0399902666754}, - "type": "line", - }, - { - "centre": {"x": 51.6406701802695, "y": 17.4368284471355}, - "end": {"x": 48.327486921182, "y": 16.662346076967}, - "radius": 3.4025, - "start": {"x": 53.2462776309876, "y": 20.436666936821}, - "type": "arc", - }, - { - "end": {"x": 48.6318676356863, "y": 16.2656694068213}, - "start": {"x": 48.327486921182, "y": 16.662346076967}, - "type": "line", - }, - { - "end": {"x": 58.3720504998259, "y": 3.57201596216157}, - "start": {"x": 48.6318676356863, "y": 16.2656694068213}, - "type": "line", - }, - { - "end": {"x": 58.6764312143302, "y": 3.17533929201596}, - "start": {"x": 58.3720504998259, "y": 3.57201596216157}, - "type": "line", - }, - { - "centre": {"x": 60.2820386650483, "y": 6.17517778170145}, - "end": {"x": 63.5952219241359, "y": 6.94966015187003}, - "radius": 3.4025, - "start": {"x": 58.6764312143302, "y": 3.17533929201596}, - "type": "arc", - }, - ], - "entities_internal": [], - "entities_polyline_type": 2, - "extrusion_blocks": [ - { - "extrusion_block_angle_step": 0, - "extrusion_block_end": 205, - "extrusion_block_start": 55, - } - ], - "incl_region_id": True, - "lamination_type": "", - "linked_regions": [], - "magnet_temp_coeff_method": 0, - "material": "Air (Motor-CAD model)", - "material_weight_component_type": 1, - "material_weight_field": "Weight_Total_RotorPocket", - "mesh_length": 0, - "name": "Rotor Pocket", - "name_unique": "Rotor Pocket_1", - "parent_name": "Rotor", - "region_coordinate": {"x": 61.753742604451, "y": 4.70347384229879}, - "region_temperature": 20, - "region_type": "Rotor Pocket", - "region_type_mapped": "No type", - "singular": False, - "thermal_loss": 0, - "use_in_weight_calculation": True, - "weight_reduction_factor": 1, - }, - "Shaft": { - "adaptive_entities": False, - "area": 628.318530717959, - "centroid": {"x": 24.0007863180929, "y": 9.94145120057268}, - "child_names": [], - "colour": {"b": 160, "g": 160, "r": 160}, - "duplications": 8, - "entities": [ - { - "centre": {"x": 0, "y": 0}, - "end": {"x": 28.2842712474619, "y": 28.2842712474619}, - "radius": 40, - "start": {"x": 40, "y": 0}, - "type": "arc", - }, - { - "end": {"x": 0, "y": 0}, - "start": {"x": 28.2842712474619, "y": 28.2842712474619}, - "type": "line", - }, - {"end": {"x": 40, "y": 0}, "start": {"x": 0, "y": 0}, "type": "line"}, - ], - "entities_internal": [], - "entities_polyline_type": 2, - "extrusion_blocks": [ - { - "extrusion_block_angle_step": 0, - "extrusion_block_end": 205, - "extrusion_block_start": 55, - } - ], - "incl_region_id": True, - "lamination_type": "", - "linked_regions": [], - "magnet_temp_coeff_method": 0, - "material": "", - "material_weight_component_type": 1, - "material_weight_field": "Weight_Total_Shaft_Active", - "mesh_length": 0, - "name": "Shaft", - "name_unique": "Shaft", - "parent_name": "", - "region_coordinate": {"x": 24.7487373415292, "y": 10.6066017177982}, - "region_temperature": 20, - "region_type": "Shaft", - "region_type_mapped": "No type", - "singular": False, - "thermal_loss": 0, - "use_in_weight_calculation": True, - "weight_reduction_factor": 1, - }, - "Stator": { - "adaptive_entities": False, - "area": 243.916173195236, - "centroid": {"x": 86.1706272589501, "y": 5.64792130351275}, - "child_names": [], - "colour": {"b": 16, "g": 0, "r": 240}, - "duplications": 48, - "entities": [ - { - "centre": {"x": 0, "y": 0}, - "end": {"x": 98.1530412760072, "y": 12.9220930297851}, - "radius": 99, - "start": {"x": 99, "y": 0}, - "type": "arc", - }, - { - "end": {"x": 65.4353608506715, "y": 8.6147286865234}, - "start": {"x": 98.1530412760072, "y": 12.9220930297851}, - "type": "line", - }, - { - "centre": {"x": 0, "y": 0}, - "end": {"x": 65.7435730840115, "y": 5.81227994398376}, - "radius": -66, - "start": {"x": 65.4353608506715, "y": 8.6147286865234}, - "type": "arc", - }, - { - "end": {"x": 66.7414320072501, "y": 5.87768307321394}, - "start": {"x": 65.7435730840115, "y": 5.81227994398376}, - "type": "line", - }, - { - "end": {"x": 67.0326906763158, "y": 6.555605986235}, - "start": {"x": 66.7414320072501, "y": 5.87768307321394}, - "type": "line", - }, - { - "end": {"x": 84.4610902121862, "y": 8.8500983140833}, - "start": {"x": 67.0326906763158, "y": 6.555605986235}, - "type": "line", - }, - { - "centre": {"x": 84.7221425966263, "y": 6.86720859133569}, - "end": {"x": 86.7156047753705, "y": 7.02878996995535}, - "radius": -2, - "start": {"x": 84.4610902121862, "y": 8.8500983140833}, - "type": "arc", - }, - { - "centre": {"x": 0, "y": 0}, - "end": {"x": 86.8137263217585, "y": 5.69007224302245}, - "radius": -87, - "start": {"x": 86.7156047753705, "y": 7.02878996995535}, - "type": "arc", - }, - { - "centre": {"x": 0, "y": 0}, - "end": {"x": 86.8911819461561, "y": 4.35000000000002}, - "radius": -87, - "start": {"x": 86.8137263217585, "y": 5.69007224302245}, - "type": "arc", - }, - { - "centre": {"x": 84.8936835106122, "y": 4.25000000000002}, - "end": {"x": 84.8936835106123, "y": 2.25000000000002}, - "radius": -2.00000000000001, - "start": {"x": 86.8911819461561, "y": 4.35000000000002}, - "type": "arc", - }, - { - "end": {"x": 67.3148950021716, "y": 2.24999999999998}, - "start": {"x": 84.8936835106123, "y": 2.25000000000002}, - "type": "line", - }, - { - "end": {"x": 66.9376413949405, "y": 2.88410630349807}, - "start": {"x": 67.3148950021716, "y": 2.24999999999998}, - "type": "line", - }, - { - "end": {"x": 65.939782471702, "y": 2.81870317426794}, - "start": {"x": 66.9376413949405, "y": 2.88410630349807}, - "type": "line", - }, - { - "centre": {"x": 0, "y": 0}, - "end": {"x": 66, "y": 0}, - "radius": -66, - "start": {"x": 65.939782471702, "y": 2.81870317426794}, - "type": "arc", - }, - {"end": {"x": 99, "y": 0}, "start": {"x": 66, "y": 0}, "type": "line"}, - ], - "entities_internal": [], - "entities_polyline_type": 2, - "extrusion_blocks": [ - { - "extrusion_block_angle_step": 0, - "extrusion_block_end": 210, - "extrusion_block_start": 50, - } - ], - "incl_region_id": True, - "lamination_type": "Laminated", - "linked_regions": [], - "magnet_temp_coeff_method": 0, - "material": "M250-35A", - "material_weight_component_type": 1, - "material_weight_field": "Weight_Total_Stator_Lam_Back_Iron", - "mesh_length": 0, - "name": "Stator", - "name_unique": "Stator", - "parent_name": "", - "region_coordinate": {"x": 97.7405934251342, "y": 6.46104651489255}, - "region_temperature": 20, - "region_type": "Stator", - "region_type_mapped": "No type", - "singular": False, - "thermal_loss": 0, - "use_in_weight_calculation": True, - "weight_reduction_factor": 0.97, - }, - "StatorAir": { - "adaptive_entities": False, - "area": 2.96590380676454, - "centroid": {"x": 66.4303884745974, "y": 4.35407769678662}, - "child_names": [], - "colour": {"b": 240, "g": 240, "r": 239}, - "duplications": 48, - "entities": [ - { - "end": {"x": 66.9376413949405, "y": 2.88410630349813}, - "start": {"x": 65.939782471702, "y": 2.81870317426794}, - "type": "line", - }, - { - "end": {"x": 66.7414320072501, "y": 5.87768307321392}, - "start": {"x": 66.9376413949405, "y": 2.88410630349813}, - "type": "line", - }, - { - "end": {"x": 65.7435730840115, "y": 5.81227994398376}, - "start": {"x": 66.7414320072501, "y": 5.87768307321392}, - "type": "line", - }, - { - "centre": {"x": 0, "y": 0}, - "end": {"x": 65.939782471702, "y": 2.81870317426794}, - "radius": -66, - "start": {"x": 65.7435730840115, "y": 5.81227994398376}, - "type": "arc", - }, - ], - "entities_internal": [], - "entities_polyline_type": 2, - "extrusion_blocks": [ - { - "extrusion_block_angle_step": 0, - "extrusion_block_end": 210, - "extrusion_block_start": 50, - } - ], - "incl_region_id": True, - "lamination_type": "", - "linked_regions": [], - "magnet_temp_coeff_method": 0, - "material": "Air", - "material_weight_component_type": 1, - "material_weight_field": "Weight_Total_Air_TSupply", - "mesh_length": 0, - "name": "StatorAir", - "name_unique": "StatorAir", - "parent_name": "", - "region_coordinate": {"x": 66.340607239476, "y": 4.60980564066148}, - "region_temperature": 20, - "region_type": "Stator Air", - "region_type_mapped": "No type", - "singular": False, - "thermal_loss": 0, - "use_in_weight_calculation": False, - "weight_reduction_factor": 1, - }, - "StatorSlot": { - "adaptive_entities": False, - "area": 108.267200703049, - "centroid": {"x": 77.5831733841295, "y": 5.08506983979084}, - "child_names": [], - "colour": {"b": 16, "g": 240, "r": 240}, - "duplications": 48, - "entities": [ - { - "end": {"x": 84.8936835106123, "y": 2.25000000000002}, - "start": {"x": 67.3148950021716, "y": 2.24999999999998}, - "type": "line", - }, - { - "centre": {"x": 84.8936835106122, "y": 4.25000000000002}, - "end": {"x": 86.8911819461561, "y": 4.35000000000002}, - "radius": 2.00000000000001, - "start": {"x": 84.8936835106123, "y": 2.25000000000002}, - "type": "arc", - }, - { - "centre": {"x": 0, "y": 0}, - "end": {"x": 86.8137263217585, "y": 5.69007224302245}, - "radius": 87, - "start": {"x": 86.8911819461561, "y": 4.35000000000002}, - "type": "arc", - }, - { - "centre": {"x": 0, "y": 0}, - "end": {"x": 86.7156047753705, "y": 7.02878996995535}, - "radius": 87, - "start": {"x": 86.8137263217585, "y": 5.69007224302245}, - "type": "arc", - }, - { - "centre": {"x": 84.7221425966263, "y": 6.86720859133569}, - "end": {"x": 84.4610902121862, "y": 8.8500983140833}, - "radius": 2, - "start": {"x": 86.7156047753705, "y": 7.02878996995535}, - "type": "arc", - }, - { - "end": {"x": 67.0326906763158, "y": 6.55560598623481}, - "start": {"x": 84.4610902121862, "y": 8.8500983140833}, - "type": "line", - }, - { - "end": {"x": 67.3148950021716, "y": 2.24999999999998}, - "start": {"x": 67.0326906763158, "y": 6.55560598623481}, - "type": "line", - }, - ], - "entities_internal": [], - "entities_polyline_type": 2, - "extrusion_blocks": [], - "incl_region_id": True, - "lamination_type": "", - "linked_regions": [], - "magnet_temp_coeff_method": 0, - "material": "Copper (Pure)", - "material_weight_component_type": 1, - "material_weight_field": "Weight_Total_Copper_Active", - "mesh_length": 0, - "name": "StatorSlot", - "name_unique": "StatorSlot", - "parent_name": "", - "region_coordinate": {"x": 83.5757121546551, "y": 5.55860429566783}, - "region_temperature": 65, - "region_type": "Stator Slot Area", - "region_type_mapped": "No type", - "singular": False, - "thermal_loss": 0, - "use_in_weight_calculation": False, - "weight_reduction_factor": 1, - }, - "StatorWedge": { - "adaptive_entities": False, - "area": 1.22513893654097, - "centroid": {"x": 67.0166785354928, "y": 4.39250517759192}, - "child_names": [], - "colour": {"b": 192, "g": 192, "r": 160}, - "duplications": 48, - "entities": [ - { - "end": {"x": 67.3148950021716, "y": 2.25}, - "start": {"x": 66.9376413949405, "y": 2.88410630349807}, - "type": "line", - }, - { - "end": {"x": 67.0326906763158, "y": 6.55560598623498}, - "start": {"x": 67.3148950021716, "y": 2.25}, - "type": "line", - }, - { - "end": {"x": 66.7414320072501, "y": 5.87768307321414}, - "start": {"x": 67.0326906763158, "y": 6.55560598623498}, - "type": "line", - }, - { - "end": {"x": 66.9376413949405, "y": 2.88410630349807}, - "start": {"x": 66.7414320072501, "y": 5.87768307321414}, - "type": "line", - }, - ], - "entities_internal": [], - "entities_polyline_type": 2, - "extrusion_blocks": [ - { - "extrusion_block_angle_step": 0, - "extrusion_block_end": 210, - "extrusion_block_start": 50, - } - ], - "incl_region_id": True, - "lamination_type": "", - "linked_regions": [], - "magnet_temp_coeff_method": 0, - "material": "", - "material_weight_component_type": 1, - "material_weight_field": "Weight_Total_SlotWedge", - "mesh_length": 0, - "name": "StatorWedge", - "name_unique": "StatorWedge", - "parent_name": "", - "region_coordinate": {"x": 67.0166785354928, "y": 4.39250517759192}, - "region_temperature": 20, - "region_type": "Wedge", - "region_type_mapped": "No type", - "singular": False, - "thermal_loss": 0, - "use_in_weight_calculation": True, - "weight_reduction_factor": 1, - }, - } -} -simple_json = { - "regions": { - "Triangle": { - "area": 0.5, - "child_names": [], - "name_unique": "Triangle", - "centroid": {"x": 0.333333333333333, "y": 0.333333333333333}, - "colour": {"b": 0, "g": 0, "r": 0}, - "duplications": 1, - "entities": [ - {"end": {"x": 1, "y": 0}, "start": {"x": 0, "y": 0}, "type": "line"}, - {"end": {"x": 0, "y": 1}, "start": {"x": 1, "y": 0}, "type": "line"}, - {"end": {"x": 0, "y": 0}, "start": {"x": 0, "y": 1}, "type": "line"}, - ], - "lamination_type": "", - "material": "air", - "mesh_length": 0, - "name": "Triangle", - "on_boundary": False, - "parent_name": "", - "region_coordinate": {"x": 0.25, "y": 0.25}, - "region_type": "Airgap", - "singular": False, - } - } -} -sample_json2 = { - "regions": { - "ArmatureSlotL1": { - "area": 54.1336003515254, - "centroid": {"x": 77.6745925290051, "y": 3.69028322112561}, - "colour": {"b": 0, "g": 255, "r": 255}, - "duplications": 48, - "entities": [ - { - "end": {"x": 67.1737928392437, "y": 4.40280299311749}, - "start": {"x": 86.8137263217585, "y": 5.69007224302245}, - "type": "line", - }, - { - "end": {"x": 67.3148950021716, "y": 2.24999999999998}, - "start": {"x": 67.1737928392437, "y": 4.40280299311749}, - "type": "line", - }, - { - "end": {"x": 84.8936835106123, "y": 2.25000000000002}, - "start": {"x": 67.3148950021716, "y": 2.24999999999998}, - "type": "line", - }, - { - "centre": {"x": 84.89368351061226, "y": 4.25000000000003}, - "end": {"x": 86.8911819461561, "y": 4.35000000000002}, - "radius": 2.00000000000001, - "start": {"x": 84.8936835106123, "y": 2.25000000000002}, - "type": "arc", - }, - { - "centre": {"x": 1.1368683772161603e-13, "y": -1.8181012251261564e-12}, - "end": {"x": 86.8137263217585, "y": 5.69007224302245}, - "radius": 87, - "start": {"x": 86.8911819461561, "y": 4.35000000000002}, - "type": "arc", - }, - ], - "lamination_type": "", - "material": "Copper (Pure)", - "mesh_length": 0, - "name": "ArmatureSlotL1", - "on_boundary": False, - "parent_name": "root", - "region_coordinate": {"x": 82.6541179328448, "y": 3.97003612151121}, - "region_type": "Split Slot", - "singular": True, - }, - "ArmatureSlotR1": { - "area": 54.1336003515217, - "centroid": {"x": 77.4917542392541, "y": 6.47985645845607}, - "colour": {"b": 0, "g": 215, "r": 255}, - "duplications": 48, - "entities": [ - { - "centre": {"x": -1.2789769243681803e-13, "y": 1.7390533457728452e-12}, - "end": {"x": 86.7156047753705, "y": 7.02878996995535}, - "radius": 87, - "start": {"x": 86.8137263217585, "y": 5.69007224302245}, - "type": "arc", - }, - { - "centre": {"x": 84.72214259662634, "y": 6.867208591335685}, - "end": {"x": 84.4610902121862, "y": 8.8500983140833}, - "radius": 2, - "start": {"x": 86.7156047753705, "y": 7.02878996995535}, - "type": "arc", - }, - { - "end": {"x": 67.0326906763158, "y": 6.55560598623458}, - "start": {"x": 84.4610902121862, "y": 8.8500983140833}, - "type": "line", - }, - { - "end": {"x": 67.1737928392437, "y": 4.40280299311749}, - "start": {"x": 67.0326906763158, "y": 6.55560598623458}, - "type": "line", - }, - { - "end": {"x": 86.8137263217585, "y": 5.69007224302245}, - "start": {"x": 67.1737928392437, "y": 4.40280299311749}, - "type": "line", - }, - ], - "lamination_type": "", - "material": "Copper (Pure)", - "mesh_length": 0, - "name": "ArmatureSlotR1", - "on_boundary": False, - "parent_name": "root", - "region_coordinate": {"x": 84.3322623694114, "y": 7.19305649200386}, - "region_type": "Split Slot", - "singular": True, - }, - "Housing": { - "area": 188.495559215387, - "centroid": {"x": 119.725253764956, "y": 7.84720771818839}, - "colour": {"b": 253, "g": 231, "r": 229}, - "duplications": 48, - "entities": [ - {"end": {"x": 126, "y": 0}, "start": {"x": 114, "y": 0}, "type": "line"}, - { - "centre": {"x": 0.0, "y": -8.668621376273222e-13}, - "end": {"x": 124.9220525331, "y": 16.4463002197265}, - "radius": 126, - "start": {"x": 126, "y": 0}, - "type": "arc", - }, - { - "end": {"x": 113.024714196614, "y": 14.8799859130859}, - "start": {"x": 124.9220525331, "y": 16.4463002197265}, - "type": "line", - }, - { - "centre": {"x": 0.0, "y": -2.9203306439740118e-12}, - "end": {"x": 114, "y": 0}, - "radius": -114, - "start": {"x": 113.024714196614, "y": 14.8799859130859}, - "type": "arc", - }, - ], - "lamination_type": "", - "material": "Aluminium (Alloy 195 Cast)", - "mesh_length": 0, - "name": "Housing", - "on_boundary": False, - "parent_name": "root", - "region_coordinate": {"x": 119.725253764956, "y": 7.84720771818839}, - "region_type": "Housing", - "singular": False, - }, - "Housing_1": { - "area": 72.9765793490131, - "centroid": {"x": 111.170714519021, "y": 7.28651359322089}, - "colour": {"b": 253, "g": 231, "r": 229}, - "duplications": 48, - "entities": [ - {"end": {"x": 114, "y": 0}, "start": {"x": 109, "y": 0}, "type": "line"}, - { - "centre": {"x": 0.0, "y": -2.8919089345436078e-12}, - "end": {"x": 113.024714196614, "y": 14.8799859130859}, - "radius": 114, - "start": {"x": 114, "y": 0}, - "type": "arc", - }, - { - "end": {"x": 108.067489889745, "y": 14.2273549519856}, - "start": {"x": 113.024714196614, "y": 14.8799859130859}, - "type": "line", - }, - { - "centre": {"x": 0.0, "y": -2.5712765250318625e-12}, - "end": {"x": 109, "y": 0}, - "radius": -109, - "start": {"x": 108.067489889745, "y": 14.2273549519856}, - "type": "arc", - }, - ], - "lamination_type": "", - "material": "Aluminium (Alloy 195 Cast)", - "mesh_length": 0, - "name": "Housing", - "on_boundary": False, - "parent_name": "root", - "region_coordinate": {"x": 111.170714519021, "y": 7.28651359322089}, - "region_type": "Housing", - "singular": False, - }, - "Housing_2": { - "area": 136.135681655558, - "centroid": {"x": 103.755368536621, "y": 6.80048613956137}, - "colour": {"b": 253, "g": 231, "r": 229}, - "duplications": 48, - "entities": [ - {"end": {"x": 109, "y": 0}, "start": {"x": 99, "y": 0}, "type": "line"}, - { - "centre": {"x": -1.4210854715202004e-14, "y": -2.5446311724408588e-12}, - "end": {"x": 108.067489889745, "y": 14.2273549519856}, - "radius": 109, - "start": {"x": 109, "y": 0}, - "type": "arc", - }, - { - "end": {"x": 98.1530412760072, "y": 12.9220930297851}, - "start": {"x": 108.067489889745, "y": 14.2273549519856}, - "type": "line", - }, - { - "centre": {"x": 0.0, "y": -2.7000623958883807e-13}, - "end": {"x": 99, "y": 0}, - "radius": -99, - "start": {"x": 98.1530412760072, "y": 12.9220930297851}, - "type": "arc", - }, - ], - "lamination_type": "", - "material": "Aluminium (Alloy 195 Cast)", - "mesh_length": 0, - "name": "Housing", - "on_boundary": False, - "parent_name": "root", - "region_coordinate": {"x": 103.755368536621, "y": 6.80048613956137}, - "region_type": "Housing", - "singular": False, - }, - "Impreg": { - "area": 97.2839971133977, - "centroid": {"x": 77.5396239396489, "y": 5.08221545839593}, - "colour": {"b": 48, "g": 224, "r": 48}, - "duplications": 48, - "entities": [ - { - "end": {"x": 67.2985091364678, "y": 2.5}, - "start": {"x": 67.0490765420196, "y": 6.30560598623492}, - "type": "line", - }, - { - "end": {"x": 84.8936835106123, "y": 2.5}, - "start": {"x": 67.2985091364678, "y": 2.5}, - "type": "line", - }, - { - "centre": {"x": 84.89368351061225, "y": 4.25}, - "end": {"x": 86.6414946417131, "y": 4.3375}, - "radius": 1.75, - "start": {"x": 84.8936835106123, "y": 2.5}, - "type": "arc", - }, - { - "centre": {"x": -1.8474111129762605e-13, "y": 3.5207392556912964e-12}, - "end": {"x": 86.5642615909489, "y": 5.67372146071491}, - "radius": 86.75, - "start": {"x": 86.6414946417131, "y": 4.3375}, - "type": "arc", - }, - { - "centre": {"x": 4.263256414560601e-14, "y": -1.0391687510491465e-13}, - "end": {"x": 86.4664220030275, "y": 7.00859229762791}, - "radius": 86.75, - "start": {"x": 86.5642615909489, "y": 5.67372146071491}, - "type": "arc", - }, - { - "centre": {"x": 84.72214259662638, "y": 6.867208591335713}, - "end": {"x": 84.4937217602412, "y": 8.60223709873987}, - "radius": 1.75, - "start": {"x": 86.4664220030275, "y": 7.00859229762791}, - "type": "arc", - }, - { - "end": {"x": 67.0490765420196, "y": 6.30560598623492}, - "start": {"x": 84.4937217602412, "y": 8.60223709873987}, - "type": "line", - }, - ], - "lamination_type": "", - "material": "", - "mesh_length": 0, - "name": "Impreg", - "on_boundary": False, - "parent_name": "root", - "region_coordinate": {"x": 82.3420980203589, "y": 5.55860429566785}, - "region_type": "Stator Impreg", - "singular": False, - }, - "L1_1Magnet1": { - "area": 96.0000000000001, - "centroid": {"x": 56.040689756688, "y": 11.8668792573194}, - "colour": {"b": 0, "g": 212, "r": 0}, - "duplications": 8, - "entities": [ - { - "end": {"x": 63.2908412096315, "y": 7.34633682201565}, - "start": {"x": 58.5307211678841, "y": 3.69376824796331}, - "type": "line", - }, - { - "end": {"x": 53.550658345492, "y": 20.0399902666754}, - "start": {"x": 63.2908412096315, "y": 7.34633682201565}, - "type": "line", - }, - { - "end": {"x": 48.7905383037446, "y": 16.3874216926231}, - "start": {"x": 53.550658345492, "y": 20.0399902666754}, - "type": "line", - }, - { - "end": {"x": 58.5307211678841, "y": 3.69376824796331}, - "start": {"x": 48.7905383037446, "y": 16.3874216926231}, - "type": "line", - }, - ], - "lamination_type": "", - "material": "N30UH", - "mesh_length": 0, - "name": "L1_1Magnet1", - "on_boundary": False, - "parent_name": "Rotor", - "region_coordinate": {"x": 56.040689756688, "y": 11.8668792573194}, - "region_type": "Magnet", - "singular": True, - }, - "L1_1Magnet2": { - "area": 96.0000000000001, - "centroid": {"x": 48.0179025436981, "y": 31.2356009549531}, - "colour": {"b": 0, "g": 212, "r": 0}, - "duplications": 8, - "entities": [ - { - "end": {"x": 43.9993584218163, "y": 38.7755812692834}, - "start": {"x": 49.9480275900591, "y": 39.5587384226037}, - "type": "line", - }, - { - "end": {"x": 46.0877774973371, "y": 22.9124634873025}, - "start": {"x": 43.9993584218163, "y": 38.7755812692834}, - "type": "line", - }, - { - "end": {"x": 52.03644666558, "y": 23.6956206406228}, - "start": {"x": 46.0877774973371, "y": 22.9124634873025}, - "type": "line", - }, - { - "end": {"x": 49.9480275900591, "y": 39.5587384226037}, - "start": {"x": 52.03644666558, "y": 23.6956206406228}, - "type": "line", - }, - ], - "lamination_type": "", - "material": "N30UH", - "mesh_length": 0, - "name": "L1_1Magnet2", - "on_boundary": False, - "parent_name": "Rotor", - "region_coordinate": {"x": 48.0179025436981, "y": 31.2356009549531}, - "region_type": "Magnet", - "singular": True, - }, - "Liner": { - "area": 10.983203589653, - "centroid": {"x": 77.968913664417, "y": 5.11035259350818}, - "colour": {"b": 0, "g": 128, "r": 0}, - "duplications": 48, - "entities": [ - { - "end": {"x": 84.8936835106123, "y": 2.25}, - "start": {"x": 67.3148950021716, "y": 2.24999999999998}, - "type": "line", - }, - { - "centre": {"x": 84.89368351061228, "y": 4.25}, - "end": {"x": 86.8911819461561, "y": 4.35}, - "radius": 2, - "start": {"x": 84.8936835106123, "y": 2.25}, - "type": "arc", - }, - { - "centre": {"x": 1.1368683772161603e-13, "y": -1.7896795156957523e-12}, - "end": {"x": 86.8137263217585, "y": 5.69007224302245}, - "radius": 87, - "start": {"x": 86.8911819461561, "y": 4.35}, - "type": "arc", - }, - { - "centre": {"x": -1.2789769243681803e-13, "y": 1.865174681370263e-12}, - "end": {"x": 86.7156047753705, "y": 7.02878996995537}, - "radius": 87, - "start": {"x": 86.8137263217585, "y": 5.69007224302245}, - "type": "arc", - }, - { - "centre": {"x": 84.72214259662634, "y": 6.867208591335713}, - "end": {"x": 84.4610902121862, "y": 8.85009831408333}, - "radius": 2, - "start": {"x": 86.7156047753705, "y": 7.02878996995537}, - "type": "arc", - }, - { - "end": {"x": 67.0326906763158, "y": 6.55560598623492}, - "start": {"x": 84.4610902121862, "y": 8.85009831408333}, - "type": "line", - }, - { - "end": {"x": 67.0490765420196, "y": 6.30560598623492}, - "start": {"x": 67.0326906763158, "y": 6.55560598623492}, - "type": "line", - }, - { - "end": {"x": 84.4937217602412, "y": 8.60223709873987}, - "start": {"x": 67.0490765420196, "y": 6.30560598623492}, - "type": "line", - }, - { - "centre": {"x": 84.72214259662638, "y": 6.867208591335713}, - "end": {"x": 86.4664220030275, "y": 7.00859229762791}, - "radius": -1.75, - "start": {"x": 84.4937217602412, "y": 8.60223709873987}, - "type": "arc", - }, - { - "centre": {"x": 4.263256414560601e-14, "y": -1.2523315717771766e-13}, - "end": {"x": 86.5642615909489, "y": 5.67372146071491}, - "radius": -86.75, - "start": {"x": 86.4664220030275, "y": 7.00859229762791}, - "type": "arc", - }, - { - "centre": {"x": -1.8474111129762605e-13, "y": 3.4994229736184934e-12}, - "end": {"x": 86.6414946417131, "y": 4.3375}, - "radius": -86.75, - "start": {"x": 86.5642615909489, "y": 5.67372146071491}, - "type": "arc", - }, - { - "centre": {"x": 84.89368351061225, "y": 4.25}, - "end": {"x": 84.8936835106123, "y": 2.5}, - "radius": -1.75, - "start": {"x": 86.6414946417131, "y": 4.3375}, - "type": "arc", - }, - { - "end": {"x": 67.2985091364678, "y": 2.5}, - "start": {"x": 84.8936835106123, "y": 2.5}, - "type": "line", - }, - { - "end": {"x": 67.3148950021716, "y": 2.24999999999998}, - "start": {"x": 67.2985091364678, "y": 2.5}, - "type": "line", - }, - ], - "lamination_type": "", - "material": "", - "mesh_length": 0, - "name": "Liner", - "on_boundary": False, - "parent_name": "root", - "region_coordinate": {"x": 79.4399567850703, "y": 8.04005751741874}, - "region_type": "Stator Liner", - "singular": False, - }, - "Rotor": { - "area": 1030.83508945915, - "centroid": {"x": 48.1444344595077, "y": 19.9420777059107}, - "colour": {"b": 240, "g": 240, "r": 0}, - "duplications": 8, - "entities": [ - {"end": {"x": 65, "y": 0}, "start": {"x": 40, "y": 0}, "type": "line"}, - { - "centre": {"x": 0.0, "y": 1.0658141036401503e-14}, - "end": {"x": 45.9619407771256, "y": 45.9619407771256}, - "radius": 65, - "start": {"x": 65, "y": 0}, - "type": "arc", - }, - { - "end": {"x": 28.2842712474619, "y": 28.2842712474619}, - "start": {"x": 45.9619407771256, "y": 45.9619407771256}, - "type": "line", - }, - { - "centre": {"x": 0.0, "y": -5.329070518200751e-15}, - "end": {"x": 40, "y": 0}, - "radius": -40, - "start": {"x": 28.2842712474619, "y": 28.2842712474619}, - "type": "arc", - }, - ], - "lamination_type": "", - "material": "M250-35A", - "mesh_length": 0, - "name": "Rotor", - "on_boundary": False, - "parent_name": "root", - "region_coordinate": {"x": 47.7895021472478, "y": 21.7999639468195}, - "region_type": "Rotor", - "singular": False, - }, - "Rotor Pocket": { - "area": 27.2374304690439, - "centroid": {"x": 47.559936471459, "y": 31.1704313411946}, - "colour": {"b": 240, "g": 241, "r": 241}, - "duplications": 8, - "entities": [ - { - "end": {"x": 43.8010694495415, "y": 38.7494760308394}, - "start": {"x": 43.7358063534315, "y": 39.2451984615263}, - "type": "line", - }, - { - "end": {"x": 45.8894885250623, "y": 22.8863582488585}, - "start": {"x": 43.8010694495415, "y": 38.7494760308394}, - "type": "line", - }, - { - "end": {"x": 45.9547516211724, "y": 22.3906358181716}, - "start": {"x": 45.8894885250623, "y": 22.8863582488585}, - "type": "line", - }, - { - "centre": {"x": 48.84516770684258, "y": 24.185768432130544}, - "end": {"x": 52.10170976169, "y": 23.1998982099359}, - "radius": 3.4025, - "start": {"x": 45.9547516211724, "y": 22.3906358181716}, - "type": "arc", - }, - { - "end": {"x": 52.03644666558, "y": 23.6956206406228}, - "start": {"x": 52.10170976169, "y": 23.1998982099359}, - "type": "line", - }, - { - "end": {"x": 46.0877774973371, "y": 22.9124634873025}, - "start": {"x": 52.03644666558, "y": 23.6956206406228}, - "type": "line", - }, - { - "end": {"x": 43.9993584218163, "y": 38.7755812692834}, - "start": {"x": 46.0877774973371, "y": 22.9124634873025}, - "type": "line", - }, - { - "end": {"x": 49.9480275900591, "y": 39.5587384226037}, - "start": {"x": 43.9993584218163, "y": 38.7755812692834}, - "type": "line", - }, - { - "end": {"x": 49.8827644939491, "y": 40.0544608532907}, - "start": {"x": 49.9480275900591, "y": 39.5587384226037}, - "type": "line", - }, - { - "centre": {"x": 46.99234840827894, "y": 38.259328239331715}, - "end": {"x": 43.7358063534315, "y": 39.2451984615263}, - "radius": 3.4025, - "start": {"x": 49.8827644939491, "y": 40.0544608532907}, - "type": "arc", - }, - ], - "lamination_type": "", - "material": "Air (Motor-CAD model)", - "mesh_length": 0, - "name": "Rotor Pocket", - "on_boundary": False, - "parent_name": "Rotor", - "region_coordinate": {"x": 45.8272822054961, "y": 39.6065511007121}, - "region_type": "Rotor Pocket", - "singular": False, - }, - "Rotor Pocket_1": { - "area": 27.2374304690439, - "centroid": {"x": 55.6707769656374, "y": 11.5891302179014}, - "colour": {"b": 240, "g": 241, "r": 241}, - "duplications": 8, - "entities": [ - { - "end": {"x": 63.2908412096315, "y": 7.34633682201565}, - "start": {"x": 63.5952219241359, "y": 6.94966015187003}, - "type": "line", - }, - { - "end": {"x": 58.5307211678841, "y": 3.69376824796331}, - "start": {"x": 63.2908412096315, "y": 7.34633682201565}, - "type": "line", - }, - { - "end": {"x": 48.7905383037446, "y": 16.3874216926231}, - "start": {"x": 58.5307211678841, "y": 3.69376824796331}, - "type": "line", - }, - { - "end": {"x": 53.550658345492, "y": 20.0399902666754}, - "start": {"x": 48.7905383037446, "y": 16.3874216926231}, - "type": "line", - }, - { - "end": {"x": 53.2462776309876, "y": 20.436666936821}, - "start": {"x": 53.550658345492, "y": 20.0399902666754}, - "type": "line", - }, - { - "centre": {"x": 51.64067018026958, "y": 17.436828447135458}, - "end": {"x": 48.327486921182, "y": 16.662346076967}, - "radius": 3.4025, - "start": {"x": 53.2462776309876, "y": 20.436666936821}, - "type": "arc", - }, - { - "end": {"x": 48.6318676356863, "y": 16.2656694068213}, - "start": {"x": 48.327486921182, "y": 16.662346076967}, - "type": "line", - }, - { - "end": {"x": 58.3720504998259, "y": 3.57201596216157}, - "start": {"x": 48.6318676356863, "y": 16.2656694068213}, - "type": "line", - }, - { - "end": {"x": 58.6764312143302, "y": 3.17533929201596}, - "start": {"x": 58.3720504998259, "y": 3.57201596216157}, - "type": "line", - }, - { - "centre": {"x": 60.28203866504834, "y": 6.175177781701426}, - "end": {"x": 63.5952219241359, "y": 6.94966015187003}, - "radius": 3.4025, - "start": {"x": 58.6764312143302, "y": 3.17533929201596}, - "type": "arc", - }, - ], - "lamination_type": "", - "material": "Air (Motor-CAD model)", - "mesh_length": 0, - "name": "Rotor Pocket", - "on_boundary": False, - "parent_name": "Rotor", - "region_coordinate": {"x": 61.753742604451, "y": 4.70347384229879}, - "region_type": "Rotor Pocket", - "singular": False, - }, - "Shaft": { - "area": 628.318530717959, - "centroid": {"x": 24.0007863180929, "y": 9.94145120057268}, - "colour": {"b": 160, "g": 160, "r": 160}, - "duplications": 8, - "entities": [ - { - "centre": {"x": -7.105427357601002e-15, "y": 1.7763568394002505e-15}, - "end": {"x": 28.2842712474619, "y": 28.2842712474619}, - "radius": 40, - "start": {"x": 40, "y": 0}, - "type": "arc", - }, - { - "end": {"x": 0, "y": 0}, - "start": {"x": 28.2842712474619, "y": 28.2842712474619}, - "type": "line", - }, - {"end": {"x": 40, "y": 0}, "start": {"x": 0, "y": 0}, "type": "line"}, - ], - "lamination_type": "", - "material": "", - "mesh_length": 0, - "name": "Shaft", - "on_boundary": False, - "parent_name": "root", - "region_coordinate": {"x": 24.7487373415292, "y": 10.6066017177982}, - "region_type": "Shaft", - "singular": False, - }, - "Stator": { - "area": 243.916173195236, - "centroid": {"x": 86.1706272589501, "y": 5.64792130351275}, - "colour": {"b": 16, "g": 0, "r": 240}, - "duplications": 48, - "entities": [ - { - "centre": {"x": 0.0, "y": -2.469136006766348e-13}, - "end": {"x": 98.1530412760072, "y": 12.9220930297851}, - "radius": 99, - "start": {"x": 99, "y": 0}, - "type": "arc", - }, - { - "end": {"x": 65.4353608506715, "y": 8.6147286865234}, - "start": {"x": 98.1530412760072, "y": 12.9220930297851}, - "type": "line", - }, - { - "centre": {"x": -7.105427357601002e-14, "y": 7.123190925995004e-13}, - "end": {"x": 65.7435730840115, "y": 5.81227994398376}, - "radius": -66, - "start": {"x": 65.4353608506715, "y": 8.6147286865234}, - "type": "arc", - }, - { - "end": {"x": 66.7414320072501, "y": 5.87768307321394}, - "start": {"x": 65.7435730840115, "y": 5.81227994398376}, - "type": "line", - }, - { - "end": {"x": 67.0326906763158, "y": 6.555605986235}, - "start": {"x": 66.7414320072501, "y": 5.87768307321394}, - "type": "line", - }, - { - "end": {"x": 84.4610902121862, "y": 8.8500983140833}, - "start": {"x": 67.0326906763158, "y": 6.555605986235}, - "type": "line", - }, - { - "centre": {"x": 84.72214259662634, "y": 6.867208591335685}, - "end": {"x": 86.7156047753705, "y": 7.02878996995535}, - "radius": -2, - "start": {"x": 84.4610902121862, "y": 8.8500983140833}, - "type": "arc", - }, - { - "centre": {"x": -1.1368683772161603e-13, "y": 1.7177370637000422e-12}, - "end": {"x": 86.8137263217585, "y": 5.69007224302245}, - "radius": -87, - "start": {"x": 86.7156047753705, "y": 7.02878996995535}, - "type": "arc", - }, - { - "centre": {"x": 1.1368683772161603e-13, "y": -1.8394175071989594e-12}, - "end": {"x": 86.8911819461561, "y": 4.35000000000002}, - "radius": -87, - "start": {"x": 86.8137263217585, "y": 5.69007224302245}, - "type": "arc", - }, - { - "centre": {"x": 84.89368351061226, "y": 4.25000000000003}, - "end": {"x": 84.8936835106123, "y": 2.25000000000002}, - "radius": -2.00000000000001, - "start": {"x": 86.8911819461561, "y": 4.35000000000002}, - "type": "arc", - }, - { - "end": {"x": 67.3148950021716, "y": 2.24999999999998}, - "start": {"x": 84.8936835106123, "y": 2.25000000000002}, - "type": "line", - }, - { - "end": {"x": 66.9376413949405, "y": 2.88410630349807}, - "start": {"x": 67.3148950021716, "y": 2.24999999999998}, - "type": "line", - }, - { - "end": {"x": 65.939782471702, "y": 2.81870317426794}, - "start": {"x": 66.9376413949405, "y": 2.88410630349807}, - "type": "line", - }, - { - "centre": {"x": 1.4210854715202004e-14, "y": 1.2376766278521245e-12}, - "end": {"x": 66, "y": 0}, - "radius": -66, - "start": {"x": 65.939782471702, "y": 2.81870317426794}, - "type": "arc", - }, - {"end": {"x": 99, "y": 0}, "start": {"x": 66, "y": 0}, "type": "line"}, - ], - "lamination_type": "", - "material": "M250-35A", - "mesh_length": 0, - "name": "Stator", - "on_boundary": False, - "parent_name": "root", - "region_coordinate": {"x": 97.7405934251342, "y": 6.46104651489255}, - "region_type": "Stator", - "singular": False, - }, - "StatorAir": { - "area": 2.96590380676454, - "centroid": {"x": 66.4303884745974, "y": 4.35407769678662}, - "colour": {"b": 240, "g": 240, "r": 239}, - "duplications": 48, - "entities": [ - { - "end": {"x": 66.9376413949405, "y": 2.88410630349813}, - "start": {"x": 65.939782471702, "y": 2.81870317426794}, - "type": "line", - }, - { - "end": {"x": 66.7414320072501, "y": 5.87768307321392}, - "start": {"x": 66.9376413949405, "y": 2.88410630349813}, - "type": "line", - }, - { - "end": {"x": 65.7435730840115, "y": 5.81227994398376}, - "start": {"x": 66.7414320072501, "y": 5.87768307321392}, - "type": "line", - }, - { - "centre": {"x": 1.1368683772161603e-13, "y": -1.5827339439056232e-12}, - "end": {"x": 65.939782471702, "y": 2.81870317426794}, - "radius": -66, - "start": {"x": 65.7435730840115, "y": 5.81227994398376}, - "type": "arc", - }, - ], - "lamination_type": "", - "material": "Air", - "mesh_length": 0, - "name": "StatorAir", - "on_boundary": False, - "parent_name": "root", - "region_coordinate": {"x": 66.340607239476, "y": 4.60980564066148}, - "region_type": "Stator Air", - "singular": False, - }, - "StatorSlot": { - "area": 108.267200703049, - "centroid": {"x": 77.5831733841295, "y": 5.08506983979084}, - "colour": {"b": 16, "g": 240, "r": 240}, - "duplications": 48, - "entities": [ - { - "end": {"x": 84.8936835106123, "y": 2.25000000000002}, - "start": {"x": 67.3148950021716, "y": 2.24999999999998}, - "type": "line", - }, - { - "centre": {"x": 84.89368351061226, "y": 4.25000000000003}, - "end": {"x": 86.8911819461561, "y": 4.35000000000002}, - "radius": 2.00000000000001, - "start": {"x": 84.8936835106123, "y": 2.25000000000002}, - "type": "arc", - }, - { - "centre": {"x": 1.1368683772161603e-13, "y": -1.8181012251261564e-12}, - "end": {"x": 86.8137263217585, "y": 5.69007224302245}, - "radius": 87, - "start": {"x": 86.8911819461561, "y": 4.35000000000002}, - "type": "arc", - }, - { - "centre": {"x": -1.2789769243681803e-13, "y": 1.7390533457728452e-12}, - "end": {"x": 86.7156047753705, "y": 7.02878996995535}, - "radius": 87, - "start": {"x": 86.8137263217585, "y": 5.69007224302245}, - "type": "arc", - }, - { - "centre": {"x": 84.72214259662634, "y": 6.867208591335685}, - "end": {"x": 84.4610902121862, "y": 8.8500983140833}, - "radius": 2, - "start": {"x": 86.7156047753705, "y": 7.02878996995535}, - "type": "arc", - }, - { - "end": {"x": 67.0326906763158, "y": 6.55560598623481}, - "start": {"x": 84.4610902121862, "y": 8.8500983140833}, - "type": "line", - }, - { - "end": {"x": 67.3148950021716, "y": 2.24999999999998}, - "start": {"x": 67.0326906763158, "y": 6.55560598623481}, - "type": "line", - }, - ], - "lamination_type": "", - "material": "Copper (Pure)", - "mesh_length": 0, - "name": "StatorSlot", - "on_boundary": False, - "parent_name": "root", - "region_coordinate": {"x": 83.5757121546551, "y": 5.55860429566783}, - "region_type": "Stator Slot Area", - "singular": False, - }, - "StatorWedge": { - "area": 1.22513893654097, - "centroid": {"x": 67.0166785354928, "y": 4.39250517759192}, - "colour": {"b": 192, "g": 192, "r": 160}, - "duplications": 48, - "entities": [ - { - "end": {"x": 67.3148950021716, "y": 2.25}, - "start": {"x": 66.9376413949405, "y": 2.88410630349807}, - "type": "line", - }, - { - "end": {"x": 67.0326906763158, "y": 6.55560598623498}, - "start": {"x": 67.3148950021716, "y": 2.25}, - "type": "line", - }, - { - "end": {"x": 66.7414320072501, "y": 5.87768307321414}, - "start": {"x": 67.0326906763158, "y": 6.55560598623498}, - "type": "line", - }, - { - "end": {"x": 66.9376413949405, "y": 2.88410630349807}, - "start": {"x": 66.7414320072501, "y": 5.87768307321414}, - "type": "line", - }, - ], - "lamination_type": "", - "material": "", - "mesh_length": 0, - "name": "StatorWedge", - "on_boundary": False, - "parent_name": "root", - "region_coordinate": {"x": 67.0166785354928, "y": 4.39250517759192}, - "region_type": "Wedge", - "singular": False, - }, - } -} - -sample_tree = GeometryTree.from_json(mc.get_geometry_tree()) - - -def get_simple_tree(): +@pytest.fixture(scope="function") +def basic_tree(): """Return a simple GeometryTree for the purposes of testing""" p1 = Coordinate(0, 0) p2 = Coordinate(1, 0) @@ -2362,34 +62,40 @@ def get_simple_tree(): return tree -def test_get_json(): - test_json = mc.get_geometry_tree() - assert test_json == sample_json1 - +def test_get_tree(sample_tree): + node_keys = set(sample_tree) + # Check each item is listed only once among all children + # Check also that each item is the child of something, except root + valid = True + for node in sample_tree.values(): + for child in node.children: + try: + node_keys.remove(child.key) + except KeyError: + valid = False + assert node == child.parent + assert node_keys == {"root"} + assert valid -def test_from_json(): - test_tree = get_simple_tree() - assert test_tree == GeometryTree.from_json(simple_json) +# def test_to_json(sample_tree): +# test_json = sample_tree.to_json() +# assert test_json == sample_json2 -def test_to_json(): - test_json = sample_tree.to_json() - assert test_json == sample_json2 - -def test_get_node(): +def test_get_node(sample_tree): assert sample_tree.get_node("rotor") == sample_tree["Rotor"] -def test_add_node(): +def test_add_node(basic_tree): # Tests the basic functionality of adding a node - test_tree = deepcopy(get_simple_tree()) + test_tree = deepcopy(basic_tree) new_node = GeometryNode(parent=test_tree["root"]) new_node.name = "node" new_node.key = "node" test_tree["node"] = new_node - function_tree = deepcopy(get_simple_tree()) + function_tree = deepcopy(basic_tree) new_node2 = GeometryNode(parent=function_tree["root"]) new_node2.name = "node" function_tree.add_node(new_node2) @@ -2397,9 +103,9 @@ def test_add_node(): assert test_tree == function_tree -def test_add_node_with_children(): +def test_add_node_with_children(basic_tree): # Tests the parent and child reassignment performed when including those values - test_tree = deepcopy(get_simple_tree()) + test_tree = deepcopy(basic_tree) new_node = GeometryNode() new_node.parent = test_tree["root"] new_node.children.append(test_tree["Triangle"]) @@ -2410,7 +116,7 @@ def test_add_node_with_children(): test_tree["node"] = new_node test_tree["Triangle"].parent = new_node - function_tree = deepcopy(get_simple_tree()) + function_tree = deepcopy(basic_tree) new_node2 = Region() new_node2.name = "node" function_tree.add_node(new_node2, parent="root", children=["Triangle"]) @@ -2418,9 +124,9 @@ def test_add_node_with_children(): assert test_tree == function_tree -def test_add_node_with_children_2(): +def test_add_node_with_children_2(basic_tree): # Same test as above, but testing different mode of function input - test_tree = deepcopy(get_simple_tree()) + test_tree = deepcopy(basic_tree) new_node = GeometryNode() new_node.parent = test_tree["root"] new_node.children.append(test_tree["Triangle"]) @@ -2431,7 +137,7 @@ def test_add_node_with_children_2(): test_tree["node1"] = new_node test_tree["Triangle"].parent = new_node - function_tree = deepcopy(get_simple_tree()) + function_tree = deepcopy(basic_tree) new_node2 = Region() new_node2.name = "node" function_tree.add_node( @@ -2441,71 +147,66 @@ def test_add_node_with_children_2(): assert test_tree == function_tree -def test_add_node_errors(): - function_tree = deepcopy(get_simple_tree()) +def test_add_node_errors(basic_tree): new_node2 = Region() new_node2.name = "node" with pytest.raises(TypeError, match="Parent must be a GeometryNode or str"): - function_tree.add_node(new_node2, parent=0) + basic_tree.add_node(new_node2, parent=0) with pytest.raises(TypeError, match="Children must be a GeometryNode or str"): - function_tree.add_node(new_node2, children=[0, "root"]) + basic_tree.add_node(new_node2, children=[0, "root"]) -def test_remove_node(): - test_tree = deepcopy(sample_tree) - magnet = test_tree["L1_1Magnet1"] - test_tree["Rotor"].children.remove(magnet) - test_tree.pop("L1_1Magnet1") +def test_remove_node(basic_tree): + # Tests the basic functionality of removing a node + test_tree = deepcopy(basic_tree) - function_tree = deepcopy(sample_tree) - function_tree.remove_node("L1_1Magnet1") + function_tree = deepcopy(basic_tree) + new_node2 = GeometryNode(parent=function_tree["root"]) + new_node2.name = "node" + function_tree.add_node(new_node2, children=["Triangle"]) + function_tree.remove_node(new_node2) assert test_tree == function_tree -def test_remove_branch(): - test_tree = deepcopy(sample_tree) - test_tree["root"].children.remove(test_tree["Rotor"]) - for child_key in test_tree["Rotor"].child_keys: - test_tree.pop(child_key) - test_tree.pop("Rotor") - - function_tree = deepcopy(sample_tree) - function_tree.remove_branch("Rotor") +def test_remove_branch(basic_tree): + # Tests the basic functionality of removing a branch + test_tree = deepcopy(basic_tree) + test_tree.remove_node("Triangle") + function_tree = deepcopy(basic_tree) + new_node = GeometryNode(parent=function_tree["root"]) + new_node.name = "node" + function_tree.add_node(new_node, children=["Triangle"]) + function_tree.remove_branch(new_node) assert test_tree == function_tree -def test_remove_branch2(): +def test_remove_branch2(basic_tree): # Same test, slightly different function input - test_tree1 = deepcopy(sample_tree) - test_tree1["root"].children.remove(test_tree1["Rotor"]) - for child_key in test_tree1["Rotor"].child_keys: - test_tree1.pop(child_key) - test_tree1.pop("Rotor") - - test_tree2 = deepcopy(sample_tree) - test_tree2.remove_branch(test_tree2["Rotor"]) - - assert test_tree1 == test_tree2 + test_tree = deepcopy(basic_tree) + test_tree.remove_node(test_tree["Triangle"]) + function_tree = deepcopy(basic_tree) + new_node = GeometryNode(parent=function_tree["root"]) + new_node.name = "node" + function_tree.add_node(new_node, children=["Triangle"]) + function_tree.remove_branch(function_tree["node"]) + assert test_tree == function_tree -def test_get_parent(): - test_tree = get_simple_tree() - - assert test_tree["root"] == test_tree["Triangle"].parent - assert test_tree["root"].key == test_tree["Triangle"].parent_key +def test_get_parent(basic_tree): + assert basic_tree["root"] == basic_tree["Triangle"].parent - assert test_tree["root"].name == test_tree["Triangle"].parent_name + assert basic_tree["root"].key == basic_tree["Triangle"].parent_key + assert basic_tree["root"].name == basic_tree["Triangle"].parent_name -def test_get_children(): - test_tree = get_simple_tree() - assert test_tree["root"].children == [test_tree["Triangle"]] +def test_get_children(basic_tree): + assert basic_tree["root"].children == [basic_tree["Triangle"]] - assert test_tree["root"].child_names == ["Triangle"] + assert basic_tree["root"].child_names == ["Triangle"] - assert test_tree["root"].child_keys == ["Triangle"] + assert basic_tree["root"].child_keys == ["Triangle"] From a82f170285f37914a195018b9315c00ac9dde65d Mon Sep 17 00:00:00 2001 From: Aidan Crandell Date: Mon, 30 Jun 2025 15:01:18 +0100 Subject: [PATCH 07/29] Amended tests for coverage and fixed issues in parent reassignment --- .../motorcad/core/methods/geometry_tree.py | 30 +++---- tests/test_geometry_tree.py | 82 ++++++++++++++++--- 2 files changed, 87 insertions(+), 25 deletions(-) diff --git a/src/ansys/motorcad/core/methods/geometry_tree.py b/src/ansys/motorcad/core/methods/geometry_tree.py index 676902bd4..d935e59b5 100644 --- a/src/ansys/motorcad/core/methods/geometry_tree.py +++ b/src/ansys/motorcad/core/methods/geometry_tree.py @@ -178,18 +178,23 @@ def add_node(self, region, key=None, parent=None, children=None): else: raise TypeError("Children must be a GeometryNode or str") # Essentially, slotting the given node in between the given parent and children + # Children are removed from their old spot and placed in the new one + # Children's children become assigned to child's old parent for child in region.children: - child.parent.children.remove(child) + self.remove_node(child) child.parent = region + child.children = list() + self[child.key] = child if parent is None: region.parent = self["root"] + self["root"].children.append(region) else: if isinstance(parent, GeometryNode): region.parent = parent parent.children.append(region) elif isinstance(parent, str): - region.parent = self[parent] + region.parent = self.get_node(parent) self[parent].children.append(region) else: raise TypeError("Parent must be a GeometryNode or str") @@ -227,26 +232,23 @@ def dive(node): class GeometryNode(Region): - """Subclass of Region used for entries in GeometryTree.""" + """Subclass of Region used for entries in GeometryTree. - def __init__(self, region_type=RegionType.adaptive, parent=None, child_nodes=None): + Nodes should not have a parent or children unless they are part of a tree. + """ + + def __init__(self, region_type=RegionType.adaptive): """Initialize the geometry node. + Parent and children are defined when the node is added to a tree. + Parameters ---------- region_type: RegionType - parent: GeometryNode - child_nodes: list of GeometryNode """ super().__init__(region_type=region_type) - if parent is not None: - self.parent = parent - parent.children.append(self) - else: - self.parent = None - if child_nodes is None: - child_nodes = list() - self.children = child_nodes + self.children = list() + self.parent = None @classmethod def from_json(cls, tree, node_json, parent): diff --git a/tests/test_geometry_tree.py b/tests/test_geometry_tree.py index c591b6be5..e843ce66d 100644 --- a/tests/test_geometry_tree.py +++ b/tests/test_geometry_tree.py @@ -90,15 +90,17 @@ def test_get_node(sample_tree): def test_add_node(basic_tree): # Tests the basic functionality of adding a node test_tree = deepcopy(basic_tree) - new_node = GeometryNode(parent=test_tree["root"]) + new_node = GeometryNode() + new_node.parent = test_tree["root"] new_node.name = "node" new_node.key = "node" + test_tree.get_node("root").children.append(new_node) test_tree["node"] = new_node function_tree = deepcopy(basic_tree) - new_node2 = GeometryNode(parent=function_tree["root"]) + new_node2 = GeometryNode() new_node2.name = "node" - function_tree.add_node(new_node2) + function_tree.add_node(new_node2, parent=function_tree["root"]) assert test_tree == function_tree @@ -163,24 +165,79 @@ def test_remove_node(basic_tree): test_tree = deepcopy(basic_tree) function_tree = deepcopy(basic_tree) - new_node2 = GeometryNode(parent=function_tree["root"]) + new_node2 = GeometryNode() new_node2.name = "node" function_tree.add_node(new_node2, children=["Triangle"]) function_tree.remove_node(new_node2) assert test_tree == function_tree +def test_equality_1(basic_tree): + test_tree = deepcopy(basic_tree) + test_tree["root"].children.remove(test_tree["Triangle"]) + test_tree.pop("Triangle") + assert test_tree != basic_tree + + +def test_equality_2(basic_tree): + test_tree1 = deepcopy(basic_tree) + new_node1 = GeometryNode() + new_node1.name = "node" + test_tree1.add_node(new_node1, parent="root", children=["Triangle"]) + + test_tree2 = deepcopy(basic_tree) + new_node2 = GeometryNode() + new_node2.name = "node" + test_tree2.add_node(new_node2, parent=test_tree2["Triangle"]) + + assert test_tree2 != test_tree1 + + +def test_equality_3(basic_tree): + test_tree1 = deepcopy(basic_tree) + new_node1 = GeometryNode() + new_node1.name = "node1" + test_tree1.add_node(new_node1, parent="root") + new_node2 = GeometryNode() + new_node2.name = "node2" + test_tree1.add_node(new_node2, parent="node1", children=["Triangle"]) + + test_tree2 = deepcopy(basic_tree) + new_node3 = GeometryNode() + new_node3.name = "node1" + test_tree2.add_node(new_node3, parent="root", children=["Triangle"]) + new_node4 = GeometryNode() + new_node4.name = "node2" + test_tree2.add_node(new_node4, parent="Triangle") + + assert test_tree1 != test_tree2 + + +def test_equality_4(basic_tree): + test_tree = deepcopy(basic_tree) + test_tree["Triangle"].entities.append(Line(Coordinate(0, 0), Coordinate(-1, 0))) + assert test_tree != basic_tree + + def test_remove_branch(basic_tree): # Tests the basic functionality of removing a branch test_tree = deepcopy(basic_tree) test_tree.remove_node("Triangle") - function_tree = deepcopy(basic_tree) - new_node = GeometryNode(parent=function_tree["root"]) - new_node.name = "node" - function_tree.add_node(new_node, children=["Triangle"]) - function_tree.remove_branch(new_node) - assert test_tree == function_tree + function_tree1 = deepcopy(basic_tree) + new_node1 = GeometryNode() + new_node1.name = "node" + function_tree1.add_node(new_node1, parent=function_tree1["root"], children=["Triangle"]) + function_tree1.remove_branch(new_node1) + + function_tree2 = deepcopy(basic_tree) + new_node2 = GeometryNode() + new_node2.name = "node" + function_tree2.add_node(new_node2, parent=function_tree2["root"], children=["Triangle"]) + function_tree2.remove_branch("node") + + assert test_tree == function_tree1 + assert test_tree == function_tree2 def test_remove_branch2(basic_tree): @@ -189,7 +246,7 @@ def test_remove_branch2(basic_tree): test_tree.remove_node(test_tree["Triangle"]) function_tree = deepcopy(basic_tree) - new_node = GeometryNode(parent=function_tree["root"]) + new_node = GeometryNode() new_node.name = "node" function_tree.add_node(new_node, children=["Triangle"]) function_tree.remove_branch(function_tree["node"]) @@ -203,6 +260,9 @@ def test_get_parent(basic_tree): assert basic_tree["root"].name == basic_tree["Triangle"].parent_name + assert basic_tree["root"].parent_name == "" + assert basic_tree["root"].parent_key == "" + def test_get_children(basic_tree): assert basic_tree["root"].children == [basic_tree["Triangle"]] From 63d8ca6ecfd03eec1fb5a393bee6b5e7351ad8db Mon Sep 17 00:00:00 2001 From: Aidan Crandell Date: Mon, 7 Jul 2025 09:41:52 +0100 Subject: [PATCH 08/29] fix mistaken change to geometry.py --- src/ansys/motorcad/core/geometry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ansys/motorcad/core/geometry.py b/src/ansys/motorcad/core/geometry.py index 8cc2259f6..7662dd530 100644 --- a/src/ansys/motorcad/core/geometry.py +++ b/src/ansys/motorcad/core/geometry.py @@ -388,7 +388,7 @@ def child_names(self): Returns ------- - list of string\ + list of string list of child region names """ return self._child_names From 375c5d4da981b07540e0b16aa6edd176210bc50a Mon Sep 17 00:00:00 2001 From: Aidan Crandell Date: Wed, 9 Jul 2025 13:17:47 +0100 Subject: [PATCH 09/29] update get_node to be more flexible --- .../motorcad/core/methods/geometry_tree.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/ansys/motorcad/core/methods/geometry_tree.py b/src/ansys/motorcad/core/methods/geometry_tree.py index d935e59b5..5ace8909a 100644 --- a/src/ansys/motorcad/core/methods/geometry_tree.py +++ b/src/ansys/motorcad/core/methods/geometry_tree.py @@ -29,7 +29,7 @@ class GeometryTree(dict): """Class used to build geometry trees.""" def __init__(self, empty=False): - """Initialize the geometry tree. + """Initialise the geometry tree. Parameters ---------- @@ -121,11 +121,9 @@ def to_json(self): def get_node(self, key): """Get a region from the tree (case-insensitive).""" - try: - node = self[key] - except KeyError: - node = self[key.capitalize()] - return node + if key.lower() in self.lowercase_keys: + return self[self.lowercase_keys[key.lower()]] + raise KeyError() def _build_tree(self, tree_json, node, parent=None): """Recursively builds tree. @@ -144,9 +142,7 @@ def _build_tree(self, tree_json, node, parent=None): # Recur for each child. if node["child_names"] != []: for child_name in node["child_names"]: - self._build_tree( - tree_json, tree_json[child_name], self.get_node(node["name_unique"]) - ) + self._build_tree(tree_json, tree_json[child_name], self[node["name_unique"]]) def add_node(self, region, key=None, parent=None, children=None): """Add node to tree. @@ -230,6 +226,11 @@ def dive(node): dive(node) node.parent.children.remove(node) + @property + def lowercase_keys(self): + """Return a dict of lowercase keys and their corresponding real keys.""" + return dict((key.lower(), key) for key in self) + class GeometryNode(Region): """Subclass of Region used for entries in GeometryTree. From fae774ad320d2916802eadea81af67e8d9626dd4 Mon Sep 17 00:00:00 2001 From: Aidan Crandell Date: Tue, 15 Jul 2025 10:23:10 +0100 Subject: [PATCH 10/29] Begun implementation of checks for trees --- .../core/methods/adaptive_geometry.py | 2 +- .../motorcad/core/methods/geometry_tree.py | 89 ++++++++++++++++++- 2 files changed, 89 insertions(+), 2 deletions(-) diff --git a/src/ansys/motorcad/core/methods/adaptive_geometry.py b/src/ansys/motorcad/core/methods/adaptive_geometry.py index 836746bac..10097b097 100644 --- a/src/ansys/motorcad/core/methods/adaptive_geometry.py +++ b/src/ansys/motorcad/core/methods/adaptive_geometry.py @@ -310,7 +310,7 @@ def get_geometry_tree(self): """Do placeholder.""" method = "GetGeometryTree" json = self.connection.send_and_receive(method) - return GeometryTree._from_json(json) + return GeometryTree._from_json(json, self) def set_geometry_tree(self, tree): """Do placeholder.""" diff --git a/src/ansys/motorcad/core/methods/geometry_tree.py b/src/ansys/motorcad/core/methods/geometry_tree.py index 5ace8909a..5eaea42a1 100644 --- a/src/ansys/motorcad/core/methods/geometry_tree.py +++ b/src/ansys/motorcad/core/methods/geometry_tree.py @@ -75,7 +75,7 @@ def __ne__(self, other): return not self.__eq__(other) @classmethod - def _from_json(cls, tree): + def _from_json(cls, tree, mc): """Return a GeometryTree representation of the geometry defined within a JSON. Parameters @@ -106,6 +106,7 @@ def _from_json(cls, tree): root["child_names"].append(region["name_unique"]) self._build_tree(tree_json, root) + self.mc = mc return self def to_json(self): @@ -144,6 +145,92 @@ def _build_tree(self, tree_json, node, parent=None): for child_name in node["child_names"]: self._build_tree(tree_json, tree_json[child_name], self[node["name_unique"]]) + def fix_geometry(self, node): + """Fix geometry to work with FEA. + + Parameters + ---------- + node: node representing region to be fixed + + Returns + ------- + None + + """ + # Splits regions apart, if necessary, to enforce valid geometry + node = self.get_node(node) + name_length = len(node.key) + duplication_angle = 360 / node.duplications + + # brush1 used to find the valid portion just above angle 0 + brush1 = Region(region_type=RegionType.airgap) + brush_length = self.mc.get_variable("Stator_Lam_Dia") + p1 = Coordinate(0, 0) + p2 = Coordinate(brush_length, 0) + brush1.entities.append(Line(p2, p1)) + + brush1.entities.append(Arc(p1, p2, centre=Coordinate(brush_length / 2, 1))) + valid_regions_lower = self.mc.subtract_region(node, brush1) + + # Case where there is no lower intersection + if (len(valid_regions_lower) == 1) and (valid_regions_lower[0].entities == node.entities): + # now perform the upper check + # brush3 used to find the valid portion just below duplication angle + brush3 = Region(region_type=RegionType.airgap) + p1 = Coordinate(0, 0) + p2 = Coordinate.from_polar_coords(brush_length, duplication_angle) + brush3.entities.append(Line(p1, p2)) + brush3.entities.append(Arc(p2, p1, radius=brush_length / 2)) + valid_regions_upper = self.mc.subtract_region(node, brush3) + + # Case where no slicing necessary + if (len(valid_regions_upper) == 1) and ( + valid_regions_upper[0].entities == node.entities + ): + return False + # Case where upper slicing necessary + else: + for new_valid_region in valid_regions_upper: + self.add_node(new_valid_region, parent=node.parent) + # now perform the upper check + # brush4 used to find the invalid portion just above duplication angle + brush4 = Region(region_type=RegionType.airgap) + p1 = Coordinate(0, 0) + p2 = Coordinate.from_polar_coords(brush_length, duplication_angle) + brush4.entities.append(Line(p2, p1)) + brush4.entities.append(Arc(p1, p2, radius=brush_length / 2)) + invalid_regions_upper = self.mc.subtract_region(node, brush4) + for i, new_lower_valid_region in enumerate(invalid_regions_upper): + new_lower_valid_region.rotate(Coordinate(0, 0), -duplication_angle) + new_lower_valid_region.name = new_lower_valid_region.name[0 : name_length + 1] + new_lower_valid_region.name += str(i + len(valid_regions_upper) + 1) + self.add_node(new_lower_valid_region, parent=node.parent) + self.remove_node(node) + return True + # Case where lower slicing necessary + else: + # first, handle the valid regions returned + for new_valid_region in valid_regions_lower: + self.add_node(new_valid_region, parent=node.parent) + + # brush2 used to find the invalid portion just below angle 0 + brush2 = Region(region_type=RegionType.airgap) + p1 = Coordinate(0, 0) + p2 = Coordinate(brush_length, 0) + brush2.entities.append(Line(p1, p2)) + brush2.entities.append(Arc(p2, p1, centre=Coordinate(brush_length / 2, -1))) + # Upper in this case referring to the fact that this region will + # form the upper half of the ellipse. + # It will be below the other half in terms of relative positioning + invalid_regions_lower = self.mc.subtract_region(node, brush2) + for i, new_upper_valid_region in enumerate(invalid_regions_lower): + new_upper_valid_region.rotate(Coordinate(0, 0), duplication_angle) + new_upper_valid_region.name = new_upper_valid_region.name[0 : name_length + 1] + new_upper_valid_region.name += str(i + len(valid_regions_lower) + 1) + self.add_node(new_upper_valid_region, parent=node.parent) + self.remove_node(node) + return True + def add_node(self, region, key=None, parent=None, children=None): """Add node to tree. From 008c039fbc75f38aecb30b4a23ab3037f1a56db6 Mon Sep 17 00:00:00 2001 From: Aidan Crandell Date: Tue, 15 Jul 2025 13:27:15 +0100 Subject: [PATCH 11/29] Fixed error in last push --- src/ansys/motorcad/core/methods/geometry_tree.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ansys/motorcad/core/methods/geometry_tree.py b/src/ansys/motorcad/core/methods/geometry_tree.py index 5eaea42a1..09f03d7a6 100644 --- a/src/ansys/motorcad/core/methods/geometry_tree.py +++ b/src/ansys/motorcad/core/methods/geometry_tree.py @@ -22,7 +22,7 @@ """Methods for building geometry trees.""" -from ansys.motorcad.core.geometry import Region, RegionMagnet, RegionType +from ansys.motorcad.core.geometry import Arc, Coordinate, Line, Region, RegionMagnet, RegionType class GeometryTree(dict): From caab77e79bb054a515c023bd59519ba541617110 Mon Sep 17 00:00:00 2001 From: Aidan Crandell Date: Wed, 16 Jul 2025 09:42:37 +0100 Subject: [PATCH 12/29] Added missing endcap regiontype --- src/ansys/motorcad/core/geometry.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ansys/motorcad/core/geometry.py b/src/ansys/motorcad/core/geometry.py index 7662dd530..30ae97ed3 100644 --- a/src/ansys/motorcad/core/geometry.py +++ b/src/ansys/motorcad/core/geometry.py @@ -75,6 +75,7 @@ class RegionType(Enum): barrier = "Barrier" mounting_base = "Base Mount" mounting_plate = "Plate Mount" + endcap = "Endcap" banding = "Banding" sleeve = "Sleeve" rotor_cover = "Rotor Cover" From 5e5825fa95cd77cfa708fc0499e398082f231714 Mon Sep 17 00:00:00 2001 From: Aidan Crandell Date: Wed, 16 Jul 2025 15:54:24 +0100 Subject: [PATCH 13/29] Initial Testing --- src/ansys/motorcad/core/geometry_drawing.py | 53 +++++++++++++++++---- 1 file changed, 43 insertions(+), 10 deletions(-) diff --git a/src/ansys/motorcad/core/geometry_drawing.py b/src/ansys/motorcad/core/geometry_drawing.py index ac175d9f7..152645941 100644 --- a/src/ansys/motorcad/core/geometry_drawing.py +++ b/src/ansys/motorcad/core/geometry_drawing.py @@ -109,10 +109,10 @@ def _plot_text_no_overlap(self, point, text, colour): color=colour, ) - def draw_region(self, region, colour): + def draw_region_debug(self, region, colour): """Draw a region.""" for entity in region.entities: - self.draw_entity(entity, colour) + self.draw_entity(entity, colour, debug=True) for entity_num, entity in enumerate(region.entities): text = "e{}".format(entity_num) @@ -129,7 +129,26 @@ def draw_coordinate(self, coordinate, colour): """Draw coordinate onto plot.""" plt.plot(coordinate.x, coordinate.y, "x", color=colour) - def draw_entity(self, entity, colour): + def draw_region(self, region, colour, labels): + colour = tuple(channel / 255 for channel in colour) + fill_points_x = [] + fill_points_y = [] + for entity in region.entities: + point_0 = entity.start + fill_points_x.append(point_0.x) + fill_points_y.append(point_0.y) + for i in range(1, int(entity.length / 0.1)): + point_i = entity.get_coordinate_from_distance(point_0, distance=i * 0.1) + fill_points_x.append(point_i.x) + fill_points_y.append(point_i.y) + self.draw_entity(entity, "black") + + plt.fill(fill_points_x, fill_points_y, color=colour) + + if labels: + self._plot_text_no_overlap(region.centroid, region.name, "black") + + def draw_entity(self, entity, colour, debug=False): """Draw entity onto plot.""" entity_coords = [] @@ -140,7 +159,17 @@ def draw_entity(self, entity, colour): entity_coords += [Coordinate(mid_point.x, mid_point.y)] if isinstance(entity, Line): - plt.plot([entity.start.x, entity.end.x], [entity.start.y, entity.end.y], color=colour) + if debug: + plt.plot( + [entity.start.x, entity.end.x], [entity.start.y, entity.end.y], color=colour + ) + else: + plt.plot( + [entity.start.x, entity.end.x], + [entity.start.y, entity.end.y], + color=colour, + lw=0.6, + ) elif isinstance(entity, Arc): width = abs(entity.radius * 2) @@ -155,10 +184,14 @@ def draw_entity(self, entity, colour): else: start_angle = angle2 end_angle = angle1 - - arc = mpatches.Arc( - centre, width, height, theta1=start_angle, theta2=end_angle, color=colour - ) + if debug: + arc = mpatches.Arc( + centre, width, height, theta1=start_angle, theta2=end_angle, color=colour + ) + else: + arc = mpatches.Arc( + centre, width, height, theta1=start_angle, theta2=end_angle, color=colour, lw=2 + ) self.ax.plot(marker="-o") self.ax.add_patch(arc) @@ -205,9 +238,9 @@ def draw_objects(objects): if object is None: continue if isinstance(object, Region): - region_drawing.draw_region(object, colours[i]) + region_drawing.draw_region_debug(object, colours[i]) elif isinstance(object, Entity): - region_drawing.draw_entity(object, entity_no_region_colour) + region_drawing.draw_entity(object, entity_no_region_colour, debug=True) elif isinstance(object, Coordinate): region_drawing.draw_coordinate(object, entity_no_region_colour) else: From b28692840c95220b084dede12b9ea70cf2757d97 Mon Sep 17 00:00:00 2001 From: Aidan Crandell Date: Tue, 22 Jul 2025 15:42:20 +0100 Subject: [PATCH 14/29] Complete basic functionality for regions, trees, and entities --- src/ansys/motorcad/core/geometry.py | 9 + src/ansys/motorcad/core/geometry_drawing.py | 307 +++++++++++++++----- 2 files changed, 243 insertions(+), 73 deletions(-) diff --git a/src/ansys/motorcad/core/geometry.py b/src/ansys/motorcad/core/geometry.py index 30ae97ed3..5284a3640 100644 --- a/src/ansys/motorcad/core/geometry.py +++ b/src/ansys/motorcad/core/geometry.py @@ -1590,6 +1590,15 @@ def get_arc_intersection(self, arc): """ return arc.get_line_intersection(self) + def get_coordinate_distance(self, coordinate): + """Get distance of line with another coordinate.""" + normal_angle = self.angle - 90 + defining_point = Coordinate.from_polar_coords(1, normal_angle) + normal = Line(Coordinate(0, 0), defining_point) + normal.translate(coordinate.x, coordinate.y) + nearest_point = self.get_line_intersection(normal) + return sqrt((coordinate.x - nearest_point.x) ** 2 + (coordinate.y - nearest_point.y) ** 2) + class _BaseArc(Entity): """Internal class to allow creation of Arcs.""" diff --git a/src/ansys/motorcad/core/geometry_drawing.py b/src/ansys/motorcad/core/geometry_drawing.py index 152645941..9833dead7 100644 --- a/src/ansys/motorcad/core/geometry_drawing.py +++ b/src/ansys/motorcad/core/geometry_drawing.py @@ -25,7 +25,8 @@ import warnings from warnings import warn -from ansys.motorcad.core.geometry import Arc, Coordinate, Entity, Line, Region +from ansys.motorcad.core.geometry import GEOM_TOLERANCE, Arc, Coordinate, Entity, Line, Region +from ansys.motorcad.core.methods.geometry_tree import GeometryNode, GeometryTree from ansys.motorcad.core.rpc_client_core import is_running_in_internal_scripting try: @@ -109,10 +110,10 @@ def _plot_text_no_overlap(self, point, text, colour): color=colour, ) - def draw_region_debug(self, region, colour): + def draw_region_old(self, region, colour): """Draw a region.""" for entity in region.entities: - self.draw_entity(entity, colour, debug=True) + self.draw_entity_old(entity, colour) for entity_num, entity in enumerate(region.entities): text = "e{}".format(entity_num) @@ -125,30 +126,7 @@ def draw_region_debug(self, region, colour): self._plot_text_no_overlap(region.centroid, region.name, colour) - def draw_coordinate(self, coordinate, colour): - """Draw coordinate onto plot.""" - plt.plot(coordinate.x, coordinate.y, "x", color=colour) - - def draw_region(self, region, colour, labels): - colour = tuple(channel / 255 for channel in colour) - fill_points_x = [] - fill_points_y = [] - for entity in region.entities: - point_0 = entity.start - fill_points_x.append(point_0.x) - fill_points_y.append(point_0.y) - for i in range(1, int(entity.length / 0.1)): - point_i = entity.get_coordinate_from_distance(point_0, distance=i * 0.1) - fill_points_x.append(point_i.x) - fill_points_y.append(point_i.y) - self.draw_entity(entity, "black") - - plt.fill(fill_points_x, fill_points_y, color=colour) - - if labels: - self._plot_text_no_overlap(region.centroid, region.name, "black") - - def draw_entity(self, entity, colour, debug=False): + def draw_entity_old(self, entity, colour, debug=False): """Draw entity onto plot.""" entity_coords = [] @@ -197,69 +175,252 @@ def draw_entity(self, entity, colour, debug=False): self.ax.set_aspect("equal", adjustable="box") + def draw_coordinate(self, coordinate, colour): + """Draw coordinate onto plot.""" + plt.plot(coordinate.x, coordinate.y, "x", color=colour) -def draw_objects_debug(objects): - """Draw regions on plot if not being run in Motor-CAD. + def draw_duplicates(self, region: GeometryNode, colour, labels, depth): + duplication_angle = 360 / region.duplications + origin = Coordinate(0, 0) - Parameters - ---------- - objects : List of objects - entities to draw - """ - if not is_running_in_internal_scripting(): - draw_objects(objects) + for duplicate_number in range(0, region.duplications): + duplicate = deepcopy(region) + duplicate.rotate(Coordinate(0, 0), duplication_angle * duplicate_number) + self.draw_region(duplicate, colour, labels, depth, full_geometry=True) + def draw_region(self, region, colour, labels, depth, full_geometry=False, draw_points=False): + duplication_angle = 360 / region.duplications + colour = tuple(channel / 255 for channel in colour) + fill_points_x = [] + fill_points_y = [] + for entity in region.entities: + point_0 = entity.start + fill_points_x.append(point_0.x) + fill_points_y.append(point_0.y) + for i in range(1, int(entity.length / 0.1)): + point_i = entity.get_coordinate_from_distance(point_0, distance=i * 0.1) + fill_points_x.append(point_i.x) + fill_points_y.append(point_i.y) + if not ( + isinstance(entity, Line) + and ( + ((entity.angle % duplication_angle) < GEOM_TOLERANCE) + or ((entity.angle % duplication_angle) - duplication_angle < GEOM_TOLERANCE) + ) + and entity.get_coordinate_distance(Coordinate(0, 0)) < GEOM_TOLERANCE + and full_geometry + ): + self.draw_entity( + entity, + "black", + depth=depth, + ) -def draw_objects(objects): - """Draw geometry objects on a plot.""" - if not MATPLOTLIB_AVAILABLE: - raise ImportError( - "Failed to draw geometry. Please ensure MatPlotLib and a suitable backend " - "e.g. PyQt5 are installed" + plt.fill(fill_points_x, fill_points_y, color=colour, zorder=depth) + self.ax.set_aspect("equal", adjustable="box") + + if draw_points: + for entity_num, entity in enumerate(region.entities): + text = "e{}".format(entity_num) + self._plot_text_no_overlap(entity.midpoint, text, "black") + + points = region.entities.points + for point_num, point in enumerate(points): + text = "p{}".format(point_num) + self._plot_text_no_overlap(point, text, "black") + + if labels: + self._plot_text_no_overlap(region.centroid, region.name, "black") + + def draw_entity(self, entity, colour, depth=0, draw_points=False): + """Draw entity onto plot.""" + entity_coords = [] + + mid_point = Coordinate( + (entity.end.x + entity.start.x) / 2, (entity.end.y + entity.start.y) / 2 ) - if not isinstance(objects, list): - # Given a single region not a list - reformat - objects = [objects] + entity_coords += [Coordinate(mid_point.x, mid_point.y)] - stored_coords = [] + if isinstance(entity, Line): + plt.plot( + [entity.start.x, entity.end.x], + [entity.start.y, entity.end.y], + color=colour, + lw=2, + zorder=depth, + ) - # Some basic colours - colours = ["red", "blue", "green", "purple"] - entity_no_region_colour = "grey" - fig = plt.figure() - ax = fig.add_subplot(1, 1, 1) + elif isinstance(entity, Arc): + width = abs(entity.radius * 2) + height = abs(entity.radius * 2) + centre = entity.centre.x, entity.centre.y + rad1, angle1 = (entity.start - entity.centre).get_polar_coords_deg() + rad2, angle2 = (entity.end - entity.centre).get_polar_coords_deg() - region_drawing = _RegionDrawing(ax, stored_coords) - for i, object in enumerate(objects): - # loop through colours once end of list reached - while i > len(colours) - 1: - i -= len(colours) - if object is None: - continue - if isinstance(object, Region): - region_drawing.draw_region_debug(object, colours[i]) - elif isinstance(object, Entity): - region_drawing.draw_entity(object, entity_no_region_colour, debug=True) - elif isinstance(object, Coordinate): - region_drawing.draw_coordinate(object, entity_no_region_colour) - else: - raise TypeError("Object cannot be drawn") - plt.show() + if entity.radius > 0: + start_angle = angle1 + end_angle = angle2 + else: + start_angle = angle2 + end_angle = angle1 + arc = mpatches.Arc( + centre, + width, + height, + theta1=start_angle, + theta2=end_angle, + color=colour, + lw=2.5, + zorder=depth, + ) + self.ax.plot(marker="-o") + self.ax.add_patch(arc) + + if draw_points: + self._plot_text_no_overlap(entity.start, "s", colour) + self._plot_text_no_overlap(entity.midpoint, "m", colour) + self._plot_text_no_overlap(entity.end, "e", colour) + self.ax.set_aspect("equal", adjustable="box") -def draw_regions(regions): - """WILL BE DEPRECATED SOON - USE geometry_drawing.draw_objects() instead. - Draw regions on plot. +def draw_objects_debug(objects): + """Draw regions on plot if not being run in Motor-CAD. Parameters ---------- - regions : Region or list of Region + objects : List of objects entities to draw """ warn( - "draw_regions() WILL BE DEPRECATED SOON - USE geometry_drawing.draw_objects instead", + "draw_objects_debug() WILL BE DEPRECATED SOON - USE geometry_drawing.draw_objects instead", DeprecationWarning, ) - draw_objects(regions) + if not is_running_in_internal_scripting(): + draw_objects(objects) + + +def draw_objects(objects, labels=False, full_geometry=False, depth=0, draw_points=None): + """Draw geometry objects on a plot.""" + stored_coords = [] + fig = plt.figure() + ax = fig.add_subplot(1, 1, 1) + region_drawing = _RegionDrawing(ax, stored_coords) + + if isinstance(objects, GeometryTree): + tree = objects.values() + # List below determines order in which items are drawn, and therefore which are + # drawn above when overlaps occur + region_types = [ + "Stator", + "Rotor", + "Stator Slot Area", + "Stator Slot", + "Rotor Slot Area", + "Split Slot", + "Stator Liner", + "Rotor Liner", + "Wedge", + "Stator Duct", + "Housing", + "Magnetic Housing", + "Stator Impreg", + "Impreg Gap", + "Stator Copper", + "Stator Copper Insulation", + "Stator Divider", + "Stator Slot Spacer", + "Stator slot separator", + "Coil Insulation", + "Stator Air", + "Rotor hub", + "Rotor Air", + "Rotor Air (excluding liner area)", + "Rotor Pocket", + "Pole Spacer", + "Rotor Slot", + "Coil Separator", + "Damper Bar", + "Rotor Wedge", + "Rotor Divider", + "Rotor Copper Insulation", + "Rotor Copper", + "Rotor Impreg", + "Shaft", + "Axle", + "Rotor Duct", + "Magnet", + "Barrier", + "Base Mount", + "Plate Mount", + "Endcap", + "Banding", + "Sleeve", + "Rotor Cover", + "Slot Water Jacket Insulation", + "Slot Water Jacket Wall", + "Slot Water Jacket Duct", + "Slot Water Jacket Duct (no detail)", + "Cowling", + "Cowling Grill", + "Brush", + "Commutator", + "Airgap", + "DXF Import", + "Stator Proximity Loss Slot", + "Adaptive Region", + ] + excluded_regions = [ + "Housing", + "Stator Slot Area", + "Stator Liner", + "Stator Impreg", + "Plate Mount", + "Endcap", + "Impreg Gap", + ] + # excluded_regions = [] + for region_type in excluded_regions: + region_types.remove(region_type) + + for depth, region_type in enumerate(region_types): + if region_type == "Shaft": + pass + for node in tree: + if node.region_type.value == region_type: + if node.key != "root": + if full_geometry: + region_drawing.draw_duplicates(node, node.colour, labels, depth=depth) + else: + if draw_points is not None: + region_drawing.draw_region( + node, node.colour, labels, depth=depth, draw_points=draw_points + ) + else: + region_drawing.draw_region(node, node.colour, labels, depth=depth) + + elif isinstance(objects, list): + if all(isinstance(object, Region) for object in objects): + for region in objects: + if draw_points is not None: + region_drawing.draw_region( + region, region.colour, labels, depth=depth, draw_points=draw_points + ) + else: + region_drawing.draw_region( + region, region.colour, labels, depth=depth, draw_points=True + ) + if all(isinstance(object, Entity) for object in objects): + for entity in objects: + if draw_points is not None: + region_drawing.draw_entity(entity, "black", labels, draw_points=draw_points) + else: + region_drawing.draw_entity(entity, "black", labels, draw_points=True) + + if isinstance(objects, Region) or isinstance(objects, GeometryNode): + region_drawing.draw_region(objects, objects.colour, labels, depth=depth, draw_points=True) + + if isinstance(objects, Entity): + region_drawing.draw_entity(objects, "black", draw_points=True) + plt.show() From 10a87283bdbac25b43db117e978309c0be4fad02 Mon Sep 17 00:00:00 2001 From: Aidan Crandell Date: Wed, 23 Jul 2025 09:52:01 +0100 Subject: [PATCH 15/29] Better comments; updated functions --- src/ansys/motorcad/core/geometry.py | 2 ++ src/ansys/motorcad/core/geometry_drawing.py | 15 ++++++++++++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/ansys/motorcad/core/geometry.py b/src/ansys/motorcad/core/geometry.py index 5284a3640..fb2d6488e 100644 --- a/src/ansys/motorcad/core/geometry.py +++ b/src/ansys/motorcad/core/geometry.py @@ -1597,6 +1597,8 @@ def get_coordinate_distance(self, coordinate): normal = Line(Coordinate(0, 0), defining_point) normal.translate(coordinate.x, coordinate.y) nearest_point = self.get_line_intersection(normal) + if nearest_point is None: + return None return sqrt((coordinate.x - nearest_point.x) ** 2 + (coordinate.y - nearest_point.y) ** 2) diff --git a/src/ansys/motorcad/core/geometry_drawing.py b/src/ansys/motorcad/core/geometry_drawing.py index 9833dead7..69e54ab53 100644 --- a/src/ansys/motorcad/core/geometry_drawing.py +++ b/src/ansys/motorcad/core/geometry_drawing.py @@ -85,7 +85,7 @@ def _find_coord_no_overlap(self, entity_coord, tried_coords, modifier): break return result - def _plot_text_no_overlap(self, point, text, colour): + def _plot_text_no_overlap(self, point, text, colour, depth=0): # Reset params for recursive function tried_coords = [] modifier = 0 @@ -108,6 +108,7 @@ def _plot_text_no_overlap(self, point, text, colour): ha="right", arrowprops=dict(arrowstyle="->", shrinkA=0, color=colour, alpha=0.5), color=colour, + zorder=depth + 1, ) def draw_region_old(self, region, colour): @@ -180,8 +181,8 @@ def draw_coordinate(self, coordinate, colour): plt.plot(coordinate.x, coordinate.y, "x", color=colour) def draw_duplicates(self, region: GeometryNode, colour, labels, depth): + """Draw all region duplications.""" duplication_angle = 360 / region.duplications - origin = Coordinate(0, 0) for duplicate_number in range(0, region.duplications): duplicate = deepcopy(region) @@ -189,6 +190,7 @@ def draw_duplicates(self, region: GeometryNode, colour, labels, depth): self.draw_region(duplicate, colour, labels, depth, full_geometry=True) def draw_region(self, region, colour, labels, depth, full_geometry=False, draw_points=False): + # Draw region onto a plot duplication_angle = 360 / region.duplications colour = tuple(channel / 255 for channel in colour) fill_points_x = [] @@ -207,9 +209,14 @@ def draw_region(self, region, colour, labels, depth, full_geometry=False, draw_p ((entity.angle % duplication_angle) < GEOM_TOLERANCE) or ((entity.angle % duplication_angle) - duplication_angle < GEOM_TOLERANCE) ) + # Check start and end of a line are not the same, to avoid unsupported + # operand types in the next line + and entity.start != entity.end and entity.get_coordinate_distance(Coordinate(0, 0)) < GEOM_TOLERANCE and full_geometry ): + if region.points[0].x > 125: + pass self.draw_entity( entity, "black", @@ -230,7 +237,7 @@ def draw_region(self, region, colour, labels, depth, full_geometry=False, draw_p self._plot_text_no_overlap(point, text, "black") if labels: - self._plot_text_no_overlap(region.centroid, region.name, "black") + self._plot_text_no_overlap(region.centroid, region.name, "black", depth=depth) def draw_entity(self, entity, colour, depth=0, draw_points=False): """Draw entity onto plot.""" @@ -379,6 +386,8 @@ def draw_objects(objects, labels=False, full_geometry=False, depth=0, draw_point "Plate Mount", "Endcap", "Impreg Gap", + "Rotor Impreg", + "Stator Duct", ] # excluded_regions = [] for region_type in excluded_regions: From 570ca26e5d32b036caba31485d19889f8f962bc3 Mon Sep 17 00:00:00 2001 From: Aidan Crandell Date: Wed, 23 Jul 2025 14:21:26 +0100 Subject: [PATCH 16/29] New display methods --- .../motorcad/core/methods/geometry_tree.py | 100 ++++++++++++++++-- 1 file changed, 94 insertions(+), 6 deletions(-) diff --git a/src/ansys/motorcad/core/methods/geometry_tree.py b/src/ansys/motorcad/core/methods/geometry_tree.py index 09f03d7a6..b1e8557d1 100644 --- a/src/ansys/motorcad/core/methods/geometry_tree.py +++ b/src/ansys/motorcad/core/methods/geometry_tree.py @@ -34,7 +34,8 @@ def __init__(self, empty=False): Parameters ---------- empty: bool - Return an empty geometry tree, mostly used for the purposes of debugging. + Return an empty geometry tree, mostly used for the purposes of debugging and + internal construction. """ if empty: super().__init__() @@ -47,6 +48,37 @@ def __init__(self, empty=False): pair = [("root", root)] super().__init__(pair) + def __iter__(self): + """Define ordering according to tree structure.""" + well_ordered = [] + + def dive(node=self.start): + well_ordered.append(node) + for child in node.children: + dive(child) + + dive() + return iter(well_ordered) + + def __str__(self): + """Return string representation of the geometry tree.""" + string = "" + starting_depth = list(self.values())[0].depth + + for node in self.values(): + relative_depth = node.depth - starting_depth + string += "│ " * (relative_depth - 1) + if relative_depth == 0: + cap = "" + elif node == node.parent.children[-1]: + cap = "└── " + else: + cap = "├── " + string += cap + string += node.key + string += "\n" + return string + def __eq__(self, other): """Define equality operator. @@ -122,9 +154,28 @@ def to_json(self): def get_node(self, key): """Get a region from the tree (case-insensitive).""" - if key.lower() in self.lowercase_keys: - return self[self.lowercase_keys[key.lower()]] - raise KeyError() + if isinstance(key, str): + if key.lower() in self.lowercase_keys: + return self[self.lowercase_keys[key.lower()]] + raise KeyError() + elif isinstance(key, GeometryNode): + return key + else: + raise TypeError("key must be a string or GeometryNode") + + def get_subtree(self, node): + """Get all GeometryTree consisting of all nodes descended from the supplied one.""" + if node == self["root"]: + return self + subtree = GeometryTree(empty=True) + + def dive(node): + subtree[node.key] = node + for child in node.children: + dive(child) + + dive(self.get_node(node)) + return subtree def _build_tree(self, tree_json, node, parent=None): """Recursively builds tree. @@ -249,7 +300,8 @@ def add_node(self, region, key=None, parent=None, children=None): Children objects or children keys (must be already within tree) """ - region.__class__ = GeometryNode + if not isinstance(region, GeometryNode): + region.__class__ = GeometryNode if children is None: region.children = list() else: @@ -316,7 +368,21 @@ def dive(node): @property def lowercase_keys(self): """Return a dict of lowercase keys and their corresponding real keys.""" - return dict((key.lower(), key) for key in self) + return dict((node.key.lower(), node.key) for node in self) + + @property + def start(self): + """Return the start of the tree.""" + # Find starting point + for node in self.values(): + if node.parent is None: + start = node + else: + try: + self[node.parent.key] + except KeyError: + start = node + return start class GeometryNode(Region): @@ -337,6 +403,14 @@ def __init__(self, region_type=RegionType.adaptive): super().__init__(region_type=region_type) self.children = list() self.parent = None + self.key = None + + def __repr__(self): + """Return string representation of GeometryNode.""" + try: + return self.key + except AttributeError: + return self.name @classmethod def from_json(cls, tree, node_json, parent): @@ -366,6 +440,20 @@ def from_json(cls, tree, node_json, parent): new_region.key = node_json["name_unique"] return new_region + @property + def depth(self): + """Depth of node.""" + depth = 0 + node = self + + while True: + if node.key == "root": + break + depth += 1 + node = node.parent + + return depth + @property def parent(self): """Get or set parent region. From 2d4afbf9f7e1cf472c5b44bfc1301bee6d5686e6 Mon Sep 17 00:00:00 2001 From: Aidan Crandell Date: Thu, 24 Jul 2025 14:20:13 +0100 Subject: [PATCH 17/29] Better drawing algorithm; basic legend, all around improvements --- src/ansys/motorcad/core/geometry_drawing.py | 153 ++++++------------ .../motorcad/core/methods/geometry_tree.py | 2 +- 2 files changed, 53 insertions(+), 102 deletions(-) diff --git a/src/ansys/motorcad/core/geometry_drawing.py b/src/ansys/motorcad/core/geometry_drawing.py index 69e54ab53..203d1713a 100644 --- a/src/ansys/motorcad/core/geometry_drawing.py +++ b/src/ansys/motorcad/core/geometry_drawing.py @@ -44,6 +44,7 @@ class _RegionDrawing: def __init__(self, ax, stored_coords): self.ax = ax self.stored_coords = stored_coords + self.legend_objects = [] def _get_plot_range(self): # plot should be square so get_xlim() == get_ylim() @@ -203,8 +204,13 @@ def draw_region(self, region, colour, labels, depth, full_geometry=False, draw_p point_i = entity.get_coordinate_from_distance(point_0, distance=i * 0.1) fill_points_x.append(point_i.x) fill_points_y.append(point_i.y) + # If geometry is full, lines separating region duplications shouldn't be drawn. + # These are determined by seeing if, for Line entities, the angle + # (modulo duplication angle) matches zero or the duplication angle, and seeing + # if they also pass through the origin. if not ( - isinstance(entity, Line) + full_geometry + and isinstance(entity, Line) and ( ((entity.angle % duplication_angle) < GEOM_TOLERANCE) or ((entity.angle % duplication_angle) - duplication_angle < GEOM_TOLERANCE) @@ -213,17 +219,16 @@ def draw_region(self, region, colour, labels, depth, full_geometry=False, draw_p # operand types in the next line and entity.start != entity.end and entity.get_coordinate_distance(Coordinate(0, 0)) < GEOM_TOLERANCE - and full_geometry ): - if region.points[0].x > 125: - pass self.draw_entity( entity, "black", depth=depth, ) - plt.fill(fill_points_x, fill_points_y, color=colour, zorder=depth) + self.legend_objects.append( + plt.fill(fill_points_x, fill_points_y, color=colour, zorder=depth, label=region.name)[0] + ) self.ax.set_aspect("equal", adjustable="box") if draw_points: @@ -240,15 +245,11 @@ def draw_region(self, region, colour, labels, depth, full_geometry=False, draw_p self._plot_text_no_overlap(region.centroid, region.name, "black", depth=depth) def draw_entity(self, entity, colour, depth=0, draw_points=False): - """Draw entity onto plot.""" - entity_coords = [] - - mid_point = Coordinate( - (entity.end.x + entity.start.x) / 2, (entity.end.y + entity.start.y) / 2 - ) - - entity_coords += [Coordinate(mid_point.x, mid_point.y)] + """Draw entity onto plot. + Entities are drawn with relatively large line width, so that they do not end up covered + by the region's filled interior colours. + """ if isinstance(entity, Line): plt.plot( [entity.start.x, entity.end.x], @@ -308,106 +309,43 @@ def draw_objects_debug(objects): draw_objects(objects) -def draw_objects(objects, labels=False, full_geometry=False, depth=0, draw_points=None): +def draw_objects( + objects, + labels=False, + full_geometry=False, + depth=0, + draw_points=None, + save=None, + dpi=None, + legend=False, +): """Draw geometry objects on a plot.""" + if is_running_in_internal_scripting(): + return stored_coords = [] fig = plt.figure() ax = fig.add_subplot(1, 1, 1) region_drawing = _RegionDrawing(ax, stored_coords) if isinstance(objects, GeometryTree): - tree = objects.values() - # List below determines order in which items are drawn, and therefore which are - # drawn above when overlaps occur - region_types = [ - "Stator", - "Rotor", - "Stator Slot Area", - "Stator Slot", - "Rotor Slot Area", - "Split Slot", - "Stator Liner", - "Rotor Liner", - "Wedge", - "Stator Duct", - "Housing", - "Magnetic Housing", - "Stator Impreg", - "Impreg Gap", - "Stator Copper", - "Stator Copper Insulation", - "Stator Divider", - "Stator Slot Spacer", - "Stator slot separator", - "Coil Insulation", - "Stator Air", - "Rotor hub", - "Rotor Air", - "Rotor Air (excluding liner area)", - "Rotor Pocket", - "Pole Spacer", - "Rotor Slot", - "Coil Separator", - "Damper Bar", - "Rotor Wedge", - "Rotor Divider", - "Rotor Copper Insulation", - "Rotor Copper", - "Rotor Impreg", - "Shaft", - "Axle", - "Rotor Duct", - "Magnet", - "Barrier", - "Base Mount", - "Plate Mount", - "Endcap", - "Banding", - "Sleeve", - "Rotor Cover", - "Slot Water Jacket Insulation", - "Slot Water Jacket Wall", - "Slot Water Jacket Duct", - "Slot Water Jacket Duct (no detail)", - "Cowling", - "Cowling Grill", - "Brush", - "Commutator", - "Airgap", - "DXF Import", - "Stator Proximity Loss Slot", - "Adaptive Region", - ] - excluded_regions = [ - "Housing", - "Stator Slot Area", - "Stator Liner", - "Stator Impreg", - "Plate Mount", - "Endcap", - "Impreg Gap", - "Rotor Impreg", - "Stator Duct", - ] - # excluded_regions = [] - for region_type in excluded_regions: - region_types.remove(region_type) - - for depth, region_type in enumerate(region_types): - if region_type == "Shaft": - pass - for node in tree: - if node.region_type.value == region_type: - if node.key != "root": + # The list below determines which objects (and children) are by default drawn. + + region_types = ["Stator", "Split Slot", "Wedge", "Stator Air", "Rotor", "Shaft"] + for starting_node in objects: + if starting_node.region_type.value in region_types: + if starting_node.key != "root": + subtree = objects.get_subtree(starting_node) + for node in subtree: + # Draw 360 degrees of each region if requested if full_geometry: - region_drawing.draw_duplicates(node, node.colour, labels, depth=depth) + region_drawing.draw_duplicates(node, node.colour, labels, depth=0) else: if draw_points is not None: region_drawing.draw_region( - node, node.colour, labels, depth=depth, draw_points=draw_points + node, node.colour, labels, depth=0, draw_points=draw_points ) else: - region_drawing.draw_region(node, node.colour, labels, depth=depth) + region_drawing.draw_region(node, node.colour, labels, depth=0) elif isinstance(objects, list): if all(isinstance(object, Region) for object in objects): @@ -432,4 +370,17 @@ def draw_objects(objects, labels=False, full_geometry=False, depth=0, draw_point if isinstance(objects, Entity): region_drawing.draw_entity(objects, "black", draw_points=True) - plt.show() + + if legend: + plt.legend( + handles=region_drawing.legend_objects, + fontsize="small", + draggable=True, + loc="center", + bbox_to_anchor=(0.9, 0.5), + ) + + if save is None: + plt.show() + else: + plt.savefig(save, dpi=dpi) diff --git a/src/ansys/motorcad/core/methods/geometry_tree.py b/src/ansys/motorcad/core/methods/geometry_tree.py index b1e8557d1..c236bbbb9 100644 --- a/src/ansys/motorcad/core/methods/geometry_tree.py +++ b/src/ansys/motorcad/core/methods/geometry_tree.py @@ -65,7 +65,7 @@ def __str__(self): string = "" starting_depth = list(self.values())[0].depth - for node in self.values(): + for node in self: relative_depth = node.depth - starting_depth string += "│ " * (relative_depth - 1) if relative_depth == 0: From d111444efef582629c833b8760fffab80f16d8c3 Mon Sep 17 00:00:00 2001 From: Aidan Crandell Date: Fri, 25 Jul 2025 11:50:54 +0100 Subject: [PATCH 18/29] Separating drawing changes --- src/ansys/motorcad/core/geometry_drawing.py | 268 +++++--------------- 1 file changed, 57 insertions(+), 211 deletions(-) diff --git a/src/ansys/motorcad/core/geometry_drawing.py b/src/ansys/motorcad/core/geometry_drawing.py index 203d1713a..ac175d9f7 100644 --- a/src/ansys/motorcad/core/geometry_drawing.py +++ b/src/ansys/motorcad/core/geometry_drawing.py @@ -25,8 +25,7 @@ import warnings from warnings import warn -from ansys.motorcad.core.geometry import GEOM_TOLERANCE, Arc, Coordinate, Entity, Line, Region -from ansys.motorcad.core.methods.geometry_tree import GeometryNode, GeometryTree +from ansys.motorcad.core.geometry import Arc, Coordinate, Entity, Line, Region from ansys.motorcad.core.rpc_client_core import is_running_in_internal_scripting try: @@ -44,7 +43,6 @@ class _RegionDrawing: def __init__(self, ax, stored_coords): self.ax = ax self.stored_coords = stored_coords - self.legend_objects = [] def _get_plot_range(self): # plot should be square so get_xlim() == get_ylim() @@ -86,7 +84,7 @@ def _find_coord_no_overlap(self, entity_coord, tried_coords, modifier): break return result - def _plot_text_no_overlap(self, point, text, colour, depth=0): + def _plot_text_no_overlap(self, point, text, colour): # Reset params for recursive function tried_coords = [] modifier = 0 @@ -109,13 +107,12 @@ def _plot_text_no_overlap(self, point, text, colour, depth=0): ha="right", arrowprops=dict(arrowstyle="->", shrinkA=0, color=colour, alpha=0.5), color=colour, - zorder=depth + 1, ) - def draw_region_old(self, region, colour): + def draw_region(self, region, colour): """Draw a region.""" for entity in region.entities: - self.draw_entity_old(entity, colour) + self.draw_entity(entity, colour) for entity_num, entity in enumerate(region.entities): text = "e{}".format(entity_num) @@ -128,7 +125,11 @@ def draw_region_old(self, region, colour): self._plot_text_no_overlap(region.centroid, region.name, colour) - def draw_entity_old(self, entity, colour, debug=False): + def draw_coordinate(self, coordinate, colour): + """Draw coordinate onto plot.""" + plt.plot(coordinate.x, coordinate.y, "x", color=colour) + + def draw_entity(self, entity, colour): """Draw entity onto plot.""" entity_coords = [] @@ -139,17 +140,7 @@ def draw_entity_old(self, entity, colour, debug=False): entity_coords += [Coordinate(mid_point.x, mid_point.y)] if isinstance(entity, Line): - if debug: - plt.plot( - [entity.start.x, entity.end.x], [entity.start.y, entity.end.y], color=colour - ) - else: - plt.plot( - [entity.start.x, entity.end.x], - [entity.start.y, entity.end.y], - color=colour, - lw=0.6, - ) + plt.plot([entity.start.x, entity.end.x], [entity.start.y, entity.end.y], color=colour) elif isinstance(entity, Arc): width = abs(entity.radius * 2) @@ -164,132 +155,13 @@ def draw_entity_old(self, entity, colour, debug=False): else: start_angle = angle2 end_angle = angle1 - if debug: - arc = mpatches.Arc( - centre, width, height, theta1=start_angle, theta2=end_angle, color=colour - ) - else: - arc = mpatches.Arc( - centre, width, height, theta1=start_angle, theta2=end_angle, color=colour, lw=2 - ) - self.ax.plot(marker="-o") - self.ax.add_patch(arc) - - self.ax.set_aspect("equal", adjustable="box") - - def draw_coordinate(self, coordinate, colour): - """Draw coordinate onto plot.""" - plt.plot(coordinate.x, coordinate.y, "x", color=colour) - - def draw_duplicates(self, region: GeometryNode, colour, labels, depth): - """Draw all region duplications.""" - duplication_angle = 360 / region.duplications - - for duplicate_number in range(0, region.duplications): - duplicate = deepcopy(region) - duplicate.rotate(Coordinate(0, 0), duplication_angle * duplicate_number) - self.draw_region(duplicate, colour, labels, depth, full_geometry=True) - - def draw_region(self, region, colour, labels, depth, full_geometry=False, draw_points=False): - # Draw region onto a plot - duplication_angle = 360 / region.duplications - colour = tuple(channel / 255 for channel in colour) - fill_points_x = [] - fill_points_y = [] - for entity in region.entities: - point_0 = entity.start - fill_points_x.append(point_0.x) - fill_points_y.append(point_0.y) - for i in range(1, int(entity.length / 0.1)): - point_i = entity.get_coordinate_from_distance(point_0, distance=i * 0.1) - fill_points_x.append(point_i.x) - fill_points_y.append(point_i.y) - # If geometry is full, lines separating region duplications shouldn't be drawn. - # These are determined by seeing if, for Line entities, the angle - # (modulo duplication angle) matches zero or the duplication angle, and seeing - # if they also pass through the origin. - if not ( - full_geometry - and isinstance(entity, Line) - and ( - ((entity.angle % duplication_angle) < GEOM_TOLERANCE) - or ((entity.angle % duplication_angle) - duplication_angle < GEOM_TOLERANCE) - ) - # Check start and end of a line are not the same, to avoid unsupported - # operand types in the next line - and entity.start != entity.end - and entity.get_coordinate_distance(Coordinate(0, 0)) < GEOM_TOLERANCE - ): - self.draw_entity( - entity, - "black", - depth=depth, - ) - - self.legend_objects.append( - plt.fill(fill_points_x, fill_points_y, color=colour, zorder=depth, label=region.name)[0] - ) - self.ax.set_aspect("equal", adjustable="box") - - if draw_points: - for entity_num, entity in enumerate(region.entities): - text = "e{}".format(entity_num) - self._plot_text_no_overlap(entity.midpoint, text, "black") - - points = region.entities.points - for point_num, point in enumerate(points): - text = "p{}".format(point_num) - self._plot_text_no_overlap(point, text, "black") - - if labels: - self._plot_text_no_overlap(region.centroid, region.name, "black", depth=depth) - - def draw_entity(self, entity, colour, depth=0, draw_points=False): - """Draw entity onto plot. - - Entities are drawn with relatively large line width, so that they do not end up covered - by the region's filled interior colours. - """ - if isinstance(entity, Line): - plt.plot( - [entity.start.x, entity.end.x], - [entity.start.y, entity.end.y], - color=colour, - lw=2, - zorder=depth, - ) - - elif isinstance(entity, Arc): - width = abs(entity.radius * 2) - height = abs(entity.radius * 2) - centre = entity.centre.x, entity.centre.y - rad1, angle1 = (entity.start - entity.centre).get_polar_coords_deg() - rad2, angle2 = (entity.end - entity.centre).get_polar_coords_deg() - if entity.radius > 0: - start_angle = angle1 - end_angle = angle2 - else: - start_angle = angle2 - end_angle = angle1 arc = mpatches.Arc( - centre, - width, - height, - theta1=start_angle, - theta2=end_angle, - color=colour, - lw=2.5, - zorder=depth, + centre, width, height, theta1=start_angle, theta2=end_angle, color=colour ) self.ax.plot(marker="-o") self.ax.add_patch(arc) - if draw_points: - self._plot_text_no_overlap(entity.start, "s", colour) - self._plot_text_no_overlap(entity.midpoint, "m", colour) - self._plot_text_no_overlap(entity.end, "e", colour) - self.ax.set_aspect("equal", adjustable="box") @@ -301,86 +173,60 @@ def draw_objects_debug(objects): objects : List of objects entities to draw """ - warn( - "draw_objects_debug() WILL BE DEPRECATED SOON - USE geometry_drawing.draw_objects instead", - DeprecationWarning, - ) if not is_running_in_internal_scripting(): draw_objects(objects) -def draw_objects( - objects, - labels=False, - full_geometry=False, - depth=0, - draw_points=None, - save=None, - dpi=None, - legend=False, -): +def draw_objects(objects): """Draw geometry objects on a plot.""" - if is_running_in_internal_scripting(): - return + if not MATPLOTLIB_AVAILABLE: + raise ImportError( + "Failed to draw geometry. Please ensure MatPlotLib and a suitable backend " + "e.g. PyQt5 are installed" + ) + + if not isinstance(objects, list): + # Given a single region not a list - reformat + objects = [objects] + stored_coords = [] + + # Some basic colours + colours = ["red", "blue", "green", "purple"] + entity_no_region_colour = "grey" fig = plt.figure() ax = fig.add_subplot(1, 1, 1) + region_drawing = _RegionDrawing(ax, stored_coords) + for i, object in enumerate(objects): + # loop through colours once end of list reached + while i > len(colours) - 1: + i -= len(colours) + if object is None: + continue + if isinstance(object, Region): + region_drawing.draw_region(object, colours[i]) + elif isinstance(object, Entity): + region_drawing.draw_entity(object, entity_no_region_colour) + elif isinstance(object, Coordinate): + region_drawing.draw_coordinate(object, entity_no_region_colour) + else: + raise TypeError("Object cannot be drawn") + plt.show() - if isinstance(objects, GeometryTree): - # The list below determines which objects (and children) are by default drawn. - - region_types = ["Stator", "Split Slot", "Wedge", "Stator Air", "Rotor", "Shaft"] - for starting_node in objects: - if starting_node.region_type.value in region_types: - if starting_node.key != "root": - subtree = objects.get_subtree(starting_node) - for node in subtree: - # Draw 360 degrees of each region if requested - if full_geometry: - region_drawing.draw_duplicates(node, node.colour, labels, depth=0) - else: - if draw_points is not None: - region_drawing.draw_region( - node, node.colour, labels, depth=0, draw_points=draw_points - ) - else: - region_drawing.draw_region(node, node.colour, labels, depth=0) - - elif isinstance(objects, list): - if all(isinstance(object, Region) for object in objects): - for region in objects: - if draw_points is not None: - region_drawing.draw_region( - region, region.colour, labels, depth=depth, draw_points=draw_points - ) - else: - region_drawing.draw_region( - region, region.colour, labels, depth=depth, draw_points=True - ) - if all(isinstance(object, Entity) for object in objects): - for entity in objects: - if draw_points is not None: - region_drawing.draw_entity(entity, "black", labels, draw_points=draw_points) - else: - region_drawing.draw_entity(entity, "black", labels, draw_points=True) - - if isinstance(objects, Region) or isinstance(objects, GeometryNode): - region_drawing.draw_region(objects, objects.colour, labels, depth=depth, draw_points=True) - - if isinstance(objects, Entity): - region_drawing.draw_entity(objects, "black", draw_points=True) - - if legend: - plt.legend( - handles=region_drawing.legend_objects, - fontsize="small", - draggable=True, - loc="center", - bbox_to_anchor=(0.9, 0.5), - ) - if save is None: - plt.show() - else: - plt.savefig(save, dpi=dpi) +def draw_regions(regions): + """WILL BE DEPRECATED SOON - USE geometry_drawing.draw_objects() instead. + + Draw regions on plot. + + Parameters + ---------- + regions : Region or list of Region + entities to draw + """ + warn( + "draw_regions() WILL BE DEPRECATED SOON - USE geometry_drawing.draw_objects instead", + DeprecationWarning, + ) + draw_objects(regions) From 3e9c350253c99d5fae6986752ea54616948f5926 Mon Sep 17 00:00:00 2001 From: Aidan Crandell Date: Fri, 25 Jul 2025 12:48:25 +0100 Subject: [PATCH 19/29] Restored mc.set_geometry_tree --- .../motorcad/core/methods/adaptive_geometry.py | 4 ++-- src/ansys/motorcad/core/methods/geometry_tree.py | 16 +++++++++------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/ansys/motorcad/core/methods/adaptive_geometry.py b/src/ansys/motorcad/core/methods/adaptive_geometry.py index 10097b097..3cf361f33 100644 --- a/src/ansys/motorcad/core/methods/adaptive_geometry.py +++ b/src/ansys/motorcad/core/methods/adaptive_geometry.py @@ -312,8 +312,8 @@ def get_geometry_tree(self): json = self.connection.send_and_receive(method) return GeometryTree._from_json(json, self) - def set_geometry_tree(self, tree): + def set_geometry_tree(self, tree: GeometryTree): """Do placeholder.""" - params = [tree] + params = [tree._to_json()] method = "SetGeometryTree" return self.connection.send_and_receive(method, params) diff --git a/src/ansys/motorcad/core/methods/geometry_tree.py b/src/ansys/motorcad/core/methods/geometry_tree.py index c236bbbb9..b13ae7621 100644 --- a/src/ansys/motorcad/core/methods/geometry_tree.py +++ b/src/ansys/motorcad/core/methods/geometry_tree.py @@ -144,12 +144,12 @@ def _from_json(cls, tree, mc): def to_json(self): """Return a dict object used to set geometry.""" regions = dict() - for key in self: - if key != "root": - if self[key].region_type == "Magnet": - regions[key] = RegionMagnet._to_json(self[key]) + for node in self: + if node.key != "root": + if node.region_type == "Magnet": + regions[node.key] = RegionMagnet._to_json(node) else: - regions[key] = Region._to_json(self[key]) + regions[node.key] = Region._to_json(node) return {"regions": regions} def get_node(self, key): @@ -196,9 +196,12 @@ def _build_tree(self, tree_json, node, parent=None): for child_name in node["child_names"]: self._build_tree(tree_json, tree_json[child_name], self[node["name_unique"]]) - def fix_geometry(self, node): + def fix_duct_geometry(self, node): """Fix geometry to work with FEA. + Meant primarily for ducts; splitting apart magnet or other regions in this way can result + in errors when solving. + Parameters ---------- node: node representing region to be fixed @@ -206,7 +209,6 @@ def fix_geometry(self, node): Returns ------- None - """ # Splits regions apart, if necessary, to enforce valid geometry node = self.get_node(node) From ed1b3b6bab732b285781494d8a0373af1847ba93 Mon Sep 17 00:00:00 2001 From: Aidan Crandell Date: Fri, 25 Jul 2025 12:50:05 +0100 Subject: [PATCH 20/29] Restored mc.set_geometry_tree --- src/ansys/motorcad/core/methods/geometry_tree.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ansys/motorcad/core/methods/geometry_tree.py b/src/ansys/motorcad/core/methods/geometry_tree.py index b13ae7621..1f31ff06c 100644 --- a/src/ansys/motorcad/core/methods/geometry_tree.py +++ b/src/ansys/motorcad/core/methods/geometry_tree.py @@ -141,7 +141,7 @@ def _from_json(cls, tree, mc): self.mc = mc return self - def to_json(self): + def _to_json(self): """Return a dict object used to set geometry.""" regions = dict() for node in self: From 772fe0c8dedcb9944cd6b82f2504ca510159bdbc Mon Sep 17 00:00:00 2001 From: Aidan Crandell Date: Fri, 25 Jul 2025 14:35:42 +0100 Subject: [PATCH 21/29] Restored testing --- src/ansys/motorcad/core/methods/geometry_tree.py | 12 +++++++++++- tests/test_geometry_tree.py | 11 +++++------ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/ansys/motorcad/core/methods/geometry_tree.py b/src/ansys/motorcad/core/methods/geometry_tree.py index 1f31ff06c..e1c5ee82d 100644 --- a/src/ansys/motorcad/core/methods/geometry_tree.py +++ b/src/ansys/motorcad/core/methods/geometry_tree.py @@ -156,7 +156,8 @@ def get_node(self, key): """Get a region from the tree (case-insensitive).""" if isinstance(key, str): if key.lower() in self.lowercase_keys: - return self[self.lowercase_keys[key.lower()]] + lower_key = key.lower() + return self[self.lowercase_keys[lower_key]] raise KeyError() elif isinstance(key, GeometryNode): return key @@ -257,6 +258,10 @@ def fix_duct_geometry(self, node): new_lower_valid_region.rotate(Coordinate(0, 0), -duplication_angle) new_lower_valid_region.name = new_lower_valid_region.name[0 : name_length + 1] new_lower_valid_region.name += str(i + len(valid_regions_upper) + 1) + # Linked regions currently only guaranteed to work if only one new region is + # formed at top and bottom; will change once regions can be multiply linked. + new_lower_valid_region.linked_region = valid_regions_upper[i] + valid_regions_upper[i].linked_region = new_lower_valid_region self.add_node(new_lower_valid_region, parent=node.parent) self.remove_node(node) return True @@ -280,6 +285,10 @@ def fix_duct_geometry(self, node): new_upper_valid_region.rotate(Coordinate(0, 0), duplication_angle) new_upper_valid_region.name = new_upper_valid_region.name[0 : name_length + 1] new_upper_valid_region.name += str(i + len(valid_regions_lower) + 1) + # Linked regions currently only guaranteed to work if only one new region is + # formed at top and bottom; will change once regions can be multiply linked. + new_upper_valid_region.linked_region = valid_regions_lower[i] + valid_regions_lower[i].linked_region = new_upper_valid_region self.add_node(new_upper_valid_region, parent=node.parent) self.remove_node(node) return True @@ -379,6 +388,7 @@ def start(self): for node in self.values(): if node.parent is None: start = node + break else: try: self[node.parent.key] diff --git a/tests/test_geometry_tree.py b/tests/test_geometry_tree.py index e843ce66d..44df7ba7e 100644 --- a/tests/test_geometry_tree.py +++ b/tests/test_geometry_tree.py @@ -63,7 +63,7 @@ def basic_tree(): def test_get_tree(sample_tree): - node_keys = set(sample_tree) + node_keys = set(node.key for node in sample_tree) # Check each item is listed only once among all children # Check also that each item is the child of something, except root valid = True @@ -78,15 +78,14 @@ def test_get_tree(sample_tree): assert valid -# def test_to_json(sample_tree): -# test_json = sample_tree.to_json() -# assert test_json == sample_json2 - - def test_get_node(sample_tree): assert sample_tree.get_node("rotor") == sample_tree["Rotor"] +def test_tostring(sample_tree): + print(sample_tree) + + def test_add_node(basic_tree): # Tests the basic functionality of adding a node test_tree = deepcopy(basic_tree) From 77e4e1cec7806b08475ab599ff7835c0b895d6f4 Mon Sep 17 00:00:00 2001 From: Aidan Crandell Date: Tue, 29 Jul 2025 09:46:06 +0100 Subject: [PATCH 22/29] More tests for setting geometry and fixing ducts --- .../motorcad/core/methods/geometry_tree.py | 47 +++--- tests/test_geometry_tree.py | 141 ++++++++++++++++-- 2 files changed, 159 insertions(+), 29 deletions(-) diff --git a/src/ansys/motorcad/core/methods/geometry_tree.py b/src/ansys/motorcad/core/methods/geometry_tree.py index e1c5ee82d..434bbe4c6 100644 --- a/src/ansys/motorcad/core/methods/geometry_tree.py +++ b/src/ansys/motorcad/core/methods/geometry_tree.py @@ -100,7 +100,7 @@ def dive(key): return False return True - return dive("root") + return dive(self.start.key) def __ne__(self, other): """Define inequality.""" @@ -166,7 +166,8 @@ def get_node(self, key): def get_subtree(self, node): """Get all GeometryTree consisting of all nodes descended from the supplied one.""" - if node == self["root"]: + node = self.get_node(node) + if node.key == "root": return self subtree = GeometryTree(empty=True) @@ -175,7 +176,7 @@ def dive(node): for child in node.children: dive(child) - dive(self.get_node(node)) + dive(node) return subtree def _build_tree(self, tree_json, node, parent=None): @@ -200,8 +201,9 @@ def _build_tree(self, tree_json, node, parent=None): def fix_duct_geometry(self, node): """Fix geometry to work with FEA. - Meant primarily for ducts; splitting apart magnet or other regions in this way can result - in errors when solving. + Check if a region crosses over its upper or lower duplication angle, and splits it + apart into two regions within the valid sector. Meant primarily for ducts; splitting + apart magnet or other regions in this way can result in errors when solving. Parameters ---------- @@ -209,7 +211,7 @@ def fix_duct_geometry(self, node): Returns ------- - None + Bool: bool representing whether splitting occurred """ # Splits regions apart, if necessary, to enforce valid geometry node = self.get_node(node) @@ -244,7 +246,8 @@ def fix_duct_geometry(self, node): return False # Case where upper slicing necessary else: - for new_valid_region in valid_regions_upper: + for i, new_valid_region in enumerate(valid_regions_upper): + new_valid_region.name += f"_{i + 1}" self.add_node(new_valid_region, parent=node.parent) # now perform the upper check # brush4 used to find the invalid portion just above duplication angle @@ -257,7 +260,7 @@ def fix_duct_geometry(self, node): for i, new_lower_valid_region in enumerate(invalid_regions_upper): new_lower_valid_region.rotate(Coordinate(0, 0), -duplication_angle) new_lower_valid_region.name = new_lower_valid_region.name[0 : name_length + 1] - new_lower_valid_region.name += str(i + len(valid_regions_upper) + 1) + new_lower_valid_region.name += f"_{i + len(valid_regions_upper) + 1}" # Linked regions currently only guaranteed to work if only one new region is # formed at top and bottom; will change once regions can be multiply linked. new_lower_valid_region.linked_region = valid_regions_upper[i] @@ -268,7 +271,8 @@ def fix_duct_geometry(self, node): # Case where lower slicing necessary else: # first, handle the valid regions returned - for new_valid_region in valid_regions_lower: + for i, new_valid_region in enumerate(valid_regions_lower): + new_valid_region.name += f"_{i+1}" self.add_node(new_valid_region, parent=node.parent) # brush2 used to find the invalid portion just below angle 0 @@ -284,7 +288,7 @@ def fix_duct_geometry(self, node): for i, new_upper_valid_region in enumerate(invalid_regions_lower): new_upper_valid_region.rotate(Coordinate(0, 0), duplication_angle) new_upper_valid_region.name = new_upper_valid_region.name[0 : name_length + 1] - new_upper_valid_region.name += str(i + len(valid_regions_lower) + 1) + new_upper_valid_region.name += f"_{i + len(valid_regions_lower) + 1}" # Linked regions currently only guaranteed to work if only one new region is # formed at top and bottom; will change once regions can be multiply linked. new_upper_valid_region.linked_region = valid_regions_lower[i] @@ -307,12 +311,24 @@ def add_node(self, region, key=None, parent=None, children=None): Key to be used for dict parent: GeometryNode or str Parent object or parent key (must be already within tree) - children: list of GeometryNode or str - Children objects or children keys (must be already within tree) + children: list + List of children objects or children keys (must be already within tree) """ if not isinstance(region, GeometryNode): region.__class__ = GeometryNode + + if key is None: + region.key = region.name + else: + region.key = key + + # Make certain any nodes being replaced are properly removed + try: + self.remove_node(region.key) + except KeyError: + pass + if children is None: region.children = list() else: @@ -345,12 +361,7 @@ def add_node(self, region, key=None, parent=None, children=None): else: raise TypeError("Parent must be a GeometryNode or str") - if key is None: - self[region.name] = region - region.key = region.name - else: - self[key] = region - region.key = key + self[region.key] = region def remove_node(self, node): """Remove Node from tree, attach children of removed node to parent.""" diff --git a/tests/test_geometry_tree.py b/tests/test_geometry_tree.py index 44df7ba7e..033d90f59 100644 --- a/tests/test_geometry_tree.py +++ b/tests/test_geometry_tree.py @@ -21,7 +21,7 @@ # SOFTWARE. -from copy import deepcopy +from copy import copy, deepcopy import pytest @@ -31,16 +31,16 @@ @pytest.fixture(scope="session") def sample_tree(mc): - mc.display_screen("Geometry;Radial") + mc.reset_adaptive_geometry() return mc.get_geometry_tree() @pytest.fixture(scope="function") def basic_tree(): """Return a simple GeometryTree for the purposes of testing""" - p1 = Coordinate(0, 0) - p2 = Coordinate(1, 0) - p3 = Coordinate(0, 1) + p1 = Coordinate(25, 2.5) + p2 = Coordinate(25, -2.5) + p3 = Coordinate(29.330127018922, 0) line1 = Line(p1, p2) line2 = Line(p2, p3) line3 = Line(p3, p1) @@ -52,6 +52,7 @@ def basic_tree(): triangle.name = "Triangle" triangle.key = "Triangle" triangle.children = [] + triangle.duplications = 8 tree = GeometryTree() @@ -62,6 +63,54 @@ def basic_tree(): return tree +@pytest.fixture(scope="function") +def split_tree(): + """Return a tree with a duct split across a duplication angle""" + p3 = Coordinate(25, 0) + p2 = Coordinate(25, 2.5) + p1 = Coordinate(29.330127018922, 0) + + line1 = Line(p1, p2) + line2 = Line(p2, p3) + line3 = Line(p3, p1) + triangle1 = Region(region_type=RegionType.airgap) + triangle1.__class__ = GeometryNode + triangle1.entities.append(line1) + triangle1.entities.append(line2) + triangle1.entities.append(line3) + triangle1.name = "Triangle_1" + triangle1.key = "Triangle_1" + triangle1.children = [] + triangle1.duplications = 8 + + p4 = Coordinate(25, -2.5) + line1 = Line(p1, p3) + line2 = Line(p3, p4) + line3 = Line(p4, p1) + triangle2 = Region(region_type=RegionType.airgap) + triangle2.__class__ = GeometryNode + triangle2.entities.append(line1) + triangle2.entities.append(line2) + triangle2.entities.append(line3) + triangle2.name = "Triangle_2" + triangle2.key = "Triangle_2" + triangle2.children = [] + triangle2.duplications = 8 + triangle2.rotate(Coordinate(0, 0), 360 / 8) + + test_tree = GeometryTree() + + triangle1.parent = test_tree["root"] + test_tree["root"].children.append(triangle1) + test_tree["Triangle_1"] = triangle1 + + triangle2.parent = test_tree["root"] + test_tree["root"].children.append(triangle2) + test_tree["Triangle_2"] = triangle2 + + return test_tree + + def test_get_tree(sample_tree): node_keys = set(node.key for node in sample_tree) # Check each item is listed only once among all children @@ -83,24 +132,36 @@ def test_get_node(sample_tree): def test_tostring(sample_tree): - print(sample_tree) + # Test that all nodes are, at the least, present in the string representation + string_repr = str(sample_tree) + for node in sample_tree: + assert node.key in string_repr def test_add_node(basic_tree): # Tests the basic functionality of adding a node - test_tree = deepcopy(basic_tree) new_node = GeometryNode() - new_node.parent = test_tree["root"] + new_node.parent = basic_tree["root"] new_node.name = "node" new_node.key = "node" - test_tree.get_node("root").children.append(new_node) - test_tree["node"] = new_node + basic_tree.get_node("root").children.append(new_node) + basic_tree["node"] = new_node function_tree = deepcopy(basic_tree) new_node2 = GeometryNode() new_node2.name = "node" function_tree.add_node(new_node2, parent=function_tree["root"]) + assert basic_tree == function_tree + + +def test_get_subtree(basic_tree): + # Test fetching subtrees + test_tree = deepcopy(basic_tree) + assert test_tree.get_subtree("root") == basic_tree + + test_tree.pop("root") + function_tree = basic_tree.get_subtree("Triangle") assert test_tree == function_tree @@ -160,7 +221,7 @@ def test_add_node_errors(basic_tree): def test_remove_node(basic_tree): - # Tests the basic functionality of removing a node + # Test the basic functionality of removing a node test_tree = deepcopy(basic_tree) function_tree = deepcopy(basic_tree) @@ -172,6 +233,7 @@ def test_remove_node(basic_tree): def test_equality_1(basic_tree): + # Test trees with different sizes are detected test_tree = deepcopy(basic_tree) test_tree["root"].children.remove(test_tree["Triangle"]) test_tree.pop("Triangle") @@ -179,6 +241,7 @@ def test_equality_1(basic_tree): def test_equality_2(basic_tree): + # Test trees with the same nodes that only differ in structure are detected test_tree1 = deepcopy(basic_tree) new_node1 = GeometryNode() new_node1.name = "node" @@ -193,6 +256,7 @@ def test_equality_2(basic_tree): def test_equality_3(basic_tree): + # Further test that similar but distinct structures are detected test_tree1 = deepcopy(basic_tree) new_node1 = GeometryNode() new_node1.name = "node1" @@ -213,6 +277,7 @@ def test_equality_3(basic_tree): def test_equality_4(basic_tree): + # Test that trees with the same structure and names, but different geometries are detected test_tree = deepcopy(basic_tree) test_tree["Triangle"].entities.append(Line(Coordinate(0, 0), Coordinate(-1, 0))) assert test_tree != basic_tree @@ -269,3 +334,57 @@ def test_get_children(basic_tree): assert basic_tree["root"].child_names == ["Triangle"] assert basic_tree["root"].child_keys == ["Triangle"] + + +def test_fix_region1(split_tree, basic_tree, mc): + # Test that a region is correctly fixed when it crosses the lower boundary + + basic_tree.mc = mc + basic_tree.fix_duct_geometry("Triangle") + + assert split_tree == basic_tree + + +def test_fix_region2(basic_tree, split_tree, mc): + # Test that a region is correctly fixed when it crosses the upper boundary + + basic_tree.mc = mc + + basic_tree["Triangle"].rotate(Coordinate(0, 0), 45) + basic_tree.fix_duct_geometry("Triangle") + + # Labeling is slightly different, so the split tree must be updated + node1 = copy(split_tree["Triangle_1"]) + node2 = copy(split_tree["Triangle_2"]) + + node1.name = "Triangle_2" + node1.key = "Triangle_2" + split_tree.add_node(node1) + + node2.name = "Triangle_1" + node2.key = "Triangle_1" + split_tree.add_node(node2) + + assert split_tree == basic_tree + + +def test_fix_region3(basic_tree, mc): + # Test that a region is unaffected when already valid + + basic_tree["Triangle"].rotate(Coordinate(0, 0), 22.5) + + function_tree = deepcopy(basic_tree) + function_tree.mc = mc + function_tree.fix_duct_geometry("Triangle") + + assert basic_tree == function_tree + + +def test_set_tree(mc, sample_tree): + # Test that tree is correctly set after modification + + sample_tree.remove_node("Stator") + mc.set_geometry_tree(sample_tree) + new_tree = mc.get_geometry_tree() + + assert new_tree == sample_tree From a288445db8f8154cb85ac12f1b4491e3466dc8c6 Mon Sep 17 00:00:00 2001 From: Aidan Crandell Date: Tue, 29 Jul 2025 09:53:36 +0100 Subject: [PATCH 23/29] Several docstrings --- src/ansys/motorcad/core/methods/adaptive_geometry.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ansys/motorcad/core/methods/adaptive_geometry.py b/src/ansys/motorcad/core/methods/adaptive_geometry.py index 3cf361f33..7dc97656d 100644 --- a/src/ansys/motorcad/core/methods/adaptive_geometry.py +++ b/src/ansys/motorcad/core/methods/adaptive_geometry.py @@ -307,13 +307,13 @@ def reset_adaptive_geometry(self): return self.connection.send_and_receive(method) def get_geometry_tree(self): - """Do placeholder.""" + """Fetch a GeometryTree object containing all the defining geometry of the loaded motor.""" method = "GetGeometryTree" json = self.connection.send_and_receive(method) return GeometryTree._from_json(json, self) def set_geometry_tree(self, tree: GeometryTree): - """Do placeholder.""" + """Use a GeometryTree object to set the defining geometry of the loaded motor.""" params = [tree._to_json()] method = "SetGeometryTree" return self.connection.send_and_receive(method, params) From 9ab34039e3a59950374d054e129d2826cae55d8b Mon Sep 17 00:00:00 2001 From: Aidan Crandell Date: Tue, 29 Jul 2025 14:14:55 +0100 Subject: [PATCH 24/29] Nodes now have a set _motorcad_instance attribute --- src/ansys/motorcad/core/methods/geometry_tree.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/ansys/motorcad/core/methods/geometry_tree.py b/src/ansys/motorcad/core/methods/geometry_tree.py index 434bbe4c6..b69bb4339 100644 --- a/src/ansys/motorcad/core/methods/geometry_tree.py +++ b/src/ansys/motorcad/core/methods/geometry_tree.py @@ -137,8 +137,8 @@ def _from_json(cls, tree, mc): region["parent_name"] = "root" root["child_names"].append(region["name_unique"]) - self._build_tree(tree_json, root) - self.mc = mc + self._build_tree(tree_json, root, mc) + self._motorcad_instance = mc return self def _to_json(self): @@ -179,7 +179,7 @@ def dive(node): dive(node) return subtree - def _build_tree(self, tree_json, node, parent=None): + def _build_tree(self, tree_json, node, mc, parent=None): """Recursively builds tree. Parameters @@ -191,12 +191,12 @@ def _build_tree(self, tree_json, node, parent=None): parent: None or GeometryNode """ # Convert current node to GeometryNode and add it to tree - self[node["name_unique"]] = GeometryNode.from_json(self, node, parent) + self[node["name_unique"]] = GeometryNode.from_json(node, parent, mc) # Recur for each child. if node["child_names"] != []: for child_name in node["child_names"]: - self._build_tree(tree_json, tree_json[child_name], self[node["name_unique"]]) + self._build_tree(tree_json, tree_json[child_name], mc, self[node["name_unique"]]) def fix_duct_geometry(self, node): """Fix geometry to work with FEA. @@ -436,14 +436,14 @@ def __repr__(self): return self.name @classmethod - def from_json(cls, tree, node_json, parent): + def from_json(cls, node_json, parent, mc): """Create a GeometryNode from JSON data. Parameters ---------- - tree: dict node_json: dict parent: GeometryNode + mc: Motorcad Returns ------- @@ -461,6 +461,8 @@ def from_json(cls, tree, node_json, parent): new_region.children = list() parent.children.append(new_region) new_region.key = node_json["name_unique"] + + new_region._motorcad_instance = mc return new_region @property From 8378227d726624f09ac09e76031cfe388a12b3b9 Mon Sep 17 00:00:00 2001 From: Aidan Crandell Date: Tue, 29 Jul 2025 14:19:56 +0100 Subject: [PATCH 25/29] Nodes now have a set _motorcad_instance attribute --- src/ansys/motorcad/core/methods/geometry_tree.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ansys/motorcad/core/methods/geometry_tree.py b/src/ansys/motorcad/core/methods/geometry_tree.py index b69bb4339..58c40d79a 100644 --- a/src/ansys/motorcad/core/methods/geometry_tree.py +++ b/src/ansys/motorcad/core/methods/geometry_tree.py @@ -360,7 +360,7 @@ def add_node(self, region, key=None, parent=None, children=None): self[parent].children.append(region) else: raise TypeError("Parent must be a GeometryNode or str") - + region._motorcad_instance = self._motorcad_instance self[region.key] = region def remove_node(self, node): From 32edc9a951e63a47ec0d18ba1cf534fc9a56e0d8 Mon Sep 17 00:00:00 2001 From: Aidan Crandell Date: Tue, 29 Jul 2025 14:25:24 +0100 Subject: [PATCH 26/29] Fixed error with motorcad instance left empty for default constructor of GeometryTrees --- src/ansys/motorcad/core/methods/geometry_tree.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/ansys/motorcad/core/methods/geometry_tree.py b/src/ansys/motorcad/core/methods/geometry_tree.py index 58c40d79a..db6c9c55f 100644 --- a/src/ansys/motorcad/core/methods/geometry_tree.py +++ b/src/ansys/motorcad/core/methods/geometry_tree.py @@ -28,7 +28,7 @@ class GeometryTree(dict): """Class used to build geometry trees.""" - def __init__(self, empty=False): + def __init__(self, empty=False, mc=None): """Initialise the geometry tree. Parameters @@ -39,6 +39,7 @@ def __init__(self, empty=False): """ if empty: super().__init__() + else: root = GeometryNode(region_type=RegionType.airgap) root.parent = None @@ -48,6 +49,8 @@ def __init__(self, empty=False): pair = [("root", root)] super().__init__(pair) + self._motorcad_instance = mc + def __iter__(self): """Define ordering according to tree structure.""" well_ordered = [] From b7087e9f5774ffd23d93e7c3c57c19d3c4946300 Mon Sep 17 00:00:00 2001 From: Aidan Crandell Date: Wed, 30 Jul 2025 10:50:17 +0100 Subject: [PATCH 27/29] Added regiontypes --- src/ansys/motorcad/core/geometry.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/ansys/motorcad/core/geometry.py b/src/ansys/motorcad/core/geometry.py index fb2d6488e..4085823f3 100644 --- a/src/ansys/motorcad/core/geometry.py +++ b/src/ansys/motorcad/core/geometry.py @@ -61,6 +61,7 @@ class RegionType(Enum): rotor_pocket = "Rotor Pocket" pole_spacer = "Pole Spacer" rotor_slot = "Rotor Slot" + rotor_bar_end_ring = "Rotor Bar End Ring" coil_separator = "Coil Separator" damper_bar = "Damper Bar" wedge_rotor = "Rotor Wedge" @@ -85,7 +86,9 @@ class RegionType(Enum): slot_wj_duct_no_detail = "Slot Water Jacket Duct (no detail)" cowling = "Cowling" cowling_gril = "Cowling Grill" + cowling_grill_hole = "Cowling Grill Hole" brush = "Brush" + bearings = "Bearings" commutator = "Commutator" airgap = "Airgap" dxf_import = "DXF Import" From 10ae26cdfdc8fd97b4601d072c1420031b368af1 Mon Sep 17 00:00:00 2001 From: Aidan Crandell Date: Wed, 30 Jul 2025 12:24:10 +0100 Subject: [PATCH 28/29] Slightly improved testing --- tests/test_geometry.py | 6 ++++++ tests/test_geometry_tree.py | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/tests/test_geometry.py b/tests/test_geometry.py index 3e242d92e..d5bb4b510 100644 --- a/tests/test_geometry.py +++ b/tests/test_geometry.py @@ -653,6 +653,12 @@ def test_line_length(): assert line.length == sqrt(2) +def test_line_get_coordinate_distance(): + line = geometry.Line(geometry.Coordinate(0, 0), geometry.Coordinate(0, 2)) + point = Coordinate(1, 1) + assert line.get_coordinate_distance(point) == 1 + + def test_arc_get_coordinate_from_fractional_distance(): arc = geometry.Arc( geometry.Coordinate(-1, 0), geometry.Coordinate(1, 0), geometry.Coordinate(0, 0), 1 diff --git a/tests/test_geometry_tree.py b/tests/test_geometry_tree.py index 033d90f59..962329c5b 100644 --- a/tests/test_geometry_tree.py +++ b/tests/test_geometry_tree.py @@ -130,6 +130,11 @@ def test_get_tree(sample_tree): def test_get_node(sample_tree): assert sample_tree.get_node("rotor") == sample_tree["Rotor"] + with pytest.raises(TypeError) as e_info: + sample_tree.get_node(5) + + assert "key must be a string or GeometryNode" in str(e_info.value) + def test_tostring(sample_tree): # Test that all nodes are, at the least, present in the string representation From 890c0b0f0542d4e6785d0940441f21e359dcab2d Mon Sep 17 00:00:00 2001 From: Aidan Crandell Date: Wed, 30 Jul 2025 12:37:18 +0100 Subject: [PATCH 29/29] Added function to get nodes from supplied region type --- src/ansys/motorcad/core/methods/geometry_tree.py | 16 ++++++++++++++++ tests/test_geometry_tree.py | 7 +++++++ 2 files changed, 23 insertions(+) diff --git a/src/ansys/motorcad/core/methods/geometry_tree.py b/src/ansys/motorcad/core/methods/geometry_tree.py index db6c9c55f..37085f20e 100644 --- a/src/ansys/motorcad/core/methods/geometry_tree.py +++ b/src/ansys/motorcad/core/methods/geometry_tree.py @@ -182,6 +182,22 @@ def dive(node): dive(node) return subtree + def get_nodes_from_type(self, node_type): + """Return all nodes in the tree of the supplied region type. + + Parameters + ---------- + node_type: str or RegionType + Region type to be fetched + """ + if isinstance(node_type, RegionType): + node_type = node_type.value + nodes = [] + for node in self: + if node.region_type.value == node_type and node.key != "root": + nodes.append(node) + return nodes + def _build_tree(self, tree_json, node, mc, parent=None): """Recursively builds tree. diff --git a/tests/test_geometry_tree.py b/tests/test_geometry_tree.py index 962329c5b..2b4b1e183 100644 --- a/tests/test_geometry_tree.py +++ b/tests/test_geometry_tree.py @@ -136,6 +136,13 @@ def test_get_node(sample_tree): assert "key must be a string or GeometryNode" in str(e_info.value) +def test_get_region_type(sample_tree): + nodes = sample_tree.get_nodes_from_type("Magnet") + + for node in nodes: + assert node.region_type.value == "Magnet" + + def test_tostring(sample_tree): # Test that all nodes are, at the least, present in the string representation string_repr = str(sample_tree)