概述

随着 CAE 的广泛应用,CAE 用户的水平日渐提高,新需求日渐增多,CAE 软件的二次开发逐渐成为工程师的必备技能。Python 语言简单易懂、开源包众多,是非常流行的编程语言。Abaqus 作为一款以 Python 语言为前后处理内核的 CAE 软件,兼具 CAD 软件基于特征建模的方法,还提供了内核与 GUI 二次开发接口,所以基于 Abaqus 进行二次开发的自由度非常大。

Abaqus 的内核脚本二次开发可以从学习日志文件 abaqus.rpy 起步,GUI 脚本二次开发可以从 GUI 用户手册起步。熟悉之后,简单问题可以直接用脚本编写。但是 Abaqus 毕竟不是专业的集成开发环境(IDE),代码补全、参数提示功能较弱,借助专业 IDE 可提高开发效率。由于 Abaqus 的 Python 解释器无法被专业 IDE 直接调用,需要创建代理模块模拟 Abaqus 各种方法的接口。

本文从学习研究的角度探讨了创建 Abaqus 代理接口的方法,提供了有关脚本及生成的代理文件下载链接(欢迎打赏下载币)。

Abaqus内核及GUI代理的应用效果

在pyCharm中,内核脚本的快捷文档(光标在方法后、括号前,按Ctrl+Q)如下:

在 pyCharm 中,GUI 脚本的方法提示如下:

在 pyCharm 中,内核脚本的参数提示(Ctrl+P)如下:

pyCharm 配置说明

  1. 下载上述文件,从“推荐使用的代理组合”目录下,复制 abaqus.py, abaqusConstants.py, abaqusGui.py 复制到 Python 解释器的 Lib/site-packages目录下,例如: C:\ProgramData\Anaconda2\Lib\site-packages
  2. 修改 pyCharm 设置,Help–>Edit Custom Properties,在 idea.properties 文件中添加一行
    idea.max.intellisense.filesize=99999【#默认是2500kb】,解决 pyCharm 中 File size exceeds configured limit 文件大小不够的问题。原文链接
  3. 重启 pyCharm即可

开发相关背景

Abaqus 自身的 CLI (command line interface) 窗口具有代码补全功能,补全快捷键为 Tab,其核心模块为 completerABQ.pyc(实现自动补全功能) 和 docstringLookup.pyc(多个字典,保存了方法的路径、参数及说明)。自动补全只支持 CLI 窗口的内核脚本,不支持 GUI 脚本。

Abaqus 的 GUI 二次开发较为繁琐,无法直接调试,查找方法非常不方便。pyCham 的代码补全、参数提示功能很棒,Ctrl+P 参数提示, Ctrl+Q 查看函数、方法文档,很方便。Abaqus 的 python 2.x 解释器经过订制,无法(未找到方法)用于 pyCharm 调试 Abaqus 脚本。

通过生成 Abaqus 代理模块,模拟 Abaqus 各种方法的接口定义,可在通用 Python 解释器中“简单调试”(参数提示、查看文档、语法检查) Abaqus 内核脚本及 GUI 脚本。

开发脚本说明

1. gen_Abaqus_Agent_from_2020_docs.py
运行环境:Python3
脚本说明:通过解析 htm 文件,将 Abauqs 2020 的脚本参考手册转化为代理模块 abaqus.py,输入参数、返回值都有类型及说明。因为部分 htm 文件的编码为 utf-8,为便于处理,选用 python3。
2. gen_AbaqusConstants_Agent.py
运行环境:Abaqus CAE
脚本说明:将 abaqusConstants 模块转为代理模型 abaqusConstants.py。调用内置模块,必须在 Abaqus 环境执行。
3. getAbaquStructure.py
运行环境:Abaqus CAE
脚本说明:(不推荐)借助 docstringLookup 模块生成代理 abaqus.py, xyPlot.py 等,输入参数无类型,无返回值。 调用内置模块,必须在 Abaqus 环境执行。
4. transferAbaquGUIFunctionFromDocumentation.py
运行环境:Python
脚本说明:GUI 参考手册的结构比较复杂(或者说比较混乱),很多类型没有介绍,无法程序自动识别。此脚本只起辅助作用,从 GUI 参考手册的网页中手动复制内容到文本文件,然后用脚本解析文本文件,生成单个类型,然后手动添加到 abaqusGui.py 中,需要注意类型的创建顺序。

脚本内容

1.gen_Abaqus_Agent_from_2020_docs.py

