作者归档:guage

【网络分享】转载:“工业软件” 语录摘要100句

“多物理场仿真技术” 公众号里,邓老师的一篇文章写的特别好,句句都是干货,留言征得同意后转载过来分享一下。

源文链接:

https://mp.weixin.qq.com/s/e2BZcIfim8cKesLXBUcRiQ

1. 技术短期容易被高估,长期被低估。最常见的就是互联网行业,创业阶段高薪聘请软件工程师,一旦产品上线,立马开始裁人

2. 软件工程师C++水平快速提升就在工作开始1-2年,后续就应该注重设计和业务,而非C++本身

3. 代码越简单越好。让代码变复杂是很简单的事情,让代码变简单则是很复杂的事。这也是为什么会有代码评审,敏捷开发,还折腾出结对编程,极限编程各种稀奇古怪的东西

4. 轻代码,重设计,是每个软件工程师晋升的必由阶段

5. 能百度出来的内容就不要在会议上讨论

6. 百分之九十九点九的软件工程师不需要自己写算法。需要的是在理解已有算法的基础上,应用到业务场景

7. 设计模式本质上是一种代码规范


8. 工业软件想着一年出产品,三年比肩国外同类。评论:请尊重这个行业

9. 一个超级程序员顶5个普通软件工程师,亲自体会过,可遇不可求

10. “工业软件”是一个单词,请不要拆开

11. 如代码拿来就能出产品,那现在工业软件大厂的数量应该乘以10

12. 未来10-20年,是国内工业软件高速发展时期。出现类似DASSAULT,ANSYS,SIMENS的公司是大概率事件

13. 工业软件领域,开源和商用是两个隔绝生态链。

14. 工业软件的底层技术几乎是一成不变的,变的只是上层的应用。最早的仿真软件安装包最大就百来兆,现在动不动就是几十个G。想了解工业软件底层内容建议多看看公众号的内容,一般书上不会介绍

15. 互联网大厂在工业软件方面没有积累,没有人才,也没有动力去做

16. 工业软件代理本身没问题,属于商务上的事。但是一些公司明明干着代理的事,却套着“自主研发”的皮,坑完甲方坑投资,坑完投资坑zf,多提醒两句也是希望行业更规范。

17. 工业软件领域的人才不是一般的缺乏,而是非常匮乏。

18. Techsoft3d的发展历史说明:只有保护好知识产权,普及知识产权意识,让软件技术产品成为高附加值产品,才能从底层推动整个工业软件科技的发展

19. ACIS 和 ANSYS 发音很近,但是是两个东西,注意区分

20. 工业软件在国内是典型的朝阳行业,但风口期和窗口期也就这几年

21. 工业软件研发,35岁只是起点

22. 最近几年,“卡脖子”思维已经明显刹不住车了,一般来讲,对方卡自己脖子,应该立马卡回去。卡脖子技术有其鲜明的特点:

  1. 技术门槛高,起步投入资金多;
  2. 研发耗时费力,需要长期积累,3年起步,成熟5-10年非常正常;
  3. 花钱买不到,买到只能用,无法转成自己的东西;
  4. 保密,专利核心技术点多。简单讲就是即使投入研发也不一定做的出来;
  5. 在行业内具有垄断性,无可替代

所以不用事事往卡脖子上靠,真没必要

23. 2-3个人不是不能开发工业软件,而是人太少的话,很难把产品做好,这是行业特点决定的。单独开发前沿或专利技术模块除外。

24. 低端的工业软件没有市场。低端不是不做的理由,而应该成为向高端进发的动力

25. 工业软件研发要积极融入全球生态,避免闭门造车

26. 工业软件一般始于技术驱动,成长于用户迭代,成功于商业模式

27. 全球工业软件行业并没有百花齐放,相反垄断正在进一步加剧

28. 工业软件项目可以从软件技术,工程技术和市场价值策略三方面评估

29. 一套行业内标杆非线性有限元软件,国内价格是国外几倍。所以就有了要求国内软件厂商功能要达到80%,价格只能有1/3的看似不合理要求

30. 开源软件可以用,但一定要合规,不要让“木兰”“红芯”在工业软件领域上演。切忌切记

31. 最近几年的技术“断供”,是各方博弈的过程,有诸多商业因素,和上世纪五六十年代的技术封锁有本质区别。

32. 2020年,SimScale融资2700万欧元(约2亿多人民币),仿真上云是趋势,以后也会越来越成为基本配置

33. 给中小型研发企业的五条建议:规范研发流程;核心技术突破;快速迭代;基础组件替换;少做营销多跑客户。

34. 大规模线性方程组的解法基本没有太多突破,所以任何时候钻研都不过时

35. 整个软件生命周期中,写代码大概只占到30-40%

36. AI技术已经在工业软件设计领域开始发挥作用,和数值计算方法(FVM,FEM等)融合仍然是研究的重点和难点。

37. 工业软件研发有其特点和规律:技术积累非常重要,需要长期投入;开发内容涉及面广,对研发人员要求较高;用户迭代和解决实际工程是重要一环。2000年的时候COMSOL还只是MATLAB工具箱下的一个小程序

38. 工业软件真正的难是:别人研发四五十年,而你只想三年就赶上 

