axial_force_final.py 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. # -*- coding: utf-8 -*-
  2. """
  3. MARS SSSR 轴向磁拉力 — 正式计算
  4. =================================
  5. 第5轮探测确认: AFM 力数据在 3D lumped 力图, 命名沿用径向机惯例, 其 "Fr"
  6. (法向力) 在 AFM 2.5D 展开模型中即轴向力:
  7. Fr_Rotor_OL_Lumped / Fr_Rotor_OC_Lumped (转子, 负载/空载)
  8. Fr_Stator_OL_Lumped / Fr_Stator_OC_Lumped (定子, 反作用)
  9. 节点: 转子 10 (36°步, 首尾重复共11点), 定子 12 (30°步, 共13点); 单位 N。
  10. 两个径向切片 (sec1 r=28.25mm, sec2 r=34.75mm) 分别读, 节点求和+切片求和
  11. 得净轴向力; 按时间步扫描得波形。
  12. 校核: (a) 定子合力 ≈ -转子合力; (b) Σ(Ft×r) ≈ 电磁转矩图;
  13. (c) 解析 F ≈ A/(2μ0)·mean(B²)。
  14. 用法: python axial_force_final.py [--quit]
  15. """
  16. import json
  17. import math
  18. import os
  19. import sys
  20. import time
  21. BASE = os.path.dirname(os.path.abspath(__file__))
  22. MOT_SRC = os.path.join(BASE, "MARS-12S10P_SSSR_D76-C150_V5.0-0819.mot")
  23. OUT_DIR = os.path.join(BASE, "output_motorcad")
  24. MU0 = 4e-7 * math.pi
  25. SEC_RADII_MM = [28.25, 34.75] # AFM_SectionCentreRadius_Array
  26. MAX_TSTEPS = 64
  27. MAX_NODES = 40
  28. def stats(ys):
  29. if not ys:
  30. return None
  31. return {"mean": sum(ys) / len(ys), "min": min(ys), "max": max(ys),
  32. "pk2pk": max(ys) - min(ys), "n": len(ys)}
  33. def read_nodes(mc, graph, sec, tstep):
  34. """读某时间步的全部节点 (x=角度, y=力N); 首尾重复点保留由调用方处理。"""
  35. xs, ys = [], []
  36. for i in range(MAX_NODES):
  37. try:
  38. x, y = mc.get_magnetic_3d_graph_point(graph, sec, i, tstep)
  39. except Exception:
  40. break
  41. xs.append(x)
  42. ys.append(y)
  43. return xs, ys
  44. def net_force_series(mc, graph):
  45. """净力时间序列: 对两切片、去重节点求和; 返回 (series, meta)。"""
  46. series = []
  47. meta = {"sections": {}}
  48. for tstep in range(MAX_TSTEPS):
  49. total = 0.0
  50. got = False
  51. for sec in (1, 2):
  52. xs, ys = read_nodes(mc, graph, sec, tstep)
  53. if not ys:
  54. continue
  55. got = True
  56. # 首尾重复 (0°与360°同一节点) 则去掉末点
  57. n_unique = len(ys) - 1 if (len(xs) > 1 and
  58. abs(xs[-1] - xs[0] - 360.0) < 1e-6) \
  59. else len(ys)
  60. total += sum(ys[:n_unique])
  61. if tstep == 0:
  62. meta["sections"][sec] = {"n_points": len(ys),
  63. "n_unique": n_unique, "x": xs}
  64. if not got:
  65. break
  66. series.append(total)
  67. return series, meta
  68. def torque_from_ft(mc, graph):
  69. """t=0 时刻 Σ(Ft×r) 粗校核 (Nm)。"""
  70. tq = 0.0
  71. for sec, r_mm in zip((1, 2), SEC_RADII_MM):
  72. xs, ys = read_nodes(mc, graph, sec, 0)
  73. if not ys:
  74. return None
  75. n_unique = len(ys) - 1 if (len(xs) > 1 and
  76. abs(xs[-1] - xs[0] - 360.0) < 1e-6) \
  77. else len(ys)
  78. tq += sum(ys[:n_unique]) * (r_mm * 1e-3)
  79. return tq
  80. def read_2d(mc, graph, maxpts=64):
  81. xs, ys = [], []
  82. for i in range(maxpts):
  83. try:
  84. x, y = mc.get_magnetic_graph_point(graph, i)
  85. except Exception:
  86. break
  87. xs.append(x)
  88. ys.append(y)
  89. return xs, ys
  90. def main(argv):
  91. quit_after = "--quit" in argv
  92. os.makedirs(OUT_DIR, exist_ok=True)
  93. ts = time.strftime("%m%d_%H%M%S")
  94. from ansys.motorcad.core import MotorCAD, set_motorcad_exe
  95. # Motor-CAD 2026R1 新装机器可能未注册 MOTORCAD_ACTIVEX (pymotorcad 0.8.x
  96. # 仍依赖它); 此时显式定位 exe, 可用环境变量 MOTORCAD_EXE 覆盖路径。
  97. if not os.environ.get("MOTORCAD_ACTIVEX"):
  98. exe = os.environ.get(
  99. "MOTORCAD_EXE",
  100. r"D:\Program Files\ANSYS Inc\v261\motorcad\MotorCAD.exe")
  101. if os.path.isfile(exe):
  102. set_motorcad_exe(exe)
  103. print("启动 Motor-CAD (前台) ...")
  104. mc = MotorCAD()
  105. # /SCRIPTING 模式在部分机器上窗口创建但不显示 (任务栏有图标点不开),
  106. # 强制可见; 已可见时无副作用 (2026-08-26 同事复现机实测该问题)
  107. try:
  108. mc.set_visible(True)
  109. except Exception as e:
  110. print(" [提示] set_visible 失败 (不影响计算): %s" % e)
  111. results = {"when": ts, "source_mot": os.path.basename(MOT_SRC),
  112. "convention_note": ("AFM 2.5D 展开模型中 Fr(法向)=轴向力; "
  113. "OL=负载(RMS 21A), OC=空载开路")}
  114. try:
  115. mc.load_from_file(MOT_SRC)
  116. out_mot = os.path.join(OUT_DIR, "MARS_SSSR_axialF_%s.mot" % ts)
  117. mc.save_to_file(out_mot)
  118. results["work_mot"] = out_mot
  119. for key, names in [("RMSCurrent_A", ["RMSCurrent"]),
  120. ("ShaftSpeed_rpm", ["ShaftSpeed"]),
  121. ("Airgap_mm", ["Airgap"]),
  122. ("Stator_Lam_Dia_mm", ["Stator_Lam_Dia"]),
  123. ("Stator_Bore_mm", ["Stator_Bore"])]:
  124. try:
  125. results[key] = mc.get_variable(names[0])
  126. except Exception:
  127. results[key] = None
  128. for var in ["ElectromagneticForcesCalc_Load",
  129. "ElectromagneticForcesCalc_OC"]:
  130. mc.set_variable(var, True)
  131. print("求解 (负载点 RMS %sA, OC+OL 力同算) ..." % results["RMSCurrent_A"])
  132. t0 = time.time()
  133. mc.do_magnetic_calculation()
  134. results["solve_seconds"] = time.time() - t0
  135. print(" 耗时 %.1f s" % results["solve_seconds"])
  136. # ---- 净轴向力: 转子/定子 x OL/OC ----
  137. forces = {}
  138. for graph in ["Fr_Rotor_OL_Lumped", "Fr_Stator_OL_Lumped",
  139. "Fr_Rotor_OC_Lumped", "Fr_Stator_OC_Lumped"]:
  140. series, meta = net_force_series(mc, graph)
  141. if series:
  142. forces[graph] = {"series_N": series, "stats": stats(series),
  143. "meta": meta}
  144. print(" %s: %s" % (graph, stats(series)))
  145. else:
  146. forces[graph] = None
  147. print(" [如实] %s 无数据" % graph)
  148. results["axial_forces"] = forces
  149. # ---- 校核 a: 定转子合力反号 ----
  150. checks = {}
  151. for case in ("OL", "OC"):
  152. fr = forces.get("Fr_Rotor_%s_Lumped" % case)
  153. fs = forces.get("Fr_Stator_%s_Lumped" % case)
  154. if fr and fs:
  155. mr, ms = fr["stats"]["mean"], fs["stats"]["mean"]
  156. checks["action_reaction_%s" % case] = {
  157. "rotor_mean_N": mr, "stator_mean_N": ms,
  158. "imbalance_pct": abs(mr + ms) / max(abs(mr), 1e-9) * 100}
  159. # ---- 校核 b: Σ(Ft×r) vs 转矩 ----
  160. tq_ft = torque_from_ft(mc, "Ft_Rotor_OL_Lumped")
  161. _, tq_graph = None, None
  162. txs, tys = read_2d(mc, 17) # id17 = 总转矩 (第4轮已辨认)
  163. tq_graph = stats(tys)["mean"] if tys else None
  164. checks["torque_crosscheck"] = {"sum_Ft_x_r_Nm_t0": tq_ft,
  165. "torque_graph_mean_Nm": tq_graph}
  166. # ---- 校核 c: 解析 F ≈ A/(2μ0)·mean(B²), B 取气隙磁密图 ----
  167. bxs, bys = read_2d(mc, "FluxDensityAirgap")
  168. if bys:
  169. b2 = sum(b * b for b in bys) / len(bys)
  170. d_out = float(results["Stator_Lam_Dia_mm"]) * 1e-3
  171. d_in = float(results["Stator_Bore_mm"]) * 1e-3
  172. area = math.pi / 4.0 * (d_out ** 2 - d_in ** 2)
  173. checks["analytic"] = {"mean_B2_T2": b2, "area_m2": area,
  174. "F_est_N": area / (2 * MU0) * b2}
  175. results["checks"] = checks
  176. print("校核: %s" % json.dumps(checks, ensure_ascii=False, indent=1))
  177. # ---- CSV 波形 ----
  178. csv_path = os.path.join(OUT_DIR, "axial_force_%s.csv" % ts)
  179. with open(csv_path, "w", encoding="utf-8") as f:
  180. f.write("tstep,Fr_Rotor_OL_N,Fr_Stator_OL_N,"
  181. "Fr_Rotor_OC_N,Fr_Stator_OC_N\n")
  182. nmax = max(len(v["series_N"]) if v else 0
  183. for v in forces.values())
  184. for i in range(nmax):
  185. row = [str(i)]
  186. for g in ["Fr_Rotor_OL_Lumped", "Fr_Stator_OL_Lumped",
  187. "Fr_Rotor_OC_Lumped", "Fr_Stator_OC_Lumped"]:
  188. v = forces.get(g)
  189. row.append("%.4f" % v["series_N"][i]
  190. if v and i < len(v["series_N"]) else "")
  191. f.write(",".join(row) + "\n")
  192. results["csv"] = csv_path
  193. res_path = os.path.join(OUT_DIR, "axialforce_final_%s.json" % ts)
  194. with open(res_path, "w", encoding="utf-8") as f:
  195. json.dump(results, f, ensure_ascii=False, indent=2)
  196. print("RESULTS: %s" % res_path)
  197. print("CSV: %s" % csv_path)
  198. # ---- 结论摘要 ----
  199. for case, label in (("OL", "负载(RMS 21A)"), ("OC", "空载")):
  200. v = forces.get("Fr_Rotor_%s_Lumped" % case)
  201. if v:
  202. s = v["stats"]
  203. print("[结论] %s 转子净轴向力: 均值 %.1f N, 纹波峰峰 %.2f N"
  204. % (label, s["mean"], s["pk2pk"]))
  205. return 0
  206. finally:
  207. if quit_after:
  208. try:
  209. mc.quit()
  210. except Exception:
  211. pass
  212. else:
  213. print("[提示] Motor-CAD 保持前台打开供检查。")
  214. if __name__ == "__main__":
  215. sys.exit(main(sys.argv[1:]))