#-*- coding:gbk -*-
from bs4 import BeautifulSoup
import re,os,pickle
'''This script transfers "Abaqus 2020 Scripting Reference Documentatation" to an Agent Model File : abaqus.pyThe generated agent "abaqus.py" can be used in pycham for code completion 1.replace pathDir in __main__   eg:pathDir = r'C:\Program Files\Dassault Systemes\SIMULIA2020doc\English\SIMACAEKERRefMap'2. run this scipt in python3  get "abaqus.py"3. run gen_AbaqusConstants_Agent.py in abaqus to get 'abaqusConstants.py'by alienwxy 2020-05-05'''def cutInfo(info):info = info.strip()return  infocutLen = 50rst = re.findall('[A-Z][A-Z_0-9]+',info)if rst and info.startswith('A SymbolicConstant'):return ', '.join(rst)else:if len(info)>cutLen*2:return info[:cutLen]+' ... '+info[-cutLen:]else:return info
#解析目录所有htm文件
def partseDir(dirPath):modules = {} #'moduleName': methods, constructs, membersobjects = {}failed = []# 先更新所有方法for fname in os.listdir(dirPath):if fname.endswith('.pkl'):try:with open(os.path.join(dirPath, fname), 'rb') as fp:titleList, methodDict, constructDict, memberList = pickle.load(fp)titletype,titlename,titleintro = titleListif titletype == 'object':if titlename in objects: #对象已经存在,忽略标题,合并方法,合并成员变量dic =objects[titlename] #对象字典if methodDict:mtddic = dic['methodDict']for name,body in methodDict.items():if name in mtddic: #方法名已存在if mtddic[name] == body:continueelse:pname = name[:-1] if name[-1].isdigit() else name #不含结尾数字的名称name = pname+str(len([n for n in mtddic.keys() if n.startswith(pname)])) #新名称mtddic[name] = bodyif memberList:mmblst = dic['memberList']mmblst.extend(memberList)else: #对象不存在,创建标题,方法字典、成员变量列表objects[titlename] = {'intro':titleintro, #'''methodDict':methodDict,#intro reqargs, optArgs, returns'memberList':memberList, #[[n,i,v],[n,i,v]]  []}elif titletype =='module':if titlename in modules:  # 对象已经存在,忽略标题,合并方法,合并成员变量dic = modules[titlename]  # 对象字典if methodDict:mtddic = dic['methodDict']for name, body in methodDict.items():if name in mtddic:  # 方法名已存在if mtddic[name] == body:continueelse:pname = name[:-1] if name[-1].isdigit() else name  # 不含结尾数字的名称name = pname + str(len([n for n in mtddic.keys() if n.startswith(pname)]))  # 新名称mtddic[name] = bodyif memberList:mmblst = dic['memberList']mmblst.extend(memberList)else:  # 对象不存在,创建标题,方法字典、成员变量列表modules[titlename] = {'intro': titleintro,  # '''methodDict': methodDict,  # intro reqargs, optArgs, returns'memberList': memberList,  # [[n,i,v],[n,i,v]]  []}else:continueexcept:print(fname)failed.append(os.path.join(dirPath,fname))# 更新所有构造器--只针对 objectsfor fname in os.listdir(dirPath):if fname.endswith('.pkl'):# try:with open(os.path.join(dirPath, fname), 'rb') as fp:titleList, methodDict, constructDict, memberList = pickle.load(fp)titletype, titlename, titleintro = titleListif constructDict:for name,body in constructDict.items():if body['paths']:for path in body['paths']:objectName = recoverObjectFromPathString(path)if not objectName: continueif objectName in objects:  # 对象已经存在,添加方法dic = objects[objectName]  # 对象字典mtddic = dic['methodDict']if name in mtddic: # 方法名已存在if mtddic[name] == body:passelse:pname = name[:-1] if name[-1].isdigit() else name  # 不含结尾数字的名称name = pname + str(len([n for n in mtddic.keys() if n.startswith(pname)]))  # 新名称mtddic[name] = bodyelse:  # 对象不存在,创建标题,方法字典、成员变量列表objects[objectName] = {'intro': '',  # '''methodDict': {name:body},  # intro reqargs, optArgs, returns'memberList': [],  # [[n,i,v],[n,i,v]]  []}# except:#     passmodules = list(modules.items())objects = list(objects.items())modules.sort()objects.sort()writeObjects(objects)# writeModules(modules)if failed:with open('failed.txt','w') as fp:for i in failed:fp.write('%s\n' % i)#写入函数
def writeFunction2file(fp, funcName,funcBody):intro = funcBody['intro']reqArgs = funcBody['reqArgs']optArgs = funcBody['optArgs']returns = funcBody['returns']if reqArgs:reqstr = ', '.join([i[0] for i in reqArgs])if optArgs:optstr = ', '.join(['%s=%s' % (i[0], i[2]) for i in optArgs])argstr = ', '.join([reqstr, optstr])else:argstr = reqstrelse:if optArgs:optstr = ', '.join(['%s=%s' % (i[0], i[2]) for i in optArgs])argstr = optstrelse:argstr = ''if funcName in ('print','import','class','def'):funcName = funcName + '_'fp.write('def %s(%s):\n' % (funcName, argstr))fp.write('    """\n')fp.write('    %s\n\n' % cutInfo(intro))if reqArgs:for arg, info in reqArgs:fp.write('    :param %s: %s\n' % (arg, cutInfo(info)))if optArgs:for arg, info, value in optArgs:fp.write('    :param %s: %s\n' % (arg, cutInfo(info)))if returns:value, info = returnsfp.write('    :return %s: %s\n' % (value, cutInfo(info)))fp.write('    """\n')if returns:value, info = returnsfp.write('    return %s\n' % (value,))
#写入方法
def writeMethod2file(fp,funcName,funcBody):intro = funcBody['intro']reqArgs = funcBody['reqArgs']optArgs = funcBody['optArgs']returns = funcBody['returns']if reqArgs:reqstr = ', '.join([i[0] for i in reqArgs])if optArgs:optstr = ', '.join(['%s=%s' % (i[0], i[2]) for i in optArgs])argstr = ', '.join(['self',reqstr, optstr])else:argstr = ', '.join(['self',reqstr])else:if optArgs:optstr = ', '.join(['%s=%s' % (i[0], i[2]) for i in optArgs])argstr = ', '.join(['self', optstr])else:argstr = 'self'if funcName in ('print','import','class','def'):funcName = funcName + '_'fp.write('    def %s(%s):\n' % (funcName, argstr))fp.write('        """\n')fp.write('        %s\n\n' % cutInfo(intro))if reqArgs:for arg, info in reqArgs:fp.write('        :param %s: %s\n' % (arg, cutInfo(info)))if optArgs:for arg, info, value in optArgs:fp.write('        :param %s: %s\n' % (arg, cutInfo(info)))if returns:value, info = returnsfp.write('        :return %s: %s\n' % (value, cutInfo(info)))fp.write('        """\n')if returns:value, info = returnsfp.write('        return %s\n' % (value,))
def _writeRepositoryClass2file(fp,className,classBody,objects):intro = classBody['intro']methodDict = classBody['methodDict']memberList = classBody['memberList']fp.write('class %s(tuple):\n' % className)fp.write('    def __init__(self, rtype="", *args, **kwargs):\n')fp.write('        tuple.__init__(self,*args,**kwargs)\n')fp.write('        self.rtype=rtype\n')fp.write('    def items(self):\n')fp.write('        return (("key",self.__getitem__(0)),)\n')fp.write('    def keys(self):\n')fp.write('        return ("key",)\n')fp.write('    def values(self):\n')fp.write('        return (self.__getitem__(0),)\n')fp.write('    def __getitem__(self,key):\n')fp.write('        if not self.rtype:\n')fp.write('            return None\n')for name, dic in objects:fp.write('        elif rtype=="%s":\n' % (name,))fp.write('            return %s()\n' % (name,))funcNames = list(methodDict.keys())funcNames.sort()for funName in funcNames:funBody = methodDict[funName]writeMethod2file(fp,funName,funBody)
def _writeArrayClass2file(fp,className,classBody):intro = classBody['intro']methodDict = classBody['methodDict']memberList = classBody['memberList']fp.write('class %s(tuple):\n' % className)fp.write('    def __init__(self, *args, **kwargs):\n')fp.write('        tuple.__init__(self,*args,**kwargs)\n')fp.write('        """%s"""\n' % cutInfo(intro))fp.write('    def __getitem__(self,key):\n')fp.write('        return %s()\n' % className[:-5])if memberList:memberList.sort()for name,info,value in memberList:fp.write('        self.%s = %s # %s\n' % (name.strip(),value,cutInfo(info)))funcNames = list(methodDict.keys())funcNames.sort()for funName in funcNames:funBody = methodDict[funName]writeMethod2file(fp,funName,funBody)
#写入类
def writeClass2file(fp,className,classBody):intro = classBody['intro']methodDict = classBody['methodDict']memberList = classBody['memberList']fp.write('class %s(object):\n' % className)fp.write('    def __init__(self, *args, **kwargs):\n')fp.write('        """%s"""\n' % cutInfo(intro))if memberList:memberList.sort()for name,info,value in memberList:fp.write('        self.%s = %s # %s\n' % (name.strip(),value,cutInfo(info)))funcNames = list(methodDict.keys())funcNames.sort()for funName in funcNames:funBody = methodDict[funName]writeMethod2file(fp,funName,funBody)#写入abauqs对象
def writeObjects(objects,subdir='agent'):if not os.path.exists(subdir):os.mkdir(subdir)with open(os.path.join(subdir,'abaqus.py'),'w',encoding='utf-8') as fp:fp.write('# -*- coding:utf-8 -*-\n')fp.write('from abaqusConstants import *\n')for objectName, objectDict in objects:if objectName == 'Abaqus':methodDict = objectDict['methodDict']funNames = list(methodDict.keys())funNames.sort()for funName in funNames:if funName[0].islower():writeFunction2file(fp, funName, methodDict[funName])elif objectName == 'Repository':_writeRepositoryClass2file(fp,objectName,objectDict,objects)elif objectName.endswith('Array'):_writeArrayClass2file(fp,objectName,objectDict)else:writeClass2file(fp,objectName,objectDict)fp.write('mdb = Mdb()\n')fp.write('session = Session()\n')
#写入模块
def writeModules(modules,subdir='agent'):if not os.path.exists(subdir):os.mkdir(subdir)for modulename,modulebody in modules:with open(os.path.join(subdir,modulename+'.py'),'w',encoding='utf-8') as fp:fp.write('# -*- coding:utf-8 -*-\n')info = modulebody['intro']fp.write('"""\n%s\n"""\n' % cutInfo(info))methodDict =modulebody['methodDict']funNames = list(methodDict.keys())funNames.sort()for funName in funNames:writeFunction2file(fp, funName, methodDict[funName])#无成员pass
#解析单个html文件
def parseHtmlFile(fileName):soup = BeautifulSoup(open(fileName,'r',encoding='utf-8'), 'lxml')titleList = getTitle(soup)methodDict = getMethods(soup)constructDict = getConstructs(soup)memberList = getMembers(soup)return  titleList, methodDict, constructDict, memberList#写入单个文件--调试
def writeHtmlFile(fileName):titleList, methodDict, constructDict, memberList = parseHtmlFile(fileName)if not titleList[0]: returnwith open(fileName+'.txt','w',encoding='utf-8') as fp:fp.write('===========================titleList:\n')fp.write('titletype:%s\n' % titleList[0])fp.write('titlename:%s\n' % titleList[1])fp.write('titleintro:%s\n' % titleList[2])fp.write('===========================methodDict:\n')if methodDict:for methodName,body in methodDict.items():fp.write('--------------%s--------------\n' % methodName)fp.write('intro:%s\n' % body['intro'])fp.write('++++reqArgs:\n')if body['reqArgs']:for arg,info in body['reqArgs']:fp.write('%-20s  %s\n' % (arg,info))fp.write('++++optArgs:\n')if body['optArgs']:for arg,info,value in body['optArgs']:fp.write('%-20s = %-20s  %s\n' % (arg,value,info))fp.write('++++returns\n')fp.write('%s  %s\n' % tuple(body['returns']))fp.write('===========================constructDict:\n')if constructDict:for name,body in constructDict.items():fp.write('--------------%s--------------\n' % name)fp.write('intro:%s\n' % body['intro'])fp.write('++++paths:\n')if body['paths']:for path in body['paths']:fp.write('%s\n' % path)fp.write('++++reqArgs:\n')if body['reqArgs']:for arg,info in body['reqArgs']:fp.write('%-20s  %s\n' % (arg,info))fp.write('++++optArgs:\n')if body['optArgs']:for arg,info,value in body['optArgs']:fp.write('%-20s = %s  %s\n' % (arg,value,info))fp.write('++++returns\n')value,info = body['returns']fp.write('%s  %s\n' % (value,info))fp.write('===========================members:\n')if memberList:for name,info,value in memberList:fp.write('%-20s  %s  %s\n' % (name, value, info))with open(fileName+'.pkl','wb') as fp:pickle.dump([titleList, methodDict, constructDict, memberList], fp)
#写入文件夹--调试
def writeDir(dirPath):cnt = 0for fname in os.listdir(dirPath):lfname = fname.lower()if lfname.endswith('cpp.htm') or lfname.endswith('cpp.html'):continueif lfname.endswith('htm') or lfname.endswith('html'):cnt+=1if cnt%10 == 0: print('%d' % cnt)try:writeHtmlFile(os.path.join(dirPath,fname))except:print(os.path.join(dirPath,fname))raise
#查看所有路径
def writePaths(dirPath):paths = []titles = []for fname in os.listdir(dirPath):if fname.endswith('.pkl'):with open(os.path.join(dirPath,fname),'rb') as fp:titleList, methodDict, constructDict, memberList = pickle.load(fp)titles.append(titleList[:2])if constructDict:for name,body in constructDict.items():if body['paths']:paths.extend(body['paths'])upaths=list(set(paths))upaths.sort()with open('paths.txt', 'w') as fp:for path in upaths:fp.write('%s\n' % path)with open('paths.remap.txt','w') as fp:for path in upaths:fp.write('%s   %s\n' % (recoverObjectFromPathString(path),path))with open('titles.txt','w') as fp:for t,n in titles:fp.write('type:%s - name:%s\n' % (t,n))
#从访问路径恢复对象类型
def recoverObjectFromPathString(path):if ' ' in path: #模块return  'Abaqus'if '.' not in path: #单个对象,全部归入 abaqus 模块,自身就是构造器#Leaf displayGrooupMDBToolset#Leaf displayGrooupODBToolset#ElemType mesh#Region regiontoolset# caePrefsAccess caePrefsAccessreturn 'Abaqus'txt = path.split('.')[-2].strip()txt = re.search('\w+',txt).group() #清理中括号# hes -->h# les --> le# des --> de# mes --> me# ses -> s# ies --> y# res --> reif txt == 'iterations': return 'AdaptivityIteration'if txt == 'viscous': return 'Viscous'if txt == 'eos': return 'Eos'if txt == 'odbAccess': return 'OdbAccess'if txt == 'sketches': return  'ConstrainedSketch'if txt.endswith('hes'): return txt[0].upper() + txt[1:-2]if txt.endswith('les'): return txt[0].upper() + txt[1:-1]if txt.endswith('des'): return txt[0].upper() + txt[1:-1]if txt.endswith('mes'): return txt[0].upper() + txt[1:-1]if txt.endswith('ses'): return txt[0].upper() + txt[1:-2]if txt.endswith('ies'): return txt[0].upper() + txt[1:-3] + 'y'if txt.endswith('res'): return txt[0].upper() + txt[1:-1]if txt.endswith('s'): return txt[0].upper() + txt[1:-1]if txt == 'rootAssembly': return 'Assembly'return txt[0].upper()+txt[1:]# print(path)# return txt#获取标题
def getTitle(soup): #'', '',''    type,name,intro# title = soup.title.texttry:title = soup.h1.textif not title: return '','',''except:return '', '', ''if 'object' in title:titleType = 'object'titleName = re.search('\w+', title).group()elif 'module' in title:titleType = 'module'titleName = re.search('\w+', title).group()else:titleType = ''titleName = ''a = soup.find(name='table', class_='DocThemeIntro')# titleIntro = re.sub('\s+', ' ', a.text) if a else ''lst = []for i in a.strings:if 'topics' in i:breakelse:lst.append(i)txt = '\n'.join(lst)titleIntro = re.sub('\s+', ' ', txt)return titleType, titleName, titleIntro
#获取返回值
def getRtnArgs(rtndiv): #value, valueinfoif rtndiv.attrs['class'][-1] == 'nopyreturnsect':return [None, '']else: #pyreturnsectlst = []for i in rtndiv.contents:if i.name != 'h3':try:lst.append(i.text)except:passvalueinfo = '\n'.join(lst)if valueinfo:valueinfo = re.sub('\s+',' ',valueinfo)return [getMemberType(valueinfo), valueinfo ]else:return [None, '']
#获得变量类型
def getMemberType(instr): #变量或成员的说明# 默认值的表述方式# The whole assembly is queried by default.# The default value is LOW.# The default value is False.# The default value is an integer.# The user-specified density should be greater than 0.# By default if the moments of inertia are not being evaluated about the center of mass, they will be evaluated about the origin.# 参数类型的说明# A MeshElementArray, CellArray, FaceArray, Ed# A SymbolicConstant specifying     符号# A String specifying the name      字符串# A Boolean specifying whether      布尔值# A positive Integer specifying     正整数# An Int specifying that feature    整数# A Float specifying which gives a  浮点数# A double value specifying the     浮点数# A tuple of three floats specifying th   浮点数元组   list  List  Tuple# A tuple of dictionary objects           字典元组# A sequence of Ints.                    序列# A sequence of Strings# A sequence of PartInstance objects# A sequence of PartInstance objects# A ConnectorOrientationArray object.# A Dictionary object with the following items  字典# A VertexArray object specifyi# An EdgeArray object specifyin# A MeshElementArray object spe# A MeshNodeArray object specif# A PartInstance object specify# An EngineeringFeature object.# A ConnectorOrientationArray o# A SectionAssignmentArray obje# A repository of PartInstance objects# A repository of Datum objects# A repository of Feature objec# A repository of Surface objec# A repository of Set objects.# A repository of Skin objects# A repository of Stringer obje# A repository of ReferencePoin# A repository of ModelInstanceinstr = re.sub('\s+',' ',instr)# 有默认值的情况,直接返回取值rst = ''if 'Possible values' in instr:r = re.findall('Possible values are (\w+)', instr)if r: rst = r[0]if 'The default value is' in instr:r = re.findall('The default value is (\w+)', instr)if r: rst = r[0]if rst.isupper() or rst.isdigit(): #符号常量 数字return rsttry:eval(rst)return rstexcept:# return None #抑制定义顺序不同导致的出错passptn = re.compile('\w+')rst = ptn.findall(instr)if len(rst) < 2:# print(instr)# raise(ValueError, instr)return  None#无默认值或无法识别默认值,用对象表示mType = rst[1]lmType = mType.lower()#内建类型if lmType in ('int',): return 0if lmType in ('float', 'double'): return 0.0if lmType in ('string', ): return 'str()'if lmType in ('boolean', 'bool','abaqusboolean'): return Trueif lmType in ('dictionary', 'dict'): return 'dict()'if lmType in ('tuple', 'list', 'sequence'): return 'tuple()'if lmType in ('positive', 'negative'):if rst[2] == 'Integer' :return 1else:return  0.0#Abaqus类型if mType[0].isupper():return mType+'()'elif lmType == 'repository':return 'Repository("%s")' % rst[3]# return 'Repository(%s)' % rst[3]else:# print mType, instr# raise TypeErrorreturn  None
#获取所有方法
def getMethods(soup): #{}  {xxx}methodDict = {}for mtd in soup.find_all(class_="pymethodsect"):#方法名称methodName = re.search('\w+',mtd.h2.text).group() #方法名及参数#方法主体# methodBody = mtd.div #pymethodsect-bodymethodBody = mtd.find(class_='pymethodsect-body') #pymethodsect-bodyif not methodBody:print('Method %s is invalid!' % methodName)# for parent in soup.parents:#     print(parent.text)# raisecontinue#方法介绍lst = []for i in methodBody.contents:if i.name != 'div':try:lst.append(i.text)except:passelse:breakmethodIntro = '\n'.join(lst)methodIntro = re.sub('\s+', ' ', methodIntro)#无参数# rst = methodBody.find(class_='nopymethodarglist')# rst = methodBody.find(class_=['nopymethodarglistreq', 'pymethodarglistreq', 'nopymethodarglistopt', 'pymethodarglistopt'])# if rst:reqArgs = []optArgs = []#必须参数#无 nopymethodarglistreq#有 pymethodarglistreqreqdiv = methodBody.find(class_='nopymethodarglistreq')if reqdiv: reqArgs = []reqdiv = methodBody.find(class_='pymethodarglistreq')if reqdiv:dl = reqdiv.dlif dl:names = [i.text.strip() for i in dl.find_all(name='dt')]infos = [re.sub('\s+', ' ', i.text) for i in dl.find_all(name='dd')]reqArgs = list(zip(names, infos))#可选参数#无 nopymethodarglistopt#有 pymethodarglistoptoptdiv = methodBody.find(class_='nopymethodarglistopt')if optdiv: optArgs = []optdiv = methodBody.find(class_='pymethodarglistopt')if optdiv:dl = optdiv.dlif dl:names = [i.text.strip() for i in dl.find_all(name='dt')]infos = [re.sub('\s+', ' ', i.text) for i in dl.find_all(name='dd')]values = [getMemberType(v) for v in infos]  # 默认值optArgs = list(zip(names, infos, values))#返回值# 有 pyreturnsect# 无 nopyreturnsectrtndiv = methodBody.find(class_='pyreturnsect')if rtndiv:returns = getRtnArgs(rtndiv)else:  # nopyreturnsectreturns = [None, '']  # value, infoif methodName in methodDict:print('%s already exists' % methodName)lst = [key for key in methodDict.keys() if key.startswith(methodName)]methodDict[methodName + str(len(lst))] = {'intro': methodIntro,  # 方法介绍 str'reqArgs': reqArgs,  # 必须参数表 []  [[arg1,info1],[arg2,info2]]'optArgs': optArgs,  # 可选参数表 []  [[arg1,info1,value1],[arg2,info2,value2]]'returns': returns,  # 返回值列表 [None, '']   [Value, intor]}else:methodDict[methodName] = {'intro': methodIntro,  # 方法介绍 str'reqArgs': reqArgs,  # 必须参数表 []  [[arg1,info1],[arg2,info2]]'optArgs': optArgs,  # 可选参数表 []  [[arg1,info1,value1],[arg2,info2,value2]]'returns': returns,  # 返回值列表 [None, '']   [Value, intor]}return methodDict
#获取构造器
def getConstructs(soup): #{}  {xxx}constructDict = {}for mtd in soup.find_all(class_="pyconstructsect"):#构造器名称constructName = re.search('\w+',mtd.h2.text).group() #方法名及参数#构造器主体# constructBody = mtd.div #pyconstructsect-bodyconstructBody = mtd.find(class_='pyconstructsect-body') #pyconstructsect-bodyif not constructBody:print('Construct %s is invalid!' % constructName)continue#构造器介绍lst = []for i in constructBody.contents:if i.name != 'div':try:lst.append(i.text)except:passelse:breakstructIntro = '\n'.join(lst)structIntro = re.sub('\s+', ' ', structIntro)#构造器访问路径 列表path = constructBody.find(class_='pypath')try:paths = []for ppre in path.find_all(name='pre'):if ppre is None: continuetxt = re.sub('\\\\\n','',ppre.text)# paths = re.findall('[\.\[\]\w]+',txt)paths.extend([i.strip() for i in txt.split('\n')])for ppre in path.find_all(name='p'):if ppre is None: continuetxt = re.sub('\\\\\n','',ppre.text)# paths = re.findall('[\.\[\]\w]+',txt)paths.append(txt)except:paths = []#无参数# rst = constructBody.find(class_='nopyctorarglist')# rst = constructBody.find(class_=['nopyctorarglistreq', 'pyctorarglistreq', 'nopyctorarglistopt', 'pyctorarglistopt'])# if rst:reqArgs = []optArgs = []#必须参数# 无参数 nopyctorarglistreq# 1个   pyctorarglistreq# 多个   pyctorarglistreqreqdiv = constructBody.find(class_='nopyctorarglistreq')if reqdiv: reqArgs = []reqdiv = constructBody.find(class_='pyctorarglistreq')if reqdiv:dl = reqdiv.dlif dl:names = [i.text.strip() for i in dl.find_all(name='dt')]infos = [re.sub('\s+', ' ', i.text) for i in dl.find_all(name='dd')]reqArgs = list(zip(names, infos))#可选参数# 无参数 nopyctorarglistopt# 1个   pyctorarglistopt# 多个  pyctorarglistoptoptdiv = constructBody.find(class_='nopyctorarglistopt')if optdiv: optArgs = []optdiv = constructBody.find(class_='pyctorarglistopt')if optdiv:dl = optdiv.dlif dl:names = [i.text.strip() for i in dl.find_all(name='dt')]infos = [re.sub('\s+', ' ', i.text) for i in dl.find_all(name='dd')]values = [getMemberType(v) for v in infos] #默认值optArgs = list(zip(names, infos, values))# 返回值# 有 pyreturnsect# 无 nopyreturnsectrtndiv = constructBody.find(class_='pyreturnsect')if rtndiv:returns = getRtnArgs(rtndiv)else: #nopyreturnsectreturns = [None,''] #value, infoif constructName in constructDict:print('%s already exists' % constructName)# print(str(paths))lst = [key for key in constructDict.keys() if key.startswith(constructName)]constructDict[constructName+str(len(lst))] = {'intro': structIntro,  # 构造器介绍 str'paths': paths,  # 路径列表 [path1, path2]  []'reqArgs': reqArgs,  # 必须参数表 []  [[arg1,info1],[arg2,info2]]'optArgs': optArgs,  # 可选参数表 []  [[arg1,info1,value1],[arg2,info2,value2]]'returns': returns,  # 返回值列表 [None, '']   [Value, intor]}else:constructDict[constructName] = {'intro':structIntro, #构造器介绍 str'paths':paths, #路径列表 [path1, path2]'reqArgs':reqArgs, #必须参数表 []  [[arg1,info1],[arg2,info2]]'optArgs':optArgs, #可选参数表 []  [[arg1,info1,value1],[arg2,info2,value2]]'returns':returns, #返回值列表 [None, '']   [Value, intor]}return constructDict
#获取所有成员
def getMembers(soup): #[]  [[name,info,value],[name,info,value]]mmb = soup.find(class_="pymembersect")if mmb is None: return  []body = mmb.find(class_='pymembersect-body')if body is None: return []dl = body.find(class_='pymemberdatalist')if dl is None: return []names = [i.text for i in dl.find_all(name='dt')]infos = [re.sub('\s+', ' ', i.text) for i in dl.find_all(name='dd')]values = [getMemberType(s) for s in infos]return list(zip(names, infos, values))#清理临时文件
def clean(pathDir):for fname in os.listdir(pathDir):lfname = fname.lower()if lfname.endswith('.htm.txt') or lfname.endswith('.html.txt') or lfname.endswith('.pkl'):os.remove(os.path.join(pathDir,fname))for fname in ('failed.txt', 'paths.txt','titles.txt', 'paths.remap.txt'):if os.path.exists(fname):os.remove(fname)if __name__ == '__main__':pathDir = r'C:\Program Files\Dassault Systemes\SIMULIA2020doc\English\SIMACAEKERRefMap'writeDir(pathDir)writePaths(pathDir)partseDir(pathDir)clean(pathDir)