39. 常识告诉我们:弯道不仅不能超车,还必须减速,否则大概率就是车毁人亡

40. 在通用计算仿真领域,如果有人说产品性能提升了十倍或者百倍,那一定是之前做的太差,而不是现在做的太好。针对特定领域,特定模型,底层经过优化后,十倍甚至上百倍性能提升是可能的。

41. 过去十多年,每年都有新的技术热点和概念,比如量子计算,物联网,AR,VR,区块链,人工智能,边缘计算以及现在炙手可热,发起公司也被啪啪打脸的元宇宙。仔细探究,很多技术只不过是新瓶装旧酒,有些事背后有资本推手,有些是从实验室走向市场,有些得益于硬件发展,有些得益于市场推动

42. 在工业软件生态链中,不管是开源还是商用,软件产品和其依赖的底层组件都有被禁用的风险,研发需要做好充分评估

43. 在工业软件领域,全球范围内公司的收购并购一直是热门,但对于国内投行并没有实际参考意义

44. 2020年,“CAE”软件首次出现在政府科技工作规划中,实属不易

45. 准确,稳定,可靠是工业软件优先考虑的因素

46. 对于用户来讲,软件是工具,利用软件积累起来的工程技术经验才是核心技术。工业软件能显著提升这种经验积累的效率

47. 一款好的工业仿真软件,最大的价值在于软件所涵盖的工业知识背景,以及基于虚拟模型对真实世界,高效准确的模拟

48. 2000年左右,一套正版ANSYS售价在国内近百万,当时全国房子均价在2000,也就是一套ANSYS可以在上海北京买两套100平面的房子

49. “国产自主”是一个长期的战略指导,在立足于现实,踏踏实实做研发时,也需要加强对外沟通合作

50. SimSolid,MeshFree,基于LBM的一系列产品在实际工程中的应用表明,未来无网格方法在多物理场领域会有更多应用

51. 用开源软件搭建一个简易版的前后处理器和仿真工具非常容易,国内可以拿投资和科研资金,但无法商用。工业软件研发一定要以高质量标准要求

52. 前处理占了整个仿真流程的70-80%时间,而且是个典型的80%体力+20%脑力的活动

53. 哈工大被禁用MATLAB时,成为科技圈乃至全国新闻,现在来看,影响非常有限,一朵小浪花都算不上

54. 2022年,ANSYS收购了2017年才成立的云端服务公司OnScale,再次表明未来工业软件上云,云端化是大趋势

55. 上海2021-2023关于工业软件发展纲要: 培育引进200家工业软件企业,培育10家左右上市企业,培育5家超10亿元的重点工业软件企业,上海工业软件规模突破500亿

56. 可以学习开源软件的算法思想,但不要基于开源软件搭软件架构,会走到死胡同

57. 一篇文章入门系列,不管是从事软件研发,还是产品研发,都可以参考写给仿真软件研发的“一篇文章入门”系列(终)(点击链接查看)

58. 单物理场仿真已经很复杂,多物理场耦合不管是弱耦合还是强耦合,都更加复杂,所以算不准很正常

59. 从事工业软件行业,最好是能在某些领域能够成为专家型开发者。比如几何内核,网格划分,某些专业领域求解器,图形,数据库,云平台,优化算法,AI,业务模型,HPC等

60. 有限元计算精度和网格质量没有直接关系,真正影响精度的是网格的密度和网格分布,如果网格足够密,网格质量差点没关系,如果密度不够且分布不均匀,表现形式就是质量指标差。

70. 自适应网格核心是找出几何,物理场变化较大位置所在的网格单元,进行相应加密,并找出变化小的地方,将网格变稀疏

71. 工业仿真软件一般分为三大模块:前处理,求解器,后处理。前处理的核心是几何和离散数据(通常是网格),做一个简单类比,求解器是发动机,网格是汽油,几何是原油。好的原油可以炼出好的汽油,好的汽油有利于发动机工作。

72. 对于工业仿真软件,“求解器”(Solver)是最核心的部分,主要指使用数值计算方法求解工程问题的计算程序,也是行业内的通用叫法

73. 工业软件领域的研发知识可以分为四类:理论基础,推导计算,工程应用和软件研发,研发内容可以对号入座

74. 《科学与工程中的计算》杂志评选出的20世纪10个最伟大算法,其中有7个和工业软件紧密相关

75. 从技术角度看,工业软件研发的难在:底层涉及大量的数学,这些数学知识应用和工业需求挂钩,在现有软硬件以及技术条件下,对准确性,可靠性,性能,鲁棒性有很高要求,需要长期迭代应用打磨

76. 快速多级,多层快速多级等是提升MOM,BEM算法性能的有效方法

77. 开发求解器,矩阵论和线性代数是基础中的基础,绕不过去

78. 根据笔者的多物理场研发经验,CFD和电磁最为复杂

79. IGA是Coreform公司的等几何结构求解器。等几何降低了对网格的要求甚至不需要网格,但随之而来是矩阵特性改变,稀疏性降低,在边界,非连续性,异性几何处理方面还有很多研究的空间

80. 因为直接求解偏微分方程组(PDEs)比较困难,才出现各种变通的方法,比如人为的划分强耦合,弱耦合,单向耦合,双向耦合等,其目的是为了简化计算,但所有耦合场都是以PDEs为基础

