iolist跟list有什么区别?(经典)

本文解析了Erlang中IoList的概念及其实现原理,通过源码分析IoList的组成结构,并对比其与普通列表的区别,强调IoList在提高数据传输效率方面的作用。
看到erlang-china.org上有个帖子在问这个问题
引用
一直不太明白iolist,跟list到底有什么区别?请各位大侠指教。。


我翻看了半天erlang的文档也没写的太明白,所以就看看源码:
erts/emulator/beam/utils.c

Java代码 复制代码  收藏代码
  1. 3015int io_list_len(Eterm obj)   
  2. 3016{   
  3. 3017    Eterm* objp;   
  4. 3018    Sint len = 0;   
  5. 3019    DECLARE_ESTACK(s);   
  6. 3020    goto L_again;   
  7. 3021  
  8. 3022    while (!ESTACK_ISEMPTY(s)) {   
  9. 3023        obj = ESTACK_POP(s);   
  10. 3024    L_again:   
  11. 3025        if (is_list(obj)) {   
  12. 3026        L_iter_list:   
  13. 3027            objp = list_val(obj);   
  14. 3028            /* Head */  
  15. 3029            obj = CAR(objp);   
  16. 3030            if (is_byte(obj)) {   
  17. 3031                len++;   
  18. 3032            } else if (is_binary(obj) && binary_bitsize(obj) == 0) {   
  19. 3033                len += binary_size(obj);   
  20. 3034            } else if (is_list(obj)) {   
  21. 3035                ESTACK_PUSH(s, CDR(objp));   
  22. 3036                goto L_iter_list; /* on head */  
  23. 3037            } else if (is_not_nil(obj)) {   
  24. 3038                goto L_type_error;   
  25. 3039            }   
  26. 3040            /* Tail */  
  27. 3041            obj = CDR(objp);   
  28. 3042            if (is_list(obj))   
  29. 3043                goto L_iter_list; /* on tail */  
  30. 3044            else if (is_binary(obj) && binary_bitsize(obj) == 0) {   
  31. 3045                len += binary_size(obj);   
  32. 3046            } else if (is_not_nil(obj)) {   
  33. 3047                goto L_type_error;   
  34. 3048            }   
  35. 3049        } else if (is_binary(obj) && binary_bitsize(obj) == 0) { /* Tail was binary */  
  36. 3050            len += binary_size(obj);   
  37. 3051        } else if (is_not_nil(obj)) {   
  38. 3052            goto L_type_error;   
  39. 3053        }   
  40. 3054    }   
  41. 3055  
  42. 3056    DESTROY_ESTACK(s);   
  43. 3057    return len;   
  44. 3058  
  45. 3059 L_type_error:   
  46. 3060    DESTROY_ESTACK(s);   
  47. 3061    return -1;   
  48. 3062}  
3015int io_list_len(Eterm obj)
3016{
3017    Eterm* objp;
3018    Sint len = 0;
3019    DECLARE_ESTACK(s);
3020    goto L_again;
3021
3022    while (!ESTACK_ISEMPTY(s)) {
3023        obj = ESTACK_POP(s);
3024    L_again:
3025        if (is_list(obj)) {
3026        L_iter_list:
3027            objp = list_val(obj);
3028            /* Head */
3029            obj = CAR(objp);
3030            if (is_byte(obj)) {
3031                len++;
3032            } else if (is_binary(obj) && binary_bitsize(obj) == 0) {
3033                len += binary_size(obj);
3034            } else if (is_list(obj)) {
3035                ESTACK_PUSH(s, CDR(objp));
3036                goto L_iter_list; /* on head */
3037            } else if (is_not_nil(obj)) {
3038                goto L_type_error;
3039            }
3040            /* Tail */
3041            obj = CDR(objp);
3042            if (is_list(obj))
3043                goto L_iter_list; /* on tail */
3044            else if (is_binary(obj) && binary_bitsize(obj) == 0) {
3045                len += binary_size(obj);
3046            } else if (is_not_nil(obj)) {
3047                goto L_type_error;
3048            }
3049        } else if (is_binary(obj) && binary_bitsize(obj) == 0) { /* Tail was binary */
3050            len += binary_size(obj);
3051        } else if (is_not_nil(obj)) {
3052            goto L_type_error;
3053        }
3054    }
3055
3056    DESTROY_ESTACK(s);
3057    return len;
3058
3059 L_type_error:
3060    DESTROY_ESTACK(s);
3061    return -1;
3062}


从源码可以看出来iolist是这样的定义的:
1. []
2. binary
3. 列表, 每个元素是int(0-255)或者binary或者iolist.
其中binary是指 bitsize % 8 == 0 .
int 是0-255


root@ubuntu:/usr/src/otp# erl
Erlang R13B04 (erts-5.7.5) [source] [smp:2:2] [rq:2] [async-threads:0] [hipe] [kernel-poll:false]

Eshell V5.7.5  (abort with ^G)
2> iolist_size(<<>>).
0
3> iolist_size(<<1:1>>).
** exception error: bad argument
in function  iolist_size/1
called as iolist_size(<<1:1>>)
4> iolist_size(<<1:8>>).
1
5> iolist_size([]).
0
6> iolist_size(<<1,2>>).
2
7> iolist_size([1,2]).
2
8> iolist_size([1,2, <<1,2>>]).
4
9> iolist_size([1,2, <<1,2>>, [2]]).
5
10> iolist_size([1,2, <<1,2>>, [2]]).
5
11> iolist_size([<<1:1>>]).
** exception error: bad argument
in function  iolist_size/1
called as iolist_size([<<1:1>>])
12>