2. gen_AbaqusConstants_Agent.py

# -*- coding:utf-8 -*-
'''
run this file in abaqus cae or abaqus python ,generate abaqusConstants agent.by alienwxy 2020-05-05
'''
import types,os
import abaqusConstants
subdir = 'agent'
if not os.path.exists(subdir):os.mkdir(subdir)
with file(os.path.join(subdir,'abaqusConstants.py'),'w') as fp:fp.write('class SymbolicConstant(object):\n')fp.write('    def __init__(self,instr=""):\n')fp.write('        self.text = instr\n')for name in dir(abaqusConstants):if name.isupper():fp.write('%s = SymbolicConstant("%s")\n' % (name,name))fp.write('NO = OFF = False\n')fp.write('ON = YES = True\n')

3. getAbaquStructure.py

# -*- coding:utf-8 -*-
'''abaqus class method agent for pyCharm1. run this file in abaqus cae
2. copy  the files in abaqusAgent folder to xxx/Lib/site-packages
3. enjoy!by alienwxy 2020-04-27  Usage:from abaqus import *
from abaqusConstants import *
import mesh
'''
import types,os
import abaqusConstantsdef getMethodObjectList():import docstringLookup as dlkmethodDict = {}objectDict = {}#get dictionfor name in dir(dlk):module = getattr(dlk,name)if type(module) is types.ModuleType:if hasattr(module,'objectDocstringTable'):dic = getattr(module,'objectDocstringTable')objectDict.update(dic)if hasattr(module,'methodDocstringTable'):dic = getattr(module,'methodDocstringTable')methodDict.update(dic)methodList = [(k,v) for k,v in methodDict.items()]objectList = [(k,v) for k,v in objectDict.items()]methodList.sort()objectList.sort()return methodList,objectListdef write2Txt(methodList,objectList,subDir='abaqusAgent'):if not os.path.exists(subDir):os.mkdir(subDir)with file(os.path.join(subDir,'abaqusMethodList.txt'),'w') as fp:for k,v in methodList:fp.write('%-60s\t%s\n' % (k,v))with file(os.path.join(subDir,'abaqusObjectList.txt'),'w') as fp:for k,v in objectList:fp.write('%-60s\t%s\n' % (k,v))def getMethodArgs(argstr):# 'name, edges <, startPoint, flipDirection, pointCreationMethod>'# 'stepName <, loadCaseNames, magnitude, amplitude>'# 'stepName <, allowGrowth>'# '<useCut, printResults>'# '<useCut>'# 'center, radius'# 'mask'# 'keyword argument not implemented'# ''argstr = argstr.strip()if argstr == '' or 'not implemented' in argstr:return ''if '<' not in argstr:return argstr#optionalif argstr.startswith('<'):optargs =  argstr.lstrip('<').rstrip('>').strip()return ', '.join(['%s=None' % (arg.strip(),) for arg in optargs.split(',')])keyargs,optargs = argstr.split('<,')keyargs = keyargs.strip()optargs = optargs.strip().rstrip('>').strip()if ',' in optargs: #multi optargs = ', '.join(['%s=None' % (arg.strip(),) for arg in optargs.split(',')])else:optargs = '%s=None' % optargsreturn '%s, %s' % (keyargs,optargs)
def selfMethodArgs(argstr):s = getMethodArgs(argstr)if s:return 'self, ' + selse:return 'self'def write2Python(methodList,subDir='abaqusAgent'):# XYPlot.previous# sys.modules['odbAccess'].openOdb   odbAccess 的函数# Abaqus.Mdb      abaqus 模块函数# Odb.Part        odbAccess 模块 的类 以Odb开头# OdbStep.setDefaultFieldmodules      = {} #moduleName: [ [functionName,functionArgs] ]abaqusFunctions = [] #[ [functionName,functionArgs]]odbAccesFunctions = [] #[ [functionName,functionArgs]]abaqusConstantsFunctions = []#[ [functionName,functionArgs]]odbAccesClasses = {} #className: [[methodName,methodArgs]]classes      = {} #className: [[methodName,methodArgs]]for name,lst in methodList:if 'modules' in name:if 'odbAccess' in name:odbAccesFunctions.append([name.split('.')[-1], getMethodArgs(lst[0])])elif 'abaqusConstants' in name:abaqusConstantsFunctions.append([name.split('.')[-1], getMethodArgs(lst[0])])else:modName = name.split("'")[1]if modName not in modules:modules[modName] = []modules[modName].append([name.split('.')[-1], getMethodArgs(lst[0])])elif name.startswith('Odb'): #odbAccesClassesclsName,methodName = name.split('.')if clsName not in odbAccesClasses:odbAccesClasses[clsName] = []odbAccesClasses[clsName].append([methodName, selfMethodArgs(lst[0])])elif name.startswith('Abaqus'): #abaqusFunctionsfuncName = name.split('.')[-1]if funcName != 'Mdb': #Mbd('<pathName>')abaqusFunctions.append([name.split('.')[-1], getMethodArgs(lst[0])])else:clsName,methodName = name.split('.')if clsName not in classes:classes[clsName] = []classes[clsName].append([methodName, selfMethodArgs(lst[0])])if not os.path.exists(subDir):os.mkdir(subDir)def writeAbaqusConstants():with file(os.path.join(subDir,'abaqusConstants.py'),'w') as fp:for fname,fargs in abaqusConstantsFunctions:fp.write('def %s(%s):\n' % (fname,fargs))fp.write('    pass\n')  for name in dir(abaqusConstants):if name.isupper():fp.write('%s = "%s"\n' % (name,name))def writeOdbAccess():with file(os.path.join(subDir,'odbAccess.py'),'w') as fp:writeClasses(fp,odbAccesClasses)for fn,fa in odbAccesFunctions:writeFunction(fp,fn,fa)def writeModules():for module in modules.keys():with file(os.path.join(subDir,module+'.py'),'w') as fp:for n,a in modules[module]:writeFunction(fp,n,a)def writeAbaqus():with file(os.path.join(subDir,'abaqus.py'),'w') as fp:writeClasses(fp,classes)writeClasses(fp,odbAccesClasses)for n,a in abaqusFunctions:writeFunction(fp,n,a)fp.write('mdb = Mdb()\n')fp.write('session = Session()\n')writeAbaqusConstants()writeOdbAccess()writeModules()writeAbaqus()with file(os.path.join(subDir,'__init__.py'),'w') as fp:fp.write('\n')with file(os.path.join(subDir,'deleteAbaqusAgent.bat'),'w') as fp:for name in modules.keys():fp.write('del %s.py; %s.pyc\n' % (name,name))fp.write('del abaqus.py;abaqus.pyc;abaqusConstants.py;abaqusConsta.pyc;odbAccess.py;odbAccess.pyc\n')fp.write('del deleteAbaqusAgent.bat\n')
def writeFunction(fp,functionName,functionArgs):if functionName in ('print','as','if','class','def','None','True','False','for'):functionName += '_confilictWithPythonReserveVars'fp.write('def %s(%s):\n' % (functionName,functionArgs))fp.write('    pass\n')
def writeClass(fp,clsName,clsMethods):fp.write('class %s(object):\n' % clsName)fp.write('    def __init__(self, *args, **kwargs): pass\n')for mtdname,mtdarg in clsMethods:if mtdname in ('print','as','if','elif','else','class','def','None','True','False','for','while'):mtdname += '_confilictWithPythonReserveVars'fp.write('    def %s(%s):\n' % (mtdname, mtdarg))fp.write('        return %s(%s)\n' % (mtdname, mtdarg))fp.write('    pass\n')
def writeClasses(fp,classesDict):keys = classesDict.keys()keys.sort()for key in keys:value = classesDict[key]writeClass(fp,key,value)if __name__ == '__main__':m,o = getMethodObjectList()write2Txt(m,o)write2Python(m)