81. 想从事工业软件研发,语言选择上不用纠结,学好C++

82. AMG(代数多重网格)是求解收敛缓慢大规模线性方程组的一种有效方法,和网格划分没关系

83. 从头做起的大型软件,中间大规模重构是必然的,推倒重来也很正常。软件也有生命周期,到了一定程度,添加新功能的成本会高于重做,所以架构设计很重要。

84. 求解器开发也属于软件研发范畴,理应用软件工程思想指导,但由于求解器本身特殊性,开发流程也不适合完全按照一般软件研发流程做

85. 对标“标杆”软件是研发的通用做法,但是到了一定程度,产品就应该有自己的特点和亮点

86. 通常谈到CAE时,有个误区,就是把CAE和力学等同,把力学和有限元等同。原因是力学发展得比较早也比较完善,目前大部分有限元教程都以弹性力学和结构力学为主,客观上造成了这个误区的形成。我国的冯康院士是有限元理论原创提出学者之一。

87. 基于BREP结构的几何内核不是万能的,很多领域需要自己几何数据结构,这也是PARASOLID推出的Convergent Model的原因

88. 软件解耦可以在 类,组件,模块和产品等不同层次,是一个系统性工程

89. 工业软件性能问题符合典型的“木桶理论”,几何,网格,UI,计算,渲染,调度,交互等任何一个模块都可以造成性能瓶颈

90. 没有卖不掉的软件,只有抢不到的“独代”

91. 如果仿真还没有受到硬件限制,那说明仿真还没有入门

92. 2004年,ANSYS解出了1亿自由度模型,2008年,ANSYS解出了10亿自由度的模型。即使现在,自由度超过1千万,也可以认为是个大模型

93. 仿真软件的加速可以归纳为四个方面:分治,单一模型,优化高频操作,语言和底层算法改进

94. 模型降阶(MOR)是简化模型的有效方法

95. 网格是几何上的概念,单元是数值方法中的概念,通用有限元软件中单元的种类有几百种

96. 高阶单元(也就是通常说的P单元),在处理复杂几何上更有优势,适当的设置参数,只需更少数量的网格,能获得更好的数值计算解

97. 数值精度和数值误差是两码事。常见的误差包括离散误差,累计误差和截断误差

98. 工业软件一定要能解决实际工程问题,否则求解器做得再好也难以应用,只能是纸上谈兵

99. 多物理场通常指宏观领域的物理场,包括电磁,流体,结构,热,声,光等等,偏向于工程应用领域,和理论物理中的四大物理场(强力,弱力,电磁力,万有引力)相区别

100. 最后,不管从事什么行业,不管什么年纪,选择适当的运动方式,保持健康的生活方式,坚持锻炼身体,保好“革命的本钱”

COMSOL Matlablive 多参数研究,批量化运行+导出+自动整理

COMSOL软件的研究求解模块其实提供了比较丰富的参数化运行结构,但是对不同参数的研究的结果导出方面,还是需要很多手动设置操作的才能导出批量化数据。

比如以下情况:研究对线是一个催化反应器,需要入口温度、入口流速、反应器尺寸、反应物初始孔隙率等四个不同的输入参数进行单变量的不同输入值的影响研究。那么如果直接使用COMSOL来做的话,就需要使用参数化扫描对单个参数进行扫描,然后导出动画进行分解,并保存四个不同的mph文件,用于分别研究四个参数。

针对这种情况,其实可以用Matlab livelink来进行批量的单个参数的修改,并运行求解导出相关结果(mph、图、报告、表数据)。

% 1、定义要运行的参数组
model = mphopen('param_run.mph');  % 打开模型
% 设定参数组
param_name = ["T_in" "U_in" "d_ball" "eps_init"]; % 参数名称
param_unit = ["degC" "m/s" "um" ""]; % 参数单位
param_Value = [600 650 700 750 800;...  %参数值
                0.1 0.2 0.3 0.4 0.5;...
                75 150 200 300 500;...
                0.03 0.05 0.1 0.15 0.2];

% 2、 For循环运行所有参数组
for name=1:length(param_name) % 循环研究参数名
   for value=1:size(param_Value,2) % 参数水平
       % 记录输出
       tic;
       fprintf('Start simulation: %s=%s [%s]\n',param_name(name),num2str(param_Value(name,value)),param_unit(name));
       % 设定参数
       model.param.set(param_name(name),num2str(param_Value(name,value)),param_unit(name));
       % 运行计算
       model.sol('sol1').runAll;
       % 导出数据
       model.result.report('rpt1').run; % 导出报告
       model.result.export('anim1').run; % 导出动画
       model.result.export('anim2').run;
       model.result.export('anim3').run;
       model.result.export('anim4').run;
       model.result.export('anim5').run;
       model.result.export('anim6').run;
       model.result.export('anim7').run;
       model.result.export('plot1').run; % 导出出图数据表
       model.result.export('plot2').run;
       model.result.export('plot3').run;
       % 整理导出数据
       movefile("export_dir", strcat(param_name(name),"_", num2str(param_Value(name,value))));
       copyfile("export_file",strcat(param_name(name),"_", num2str(param_Value(name,value))));
       % 完成
       toc
   end
   % 设定回原参数
   model.param.set(param_name(name),num2str(param_Value(name,1)),param_unit(name));
