diff --git a/.vscode/launch.json b/.vscode/launch.json index 472bd0c56..009dfd414 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -6,7 +6,7 @@ "configurations": [ { "name": "Python: Current File (Integrated Terminal)", - "type": "python", + "type": "debugpy", "request": "launch", "program": "${file}", "console": "integratedTerminal", @@ -17,14 +17,14 @@ }, { "name": "Python: Code gen (GUI Ui only)", - "type": "python", + "type": "debugpy", "request": "launch", "program": "${workspaceFolder}/pyleecan/Generator/run_generate_GUI.py", "console": "integratedTerminal" }, { "name": "Python: Code gen (GUI resources only)", - "type": "python", + "type": "debugpy", "request": "launch", "program": "${workspaceFolder}/pyleecan/Generator/run_generate_GUI_resources.py", "console": "integratedTerminal", @@ -34,7 +34,7 @@ }, { "name": "Python: Code gen (Classes from csv)", - "type": "python", + "type": "debugpy", "request": "launch", "program": "${workspaceFolder}/pyleecan/Generator/run_generate_classes.py", "console": "integratedTerminal", @@ -45,7 +45,7 @@ }, { "name": "Python: Run GUI", - "type": "python", + "type": "debugpy", "request": "launch", "program": "${workspaceFolder}/pyleecan/run_GUI.py", "console": "integratedTerminal", @@ -56,7 +56,7 @@ }, { "name": "Python: Run class gen GUI", - "type": "python", + "type": "debugpy", "request": "launch", "program": "${workspaceFolder}/pyleecan/Generator/run_class_generator_GUI.py", "console": "integratedTerminal", @@ -67,7 +67,7 @@ }, { "name": "Python: Run script", - "type": "python", + "type": "debugpy", "request": "launch", "program": "${workspaceFolder}/script.py", "console": "integratedTerminal", @@ -78,7 +78,7 @@ }, { "name": "Python: Current File (External Terminal)", - "type": "python", + "type": "debugpy", "request": "launch", "program": "${file}", "console": "externalTerminal" diff --git a/Tests/Methods/Machine/test_bore_and_notch.py b/Tests/Methods/Machine/test_bore_and_notch.py new file mode 100644 index 000000000..3ddcfe58c --- /dev/null +++ b/Tests/Methods/Machine/test_bore_and_notch.py @@ -0,0 +1,145 @@ +from os.path import join + +import matplotlib.pyplot as plt +import pytest +from numpy import pi +from pyleecan.Classes.NotchEvenDist import NotchEvenDist +from pyleecan.Classes.BoreSinePole import BoreSinePole +from pyleecan.Classes.BoreFlower import BoreFlower +from pyleecan.Classes.SlotCirc import SlotCirc +from pyleecan.Classes.SlotW26 import SlotW26 +from pyleecan.definitions import DATA_DIR +from pyleecan.Functions.load import load + +mm = 1e-3 + + +@pytest.mark.IPMSM +@pytest.mark.SCIM +def test_bore_and_notch_merge_type_0(is_show_fig=False): + """Validation of bore shape and notches""" + + # Load machines + Toyota_Prius = load(join(DATA_DIR, "Machine", "Toyota_Prius.json")) + + # Add notches to Toyota_Prius + p = Toyota_Prius.get_pole_pair_number() + + Nq = SlotCirc(Zs=2 * p, W0=5 * mm, H0=2 * mm) + NCirc1 = SlotCirc(Zs=2 * p, W0=8 * mm, H0=2 * mm) + + a0 = 0.2 + + Toyota_Prius.rotor.notch = [ + NotchEvenDist(alpha=0, notch_shape=Nq), # q-axis notch -> test sym. cut + NotchEvenDist(alpha=0.5 * pi / p + a0, notch_shape=NCirc1), # wide notch + NotchEvenDist(alpha=0.5 * pi / p - a0, notch_shape=NCirc1), # wide notch + ] + delta_d = Toyota_Prius.stator.Rint - Toyota_Prius.rotor.Rext + Toyota_Prius.rotor.bore = BoreSinePole( + N=8, delta_d=delta_d, delta_q=3 * mm, W0=50 * mm + ) + Toyota_Prius.rotor.bore.type_merge_slot = 0 + + Toyota_Prius.plot(sym=8, is_show_fig=is_show_fig) + Toyota_Prius.plot(is_show_fig=is_show_fig) + + return Toyota_Prius + + +@pytest.mark.IPMSM +@pytest.mark.SCIM +def test_bore_and_notch_merge_type_1(is_show_fig=False): + """Validation of rotor and stator notches""" + + # Load machines + Toyota_Prius = load(join(DATA_DIR, "Machine", "Toyota_Prius.json")) + Audi_eTron = load(join(DATA_DIR, "Machine", "AUDI_eTron.json")) + + # Add notches to Toyota_Prius + p = Toyota_Prius.get_pole_pair_number() + + Nq = SlotW26(Zs=2 * p, W0=1 * mm, H0=4 * mm, H1=0, R1=3 * mm, R2=3 * mm) + NCirc1 = SlotCirc(Zs=2 * p, W0=8 * mm, H0=2 * mm) + NCirc2 = SlotCirc(Zs=2 * p, W0=3 * mm, H0=1 * mm) + NSlotW26 = SlotW26(Zs=2 * p, W0=1 * mm, H0=2 * mm, H1=0, R1=3 * mm, R2=3 * mm) + + a0 = 0.2 + a1 = 0.33 + + Toyota_Prius.rotor.notch = [ + NotchEvenDist(alpha=0, notch_shape=Nq), # q-axis notch -> test sym. cut + NotchEvenDist(alpha=0.5 * pi / p + a0, notch_shape=NCirc1), # wide notch + NotchEvenDist(alpha=0.5 * pi / p - a0, notch_shape=NCirc1), # wide notch + NotchEvenDist( + alpha=0.5 * pi / p + a1, notch_shape=NCirc2 + ), # cut out completely + NotchEvenDist(alpha=0.5 * pi / p, notch_shape=NSlotW26), # small notch + ] + delta_d = Toyota_Prius.stator.Rint - Toyota_Prius.rotor.Rext + Toyota_Prius.rotor.bore = BoreSinePole( + N=8, delta_d=delta_d, delta_q=5 * mm, W0=50 * mm + ) + + Toyota_Prius.plot(sym=8, is_show_fig=is_show_fig) + Toyota_Prius.plot(is_show_fig=is_show_fig) + + # Add notches to Audi_eTron + NBs = SlotCirc(Zs=16, W0=0.001, H0=0.0005) + NBr = SlotCirc(Zs=29, W0=0.001, H0=0.0005) + + Audi_eTron.stator.notch = [NotchEvenDist(alpha=0, notch_shape=NBs)] + Audi_eTron.rotor.notch = [NotchEvenDist(alpha=0, notch_shape=NBr)] + Audi_eTron.stator.slot.H0 = 4 * mm + Audi_eTron.stator.bore = BoreFlower(N=4, Rarc=Audi_eTron.stator.Rint + 10 * mm) + + Audi_eTron.plot(sym=2, is_show_fig=is_show_fig) + Audi_eTron.plot(is_show_fig=is_show_fig) + + return Toyota_Prius, Audi_eTron + + +@pytest.mark.IPMSM +@pytest.mark.SCIM +def test_bore_and_notch_merge_type_2(is_show_fig=False): + """Validation of rotor and stator notches""" + + # Load machines + Toyota_Prius = load(join(DATA_DIR, "Machine", "Toyota_Prius.json")) + + # Add notches to Toyota_Prius + p = Toyota_Prius.get_pole_pair_number() + + Nq = SlotW26(Zs=2 * p, W0=1 * mm, H0=4 * mm, H1=0, R1=3 * mm, R2=3 * mm) + NCirc1 = SlotCirc(Zs=2 * p, W0=8 * mm, H0=2 * mm) + NCirc2 = SlotCirc(Zs=2 * p, W0=3 * mm, H0=1 * mm) + NSlotW26 = SlotW26(Zs=2 * p, W0=1 * mm, H0=2 * mm, H1=0, R1=3 * mm, R2=3 * mm) + + a0 = 0.2 + a1 = 0.33 + + Toyota_Prius.rotor.notch = [ + NotchEvenDist(alpha=0, notch_shape=Nq), # q-axis notch -> test sym. cut + NotchEvenDist(alpha=0.5 * pi / p + a0, notch_shape=NCirc1), # wide notch + NotchEvenDist(alpha=0.5 * pi / p - a0, notch_shape=NCirc1), # wide notch + NotchEvenDist(alpha=0.5 * pi / p + a1, notch_shape=NCirc2), # cut out at all + NotchEvenDist(alpha=0.5 * pi / p, notch_shape=NSlotW26), # small notch + ] + delta_d = Toyota_Prius.stator.Rint - Toyota_Prius.rotor.Rext + Toyota_Prius.rotor.bore = BoreSinePole( + N=8, delta_d=delta_d, delta_q=5 * mm, W0=50 * mm + ) + + Toyota_Prius.rotor.bore.type_merge_slot = 2 + + Toyota_Prius.plot(sym=8, is_show_fig=is_show_fig) + Toyota_Prius.plot(is_show_fig=is_show_fig) + + return Toyota_Prius + + +if __name__ == "__main__": + # Toyota_Prius = test_bore_and_notch_merge_type_0(is_show_fig=True) + # Toyota_Prius, Audi_eTron = test_bore_and_notch_merge_type_1(is_show_fig=True) + Toyota_Prius = test_bore_and_notch_merge_type_2(is_show_fig=True) + print("Done") diff --git a/pyleecan/Functions/Geometry/cut_lines_between_angle.py b/pyleecan/Functions/Geometry/cut_lines_between_angle.py index 51d7a4e3f..9b912fe38 100644 --- a/pyleecan/Functions/Geometry/cut_lines_between_angle.py +++ b/pyleecan/Functions/Geometry/cut_lines_between_angle.py @@ -20,11 +20,34 @@ def cut_lines_between_angles(line_list, begin_angle, end_angle): cut_lines : [Line] Cut lines between the two angles """ + # normalize angles according to numpy.angle range, i.e. (-pi, pi] + begin_angle = angle(exp(1j * begin_angle)) + end_angle = angle(exp(1j * end_angle)) + + # rotate list in case first/last line is in between begin and end angle + # first copy list + rotated_list = [line for line in line_list] + # rotate until first line is before begin angle + rotate = True + ii = 0 + while rotate: + ang = angle(rotated_list[0].get_begin()) + if (begin_angle <= end_angle) and (ang < begin_angle or ang > end_angle): + rotate = False + elif ( + end_angle < ang < begin_angle + ): # begin and end angle cross -pi, pi boundary + rotate = False + if rotate: + rotated_list.append(rotated_list.pop(0)) + ii += 1 + if ii >= len(rotated_list): # rotation failed + rotate = False first_cut = list() cut_lines = list() # First cut - for line in line_list: + for line in rotated_list: top_split_list, _ = line.split_line(0, exp(1j * begin_angle)) first_cut.extend(top_split_list) # Second cut @@ -32,8 +55,65 @@ def cut_lines_between_angles(line_list, begin_angle, end_angle): _, bot_split_list = line.split_line(0, exp(1j * end_angle)) cut_lines.extend(bot_split_list) + # plot_cut_line(line_list, begin_angle, end_angle, first_cut, cut_lines) + # Check that lines are in the correct way - if (angle(cut_lines[0].get_begin()) % (2 * pi)) - (begin_angle % (2 * pi)) > 1e-6: - cut_lines = cut_lines[::-1] + if len(cut_lines) > 1: + EPS = 1e-6 + c1 = abs(angle(cut_lines[0].get_begin() * exp(-1j * end_angle))) + c2 = abs(angle(cut_lines[0].get_end() * exp(-1j * end_angle))) + c3 = abs(angle(cut_lines[-1].get_begin() * exp(-1j * begin_angle))) + c4 = abs(angle(cut_lines[-1].get_end() * exp(-1j * begin_angle))) + if (c1 < EPS or c2 < EPS) and (c3 < EPS or c4 < EPS): + cut_lines = cut_lines[::-1] return cut_lines + + +def plot_cut_line(line_list, begin_angle, end_angle, first_cut, cut_lines): + """Plot the original lines and the cut lines + Parameters + ---------- + line_list : [Line] + list of line to cut + begin_angle : float + Begin angle of the cut [rad] + end_angle : float + End angle of the cut [rad] + + """ + import matplotlib.pyplot as plt + + fig = plt.figure() + ax = fig.add_subplot(111) + + r = 0 + for line in line_list: + z1 = line.get_begin() + z2 = line.get_end() + ax.plot([z1.real, z2.real], [z1.imag, z2.imag], color="gray") + r = max(r, abs(z1), abs(z2)) + + # plot cut lines + z1 = r * exp(1j * begin_angle) + z2 = r * exp(1j * end_angle) + + ax.plot([z1.real, -z1.real], [z1.imag, -z1.imag], color="r") + ax.plot([z2.real, -z2.real], [z2.imag, -z2.imag], color="b") + + for line in first_cut: + z1 = line.get_begin() + z2 = line.get_end() + ax.plot([z1.real, z2.real], [z1.imag, z2.imag], color="k") + r = max(r, abs(z1), abs(z2)) + + for line in cut_lines: + z1 = line.get_begin() + z2 = line.get_end() + ax.plot([z1.real, z2.real], [z1.imag, z2.imag], color="k", marker=".") + r = max(r, abs(z1), abs(z2)) + + # axis to equal size + ax.axis("equal") + + plt.show() diff --git a/pyleecan/Methods/Geometry/Segment/is_on_line.py b/pyleecan/Methods/Geometry/Segment/is_on_line.py index 925f0853c..34ba99586 100644 --- a/pyleecan/Methods/Geometry/Segment/is_on_line.py +++ b/pyleecan/Methods/Geometry/Segment/is_on_line.py @@ -2,7 +2,7 @@ def is_on_line(self, Z): - """Check is a point defined by its complex coordinate is on the segment + """Check if a point defined by its complex coordinate is on the segment Parameters ---------- @@ -26,6 +26,7 @@ def is_on_line(self, Z): if abs(Z12.real * Z13.imag - Z12.imag * Z13.real) < 1e-10: K13 = Z12.real * Z13.real + Z12.imag * Z13.imag K12 = Z12.real * Z12.real + Z12.imag * Z12.imag - if K13 >= 0 and K13 <= K12: + eps = 1e-12 + if K13 >= -eps and K13 <= K12 + eps: return True return False diff --git a/pyleecan/Methods/Machine/Bore/merge_slot_connect.py b/pyleecan/Methods/Machine/Bore/merge_slot_connect.py index 61e48c41e..df1d1862b 100644 --- a/pyleecan/Methods/Machine/Bore/merge_slot_connect.py +++ b/pyleecan/Methods/Machine/Bore/merge_slot_connect.py @@ -7,7 +7,7 @@ def merge_slot_connect(self, radius_desc_list, prop_dict, sym): """Merge the Bore shape with notches/slot on the bore/yoke Connect method: Add lines between radius and notch/slot - (To use when the radius shape have circular part matchine the radius + (To use when the radius shape have circular part matching the radius or when radius lines are bellow normal radius) Parameters @@ -61,6 +61,7 @@ def merge_slot_connect(self, radius_desc_list, prop_dict, sym): radius_desc_list[-1]["lines"] = lines # Apply merge strategy on slot/notch + siz = len(radius_desc_list) line_list = list() for ii, desc_dict in enumerate(radius_desc_list): if desc_dict["label"] == "Radius": @@ -72,30 +73,18 @@ def merge_slot_connect(self, radius_desc_list, prop_dict, sym): line.prop_dict.update(prop_dict) line_list.extend(desc_dict["lines"]) else: # Connect and add notch lines + lines = desc_dict["lines"] if len(line_list) > 0: # Connect Radius to slot/notch by Segment (if needed) - if ( - abs(line_list[-1].get_end() - desc_dict["lines"][0].get_begin()) - > 1e-6 - ): - line_list.append( - Segment( - line_list[-1].get_end(), desc_dict["lines"][0].get_begin() - ) - ) - line_list.extend(desc_dict["lines"]) + if abs(line_list[-1].get_end() - lines[0].get_begin()) > 1e-6: + connection = Segment(line_list[-1].get_end(), lines[0].get_begin()) + line_list.append(connection) + line_list.extend(lines) + # Connect slot/notch to next radius - if ( - abs( - radius_desc_list[ii + 1]["lines"][0].get_begin() - - desc_dict["lines"][-1].get_end() - ) - > 1e-6 - ): - line_list.append( - Segment( - desc_dict["lines"][-1].get_end(), - radius_desc_list[ii + 1]["lines"][0].get_begin(), - ) - ) + next_lines = radius_desc_list[(ii + 1) % siz]["lines"] + if abs(next_lines[0].get_begin() - lines[-1].get_end()) > 1e-6: + connection = Segment(lines[-1].get_end(), next_lines[0].get_begin()) + if not (sym != 1 and ii == (siz - 1)): + line_list.append(connection) return line_list diff --git a/pyleecan/Methods/Machine/Bore/merge_slot_intersect.py b/pyleecan/Methods/Machine/Bore/merge_slot_intersect.py index 9649954e2..1f7ee1d48 100644 --- a/pyleecan/Methods/Machine/Bore/merge_slot_intersect.py +++ b/pyleecan/Methods/Machine/Bore/merge_slot_intersect.py @@ -3,6 +3,8 @@ from ....Functions.Geometry.cut_lines_between_angle import cut_lines_between_angles from ....Classes.Segment import Segment +DEBUG = False + def merge_slot_intersect(self, radius_desc_list, prop_dict, sym): """Merge the Bore shape with notches/slot on the bore/yoke @@ -12,7 +14,7 @@ def merge_slot_intersect(self, radius_desc_list, prop_dict, sym): Parameters ---------- radius_desc_list : list - List of dict to describe the bore/yoke radius + List of dict to describe the bore/yoke radius (without bore shape, with notches) prop_dict : dict Property dictionary to apply on the radius lines (not on slot/notch) sym : int @@ -23,135 +25,143 @@ def merge_slot_intersect(self, radius_desc_list, prop_dict, sym): line_list : list List of lines needed to draw the radius """ + # Get all Radius lines (0 to 2*pi), i.e. the bore shape without notches + bore_shape = self.get_bore_line() + + if DEBUG: + _debug_plot(radius_desc_list, bore_shape, title="Original lines") + + # limit begin and end angles + if sym != 1: + if radius_desc_list[0]["label"] == "Radius": + radius_desc_list[0]["begin_angle"] = 0 + if radius_desc_list[-1]["label"] == "Radius": + radius_desc_list[-1]["end_angle"] = 2 * pi / sym + + # search intersection of each notch with bore shape -> cut notch + for ii, desc_dict in enumerate(radius_desc_list): + if desc_dict["label"] != "Radius": + lines = [line for line in desc_dict["lines"]] + # forward search for 1. cut + found = False + while lines and not found: + for bore in bore_shape: + intersect = lines[0].intersect_obj(bore) + if intersect: + lines[0].split_point(intersect[0], is_begin=False) + found = True + break + if not found: + lines.pop(0) + + # backward search for 2nd cut + found = False + while lines and not found: + for bore in bore_shape: + intersect = lines[-1].intersect_obj(bore) + if intersect: + lines[-1].split_point(intersect[0], is_begin=True) + found = True + break + if not found: + lines.pop(-1) + + desc_dict["lines"] = lines - # Get all Radius lines (0 to 2*pi) - radius_lines = self.get_bore_line() + if DEBUG: + _debug_plot(radius_desc_list, bore_shape, title="... after cutting notches") - # Update begin and end angle if Radius (next step already cut lines) - if sym != 1 and radius_desc_list[0]["label"] == "Radius": - radius_desc_list[0]["begin_angle"] = 0 - if sym != 1 and radius_desc_list[-1]["label"] == "Radius": - radius_desc_list[-1]["end_angle"] = 2 * pi / sym + # fix radius_desc_list in case notches are cut out by bore shape completely + # TODO print warning + idx = [ii for ii, desc in enumerate(radius_desc_list) if not desc["lines"]] - # Replace Arc radius from desc by lines from shape + # correct previous and next segments begin and end angles first ... + siz = len(radius_desc_list) + for ii in idx: + if sym != 1 and ii == 0: + radius_desc_list[1]["begin_angle"] = 0 + elif sym != 1 and ii == (siz - 1): + radius_desc_list[-2]["end_angle"] = 2 * pi / sym + else: + previous_segment = radius_desc_list[ii - 1] + next_segment = radius_desc_list[(ii + 1) % siz] + # begin and end of cut out notch on round bore shape + begin = previous_segment["end_angle"] % (2 * pi) + end = next_segment["begin_angle"] % (2 * pi) + if begin <= end: + ang = (begin + end) / 2 + else: # zero crossing of angle + ang = ((begin + end + 2 * pi) / 2) % (2 * pi) + next_segment["begin_angle"] = ang + previous_segment["end_angle"] = ang + + # ... then remove cut out notches + for ii in idx[::-1]: + radius_desc_list.pop(ii) + + # replace round bore segments by actual bore shape + siz = len(radius_desc_list) for ii, desc_dict in enumerate(radius_desc_list): if desc_dict["label"] == "Radius": - desc_dict["lines"] = cut_lines_between_angles( - radius_lines, desc_dict["begin_angle"], desc_dict["end_angle"] - ) - - # # Check that the Radius lines are correct - # for ii, desc_dict in enumerate(radius_desc_list): - # if desc_dict["label"] == "Radius": - # print(ii) - # print( - # str(desc_dict["begin_angle"]) - # + " and " - # + str(angle(desc_dict["lines"][0].get_begin()) % (2 * pi)) - # ) - # print( - # str(desc_dict["end_angle"]) - # + " and " - # + str(angle(desc_dict["lines"][-1].get_end()) % (2 * pi)) - # ) - - # If slot/notch are coliding with sym lines => Cut + previous_segment = radius_desc_list[ii - 1] + if previous_segment["label"] == "Radius": + begin = desc_dict["begin_angle"] + else: + begin = angle(previous_segment["lines"][-1].get_end()) + + next_segment = radius_desc_list[(ii + 1) % siz] + if next_segment["label"] == "Radius": + end = desc_dict["end_angle"] + else: + end = angle(next_segment["lines"][0].get_begin()) + + desc_dict["lines"] = cut_lines_between_angles(bore_shape, begin, end) + + if DEBUG: + _debug_plot(radius_desc_list, bore_shape, title="Merged (before sym. cutting)") + + # If notches are crossing sym lines => Cut if sym != 1: + begin = 0 + end = 2 * pi / sym # Cut first desc (if needed) if radius_desc_list[0]["begin_angle"] < 0: - lines = list() - for line in radius_desc_list[0]["lines"]: - top_split_list, _ = line.split_line(0, 1) - lines.extend(top_split_list) - radius_desc_list[0]["begin_angle"] = 0 + lines = cut_lines_between_angles(radius_desc_list[0]["lines"], begin, end) + radius_desc_list[0]["begin_angle"] = begin radius_desc_list[0]["lines"] = lines # Cut last desc (if needed) if radius_desc_list[-1]["end_angle"] > 2 * pi / sym: - lines = list() - for line in radius_desc_list[-1]["lines"]: - _, bot_split_list = line.split_line(0, exp(1j * 2 * pi / sym)) - lines.extend(bot_split_list) - radius_desc_list[-1]["end_angle"] = 2 * pi / sym + lines = cut_lines_between_angles(radius_desc_list[-1]["lines"], begin, end) + radius_desc_list[-1]["end_angle"] = end radius_desc_list[-1]["lines"] = lines - # Apply merge strategy on slot/notch - line_list = list() - for ii, desc_dict in enumerate(radius_desc_list): - if desc_dict["label"] == "Radius": - # Add prop_dict on all the Radius Lines - if prop_dict is not None: - for line in desc_dict["lines"]: - if line.prop_dict is None: - line.prop_dict = dict() - line.prop_dict.update(prop_dict) - line_list.extend(desc_dict["lines"]) - else: # Intersect and add slot/notch lines - # Define First cutting line - # rad_line = radius_desc_list[ii - 1]["lines"][-1] - op = desc_dict["end_angle"] - desc_dict["begin_angle"] - if not (ii == 0 and sym != 1): # No first cut for first notch on Ox - rad_line_list = cut_lines_between_angles( - radius_lines, - desc_dict["begin_angle"] - op / 4, - desc_dict["begin_angle"] + op / 4, - ) - # Find first line to intersect with cutting line - for jj in range(len(desc_dict["lines"])): - inter_list = desc_dict["lines"][jj].intersect_obj( - rad_line_list[-1], is_on_line=True - ) - if len(inter_list) > 0: - break - if jj < len(desc_dict["lines"]) - 1: - # Slot/notch was cut => Replace lines by cut ones - desc_dict["lines"] = desc_dict["lines"][ - jj: - ] # Keep all lines after cut - # Update lines to start/end at cutting point - desc_dict["lines"][0].split_point(inter_list[0], is_begin=False) - if len(line_list) == 0: # Slot/notch on Ox - radius_desc_list[-1]["lines"][-1].split_point( - inter_list[0], is_begin=True - ) - else: - line_list[-1].split_point(inter_list[0], is_begin=True) - else: # The slot is above the shape => Use shape lines - desc_dict["lines"] = cut_lines_between_angles( - radius_lines, desc_dict["begin_angle"], desc_dict["end_angle"] - ) - # Add prop_dict on all the Radius Lines - if prop_dict is not None: - for line in desc_dict["lines"]: - if line.prop_dict is None: - line.prop_dict = dict() - line.prop_dict.update(prop_dict) - line_list.extend(desc_dict["lines"]) - continue # No need to cut the other side - # Second cut - if not (ii == len(radius_desc_list) - 1 and sym != 1): - # No second cut for notch on sym line - rad_line_list = cut_lines_between_angles( - radius_lines, - desc_dict["end_angle"] - op / 4, - desc_dict["end_angle"] + op / 4, - ) - for jj in range(len(desc_dict["lines"])): - inter_list = desc_dict["lines"][-(jj + 1)].intersect_obj( - rad_line_list[0], is_on_line=True - ) - if len(inter_list) > 0: - break - if jj < len(desc_dict["lines"]): - # Slot/notch was cut => Replace lines by cut ones - if jj != 0: # Keep all the lines if last line is cut - desc_dict["lines"] = desc_dict["lines"][ - :-jj - ] # Keep all lines before cut - # Update lines to start/end at cutting point - desc_dict["lines"][-1].split_point(inter_list[0], is_begin=True) - radius_desc_list[ii + 1]["lines"][0].split_point( - inter_list[0], is_begin=False - ) - # Add slot/notch lines to final list - line_list.extend(desc_dict["lines"]) + if DEBUG: + _debug_plot(radius_desc_list, bore_shape, title="Merged completely") + + line_list = [line for desc_list in radius_desc_list for line in desc_list["lines"]] + + # Apply properties + if prop_dict is not None: + for line in line_list: + if line.prop_dict is None: + line.prop_dict = dict() + line.prop_dict.update(prop_dict) + return line_list + + +def _debug_plot(radius_desc_list, bore_shape=list(), title=""): + """Helper function for debugging""" + kwargs = dict(color="b", linewidth="1", linestyle="-") + fig, ax = None, None + for desc in radius_desc_list: + for line in desc["lines"]: + fig, ax = line.plot(fig=fig, ax=ax, **kwargs) + + kwargs = dict(color="gray", linewidth="1", linestyle="-.") + for line in bore_shape: + fig, ax = line.plot(fig=fig, ax=ax, **kwargs) + + ax.axis("equal") + ax.set(title=title) + fig.show() diff --git a/pyleecan/Methods/Machine/Bore/merge_slot_translate.py b/pyleecan/Methods/Machine/Bore/merge_slot_translate.py index 27ac97fe3..8d203070d 100644 --- a/pyleecan/Methods/Machine/Bore/merge_slot_translate.py +++ b/pyleecan/Methods/Machine/Bore/merge_slot_translate.py @@ -1,3 +1,11 @@ +from numpy import pi, exp, angle + +from ....Functions.Geometry.cut_lines_between_angle import cut_lines_between_angles +from ....Classes.Segment import Segment + +DEBUG = False + + def merge_slot_translate(self, radius_desc_list, prop_dict, sym): """Merge the Bore shape with notches/slot on the bore/yoke Translate method: Translate lines of notch/slot to match the radius of the shape @@ -6,7 +14,7 @@ def merge_slot_translate(self, radius_desc_list, prop_dict, sym): Parameters ---------- radius_desc_list : list - List of dict to describe the bore/yoke radius + List of dict to describe the bore/yoke radius (without bore shape, with notches) prop_dict : dict Property dictionary to apply on the radius lines (not on slot/notch) sym : int @@ -17,5 +25,74 @@ def merge_slot_translate(self, radius_desc_list, prop_dict, sym): line_list : list List of lines needed to draw the radius """ + # Get all Radius lines (0 to 2*pi), i.e. the bore shape without notches + bore_shape = self.get_bore_line() + + Rbo = self.parent.get_Rbo() + + if DEBUG: + _debug_plot(radius_desc_list, bore_shape, title="Original lines") + + # limit begin and end angles + if sym != 1: + if radius_desc_list[0]["label"] == "Radius": + radius_desc_list[0]["begin_angle"] = 0 + if radius_desc_list[-1]["label"] == "Radius": + radius_desc_list[-1]["end_angle"] = 2 * pi / sym + + # search intersection of bore shape with mean opening angle of slots/notches ... + # ... and translate notch to bore shape + for ii, desc_dict in enumerate(radius_desc_list): + if desc_dict["label"] != "Radius": + lines = desc_dict["lines"] + begin = desc_dict["begin_angle"] % (2 * pi) + end = desc_dict["end_angle"] % (2 * pi) + op = (begin + end) / 2 + if begin > end: + op = ((begin + end + 2 * pi) / 2) % (2 * pi) + R = 1 / 2 * abs(lines[0].get_begin() + lines[-1].get_end()) + Z = R * exp(1j * op) + line = Segment(0, 2 * Rbo * exp(1j * op)) # be sure to cross bore shape + found = False + for bore in bore_shape: + intersect = line.intersect_obj(bore) + if intersect: + found = True + break + if found: + dZ = intersect[0] - Z + for line in lines: + line.translate(dZ) + + # close gap between notch lines and round bore shape for next step + line = Segment(lines[0].get_begin() - dZ, lines[0].get_begin()) + lines.insert(0, line) + + line = Segment(lines[-1].get_end(), lines[-1].get_end() - dZ) + lines.append(line) + else: + self.get_logger().warning("Warning: Can't translate notch/slot.") + + if DEBUG: + _debug_plot(radius_desc_list, bore_shape, title="translated lines (w/o gaps)") + + line_list = self.merge_slot_intersect(radius_desc_list, prop_dict, sym) + + return line_list + + +def _debug_plot(radius_desc_list, bore_shape=list(), title=""): + """Helper function for debugging""" + kwargs = dict(color="b", linewidth="1", linestyle="-") + fig, ax = None, None + for desc in radius_desc_list: + for line in desc["lines"]: + fig, ax = line.plot(fig=fig, ax=ax, **kwargs) + + kwargs = dict(color="gray", linewidth="1", linestyle="-.") + for line in bore_shape: + fig, ax = line.plot(fig=fig, ax=ax, **kwargs) - raise Exception("Not Implemented Yet") + ax.axis("equal") + ax.set(title=title) + fig.show() diff --git a/pyleecan/Methods/Machine/Lamination/build_yoke_side_line.py b/pyleecan/Methods/Machine/Lamination/build_yoke_side_line.py index e29bdd1f5..2921e0968 100644 --- a/pyleecan/Methods/Machine/Lamination/build_yoke_side_line.py +++ b/pyleecan/Methods/Machine/Lamination/build_yoke_side_line.py @@ -106,6 +106,13 @@ def merge_line_list(Z1, Z2, label, inter_list): ) ii += 1 line.prop_dict[BOUNDARY_PROP_LAB] = label + "-" + str(ii) + line_list.append( + Segment( + line.get_begin(), + line.get_end(), + prop_dict={BOUNDARY_PROP_LAB: label + "-" + str(ii)}, + ) + ) ii += 1 Zb = line.get_end() # Add last line (or Z1 to Z2 if no intersection)