4.transferAbaquGUIFunctionFromDocumentation.py

# -*- coding:gbk -*-
import re
def handleFlags(fileName='flags.txt'):''' DEF_SWATCH_CURSOR Color swatch drag cursor.DEF_MOVE_CURSOR Move cursor.DEF_DRAGH_CURSOR Resize horizontal edge.DEF_DRAGV_CURSOR Resize vertical edge.DEF_DRAGTL_CURSOR Resize upper-leftcorner.DEF_DRAGBR_CURSOR Resize bottom-right corner.DEF_DRAGTR_CURSOR Resize upper-right corner.'''with file(fileName,'r') as fp:dat = [l.strip() for l in fp.readlines() if len(l.strip()) > 0]nums = int(len(dat)/2)with file(fileName+'.py','w') as fp:for i in range(nums):fp.write('%-40s = %2d # %s\n' % (dat[2*i], i, dat[2*i+1]))def handleFunctions(fileName='temp.txt'):'''不含尾部flagsAFXAppThis class is responsible forproviding some high-level GUI controlmethods.AFXApp(appName=Abaqus/CAE, vendorName=SIMULIA,productName='', majorNumber=-1, minorNumber=-1,updateNumber=-1, prerelease=False)Constructor.Argument Type Default Description appName String Abaqus/CAE Application registry key. vendorName String SIMULIA Vendor registry key. productName String '' Product name. majorNumber Int -1 Version number. minorNumber Int -1 Release number. updateNumber Int -1 Update number. prerelease Bool False Official/Prerelease flag. create()Creates windows for theapplication.Reimplemented fromFXApp.getAFXMainWindow()Returns a pointer to theAFXMainWindow.getBasePrerelease()Returns True if the base product isa prerelease.getBaseProductName()Returns the base productname.getBaseVersionNumbers(majorNumber, minorNumber,updateNumber)Returns the base product's major,minor, and update numbers.Argument Type Default Description majorNumber Int   Version number. minorNumber Int   Release number. updateNumber Int   Update number. getKernelInitializationCommand()Returns the command string thatwill be issued upon application startup.getPrerelease()Returns True if this is aprerelease.getProductName()Returns the productname.getVersionNumbers()Returns the major, minor, andupdate numbers.init(argc,argv)Initializes the application andconnects to the kernel.Argument Type Default Description argc Int     argv String     isLocked()Returns True if the GUI is lockedor False if otherwise.Reimplemented fromFXApp.isProductCAE()Returns True if the base product isAbaqus/CAE.isProductViewer()Returns True if the base product isAbaqus/Viewer.isStudentEdition()Returns True if the base product isa student edition.lock()Locks the GUI (normally used duringcommand and mode processing).run()Runs the main application eventloop until stop() is called,.Reimplemented fromFXApp.runUntil(condition)Run an event loop till some flagbecomes non-zero.Reimplemented fromFXApp.Argument Type Default Description condition Int     unlock()Unlocks theGUI.'''#清理空行with file(fileName,'r') as fp:dat = [l.strip() for l in fp.readlines() if len(l.strip())>0]def isMethodDefinitionLine(instr):if instr.endswith(')'):return Trueelse:return Falsewith file(fileName+'.py','w') as fp:isWritingFunction = FalseisAddParam = Falsefp.write('class %s(object):\n' % dat[0])fp.write('    """\n')fp.write('    %s\n' % dat[1])fp.write('    """\n')for l in dat[2:]:if isMethodDefinitionLine(l):isAddParam = Falseif isWritingFunction: fp.write('        """\n')isWritingFunction = Trueid = l.index('(')name = l[:id]arg = l[id+1:]# name,arg = l.split('(')if arg == ')':fp.write('    def %s(self):\n' % (name,))else:fp.write('    def %s(self, %s:\n' % (name,arg))fp.write('        """\n')else:if isWritingFunction:if l.startswith('Argument'):isAddParam = Truefp.write('        %s\n\n' % (l,))continueif isAddParam:fp.write('        @param %s\n\n' % (l,))continuefp.write('        %s\n\n' % (l,))else:fp.write('%s\n' % (l,))fp.write('        """\n')
# handleFunctions()
# handleFlags()def getUltiMate():import rewith file('abaqusGui.py','r') as fp:d = fp.read()ptn = re.compile('[A-Z]\w+')rst = ptn.findall(d)rs = [r for r in rst if r.isupper()]rs = list(set(rs))rs.sort()# with file('consts.txt','w') as fp:# for i,r in enumerate(rs):# fp.write('%-40s = %4d\n' % (r,i))return set(rs)
# getUltiMate()def getConstants2():import rewith file('abaqusGui.py','r') as fp:d = fp.readlines()lsts = [] #name, value, infofor l in d:if '=' in l and '#' in l and l.startswith('    '):lsts.append(l.lstrip())ptn = re.compile('[A-Z]\w+')clst = set([ptn.findall(l)[0] for l in lsts])aset = getUltiMate()aset = aset.difference(clst)with file('consts.txt','w') as fp:for i,v in enumerate(list(aset)):if len(v) >1:fp.write('%-40s = %4d\n' % (v,i))fp.write('\n'*4)for i,r in enumerate(lsts):fp.write(r)
# getConstants2()def transferParam():'''    @param owner    FXWindow改为 :param owner:   FXWindow'''with file('abaqusGui.py','r') as fp:d = fp.readlines()ptn = re.compile('\w+')with file('abaqusGui_new.py','w') as fp:for l  in d:if '@param' in l:l = l.replace('@',':')varName = ptn.findall(l)[1]l = l.replace(varName,varName+':')fp.write(l)
# transferParam()