end

【软件分享】网易云插件 BetterNCM

作为一个老网抑云了,早就厌倦了官方UI;然后由此在倒腾WallpaperEngine动态歌词的时候,偶然发现了这个插件。很神奇,分享一下。

项目主页:https://github.com/MicroCBer/BetterNCM

最好跟从Github主页安装最新的;

安装后右上角可以打开相关设置,自带市场有很多界面、歌词效果插件;某些源可能要梯子。

如果刚打开的时候有出现左侧列表遮挡右侧主页面的情况,手动用光标调整一下侧边栏,就立马自动恢复了。

InfLink插件就是我最开始进来的原因,可以让某些Wallpaper Engine壁纸获取音乐封面等信息;

傲慢与偏见

后记:日常交流遇见傲慢的人,难以交流。无奈之余,让Cluade帮我写了一篇命题作文,助我释怀。

人际关系中的种种困扰,在简·奥斯汀名著《傲慢与偏见》中已有精彩刻画。那些自以为是、目中无人的傲慢,恃才傲物的无知,都让人难以忍受。然而,在多变的现代社会,我们仍不可避免地会遭遇些自负者。面对他们,我们该怎样处之?

首先,要明白,傲慢常源自内心的不安全。他人的优秀激起自卑,于是强自表现来掩饰软弱。我们不必对傲慢之人太过苛责,而是体谅其内心焦虑。同时,也应警醒自己,不要被不必要的比较带入负面情绪。

其次,无知往往根源于缺乏同理心。我们生而为人,难免有局限的视角。多站在他人角度思考,便能认识到自己的偏见。同时,要时刻保持谦逊学习的心态,扩充眼界,才能避免自欺欺人的无知。

再则,傲慢与偏见让人难以忍受,但不必与之争论。我们可以保持内心的平和,用善意感化,而非训斥。退一步海阔天空,总比与傲慢之人较劲来得开心。

只有内心舒畅,我们才能活出自信从容。面对他人的傲慢偏见,我们有理由自信,但不必外露骄傲。真正的成熟是学会包容与宽恕,而非针锋相对。

人生苦短,我们每个人都还在成长。保持谦逊仁慈的心,多站在他人角度思考,用善意感化傲慢偏见,方得人生真谛。也许,我们都还在路上,都可能犯错。但只要心怀善意,便会遇见同好知音,共赴美好人生。

真正的智者不在于掌握的知识量,而在于维持一颗平和善良的心。在复杂的人际关系中,我们更需要的不是伪装出的傲慢,而是真诚的同理心。只有懂得接纳自己的不足,放下对他人的刻薄,我们的内心才能得到真正的安宁。

车灯仿真合作须知

瓜哥是一个前车灯行业研发工程师,承蒙一些同行朋友的厚爱,一直在和一些朋友一起承接一些车灯项目的仿真 工作。

承接研发内容:

  • 光学设计:照明灯设&信号灯&氛围灯
  • 电子设计:模组设计&原理图&layout图
  • 热学模拟:整灯热模拟&模组精细化模拟&自聚焦分析&太阳光聚焦分析
  • 力学模拟:振动分析&半正弦冲击模拟

工作时间:工作日9:00~22:00,欢迎随时咨询。

文件存储:具有防灾设计的文件服务器,安全可靠,永久保存所有项目文件,方便随时追溯。

报价流程:不同灯具类型不同类型分析项目根据工作量,评估了设计数据后提供报价单报价,报价提供1%普通发票税点。

收账流程:提交仿真报告后,一个月内沟通确认开出发票,提交发票,要求三个月左右回款。如不能接受请提前告知。

如有需求: cswcswcsw2008@qq.com vx: jituifanguagua

科研仿真合作须知

瓜哥是一个全职的个人仿真工作室。

科研仿真一块主要专精COMSOL多物理场仿真,自从18年开始接触COMSOL开始,就发现了这款软件独有的魅力,那会就确定了COMSOL深耕钻研的想法。这些年下来,积累了大量的项目经验,平均每年服务指导200+仿真项目。

熟悉的模块:流体&传热&结构&电化学&电磁

工作时间:5天工作制,9:00~18:00都是工作时间,工作时间都会第一时间10分钟内做响应,没有及时相应的情况可能是在会议中。对客户不明确的需求或者售后,都可以约腾讯会议线上进行沟通。我的会议ID如下,不会另外改成其他抬头或者提现其他信息。

文件存储:工作室拥有私人文件服务器,防灾级,双服务器热备份+定期冷备份,永久保存所有项目文件。无需担心项目数据安全性和保密性。

保密性&创新性:所有项目均为独一定制,不存在一个结果多用情况。

接单能力:我有两三个助理工程师,会帮我做一些基础模型处理、售后跟进、文档处理。

我的一些线上工具:

对上游合作平台的要求:

1、+vx请先表明来意,无须客套,有项目需求直接发就可以。

2、默认不提供源文件。少量项目需求不涉及绝活内容可以特例商榷。默认提供导出COMSOL项目报告。

为什么不提供源文件:

1、有出现过客户拿了我的文件去倒卖给了收集案例的人,结果我去找那个人质问,他还倒打一耙说我是贩子,这种情况出现了好几次;