Iolist的作用是用于往port送数据的时候.由于底层的系统调用如writev支持向量写, 就避免了无谓的iolist_to_binary这样的扁平话操作, 避免了内存拷贝,极大的提高了效率.
建议多用.
package com.priusis.report.controller; import com.priusis.api.domain.monitor.TrainIo; import com.priusis.api.domain.monitor.TrainIu; import com.priusis.api.domain.monitor.TrainNet; import com.priusis.core.common.lib.Result; import com.priusis.report.service.IDoorStatusService; import org.springframework.format.annotation.DateTimeFormat; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; import javax.validation.constraints.NotNull; import java.util.Date; import java.util.List; import java.util.Map; /** * @author yangli * @since 2021/07/01 */ @Validated @RestController @RequestMapping(value = "door_status") public class DoorStatusController { @Resource private IDoorStatusService doorStatusService; // @GetMapping(value = "io/list") public Result<List<TrainIo>> ioList( @RequestParam @NotNull Integer trainNo, @RequestParam @NotNull Integer carriageNo, @RequestParam @NotNull Integer doorNo, @RequestParam(value = "hour") @NotNull @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm") Date minute) { return Result.success(doorStatusService.ioList(trainNo, carriageNo, doorNo, minute)); } @GetMapping(value = "net/list") public Result<List<TrainNet>> netList( @RequestParam @NotNull Integer trainNo, @RequestParam @NotNull Integer carriageNo, @RequestParam @NotNull Integer doorNo, @RequestParam(value = "hour") @NotNull @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm") Date minute) { return Result.success(doorStatusService.netList(trainNo, carriageNo, doorNo, minute)); } 帮我解释一下
03-11
C# 后台代码: 1、SoftModels类,SoftModels类代码内容(部分代码)如下 ///开启膜厚检测线程 public void StarThreadMeasure() { if (!FlagThreadMeasureProcess) { lock (_flagAutoLock) { if (!FlagThreadMeasureProcess) { // 终止已有线程(如果存在) if (ThreadMeasureProcess?.IsAlive == true) { ThreadMeasureProcess.Abort(); ThreadMeasureProcess.Join(500); // 等待线程退出 } ThreadMeasureProcess = new Thread(MeasureThreadFunc) { IsBackground = true }; ActionLog?.Invoke("启动按钮按下,开始膜厚检测的流程"); ActionRefreshStatus?.Invoke(PcbInPosition, true); FlagThreadMeasureProcess = true; ThreadMeasureProcess.Start(); } } ; } } ///膜厚检测的流程 public void MeasureThreadFunc() { double AimZ = processParam.ProcessParamStr.adjDistanceZCoor;//测量位置Z坐标 double Blc = processParam.ProcessParamStr.adjDistanceBlc; double AimDistance = processParam.ProcessParamStr.adjDistanceAim; double BackPointZ = processParam.ProcessParamStr.adjDistanceBackPoint;//安全坐标 double refractiveIndex = processParam.ProcessParamStr.refractiveIndex; int index; if (NowSlectedSurface == 0) { index = processParam.PointDataManager.Count(); processParam.CMeasureData.Clear(); } else { index = processParam.PointSDataManager.Count(); processParam.SMeasureData.Clear(); } double PosX = 0; double PosY = 0; double difference = 0; for (int num = 0; num < index; num++) { Thread.Sleep(1000); Log.Information("膜厚测量第" + (num + 1) + "点开始"); ActionLog?.Invoke("测试点膜厚测量运行中"); if (!FlagThreadMeasureProcess) break; //DEto:新增代码 --12.02 ActionPointLocation(PosX, PosY, num, AimZ, Blc, AimDistance, BackPointZ, refractiveIndex, difference); SaveSIT1000MeasureValue(SIT1000.ShowMeasureValue); Log.Information("膜厚测量第" + (num + 1) + "点,结果:" + SIT1000.ShowMeasureValue + ""); } } ///按点位进行运动 private void ActionPointLocation(double PosX, double PosY, int num, double AimZ, double Blc, double AimDistance, double BackPointZ, double refractiveIndex, double difference) { if (NowSlectedSurface == 0) { if (PosX == 0 && PosY == 0) { PosX = processParam.PointDataManager[num].X; PosY = processParam.PointDataManager[num].Y; } } else { if (PosX == 0 && PosY == 0) { PosX = processParam.PointSDataManager[num].X; PosY = processParam.PointSDataManager[num].Y; } } if (!motionManager.XYTablePmove(PosX, PosY)) { Log.Information("膜厚测量-ActionPointLocation()-motionManager.XYTablePmove-2169"); FlagThreadMeasureProcess = false; ThreadExitValue = 1; return; } while (FlagThreadMeasureProcess) { Thread.Sleep(100); if (!motionManager.IfXYTableMoving(out status)) { Log.Information("膜厚测量-ActionPointLocation()-motionManager.IfXYTableMoving-2179"); FlagThreadMeasureProcess = false; ThreadExitValue = 1; return; } if (!status) break; Thread.Sleep(50); } if (!FlagThreadMeasureProcess) { ThreadExitValue = 0; return; } Thread.Sleep(500); //首先将Z轴运行至测量位置坐标 if (!motionManager.Z.MoveAbsTo(AimZ, out _)) { Log.Information("膜厚测量-ActionPointLocation()-motionManager.Z.MoveAbsTo-2197"); FlagThreadMeasureProcess = false; ThreadExitValue = 1; return; } } 2、MotionManager 类,MotionManager 类代码内容如下: public class MotionManager { string motionSettingsFilePath = System.AppDomain.CurrentDomain.BaseDirectory + "Config\\Motion_Settings.xml"; //motion参数设置文件路径 public List<MotionPara> AxisPara = new List<MotionPara>(); //轴参数定义 public IOlist iolist = new IOlist(); public List<int> IOOutListIndex = new List<int>(); public List<int> IOInListIndex = new List<int>(); public List<string> IOOutListDesc = new List<string>(); public List<string> IOInListDesc = new List<string>(); public InterfaceCommon MotionCard; public InterfaceIO IO; public InterfaceAxis X; public InterfaceAxis Y; public InterfaceAxis Z; public InterfaceAxis A; private Thread SystemHomeThread; public int redport; public int yellowport; public int greenport; public bool HomeThreadFlag; public bool ControlFlag; public MotionManager() { IOOutListIndex = Helper.GetAllEnumIntValue(typeof(IOOutList)); //获取每个IO的ID号 IOOutListDesc = Helper.GetAllEnumDescription(typeof(IOOutList)); IOInListIndex = Helper.GetAllEnumIntValue(typeof(IOInList)); //获取每个IO的ID号 IOInListDesc = Helper.GetAllEnumDescription(typeof(IOInList)); //三色灯 DETO:临时更改三色灯为同一状态,后续要根据实际IO口修改 //IO更改 redport = 1;//注意这里为扩展IO与主机IO区别 greenport = 2;//主机IO yellowport = 2;//注意这里为扩展IO与主机IO区别 LoadMotionSettings(); //读取轴运动参数 ControlFlag = false; } ~MotionManager() { SaveMotionSettings(); if (ControlFlag) IO.WriteOut(iolist.IOOutPara[1].Port, false, out string message); CloseMotion(out string info); } public void StopHomeThread() { HomeThreadFlag = false; } public void StartHomeThread() { if (null != SystemHomeThread) { if (SystemHomeThread.ThreadState == ThreadState.Running) return; SystemHomeThread.Abort(); } SystemHomeThread = new Thread(HomeThreadFunc) { IsBackground = true }; HomeThreadFlag = true; SystemHomeThread.Start(); } public void HomeThreadFunc() { if (!Z.HomeAxis()) { Z.StopMotion(out _); HomeThreadFlag = false; return; } bool iErr, status, statusz; do { if (!HomeThreadFlag) { Z.StopMotion(out _); return; } iErr = Z.IfAxisHomeRuning(out statusz); if (!iErr) { Z.StopMotion(out _); HomeThreadFlag = false; return; } } while (statusz); Z.ZeroPosition(out _); if (!X.HomeAxis()) { X.StopMotion(out _); HomeThreadFlag = false; return; } if (!Y.HomeAxis()) { Y.StopMotion(out _); HomeThreadFlag = false; return; } if (!A.HomeAxis()) { A.StopMotion(out _); HomeThreadFlag = false; return; } do { if (!HomeThreadFlag) { X.StopMotion(out _); Y.StopMotion(out _); A.StopMotion(out _); return; } iErr = X.IfAxisHomeRuning(out bool statusx); if (!iErr) { X.StopMotion(out _); Y.StopMotion(out _); A.StopMotion(out _); HomeThreadFlag = false; return; } iErr = Y.IfAxisHomeRuning(out bool statusy); if (!iErr) { X.StopMotion(out _); Y.StopMotion(out _); A.StopMotion(out _); HomeThreadFlag = false; return; } iErr = A.IfAxisHomeRuning(out bool statusa); if (!iErr) { X.StopMotion(out _); Y.StopMotion(out _); A.StopMotion(out _); HomeThreadFlag = false; return; } if (statusx || statusy || statusa) status = true; else status = false; } while (status); X.ZeroPosition(out _); Y.ZeroPosition(out _); A.ZeroPosition(out _); HomeThreadFlag = false; return; } /// <summary> /// 设置三色灯状态 /// </summary> /// <param name="index"></param> public void SetRedYellowGreenLight(int index) { switch (index) { case 0: { IO.WriteOut(redport, true, out string message); IO.WriteOut(yellowport, false, out string message1); IO.WriteOut(greenport, false, out string message2); } break; case 1://退出自动加工流程 { IO.WriteOut(redport, false, out string message); IO.WriteOut(yellowport, true, out string message1); IO.WriteOut(greenport, false, out string message2); } break; case 2://启动自动测量加工流程 { IO.WriteOut(redport, false, out string message); IO.WriteOut(yellowport, false, out string message1); IO.WriteOut(greenport, true, out string message2); } break; default: break; } } /// <summary> /// 设置三色灯状态 /// </summary> /// <param name="operState"></param> public void SetRedYellowGreenLight(OperStateType operState) { switch (operState) { case OperStateType.StandBy://空闲待机 ExtendedIO.Oper_ExtendedIO(1, 1, 0);//红灯灭 IO.WriteOut(greenport, false, out string message);//绿灯灭 //黄灯常亮 ExtendedIO.Oper_ExtendedIO(1, 2, 1);//黄灯亮 break; case OperStateType.RunStatus://运行 ExtendedIO.Oper_ExtendedIO(1, 2, 0);//黄灯灭 ExtendedIO.Oper_ExtendedIO(1, 1, 0);//红灯灭 //绿灯常亮 IO.WriteOut(greenport, true, out string message2); break; case OperStateType.AlarmStatus: ExtendedIO.Oper_ExtendedIO(1, 2, 0);//黄灯灭 IO.WriteOut(greenport, false, out string message3);//绿灯灭 ExtendedIO.Oper_ExtendedIO(1, 1, 1);//红灯亮 break; case OperStateType.ShieldAlarm://屏蔽报警 ExtendedIO.Oper_ExtendedIO(1, 1, 0);//红灯灭 //其他灯状态暂时不变 break; case OperStateType.NormalStatus://正常 ExtendedIO.Oper_ExtendedIO(1, 1, 0);//红灯灭 ExtendedIO.Oper_ExtendedIO(1, 2, 0);//黄灯灭 IO.WriteOut(greenport, false, out string message4);//绿灯灭 break; case OperStateType.StopStatus://停止 break; case OperStateType.OtherStatus://其他 break; } } /// <summary> /// 初始化连接卡 /// </summary> /// <param name="info"></param> /// <returns></returns> public bool Init(out string info) //初始化连接卡 { bool ret = MotionCard.Init(out string message); AssignAllAxis(); bool Newret = ExtendedIO.ExtModuleInitIO(1, 1); if (!ret || !Newret) { info = message; return false; } info = "运动控制器初始化成功"; return true; } public bool SetAllAxisServoOn(bool status, out string info) { if (X != null) { bool ret = X.SetServoOn(status, out string message); if (!ret) { info = message; return false; } } if (Y != null) { bool ret = Y.SetServoOn(status, out string message); if (!ret) { info = message; return false; } } if (Z != null) { bool ret = Z.SetServoOn(status, out string message); if (!ret) { info = message; return false; } } if (A != null) { bool ret = A.SetServoOn(status, out string message); info = message; if (!ret) return false; info = message; return true; } info = ""; return false; } public void CloseMotion(out string info) //关闭motion { if (MotionCard != null) { bool ret = SetAllAxisServoOn(false, out string message); if (!ret) info = message; MotionCard.Close(out message); info = message; } info = "运动控制卡未正常打开,无需关闭"; } public void LoadMotionSettings() //加载读取轴参数 { int AxisCount = 4; string str = XmlSerializer.LoadXML<List<MotionPara>>(motionSettingsFilePath, ref AxisPara); if (str != "") { AxisPara = new List<MotionPara>(); AxisPara.Clear(); for (int i = 0; i < AxisCount; i++) { MotionPara myPara = new MotionPara(); AxisPara.Add(myPara); } } else { if (AxisPara.Count > AxisCount) { for (int i = AxisCount; i < AxisPara.Count; i++) { AxisPara.RemoveAt(i); } } else if (AxisPara.Count < AxisCount) { for (int i = AxisPara.Count; i < AxisCount; i++) { MotionPara myPara = new MotionPara(); AxisPara.Add(myPara); } } } IO = new GTN.GTNIO(); iolist = new IOlist(); iolist.IOInPara.Clear(); iolist.IOOutPara.Clear(); // for (int i = 0; i < 12; i++) for (int i = 0; i < 16; i++) { IOProperty IOpara = new IOProperty(IOInListDesc[i]); IOpara.Port = IOInListIndex[i]; iolist.IOInPara.Add(IOpara); } for (int i = 0; i < 10; i++) { IOProperty IOpara = new IOProperty(IOOutListDesc[i]); IOpara.Port = IOOutListIndex[i]; iolist.IOOutPara.Add(IOpara); } MotionCard = new GTN.GTNCard(); } public void AssignAllAxis() { for (int i = 0; i < 4; i++) { InterfaceAxis axis = new GTN.Axis_GTN((short)(i + 1), AxisPara[i]); switch (i) { case 0: X = axis; break; case 1: Y = axis; break; case 2: Z = axis; break; case 3: A = axis; break; default: break; } } } public void SaveMotionSettings() //保存所有轴参数 { XmlSerializer.SaveXML(motionSettingsFilePath, AxisPara); } public bool XYTablePmove(double PosX, double PosY) { bool ret = Y.MoveAbsTo(PosY, out string message); if (!ret) return false; ret = X.MoveAbsTo(PosX, out message); if (!ret) return false; return true; } public bool IfXYTableMoving(out bool flag) { bool ret = X.Busy(out bool statusx); if (!ret) { flag = false; return false; } ret = Y.Busy(out bool statusy); if (!ret) { flag = false; return false; } if (statusx && statusy) flag = true; else flag = false; return true; } public bool StopXYTableMove() { bool flag = X.StopMotion(out string message); if (!flag) return false; flag = Y.StopMotion(out message); if (!flag) return false; return true; } public bool StopALLTableMoving() { bool flag = X.StopMotion(out string message); if (!flag) return false; flag = Y.StopMotion(out message); if (!flag) return false; flag = Z.StopMotion(out message); if (!flag) return false; flag = A.StopMotion(out message); if (!flag) return false; return true; } } 3、Axis_GTN类,Axis_GTN代码内容如下: class Axis_GTN : InterfaceAxis { public short axisID; MotionPara AxisProfile; public Axis_GTN(short axisid, MotionPara axispara) { axisID = axisid; AxisProfile = axispara; } private bool IsHomed; private bool IsHomeDone; private string HomeError; private Thread HomeThread; public bool ALM(out bool status) { bool ret = GetMotionIO(mc.MC_ALARM, out bool rtnval); status = rtnval; return ret; } private bool GetMotionIO(short ioType, out bool status) { int iErr = mc.GTN_GetDi(1, ioType, out int byteResult); if (iErr != 0) { status = false; return false; } status = Convert.ToBoolean((byteResult & (0x01 << (axisID - 1)))); return true; } private bool GetMotionStatus(int axisID, short axisStatusType, out string info, out bool rtnval) { short iErr = mc.GTN_GetSts(1, (short)axisID, out int byteResult, 1, out _); info = GtnInterFace.GetErrorString(iErr); if (iErr != 0) { Log.Information("GTN Axis_GTN GetMotionStatus()-GTN_GetSts Error: {info}", info + "{iErr}:" + iErr + ""); rtnval = false; return false; } rtnval = Convert.ToBoolean((byteResult & (0x01 << axisStatusType))); return true; } public bool Busy(out bool status) { bool rtn = GetMotionStatus(axisID, (short)GTNAxisStauts.IsBusy, out string info, out bool rtnval); status = rtnval; return rtn; } public MotionPara GetMotionPara() { return AxisProfile; } public bool GetPosition(out double pos) { double scale = AxisProfile.MotorScale; short iErr = mc.GTN_GetEncPos(1, axisID, out double postion, 1, out _); if (iErr == 0) { pos = postion / scale; return true; } pos = 0.0; return false; } public bool HomeAxis() { mc.THomePrm homePrm = new mc.THomePrm() { acc = 10, dec = 10, moveDir = -1, //非正整数,负向回零 mode = 10, //限位回原点 edge = 0, velHigh = (AxisProfile.HomeSpeed * AxisProfile.MotorScale) / 1000, //AxisProfile.HomeSpeed * (10000 / AxisProfile.MotorScale), velLow = AxisProfile.HomeSpeed / 5.0, searchHomeDistance = 100000000, searchIndexDistance = 100000, escapeStep = 100 }; short iErr = mc.GTN_GoHome(1, axisID, ref homePrm); if (iErr != 0) return false; return true; } public bool IfAxisHomeRuning(out bool status) { short iErr = mc.GTN_GetHomeStatus(1, axisID, out mc.THomeStatus homeStatus); if (iErr == 0) { if (homeStatus.run != 0) status = true; else status = false; return true; } status = false; return false; } public string Homing() { if (HomeThread != null && HomeThread.IsAlive) { return "axis is homing"; } HomeThread = new Thread(() => { IsHomed = false; HomeError = ""; HomeError = HomingThreadFunc(); IsHomed = true; if (HomeError != "") IsHomeDone = false; else IsHomeDone = true; }) { IsBackground = false, Name = "GTN Homing" }; HomeThread.Start(); Thread.Sleep(20); return ""; } string HomingThreadFunc() { IsHomeDone = false; IsHomed = false; int iErr; mc.THomePrm homePrm = new mc.THomePrm() { acc = 10, dec = 10, moveDir = -1, //非正整数,负向回零 mode = 10, //限位回原点 edge = 0, velHigh = (AxisProfile.HomeSpeed * AxisProfile.MotorScale) / 1000, //AxisProfile.HomeSpeed * (10000 / AxisProfile.MotorScale), velLow = AxisProfile.HomeSpeed / 5.0, searchHomeDistance = 100000000, searchIndexDistance = 100000, escapeStep = 100 }; iErr = mc.GTN_GoHome(1, axisID, ref homePrm); if (iErr == 0) { mc.THomeStatus homeStatus; do { mc.GTN_GetHomeStatus(1, axisID, out homeStatus); } while (homeStatus.run != 0); iErr = mc.GTN_ZeroPos(1, axisID, 1); if (iErr != 0) return GtnInterFace.GetErrorString(iErr); else return ""; } else return GtnInterFace.GetErrorString(iErr); } public bool MoveAbsTo(double pos, out string info) { int scale = AxisProfile.MotorScale; int pulseSpeed = (Int32)(AxisProfile.RunSpeed * AxisProfile.MotorScale); double acc = (Int32)(AxisProfile.acc_dec * AxisProfile.MotorScale); int pulsePos = (int)(pos * scale); //short iErr = mc.GTN_Stop(1, axisID, 0);//停止运动 //info = GtnInterFace.GetErrorString(iErr); //if (iErr != 0) // return false; // 1. 强制停止轴运动并验证状态 if (!StopMotion(out info)) return false; // 2. 清除轴状态(原代码缺失此步骤) short iErr = mc.GTN_ClrSts(1, axisID, 1); info = GtnInterFace.GetErrorString(iErr); if (iErr != 0) { Log.Information("GTN MoveAbsTo GTN_ClrSts Error: {info}", info + "{iErr}:" + iErr + ""); return false; } iErr = mc.GTN_PrfTrap(1, axisID);//设置为点位运动 info = GtnInterFace.GetErrorString(iErr); if (iErr != 0) { Log.Information("GTN MoveAbsTo GTN_PrfTrap Error: {info}", info + "{iErr}:" + iErr + ""); return false; } iErr = mc.GTN_GetAxisPrfPos(1, axisID, out double curPulsePos, 1, out uint clock); info = GtnInterFace.GetErrorString(iErr); if (iErr != 0) { Log.Information("GTN MoveAbsTo GTN_GetAxisPrfPos Error: {info}", info + "{iErr}:" + iErr + ""); return false; } mc.TTrapPrm trap; trap.acc = acc / 1000000;//单位:pulse/ms2 trap.dec = acc / 1000000;//单位:pulse/ms2 trap.velStart = 0;//起跳速度 trap.smoothTime = 50;//单位ms //iErr = mc.GTN_SetTrapPrm(1, axisID, ref trap);//设置加减速 //info = GtnInterFace.GetErrorString(iErr); //if (iErr != 0) //{ // Log.Information("GTN MoveAbsTo GTN_SetTrapPrm Error: {info}", info + "{iErr}:" + iErr + ""); // return false; //} // 5. 核心参数设置(增加重试机制) for (int i = 0; i < 3; i++) { short trapErr = mc.GTN_SetTrapPrm(1, axisID, ref trap); info = GtnInterFace.GetErrorString(trapErr); if (trapErr == 0) break; Log.Warning($"SetTrapPrm retry {i + 1}: {info}"); Thread.Sleep(50); } iErr = mc.GTN_SetPos(1, axisID, pulsePos);//设置位置 info = GtnInterFace.GetErrorString(iErr); if (iErr != 0) { Log.Information("GTN MoveAbsTo GTN_SetPos Error: {info}", info + "{iErr}:" + iErr + ""); return false; } iErr = mc.GTN_SetVel(1, axisID, pulseSpeed);//设置速度 info = GtnInterFace.GetErrorString(iErr); if (iErr != 0) { Log.Information("GTN MoveAbsTo GTN_SetVel Error: {info}", info + "{iErr}:" + iErr + ""); return false; } // 7. 运动执行(增加状态验证) mc.GTN_SetPos(1, axisID, pulsePos); mc.GTN_SetVel(1, axisID, Convert.ToInt32(AxisProfile.RunSpeed * AxisProfile.MotorScale)); iErr = mc.GTN_Update(1, 1 << (axisID - 1));//启动轴 info = GtnInterFace.GetErrorString(iErr); if (iErr != 0) { Log.Information("GTN MoveAbsTo GTN_Update Error: {info}", info + "{iErr}:" + iErr + ""); return false; } Thread.Sleep(10); return true; } public bool NEL(out bool status) { if (!GetMotionIO(mc.MC_LIMIT_NEGATIVE, out bool value)) { status = false; return false; } status = value; return true; } public bool ORG(out bool status) { if (!GetMotionIO(mc.MC_HOME, out bool value))//原点信号默认是1的,触碰是0 { status = false; return false; } status = !value; return status; } public bool PEL(out bool status) { if (!GetMotionIO(mc.MC_LIMIT_POSITIVE, out bool value)) { status = false; return false; } status = value; return true; } public bool ZeroPosition(out string info) { short iErr = mc.GTN_ZeroPos(1, axisID, 1); info = GtnInterFace.GetErrorString(iErr); if (iErr != 0) return false; return true; } //对指定电机轴进行使能或者断开使能操作 public bool SetServoOn(bool status, out string info) { //如果操作的是Y轴,则需要首先对抱闸进行操作。抱闸的IO端口号为2 short ret; if (axisID == 2) { ret = mc.GTN_SetDoBit(1, mc.MC_GPO, 2, Convert.ToInt16(status)); info = GtnInterFace.GetErrorString(ret); if (ret != 0) return false; } if (status) ret = mc.GTN_AxisOn(1, axisID); else ret = mc.GTN_AxisOff(1, axisID); info = GtnInterFace.GetErrorString(ret); if (ret != 0) return false; return true; } public bool StepMove(double distance, out string message) { int scale = AxisProfile.MotorScale; int speed = (Int32)(AxisProfile.ManualSpeed * AxisProfile.MotorScale); double acc = (double)(AxisProfile.acc_dec * AxisProfile.MotorScale); int distancePulse = (int)(distance * scale); int setPulsePos; bool ret = StopMotion(out string info); message = info; if (!ret) return false; short iErr = mc.GTN_ClrSts(1, axisID, 1); message = GtnInterFace.GetErrorString(iErr); if (iErr != 0) return false; iErr = mc.GTN_PrfTrap(1, axisID);//设置为点位运动 message = GtnInterFace.GetErrorString(iErr); if (iErr != 0) return false; iErr = mc.GTN_GetAxisPrfPos(1, axisID, out double curPulsePos, 1, out uint clock); message = GtnInterFace.GetErrorString(iErr); if (iErr != 0) return false; setPulsePos = (int)curPulsePos + distancePulse; mc.TTrapPrm trap; trap.acc = acc / 1000000;//单位:pulse/ms2 trap.dec = acc / 1000000;//单位:pulse/ms2 trap.velStart = 0;//起跳速度 trap.smoothTime = 50;//单位ms // 设置点位运动参数 iErr = mc.GTN_SetTrapPrm(1, axisID, ref trap);//设置加减速 message = GtnInterFace.GetErrorString(iErr); if (iErr != 0) return false; iErr = mc.GTN_SetPos(1, axisID, setPulsePos);//设置位置 message = GtnInterFace.GetErrorString(iErr); if (iErr != 0) return false; iErr = mc.GTN_SetVel(1, axisID, speed);//设置速度 message = GtnInterFace.GetErrorString(iErr); if (iErr != 0) return false; iErr = mc.GTN_Update(1, 1 << (axisID - 1));//启动轴 message = GtnInterFace.GetErrorString(iErr); if (iErr != 0) return false; return true; } /// <summary> /// jog运动 /// </summary> /// <param name="distance"></param> /// <param name="message"></param> /// <returns></returns> public bool StepJogMovee(double distance, JogMovType movType, out string message) { int scale = AxisProfile.MotorScale; int speed = (Int32)(AxisProfile.ManualSpeed * AxisProfile.MotorScale); double acc = (double)(AxisProfile.acc_dec * AxisProfile.MotorScale);//50000pulse/ms2 int distancePulse = (int)(distance * scale); int setPulsePos; // 1.停止当前运动 bool ret = StopMotion(out string info); message = info; if (!ret) return false; //2.清除轴状态 short iErr = mc.GTN_ClrSts(1, axisID, 1); message = GtnInterFace.GetErrorString(iErr); if (iErr != 0) return false; //3. 设置为Jog运动模式 iErr = mc.GTN_PrfJog(1, axisID); message = GtnInterFace.GetErrorString(iErr); if (iErr != 0) return false; mc.TJogPrm trap; trap.acc = acc / 1000000;//单位:pulse/ms2 trap.dec = acc / 1000000;//单位:pulse/ms2 trap.smooth = 0.5; // 平滑系数 (0~1) // 设置Jog运动参数 iErr = mc.GTN_SetJogPrm(1, axisID, ref trap); message = GtnInterFace.GetErrorString(iErr); if (iErr != 0) return false; switch (movType) { case JogMovType.MeditatePerly: iErr = mc.GTN_SetVel(1, axisID, speed);//设置目标速度 break; case JogMovType.Counterforce: iErr = mc.GTN_SetVel(1, axisID, -speed);//设置目标速度 break; } message = GtnInterFace.GetErrorString(iErr); if (iErr != 0) return false; iErr = mc.GTN_Update(1, 1 << (axisID - 1));//启动轴 message = GtnInterFace.GetErrorString(iErr); if (iErr != 0) return false; return true; } public bool StopMotion(out string info) { int mask = 1 << (axisID - 1); int option = 1 << (axisID - 1);//0表示平滑停止,1表示紧急停止 int iErr = mc.GTN_Stop(1, mask, 0); //mc.GTN_Stop(1, mask, option); info = GtnInterFace.GetErrorString(iErr); if (iErr != 0) { Log.Information("GTN Axis_GTN StopMotion()-GTN_Stop Error: {info}", info + "{iErr}:" + iErr + ""); return false; } return true; } public bool SVN(out bool status) { if (!GetMotionStatus(axisID, (short)GTNAxisStauts.Servo, out string info, out bool val)) { status = false; return false; } status = val; return true; } public string WaitHomeDone() { Stopwatch watch = new Stopwatch(); watch.Start(); while (true) { if (IsHomed) { if (IsHomeDone) return ""; else return "Home Error:" + HomeError; } if (watch.ElapsedMilliseconds > 30000) { return "home timeout"; } Application.DoEvents(); Thread.Sleep(15); } } public bool WaitMoveDone() { bool status; Stopwatch watch = new Stopwatch(); watch.Start(); bool ret = Busy(out status); if (!ret) return false; while (status) { if (watch.ElapsedMilliseconds > 30000) return false; Application.DoEvents(); Thread.Sleep(15); ret = Busy(out status); if (!ret) return false; } return true; } public bool WaitMovePos(double pos) { WaitMoveDone(); Stopwatch watch = new Stopwatch(); watch.Start(); while (true) { if (GetPosition(out double curPos)) { if (Math.Abs(curPos - pos) < 0.005) break; } if (watch.ElapsedMilliseconds > 3000) return false; Thread.Sleep(10); } return true; } } 背景: 1.已知用户启动程序后通过,点击按钮“自动加工”后会在SoftModels类开启执行StarThreadMeasure()方法,开启线程去执行膜厚检测的流程动作 2.已知代码MeasureThreadFunc()方法里面主要功能是: 2.1 提前设置好测试点的X/Y 具体的点位坐标值,并保存到processParam.PointDataManager列表。 例如:提前设置好12个坐标值( 528.655,300.885 397.071,300.885 210.737,336.92659 210.737,414.533 412.96,414.533 609.46,414.533 609.624,500.44 391.353,500.44 218.115,500.44 109.613,500.44 109.613,265.201752 109.613,247.143 ) 2.2 再通过 for (int num = 0; num < index; num++)去控制设备去进行多少次的动作即执行ActionPointLocation()方法里面的代码 3.当执行ActionPointLocation()里面的代码时,需要先执行 if (!motionManager.XYTablePmove(PosX, PosY))处代码,即对X/Y轴去进行点位运动 3.1 再执行 while (FlagThreadMeasureProcess)- if (!motionManager.IfXYTableMoving(out status)) 去执行固高GTN_GetSts函数(读取轴状态) 3.2 当前面操作执行完成后执行代码: if (!motionManager.Z.MoveAbsTo(AimZ, out _))即Z轴按位置进行点位运动,再触发执行GTN_GetSts函数(读取轴状态) 4.重复执行直到 for (int num = 0; num < index; num++)执行完成,即点位测量完成 5.GTN_SetTrapPrm,指令错误返回值: 若返回值为 1: (1) 若当前轴在规划运动,请调用 GTN_Stop 停止运动再调用该指令。 (2) 请检查当前轴是否为 Trap 模式,若不是,请先调用 GTN_PrfTrap 将当前轴设置为 Trap 模式。 其他返回值:请参照指令返回值列表。 问题: 1.实际设备执行运动控制,即执行StarThreadMeasure()-MeasureThreadFunc()-ActionPointLocation()时总是会设备在某一个点位就停止动作 2.当1发生是通过断点及日志添加后,排查发现SetTrapPrm retry {3}: {1},即固高GTN_SetTrapPrm指令错误并返回值为:1 需求: 1.请你做为一名C# 软件工程师,对C#后台代码(SoftModels类、MotionManager类、Axis_GTN类)、背景1-5、对问题1-2进行分析与排查动作时停止动作 (即:固高GTN_SetTrapPrm指令错误)的原因 2..请你做为一名C# 软件工程师,在分析与排查到原因后对问题1-2进行优化与完善并输出完整的解决方案与示例代码
最新发布
12-13
import sys from PyQt5.QtWidgets import QMainWindow, QApplication, QMessageBox from UIpy import Ui_MainWindow # 这里要看你生成的py文件中类名叫什么 from serial.tools import list_ports import pyvisa import configparser from 数据处理 import dataprocess from CreateTMSData import CreateInfoTXT import csv from 自定义线程 import * from queue import Queue import matplotlib.pyplot as plt queue=Queue() #进程数据传输 class Myapp(QMainWindow, Ui_MainWindow): def __init__(self): super().__init__() self.setupUi(self) self.Chip_para = CF.ChipParameter() self.PNA=None self.DC=None self.MCU=None self.config_Path=self.Chip_para.config_Path self.TestN_FilePath=r'Savedata\TestName.cf' self.MCUBusy=False #单片机繁忙标志位 self.PNABusy=False #矢网繁忙标志位 self.running=False #测试进行中标志位 self.countretest=0 #复测计数 self.ThreadArray=[] if os.path.exists(self.config_Path): conf = configparser.ConfigParser() conf.read(self.config_Path, encoding='utf-8') self.comboBox_PNA.addItem(conf.get('仪器地址','PNA_adr')) self.comboBox_DC.addItem(conf.get('仪器地址','DC_adr')) self.comboBox_COM.addItem(conf.get('仪器地址','com')) N=self.readTestN() self.text_Num.setText(str(N)) CreateInfoTXT(self.Chip_para) ######################定义机台测试线程################# self.MultiTestThread=MutliTestThread(self.Chip_para, waittingsotflg=True, queue=queue) self.MultiTestThread.signal.connect(self.callback_finishmultitest) #绑定回传信号事件 #####################sockt测试线程################### self.SocketTestThread=MutliTestThread(self.Chip_para, waittingsotflg=False, queue=queue) self.SocketTestThread.signal.connect(self.finish_runBTN) #重测进程 self.SocketReTestThread = MutliTestThread(self.Chip_para, waittingsotflg=False, rerun=True, queue=queue) self.SocketReTestThread.signal.connect(self.finish_rerunBTN) def Threadplot(self): plt.close('all') # 清除所有图片 for _ in self.Chip_para.TR_Array: fig = queue.get() fig.tight_layout() fig.show() def Event_refreshBTN(self): comlist=list_ports.comports() self.comboBox_COM.clear() if len(comlist)>0: for i in comlist: self.comboBox_COM.addItem(i.name) rm = pyvisa.ResourceManager() port = rm.list_resources() self.comboBox_PNA.clear() self.comboBox_DC.clear() if len(port)>0: for i in port: self.comboBox_PNA.addItem(i) self.comboBox_DC.addItem(i) def Event_startconnect(self): if (not self.PNABusy and not self.MCUBusy): self.PNABusy=True self.MCUBusy=True #####################连接仪器进程##################### connectThread = ConnectBTNThread(self.config_Path) connectThread.signal.connect(self.finishconnect) connectThread.finished.connect(self.on_thread_finished) self.ThreadArray.append(connectThread) connectThread.start() else: self.MessageBoxWarning('单片机或矢网繁忙') def finishconnect(self,IOlist): if self.MCU!=None: #关闭之前的com口 self.MCU.close() self.PNA=IOlist[0] self.DC=IOlist[1] self.MCU=IOlist[2] self.MCUrelease() self.PNArelease() def Event_configBTN_StartProcess(self): if self.PNABusy: self.MessageBoxWarning('矢网繁忙') elif self.PNA==None: self.MessageBoxWarning('仪器未连接') else: self.PNABusy=True Thread=configBNTthreading(self.PNA,self.DC,self.Chip_para) Thread.signal.connect(self.PNArelease) Thread.finished.connect(self.on_thread_finished) self.ThreadArray.append(Thread) Thread.start() def readTestN(self): Path, _ = os.path.split(self.TestN_FilePath) if (not os.path.exists(Path)): os.mkdir(Path) if (os.path.exists(self.TestN_FilePath)): with open(self.TestN_FilePath, 'r') as f: N = int(f.read()) if N < 0: N = 0 else: N = 0 return N def Event_runBTN(self): if self.running: self.MessageBoxWarning('测试运行中') elif self.MCUBusy or self.PNABusy: self.MessageBoxWarning('仪器繁忙') elif self.MCU==None: self.MessageBoxWarning('仪器未连接') else: self.MCUBusy=True self.PNABusy=True self.running=True self.InputNumLock() if(not os.path.exists(self.TestN_FilePath)): N=0 self.text_Num.setText(str(N)) N=int(self.text_Num.toPlainText())+1 if N<1: N=1 # runtest(int(N),self.Chip_para,self.PNA,self.DC,self.MCU) self.SocketTestThread.setIO(self.MCU, self.PNA, self.DC) self.SocketTestThread.settestNum(N) self.SocketTestThread.start() def finish_runBTN(self,test_result,chip_result): N=int(self.text_Num.toPlainText()) if test_result: #测试成功,数据记录加一 self.text_Num.setText(str(N+1)) self.SocketTestThread.join()#等待测试完成 self.Threadplot() #画图 else: pass self.releaseAllIO() def finish_rerunBTN(self,test_result,chip_result): if test_result: self.SocketReTestThread.join() # 等待测试完成 self.Threadplot() #画图 else: pass self.releaseAllIO() def Event_rerunBTN(self): if self.running: self.MessageBoxWarning('测试运行中') elif self.MCUBusy or self.PNABusy: self.MessageBoxWarning('仪器繁忙') elif self.MCU == None: self.MessageBoxWarning('仪器未连接') else: self.MCUBusy = True self.PNABusy = True self.running = True self.InputNumLock() if (not os.path.exists(self.TestN_FilePath)): print('没有已经测试的芯片,无法复测') else: N = int(self.text_Num.toPlainText()) if N<1: N=1 # runtest(int(N),self.Chip_para,self.PNA,self.DC,self.MCU,rerun=True) self.SocketReTestThread.setIO(self.MCU, self.PNA, self.DC) self.SocketReTestThread.settestNum(N) self.SocketReTestThread.start() def getpath(self): ChipName = self.text_ChipName.toPlainText() ChipNum = self.text_ChipNum.toPlainText() if ':' in ChipNum: ChipNum_array = ChipNum.split(':') start = int(ChipNum_array[0]) stop = int(ChipNum_array[1]) save_path_arrary = [ChipName % i for i in range(start, stop + 1)] else: save_path_arrary = [ChipName % int(ChipNum)] return save_path_arrary def Event_display(self): save_path_arrary = self.getpath() for i,TR in enumerate(self.Chip_para.TR_Array): dataprocess(save_path_arrary,SaveAllGain=True,TR=TR,queue=queue) self.Threadplot() def Event_H_init_StartProcess(self): if self.MCUBusy: self.MessageBoxWarning('单片机繁忙') elif self.MCU==None: self.MessageBoxWarning('仪器未连接') else: self.MCUBusy=True arg=[self.Chip_para.PSFile,self.Chip_para.SwitchFile,self.Chip_para.SwitchCHFile, self.Chip_para.SPItype,self.Chip_para.SwitchNum,self.Chip_para.SwitchCHline] ######################配置单片机的线程################### MCUThreading = ConfigMCUThreading(self.MCU,*arg) MCUThreading.signal.connect(self.MCUrelease) MCUThreading.finished.connect(self.on_thread_finished) self.ThreadArray.append(MCUThreading) MCUThreading.start() def Event_startmultitest_StartProcess(self): #开始机台测试按钮事件 if self.running: self.MessageBoxWarning('测试运行中') elif self.PNA == None or self.MCU == None or self.DC == None: self.MessageBoxWarning('仪器未连接') elif (not self.PNABusy) and (not self.MCUBusy): self.MCUBusy = True self.PNABusy = True self.running = True #测试标志位拉高 self.InputNumLock() N=int(self.text_Num.toPlainText()) self.MultiTestThread.setIO(self.MCU,self.PNA,self.DC,self.Chip_para.sottimeout) #机台测试传入IO句柄 self.MultiTestThread.settestNum(N+1) self.MultiTestThread.start() #开始测试线程 else: self.MessageBoxWarning('单片机或矢网繁忙') def callback_finishmultitest(self, test_result, Chip_result): #机台测试回传函数事件 remainnum = int(self.text_Num_2.toPlainText()) #读取剩余次数 N = int(self.text_Num.toPlainText()) #读取已经测试芯片 if test_result: print('测试完成') N = N + 1 if Chip_result: testtext = '开始下次测试' self.sendcommand('OK') self.countretest=0 elif self.countretest >=self.Chip_para.retestNum: testtext = '开始下次测试' self.sendcommand('NG') self.countretest=0 else: testtext='开始重测' self.sendcommand('retest') self.countretest=self.countretest+1 if remainnum>=0: remainnum=remainnum+1 N = N - 1 if remainnum >= 1: remainnum = remainnum - 1 self.text_Num_2.setText(str(remainnum)) # 更新剩余测试次数 self.text_Num.setText(str(N)) # 更新已测试芯片数 self.MultiTestThread.join() #等待测试完成 self.Threadplot() # 画图 if self.running and remainnum!=0: print(testtext) self.MultiTestThread.settestNum(N+1) self.MultiTestThread.start() # 开始下次测试 else: print('结束测试') self.releaseAllIO() else: print('结束测试') self.releaseAllIO() def releaseAllIO(self,*args): self.running = False self.MCUrelease() self.PNArelease() self.InputNumUnLock() def Event_finishmultitest(self): #此次测试结束后停止测试 按钮事件 if self.MultiTestThread.isRunning(): print('收到停止命令,此次测试结束后,将停止测试') self.running=False #测试标志拉低 下次测试结束 def MCUrelease(self): self.MCUBusy=False def PNArelease(self): self.PNABusy=False def MessageBoxWarning(self,message): QMessageBox.warning(self, '警告', message) def Event_FiltChip(self): TMSDATApath=r'SaveData\_TMSData' ChipOKpath=os.path.join(TMSDATApath,'ChipOK') ChipNGpath=os.path.join(TMSDATApath,'ChipNG') if (not os.path.exists(ChipOKpath)): os.mkdir(ChipOKpath) if (not os.path.exists(ChipNGpath)): os.mkdir(ChipNGpath) fileList = os.listdir(TMSDATApath) # 遍历一层 iok=0 iNG=0 for f in fileList: f_path = os.path.join(TMSDATApath, f) # 如果是文件 f_array=f.split('.') if os.path.isfile(f_path) and f_array[1] == 'csv': ChipNum=int(f_array[0].replace('Chip','')) with open(f_path,'r') as f: read=list(csv.reader(f)) #回读芯片数据 if read[-1][0] =='1': #判读为OK iok = iok + 1 read[-1][1]=ChipNum writepathfile=os.path.join(ChipOKpath,'%d.csv'%iok) elif read[-1][0] =='2': #判读为NG iNG = iNG + 1 read[-1][1]= ChipNum writepathfile = os.path.join(ChipNGpath, '%d.csv' % iNG) with open(writepathfile,'w',newline='') as f: CSVwriter=csv.writer(f) CSVwriter.writerows(read) print('分类完成') def sendcommand(self,command): if command == 'OK': write_UART(self.MCU,'23f01123',num=1) if command == 'NG': write_UART(self.MCU,'23f01223',num=1) if command == 'retest': write_UART(self.MCU,'23f01323',num=1) def InputNumLock(self): self.text_Num.setEnabled(False) self.text_Num_2.setEnabled(False) def InputNumUnLock(self): self.text_Num.setEnabled(True) self.text_Num_2.setEnabled(True) def on_thread_finished(self): self.ThreadArray=[t for t in self.ThreadArray if t.isRunning()] if __name__ == "__main__": # 解决界面模糊,缩放比例问题 QApplication.setHighDpiScaleFactorRoundingPolicy(Qt.HighDpiScaleFactorRoundingPolicy.PassThrough) # 适应高DPI设备 QApplication.setAttribute(Qt.AA_EnableHighDpiScaling) # 解决图片在不同分辨率显示模糊问题 QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps) app = QApplication(sys.argv) win1 = Myapp() win1.show() sys.exit(app.exec_())
12-05
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值