ABAQUS内核及GUI方法的代理接口相关推荐

  1. Abaqus GUI程序开发之常用的Abaqus内核指令(一)

    说明:本篇帖子是博主学习Abaqus GUI开发的笔记,基于贾利勇主编的<ABAQUS GUI程序开发指南  Python语言 第一版>这本书,仅供学习参考使用. 书的配套资料下载:网盘资 ...

  2. cmd 调用webservice接口_c# 三种方法调用WebService接口

    1.引用*.wsdl文件 WebService服务端会提供wsdl文件,客户端通过该文件生成.cs文件以及生成.dll. 注意:若服务端只提供的了URL,在URL后面加上"?wsdl&quo ...

  3. proxy connect abort处理方法_Java代理设计模式(Proxy)的几种具体实现

    Proxy是一种结构设计模型,主要解决对象直接访问带来的问题,代理又分为静态代理和动态代理(JDK代理.CGLIB代理. 静态代理:又程序创建的代理类,或者特定的工具类,在平时开发中经常用到这种代理模 ...

  4. Java 的工厂方法及代理模式

    Java 的工厂方法及代理模式 工厂方法(FactoryMethod) 概述:定义一个用于创建对象的接口,让子类决定实例化哪一个类.FactoryMethod使一个类的实例化延迟到其子类. 适用性: ...

  5. cgblib 代理接口原理_Spring5参考指南-AOP代理

    AOP代理 通常来说Spring AOP有两种代理方式,一种默认的JDK代理,只能代理接口,一种是CGLIB代理,可以代理具体的类对象. SpringAOP默认为对AOP代理使用标准的JDK动态代理. ...

  6. Lambda表达式接口更新方法引用函数式接口Stream流

    Lambda表达式&接口更新&方法引用&函数式接口&Stream流 Lambda 1.程序启动三种不同的表现形式 2.Lambda表达式的标准格式 3.练习 Lambd ...

  7. Go 学习笔记(35)— Go 接口 interface (接口声明、接口初始化、接口方法调用、接口运算、类型断言、类型查询、空接口)

    1. 接口概念 接口是双方约定的一种合作协议.接口实现者不需要关心接口会被怎样使用,调用者也不需要关心接口的实现细节.接口是一种类型,也是一种抽象结构,不会暴露所含数据的格式.类型及结构. 接口内部存 ...

  8. 30分钟入门Java8之默认方法和静态接口方法

    2019独角兽企业重金招聘Python工程师标准>>> 30分钟入门Java8之默认方法和静态接口方法 作者:@JohnTsai 本文为作者原创,转载请注明出处:http://www ...

  9. 【Linux 内核 内存管理】优化内存屏障 ③ ( 编译器屏障 | 禁止 / 开启内核抢占 与 方法保护临界区 | preempt_disable 禁止内核抢占源码 | 开启内核抢占源码 )

    文章目录 一.禁止 / 开启内核抢占 与 方法保护临界区 二.编译器优化屏障 三.preempt_disable 禁止内核抢占 源码 四.preempt_enable 开启内核抢占 源码 一.禁止 / ...

  10. Spring-方法注入lookup、方法替换MethodReplacer接口

    问题 lookup方法注入 概述 实例 方法一 通过在配置文件中配置的方式实现 方法二 通过实现接口代码的方式实现 小结 方法替换MethodReplacer接口 概述 实例 小结 总结 问题 无状态 ...

最新文章

  1. DeepMind 的新强化学习系统,是迈向通用人工智能的一步吗?
  2. 使用jQuery的9个误区
  3. Python Numpy中返回下标操作函数-节约时间的利器
  4. c++ socket学习(1.5)
  5. python目录结构生成库,使用CMake构建平台无关的目录结构
  6. CAP 原则与 BASE 理论
  7. 字符串 读取西门子_【必学技能】自己动手——基于C#实现手机APP远程访问西门子PLC...
  8. 2017.7.27 bill的挑战 失败总结
  9. asp.net asp:TextBox控件绑定值后,获取不到新值问题解决方法
  10. 构建企业级DNS系统(五)bind9日志记录
  11. 原创 VPP使用心得(十六)静态路由添加流程
  12. 电子商务概论学习总结
  13. 惠普局域网共享打印机设置_打印机usb转网络?打印机共享怎么设置?怎样设置hp打印机共享器操作方法...
  14. 墓碑上的字符C语言,墓碑上常见的“故显考、故显妣、先考、先妣”,分别是什么意思?...
  15. Android面试准备复习之Android知识点大扫描 .
  16. 史上最强的烧脑合集!能全都搞懂的只有天才!
  17. 数据分析项目- 北京房价
  18. 使用sessionStorage实现页面间传值与传对象
  19. APP STORE又崩了?可以通过DNS解决!
  20. 适用于 PC 的最佳 Android 操作系统(2022 版)

热门文章

  1. 左对齐杨辉三角python_什么是左的错误?
  2. 自我成长与团队管理——一些总结
  3. 制作Nine-Patch图片
  4. 红橙黄绿青蓝紫 RGB值 16进制 、10进制
  5. windows10安装Erlang和RabbitMQ
  6. [150529](必看)档案挂靠与打回生源地、暂缓的对比 (广州)
  7. android recycleView嵌套recycleView下拉二级列表显示(带动画)
  8. PDF文件如何修改,怎么裁剪PDF页面
  9. 新标准的发布关联的液相色谱-三重四极杆质谱技术的联用
  10. 【爬虫】使用八爪鱼爬行百度地图美食店数据