2、大部分客户没有有限元基础,提供了源文件后,客户反而问这问那,我答疑很麻烦,甚至有客户因为自己不懂质疑我的结果的合理性,我还要呕心沥血的给对方自证吃了几碗粉,没必要;

3、如果客户担心出图调整的问题,随时找我就行,在约定范围内的如果要对出图形式做调整,文字整理好反馈给我即可。也可以利用我的开发的“颜色预览Web App”进行沟通出图形式沟通。

3、结账周期不能超过2个月,因为我不想花时间在讨账上面。

4、拒绝被白嫖,对于因为各种原因而导致做完了项目,客户退单的,希望签署协议,不使用我已产出成果。

5、约会议需要约好时间,请第一时间在腾讯会议做好会议号的预约,并给我发会议链接,方便我安排时。(会议预约须知:https://guagefangzhen.cn/?p=677

【Python】GifSpliter “GIF分割器”

“GifSpliter” GIF分割器

用PyQt5重写了一个“GIF分割器”的小应用:

功能:

  • 主要就是为了能快速分解GIF文件,将所有帧保存成PNG格式。
  • 支持文件拖拽进窗口,也是唯一的文件输入方式,就是简单暴力,直接方便,xD。
  • 可选择的输出位置:可以指定位置,也可以在原文件所在位置给你输出分割帧。
  • 可以拖拽文件夹输入,只要你文件夹里有gif,都给你处理了。
  • 支持混合输入模式。可以同时选中多个gif,或者gif+文件夹的多个文件输入。

现成的软件 GifSpliter_v3.2:

链接:https://pan.baidu.com/s/1JJLOF1vDYpd7pRzrtr93Xg?pwd=ucuo
提取码:ucuo
–来自百度网盘超级会员V6的分享

为什么做这个?

COMSOL仿真导出GIF图很方便,但是想要导出不同时刻参数下的多个图像很不方便。那……不如直接导出GIF后再分割?

但是网上没有这种轻量级的,简单暴力,合我需求的应用,那直接自己撸一个小工具!

现在,直接选中文件拖进去,等几秒就好了,多好?

Update History

v3.2 231031

  • 丰富了控制台打印信息,增加了彩色的控制台输出,并添加了处理进度条;

v3.1 230831

  • 保留了控制台窗口,并优化了控制台输出,现在可以通过控制台输出信息查看处理

    情况

v3.0 230631

  • PyQt5设计了全新的窗口;
  • 增加了文件夹模式;
  • 增加了多种输出模式的选择;

v2.0

  • 增加了多文件处理模式。

v1.0

  • Tinkter界面,单gif文件的处理方式,默认输出到exe所在文件夹。

SourceCode

import sys
import os
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from ui_GifSpliter import *
from spliter import GifSplitter
import res_rc

class MyGifSpliter(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)  # 调用父类构造函数
        self.__ui = Ui_GifSpliter()
        self.__ui.setupUi(self)
        self.appDir = sys.path[0]
        print(self.appDir)
        self.gifDir = self.appDir
        self.gifDir = self.appDir
        self.specDir = self.appDir
        self.outDir = self.appDir
        self.__ui.textEdit_DirSpec.setText(self.appDir)
        # 调用Drops方法
        # self.drawn()
        self.__ui.radioButton_DirSpec.clicked.connect(self.do_setSpecDir)


    # 鼠标拖入事件
    def dragEnterEvent(self, event):
        if event.mimeData().hasUrls:
            event.accept()
        else:
            event.ignore()

    # 鼠标放开执行
    def dropEvent(self, event):
        """获取拖拽文件的路径"""
        files = []
        for url in event.mimeData().urls():
            path = url.toLocalFile()
            files.append(path)
        # 打印拖住进入窗口的文件路径
        self.gifDir = os.path.split(files[0])[0]
        # print(f'拖拽文件路径为:"{self.gifDir}"')

        # 判断输出模式
        if self.__ui.radioButton_DirGif.isEnabled():
            self.outDir = self.gifDir + '/分割后PNG'
            if not os.path.exists(self.outDir):
                os.mkdir(self.outDir)
        elif self.__ui.radioButton_DirApp.isEnabled():
            self.outDir = self.appDir
        elif self.__ui.radioButton_DirSpec.isEnabled():
            self.outDir = self.specDir
        # print(f'输出文件夹为:"{self.outDir}"')

        # 处理获取的文件
        for file in files:
            # 文件夹
            if os.path.isdir(file):
                # print(f'{file}是文件夹')
                self.gifDir = file
                self.outDir = self.gifDir + '/分割后PNG'
                if not os.path.exists(self.outDir):
                    os.mkdir(self.outDir)
                GifSplitter.split_gifs_in_folder(file, save_folder=self.outDir)
                print(f'\033[92m完成对文件夹:"{file}"的处理~~~\033[0m')
            # 文件
            else:
                # 判断扩展名是否为.gif
                if file.lower().endswith('.gif'):
                    # print(f'{file}是gif文件')
                    GifSplitter(file).export_frames(save_folder=self.outDir)
                else:
                    print(f'\033[91m! "{os.path.basename(file)}" 不是GIF文件~~~!\033[0m')
        if self.__ui.checkBox_openDirSpec.isChecked():
            os.startfile(self.outDir)

    #设定背景图片
    # def drawn(self):
    #     self.__ui.palette = QPalette()
    #     self.__ui.palette.setBrush(QPalette.Background, QBrush(QPixmap("BackGround1.png")))
    #     self.setPalette(self.__ui.palette)

    # 根据radioButton设定文件路径
    def do_setSpecDir(self):
        if self.__ui.radioButton_DirSpec.isChecked():
            self.specDir = QFileDialog.getExistingDirectory(None,"选取PNG输出文件夹","")
            self.__ui.textEdit_DirSpec.setText(self.specDir)
            print(f'设定指定输出路径为:{self.specDir}')


if __name__ == "__main__":
    app = QApplication(sys.argv)
    e = MyGifSpliter()
    e.show()
    sys.exit(app.exec_())
# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'ui_GifSpliter.ui'
#
# Created by: PyQt5 UI code generator 5.15.9
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again.  Do not edit this file unless you know what you are doing.


from PyQt5 import QtCore, QtGui, QtWidgets


class Ui_GifSpliter(object):
    def setupUi(self, GifSpliter):
        GifSpliter.setObjectName("GifSpliter")
        GifSpliter.resize(640, 480)
        GifSpliter.setMinimumSize(QtCore.QSize(640, 480))
        GifSpliter.setMaximumSize(QtCore.QSize(640, 480))
        font = QtGui.QFont()
        font.setFamily("微软雅黑")
        font.setPointSize(11)
        font.setBold(True)
        font.setUnderline(False)
        font.setWeight(75)
        GifSpliter.setFont(font)
        GifSpliter.setAcceptDrops(True)
        icon = QtGui.QIcon()
        icon.addPixmap(QtGui.QPixmap(":/icon/C cyn.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        GifSpliter.setWindowIcon(icon)
        GifSpliter.setStyleSheet("")
        self.verticalLayout_2 = QtWidgets.QVBoxLayout(GifSpliter)
        self.verticalLayout_2.setSizeConstraint(QtWidgets.QLayout.SetMinimumSize)
        self.verticalLayout_2.setObjectName("verticalLayout_2")
        self.label = QtWidgets.QLabel(GifSpliter)
        self.label.setMinimumSize(QtCore.QSize(0, 0))
        font = QtGui.QFont()
        font.setFamily("微软雅黑")
        font.setBold(False)
        font.setWeight(50)
        self.label.setFont(font)
        self.label.setAcceptDrops(True)
        self.label.setFrameShape(QtWidgets.QFrame.NoFrame)
        self.label.setFrameShadow(QtWidgets.QFrame.Plain)
        self.label.setLineWidth(1)
        self.label.setTextFormat(QtCore.Qt.AutoText)
        self.label.setAlignment(QtCore.Qt.AlignCenter)
        self.label.setOpenExternalLinks(True)
        self.label.setObjectName("label")
        self.verticalLayout_2.addWidget(self.label)
        self.horizontalLayout = QtWidgets.QHBoxLayout()
        self.horizontalLayout.setSizeConstraint(QtWidgets.QLayout.SetMinimumSize)
        self.horizontalLayout.setObjectName("horizontalLayout")
        self.radioButton_DirGif = QtWidgets.QRadioButton(GifSpliter)
        self.radioButton_DirGif.setChecked(True)
        self.radioButton_DirGif.setObjectName("radioButton_DirGif")
        self.horizontalLayout.addWidget(self.radioButton_DirGif)
        self.radioButton_DirSpec = QtWidgets.QRadioButton(GifSpliter)
        self.radioButton_DirSpec.setChecked(False)
        self.radioButton_DirSpec.setObjectName("radioButton_DirSpec")
        self.horizontalLayout.addWidget(self.radioButton_DirSpec)
        self.radioButton_DirApp = QtWidgets.QRadioButton(GifSpliter)
        self.radioButton_DirApp.setChecked(False)
        self.radioButton_DirApp.setObjectName("radioButton_DirApp")
        self.horizontalLayout.addWidget(self.radioButton_DirApp)
        self.verticalLayout_2.addLayout(self.horizontalLayout)
        self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
        self.horizontalLayout_2.setSizeConstraint(QtWidgets.QLayout.SetMinimumSize)
        self.horizontalLayout_2.setObjectName("horizontalLayout_2")
        self.label_2 = QtWidgets.QLabel(GifSpliter)
        self.label_2.setObjectName("label_2")
        self.horizontalLayout_2.addWidget(self.label_2)
        self.textEdit_DirSpec = QtWidgets.QTextEdit(GifSpliter)
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Ignored)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.textEdit_DirSpec.sizePolicy().hasHeightForWidth())
        self.textEdit_DirSpec.setSizePolicy(sizePolicy)
        font = QtGui.QFont()
        font.setPointSize(9)
        font.setBold(False)
        font.setWeight(50)
        self.textEdit_DirSpec.setFont(font)
        self.textEdit_DirSpec.setObjectName("textEdit_DirSpec")
        self.horizontalLayout_2.addWidget(self.textEdit_DirSpec)
        self.checkBox_openDirSpec = QtWidgets.QCheckBox(GifSpliter)
        self.checkBox_openDirSpec.setObjectName("checkBox_openDirSpec")
        self.horizontalLayout_2.addWidget(self.checkBox_openDirSpec)
        self.verticalLayout_2.addLayout(self.horizontalLayout_2)
        self.verticalLayout_2.setStretch(0, 1)

        self.retranslateUi(GifSpliter)
        QtCore.QMetaObject.connectSlotsByName(GifSpliter)

    def retranslateUi(self, GifSpliter):
        _translate = QtCore.QCoreApplication.translate
        GifSpliter.setWindowTitle(_translate("GifSpliter", "GIF文件分割器 v3.0"))
        self.label.setText(_translate("GifSpliter", "<html><head/><body><p align=\"center\">GIF文件分割器:   可以快速将GIF文件分割成逐帧PNG文件</p><p align=\"center\">-------------------------------------------------------------</p><p align=\"center\">使用方法:</p><p align=\"center\">将GIF文件或含有多个GIF的文件夹拖拽进此窗口即可</p><p align=\"center\">程序会自动分解所有拖拽进来的GIF并分解成单张PNG文件</p><p align=\"center\">可以在下面单选按钮中选择PNG文件输出位置</p><p align=\"center\">如果需要选择生成到特定文件夹,需要指定输出文件夹</p><p align=\"center\"><br/>-------------------------------------------------------------</p><p align=\"center\"><a href=\"https://guagefangzhen.cn/\"><span style=\" text-decoration: underline; color:#0000ff;\">更多分享:https://guagefangzhen.cn</span></a><br/></p></body></html>"))
        self.radioButton_DirGif.setText(_translate("GifSpliter", "生成到GIF所在目录"))
        self.radioButton_DirSpec.setText(_translate("GifSpliter", "生成到指定目录"))
        self.radioButton_DirApp.setText(_translate("GifSpliter", "生成到程序所在目录"))
        self.label_2.setText(_translate("GifSpliter", "指定输出目录:"))
        self.textEdit_DirSpec.setHtml(_translate("GifSpliter", "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" \"http://www.w3.org/TR/REC-html40/strict.dtd\">\n"
"<html><head><meta name=\"qrichtext\" content=\"1\" /><style type=\"text/css\">\n"
"p, li { white-space: pre-wrap; }\n"
"</style></head><body style=\" font-family:\'SimSun\'; font-size:9pt; font-weight:400; font-style:normal;\">\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">指定分割帧文件输出路径</p></body></html>"))
        self.checkBox_openDirSpec.setText(_translate("GifSpliter", "分割后打开目录"))
import res_rc
# 用于定义gif分割程序
import os
import imageio


class GifSplitter:
    def __init__(self, gif_file):
        self.gif_file = gif_file
        self.gif = imageio.get_reader(gif_file)

    def get_gif_info(self):
        """获取gif信息"""
        info = {}
        info['file_name'] = os.path.basename(self.gif_file)
        info['folder_path'] = os.path.dirname(self.gif_file)
        info['total_frames'] = len(self.gif)
        return info

    def export_frames(self, save_folder=None):
        """导出gif所有帧为png图片"""
        if save_folder is None:
            save_folder = os.path.join(self.get_gif_info()['folder_path'],
                                       'splitted')
            if not os.path.exists(save_folder):
                os.mkdir(save_folder)

        for i, frame in enumerate(self.gif):
            imageio.imsave(os.path.join(save_folder, '{}-{:03d}.png'.format(
                self.get_gif_info()['file_name'], i)), frame)
        print(f'完成分割: "{(os.path.basename(self.gif_file))}" | 输出文件夹-->"{save_folder}"')


    @classmethod
    def split_gifs_in_folder(cls, folder_path, save_folder=None):
        """分割文件夹内所有的gif文件"""
        print(f'拖拽文件夹为:"{folder_path}"')
        print(f'输出文件夹为:"{save_folder}"')
        if save_folder is None:
            save_folder = os.path.join(folder_path, 'splitted')
            if not os.path.exists(save_folder):
                os.mkdir(save_folder)

        gif_files = [file for file in os.listdir(folder_path)
                     if file.endswith('.gif')]

        i = 1
        for gif_file in gif_files:
            gif_splitter = cls(os.path.join(folder_path, gif_file))
            gif_splitter.export_frames(save_folder)
            print(f'文件夹处理进度: {i}/{len(gif_files)}')
            i = i + 1

if __name__ == '__main__':
    # 分割单个gif文件
    gif_splitter = GifSplitter('test.gif')
    gif_splitter.export_frames(save_folder="gif")

    # 分割文件夹内所有gif文件
    GifSplitter.split_gifs_in_folder('gif_folder')

【Python】Streamlit应用 “这个班上的值不值”

“这个班上的值不值” Web版

做了一个Streamlit的练手应用,用于测算“工作的性价比”。

非严谨计算,请勿认真对待,xD。

挂在服务器上了,比较轻量,应该不会下线,可以分享给朋友玩一下http://175.24.226.62:8501/

缘由

去年在某位群友的分享下,碰到一个很有意思的一个excel小应用。

一个挺有意思的小应用,一点职场人的茶余饭后的小话题。玩了两下后思考了一下,这个做成excel属实是不方便好友分享,如果做成一个web版的或者小程序版本,不是更好?这个想法就这样一直留在todo list里了。

最近工作太忙了,但是为了不被眼下的工作完全淹没自己,还是逼迫自己慢慢的完成一些todo list上的事。因为,我坚持认为,人除了要赚眼前的填饱肚子的月供之外,也要持续的坚持学习新东西,做一些对长远规划有用的东西。

五月份某天在学习调研dashboard和streamlit的时候,想起这个“这个班上的值不值”的应用,就把它当做一个学习练手的小task做了,并且部署到了我的轻量级服务器上。


做法分享:

简单来讲,就是分为两步:

1、基于python streamlit写脚本,开发一个应用。

2、将调试好的streamlit应用部署到服务器上。

主要要点:

1、我的开发环境是Anaconda+Pycharm,这也是主流的比较推荐的python科学研究的开发环境;

2、Streamlit相关的资料还是比较少,主要建议上官网看他的一些API说明:

Streamlit官方Documentation(推荐):https://docs.streamlit.io/

也有个国人做的学习手册(施工中):http://cw.hubwiz.com/card/c/streamlit-manual/

然后有了一些基础概念后,就可以面对AI编程了,哈哈。

3、我是梳理了一下应用,基本很快就确定了一个框架:siderbar作为参数输入,然后右边做输出和说明展示的。进而开始coding。

import streamlit as st

with st.sidebar:
    st.title('输入参数')
    salary = st.number_input('平均日薪酬(RMB)', value=100, min_value=50, step=25)
    work_t = st.number_input('工作时长(单位:小时,下班时间-上班时间)', value=8.0, min_value=1.0, step=0.5)
    trans_t = st.number_input('通勤时长(单位:小时,上下班花在路上的通勤时间)',value=1.0,step=0.25)
    slack_t = st.number_input('摸鱼时间(单位:小时,吃饭+不干活+午休)',value=1.0,min_value=0.0,step=0.25)

    st.title('相关系数')
    edu = st.number_input('学历系数',value=1.0,step=0.2,min_value=0.8,max_value=2.0)
    with st.expander('系数说明'):
        st.text('专科及以下 0.8\n普通本科  1.0\n高级本科  1.2\n普通硕士  1.4\n高级硕士  1.6\n普通博士  1.8\n高级博士  2.0')
    env_working = st.number_input('工作环境系数',value=1.0,step=0.2,min_value=0.8,max_value=1.1)
    with st.expander('系数说明'):
        st.text('偏僻地区  0.8\n工厂户外  0.9\n普通     1.0\n体制内   1.1')
    env_female = st.number_input('异性环境指数',value=1.0,min_value=0.9,max_value=1.1,step=0.1)
    with st.expander('异性指数说明'):
        st.text('没有      0.9\n不多不少  1.0\n很多     1.1')
    env_coleg = st.number_input('同事环境系数', value=1.0, min_value=0.95, max_value=1.05, step=0.05)
    with st.expander('同事环境系数说明'):
        st.text('SB很多   0.95\n普通很多   1.0\n优秀很多  1.05')
    env_career = st.number_input('职业资格系数', value=1.0, min_value=1.0, max_value=1.15, step=0.05)
    with st.expander('职业资格系数说明'):
        st.text('无要求、二级    1.0\n建造造价监理    1.05\n建筑岩土结构    1.1\n主任医师、教授  1.15')
    early_working = st.number_input('是否在8:30前上班', value=1.0, min_value=0.95, max_value=1.0, step=0.05)
    st.text('是否8:30前上班? 是:0.95, 否:1.0')
    final_ratio = st.number_input('综合环境系数(不要改)', value=1.0, max_value=1.1, min_value=0.9, step=0.05)

point = salary*final_ratio/(35*(work_t+trans_t-0.5*slack_t)*edu*env_career)*early_working*env_coleg*env_female

st.title(':male-technologist:这个班上的值不值?')


st.caption(f"您的工作性价比指数为: _%.2f_" % point)
if point<=0.8:
    text_out = '参考评价:您的工作:green[很惨]~~~  :weary:'
elif point<=1.5:
    text_out = '参考评价:您的工作:blue[一般]. :expressionless:'
elif point<=2.0:
    text_out = "参考评价:您的工作:red[很爽]~~!  :stuck_out_tongue_winking_eye:"
else:
    text_out = "参考评价:您的工作:red[爽到爆炸]~~~!!!  :sunglasses:"
st.caption(text_out)
st.caption('_这是一个娱乐性APP,通过对在左侧侧边栏<-输入的几个参数进行计算,评估自己这个班上的值不值~_')
st.caption('_仅为博君一笑,请勿认真对待~!_')

with st.expander("计算公式说明:"):
    st.image('img.png')
st.write('[Powered by 瓜哥 using "streamlit"](https://guagefangzhen.cn)')

4、Streamlit的发布到服务器,我百度了看到的基本都是用Docker环境安装streamlit环境。我的想法更简单: 服务器上装了miniconda –> 在miniconda里装Streamlit环境 –> 在Streamlit虚拟环境中启动上传的 JobPerformanceCalculator.py

5、Streamlit中的启动,需要添加nohup命令。这样你就可以在运行后关闭这个命令窗口,而应用一直会在服务器后台运行了。

nohup streamlit run JobPerformanceCalculator.py