首页 / 资讯中心 / 文章详情

Abaqus二次开发:内核与GUI双线协同实战指南

Abaqus二次开发:内核与GUI双线协同实战指南 ★ FEATURED ARTICLE
简介本资源为机械工业出版社出版的《Abaqus二次开发 内核与GUI应用实例详解》高清PDF电子书面向结构仿真工程师、高校理工科机械/航空航天/土木等学生及有限元二次开发初学者系统解决Abaqus内核功能扩展与定制化界面开发两大核心难题。全书共14章前两章夯实Python语言与二次开发基础第3–6章深入讲解Abaqus内核结构、数据类型、函数接口及三大典型应用实例第7–14章聚焦GUI Toolkit开发涵盖RSG对话框生成器、插件界面与主窗口二次开发并提供五个完整GUI实例。资源为1个PDF文件大小8.66MB内容完整覆盖理论、命令、代码与工程实践配套源码可通过封底扫码获取。目前已有1029人学习下载书中大量可运行示例、规范化编程提示及作者十余年一线开发经验凝练显著降低学习门槛助力读者快速构建自动化仿真流程与专业化交互工具。1. Abaqus二次开发不是写插件那么简单内核与GUI必须双线并行否则模型跑通了却没法批量交付你是不是也遇到过这种情况用Python脚本调Abaqus内核跑完一个参数化分析结果客户说“能不能点一下按钮就出报告”或者好不容易写了个GUI界面一点击就报错AttributeError: NoneType object has no attribute get查半天发现是内核还没初始化完GUI就去取结果这正是只盯内核或只做GUI的典型翻车现场。《Abaqus二次开发 内核与GUI应用实例详解》这个标题里的“内核与GUI”不是并列修饰而是强耦合依赖关系——内核负责真实计算逻辑、数据流和求解器控制GUI负责人机交互、参数校验、状态反馈和结果可视化二者通过CAE模型对象mdb/session、作业对象job和结果对象odb实时桥接。它解决的不是“会不会写Python”的问题而是“如何让仿真工程师摆脱重复建模、让设计部门直接驱动参数化分析流程”的落地瓶颈。适合三类人刚从高校转入企业、需要把毕业课题封装成工程工具的新人在某公司仿真组里被安排“把现有模板自动化”的中级工程师以及想构建跨项目仿真知识库、但卡在“每次改模型都要重开Abaqus”的技术负责人。这不是教你怎么点菜单而是教你亲手焊一条从参数输入→网格生成→求解控制→结果提取→PDF报告输出的完整流水线。2. 用Python脚本驱动Abaqus内核从最简命令到可复用的参数化建模框架Abaqus内核二次开发的本质是把交互式建模操作翻译成mdbModel Database对象上的方法调用链。它不依赖GUI进程可在无界面环境下运行如Linux服务器批处理但所有操作必须严格遵循Abaqus建模逻辑顺序先定义材料→再创建部件→装配→划分网格→设置分析步→定义载荷边界→提交作业。跳过任一环节或顺序错误都会在mdb.Job()创建时抛出IllegalStateError。2.1 最小可运行内核脚本绕过GUI启动直接生成带材料的立方体部件以下代码在Abaqus/CAE未启动状态下即可执行需配置好环境变量ABAQUS_BAT_PATH指向abq2022.bat等# run_core_only.py import sys import os # 关键必须显式导入abaqus模块且路径需正确 abaqus_path rC:\SIMULIA\Commands\abq2022.bat # Windows示例Linux为 /opt/simulia/Commands/abq2022 if not os.path.exists(abaqus_path): raise FileNotFoundError(fAbaqus command not found at {abaqus_path}) # 启动内核模式无GUI os.system(f{abaqus_path} cae noGUIcore_script.py)对应core_script.py内容# core_script.py —— 真正的内核逻辑无任何GUI依赖 from abaqus import * from abaqusConstants import * import regionToolset import part import material import section import assembly import step import interaction import load import mesh import job import sketch import visualization import xyPlot import displayGroupOdbToolset as dgo import connectorBehavior def create_cube_part(): # 1. 创建草图 s mdb.models[Model-1].ConstrainedSketch(name__profile__, sheetSize200.0) g, v, d, c s.geometry, s.vertices, s.dimensions, s.constraints s.setPrimaryObject(optionSTANDALONE) s.rectangle(point1(0.0, 0.0), point2(10.0, 10.0)) s.unsetPrimaryObject() # 2. 拉伸成部件3D实体 p mdb.models[Model-1].Part(nameCube-1, dimensionalityTHREE_D, typeDEFORMABLE_BODY) p.BaseSolidExtrude(sketchs, depth10.0) s.unsetPrimaryObject() # 3. 定义材料线弹性 mdb.models[Model-1].Material(nameSteel) mdb.models[Model-1].materials[Steel].Elastic(table((210000.0, 0.3), )) # 4. 创建截面属性并赋予部件 mdb.models[Model-1].HomogeneousSolidSection( nameSection-1, materialSteel, thicknessNone) region p.Set(facesp.faces, nameSet-1) p.SectionAssignment(regionregion, sectionNameSection-1) if __name__ __main__: # 清空默认模型避免命名冲突 mdb.models.changeKey(fromNameModel-1, toNameParametricModel) create_cube_part() print(✅ 内核建模完成立方体部件材料截面已生成)提示此脚本必须保存为.py文件且不能在Abaqus/CAE GUI中直接“Run Script”而必须通过abq2022.bat cae noGUIxxx.py方式调用。GUI中运行会因上下文缺失导致mdb对象为空。2.2 构建参数化建模函数把尺寸、材料、网格密度变成可传入变量硬编码尺寸如point1(0.0, 0.0)无法复用。我们将其抽象为函数接口def create_parametric_cube(length10.0, width10.0, height10.0, mat_nameSteel, E210000.0, nu0.3, mesh_size2.0): 创建参数化立方体部件 :param length: X方向长度 (mm) :param width: Y方向宽度 (mm) :param height: Z方向高度 (mm) :param mat_name: 材料名称 :param E: 弹性模量 (MPa) :param nu: 泊松比 :param mesh_size: 网格尺寸 (mm)影响后续mesh.seedPartInstance model_name ParametricModel if model_name not in mdb.models.keys(): mdb.Model(namemodel_name) # 草图动态使用传入尺寸 s mdb.models[model_name].ConstrainedSketch( name__profile__, sheetSizemax(length, width) * 2) s.rectangle(point1(0.0, 0.0), point2(length, width)) # 拉伸使用height p mdb.models[model_name].Part(namefCube_L{length}_W{width}_H{height}, dimensionalityTHREE_D, typeDEFORMABLE_BODY) p.BaseSolidExtrude(sketchs, depthheight) # 材料 mdb.models[model_name].Material(namemat_name) mdb.models[model_name].materials[mat_name].Elastic(table((E, nu), )) # 截面 sec_name fSection_L{length}_W{width}_H{height} mdb.models[model_name].HomogeneousSolidSection( namesec_name, materialmat_name, thicknessNone) region p.Set(facesp.faces, nameSet-AllFaces) p.SectionAssignment(regionregion, sectionNamesec_name) # 网格种子关键否则后续mesh.generateMesh会失败 a mdb.models[model_name].rootAssembly a.DatumCsysByDefault(CARTESIAN) a.Instance(nameCube-1, partp, dependentON) a.seedPartInstance(regions(a.instances[Cube-1], ), sizemesh_size, deviationFactor0.1) a.generateMesh(regions(a.instances[Cube-1], )) print(f✅ 参数化部件生成{p.name} | 尺寸({length}×{width}×{height}) | 网格尺寸{mesh_size}mm) return p # 调用示例 if __name__ __main__: cube_part create_parametric_cube(length50.0, width30.0, height20.0, mesh_size5.0)参数说明与经验deviationFactor0.1控制网格边长偏差容忍度默认0.1即允许±10%值越小网格越规整但耗时越长dependentON装配实例必须设为依赖型否则无法对其划分网格所有mdb.models[xxx]访问前务必确认模型存在否则KeyError建议用if model_name not in mdb.models.keys(): mdb.Model(namemodel_name)兜底seedPartInstance必须在generateMesh之前调用且size单位与模型单位一致Abaqus默认mm若建模用m需同步缩放。3. 用Abaqus GUI Toolkit构建交互界面从空白窗口到带参数校验的分析控制台Abaqus GUI Toolkit简称GUI不是Tkinter或PyQt而是基于Motif的专有界面库所有控件PushButton、TextField、ComboBox都必须注册到getInputs()函数中并通过getInputs()返回的字典与内核联动。它的核心约束是GUI生命周期完全由Abaqus主进程管理开发者不能自行mainloop()。3.1 最小GUI脚本弹出窗口并获取用户输入的长度值新建文件gui_launcher.py在Abaqus/CAE中通过File → Run Script执行注意此处必须用GUI模式运行# gui_launcher.py from abaqus import * from abaqusConstants import * import sys # 导入GUI Toolkit模块仅在GUI模式下可用 from caeModules import * from driverUtils import executeOnCaeStartup executeOnCaeStartup() # 定义GUI回调函数 def on_submit(button, widgetDict): 提交按钮回调读取输入值并触发内核建模 try: length float(widgetDict[length_field].getValue()) if length 0: raise ValueError(长度必须大于0) # 调用内核函数需确保core_script.py在同一目录 execfile(core_script.py) # Abaqus 2022及以前用execfile2023用exec(open(...).read()) print(f✅ GUI接收长度{length} mm已触发内核建模) except ValueError as e: from abaqusGui import sendCommand sendCommand(fprint(❌ 输入错误{str(e)})) except Exception as e: sendCommand(fprint(❌ 运行异常{str(e)})) def launch_gui(): 启动GUI窗口 from abaqusGui import getAFXApp, AFXMainWindow, AFXDialog, \ AFXTextField, AFXButton, AFXLabel, AFXVerticalAligner app getAFXApp() main_window app.getAFXMainWindow() # 创建对话框 dialog AFXDialog(main_window, 参数化立方体生成器, actionTargetNone, optsDIALOG_NORMAL) # 垂直布局容器 aligner AFXVerticalAligner(dialog) # 标签输入框 AFXLabel(aligner, 请输入立方体X方向长度 (mm):) length_field AFXTextField(aligner, 10, 10.0, 0, length_field) # 提交按钮 submit_btn AFXButton(aligner, 生成, targeton_submit, selectorAFXMode.ID_ACTIVATE, optsBUTTON_NORMAL) # 显示窗口 dialog.show() # 启动GUI launch_gui()注意execfile()在Abaqus 2023版本已被弃用需替换为with open(core_script.py, r) as f: exec(f.read())3.2 构建完整分析控制台集成参数输入、作业提交、ODB读取与结果绘图真实场景需要闭环GUI输参 → 内核建模求解 → GUI显示云图。我们扩展on_submit逻辑def on_submit_full(button, widgetDict): 增强版提交回调建模→网格→求解→结果可视化 try: # 1. 获取GUI参数 length float(widgetDict[length_field].getValue()) width float(widgetDict[width_field].getValue()) height float(widgetDict[height_field].getValue()) E float(widgetDict[E_field].getValue()) nu float(widgetDict[nu_field].getValue()) mesh_size float(widgetDict[mesh_field].getValue()) # 2. 调用内核建模复用2.2节函数 from core_script import create_parametric_cube # 假设已将函数移至core_script.py part create_parametric_cube(length, width, height, Steel, E, nu, mesh_size) # 3. 创建装配、分析步、载荷简化版固定一端另一端加1MPa压力 model mdb.models[ParametricModel] a model.rootAssembly a.Instance(nameCube-1, partpart, dependentON) # 分析步 model.StaticStep(nameStep-1, previousInitial, maxNumInc100, initialInc0.1, minInc1e-08, maxInc0.1) # 边界条件固定Z0面 f1 a.instances[Cube-1].faces.findAt(((0.0, 0.0, 0.0),)) region a.Surface(side1Facesf1, nameSurf-Z0) model.DisplacementBC(nameFixed-Z0, createStepNameStep-1, regionregion, u1UNSET, u2UNSET, u30.0, ur1UNSET, ur2UNSET, ur3UNSET) # 载荷Zheight面施加1MPa压力 f2 a.instances[Cube-1].faces.findAt(((length/2, width/2, height),)) region a.Surface(side1Facesf2, nameSurf-Zmax) model.Pressure(namePressure-Zmax, createStepNameStep-1, regionregion, magnitude1.0) # MPa # 4. 提交作业 job_name fJob_L{length}_W{width}_H{height} mdb.Job(namejob_name, modelParametricModel, description, typeANALYSIS, atTimeNone, waitMinutes0, waitHours0, queueNone, memory90, memoryUnitsPERCENTAGE, getMemoryFromAnalysisTrue, explicitOFF, nodalOutputPrecisionFULL, echoPrintOFF, modelPrintOFF, contactPrintOFF, historyPrintOFF, userSubroutine, scratch, resultsFormatODB, multiprocessingModeDEFAULT, numCpus1, numGPUs0) mdb.jobs[job_name].submit(consistencyCheckingOFF) mdb.jobs[job_name].waitForCompletion() # 5. 读取ODB并绘制Mises应力云图GUI中自动打开Visualization模块 from abaqus import session odb_path f{job_name}.odb o1 session.openOdb(nameodb_path) session.viewports[Viewport: 1].setValues(displayedObjecto1) session.viewports[Viewport: 1].odbDisplay.setPrimaryVariable( variableLabelS, outputPositionINTEGRATION_POINT, refinement(INVARIANT, Mises)) session.viewports[Viewport: 1].odbDisplay.display.setValues(plotStateCONTOURS_ON_DEF) print(f✅ 全流程完成{job_name} 已求解Mises云图已加载) except Exception as e: from abaqusGui import sendCommand sendCommand(fprint(❌ 流程中断{str(e)})) import traceback traceback.print_exc()关键细节说明waitForCompletion()会阻塞GUI线程导致界面假死。生产环境应改用job.submit() 后台轮询job.status但教学示例中为简化保留session.viewports[Viewport: 1]是Abaqus默认视口必须确保openOdb后立即setValues(displayedObject...)否则云图不显示refinement(INVARIANT, Mises)指定提取Mises等效应力而非分量S11/S22等所有findAt()定位必须传入模型空间坐标非草图坐标且精度要高如(length/2, width/2, height)比(5,5,10)更鲁棒。4. 内核与GUI的致命耦合点3个必踩坑与5条血泪排查指南内核与GUI看似独立实则共享同一mdb内存空间。当GUI修改了模型内核脚本若未重新加载或误用旧引用就会出现“界面上看到模型脚本里却报Part not found”。以下是我在某高校仿真平台项目中踩过的真坑按现象→原因→解法结构整理4.1 现象GUI中点击“生成”后内核报错KeyError: ParametricModel但手动在GUI里看模型树明明存在原因GUI脚本中execfile(core_script.py)执行时core_script.py内部使用了mdb.models[ParametricModel]但该模型是在GUI进程的mdb中创建的而execfile执行环境可能未正确绑定当前mdb上下文。Abaqus 2022版本对此限制更严。解决绝对禁止在GUI中用execfile调用含mdb操作的脚本。改为将内核逻辑封装为函数GUI中直接import并调用# gui_launcher.py 中改为 from core_script import create_parametric_cube # ✅ 正确函数式调用 cube_part create_parametric_cube(length, width, height) # ✅ 自动使用当前mdb4.2 现象多次点击GUI“生成”按钮第二次开始报错IllegalStateError: Cannot create part named Cube-1原因create_parametric_cube()函数中硬编码了部件名Cube-1第二次执行时mdb中已存在同名部件违反Abaqus唯一性约束。解决所有创建对象Part/Assembly/Step/Job必须动态命名。推荐用时间戳参数哈希import time import hashlib def generate_unique_name(base_name, params): param_str str(params).encode() hash_str hashlib.md5(param_str).hexdigest()[:6] return f{base_name}_{int(time.time())}_{hash_str} # 使用p mdb.models[model_name].Part(namegenerate_unique_name(Cube, (length,width,height)), ...)4.3 现象GUI提交作业后waitForCompletion()卡住不动日志显示Job is queued但永远不运行原因Abaqus默认作业队列queueNone在GUI模式下可能被挂起尤其当同时打开多个CAE实例时。解决显式指定本地CPU求解禁用队列mdb.Job(namejob_name, modelParametricModel, typeANALYSIS, multiprocessingModeDEFAULT, numCpus2, # 显式指定CPU数 queueNone) # 确保为None而非字符串None # 提交时加超时保护 job mdb.jobs[job_name] job.submit() for _ in range(300): # 最多等待5分钟 if job.status in (COMPLETED, FAILED, ABORTED): break time.sleep(1)4.4 现象GUI中成功生成云图但导出PNG时图片全黑或只有坐标轴原因session.viewports[Viewport: 1].plot()前未调用session.viewports[Viewport: 1].setValues()激活视口或odbDisplay未启用CONTOURS_ON_DEF。解决云图导出四步不可少vp session.viewports[Viewport: 1] vp.setValues(displayedObjecto1) # 1. 设置显示对象 vp.odbDisplay.setPrimaryVariable(variableLabelS, ... ) # 2. 设主变量 vp.odbDisplay.display.setValues(plotStateCONTOURS_ON_DEF) # 3. 开启云图 vp.plot() # 4. 强制重绘关键 vp.writeImage(fileNamestress_mises, formatPNG, width1200, height800) # 导出4.5 现象在Linux服务器用noGUI模式运行内核脚本报错ImportError: No module named caeModules原因caeModules是GUI专用模块noGUI模式下不可用。但你的脚本里可能误引入了GUI相关代码如from abaqusGui import *。解决严格分离内核与GUI代码。内核脚本core_script.py只导入from abaqus import *和from abaqusConstants import *GUI脚本gui_launcher.py才导入from caeModules import *。检查所有noGUI脚本删除任何含Gui、caemodules、driverUtils的import。避坑总原则GUI是“前端”内核是“后端”二者通信只允许通过mdb对象、文件如.inp/.odb或全局变量极不推荐。绝不混用sessionGUI专属和mdb内核/ GUI共用于同一脚本。5. 把Abaqus二次开发变成可交付工具参数校验、日志追踪与一键打包部署做到能跑通只是起点工程化交付要求用户输错参数有友好提示、每步操作留痕可追溯、最终产物能发给同事直接双击运行。这需要跳出“写脚本”思维进入“造工具”阶段。我以某公司支架疲劳分析工具为例拆解三个落地动作。5.1 参数校验用正则范围检查替代裸float()拦截90%低级错误GUI文本框TextField默认接受任意字符串float(abc)直接崩溃。我们封装校验器import re class ParamValidator: staticmethod def validate_positive_float(value_str, field_name): 验证正浮点数 if not value_str.strip(): raise ValueError(f{field_name} 不能为空) if not re.match(r^[-]?\d*\.?\d$, value_str): raise ValueError(f{field_name} 必须是数字格式如 10.5) val float(value_str) if val 0: raise ValueError(f{field_name} 必须大于0) return val staticmethod def validate_mesh_size(value_str): 验证网格尺寸0.1~100mm之间 val ParamValidator.validate_positive_float(value_str, 网格尺寸) if not (0.1 val 100.0): raise ValueError(网格尺寸应在 0.1 ~ 100.0 mm 之间) return val # 在on_submit_full中调用 try: length ParamValidator.validate_positive_float( widgetDict[length_field].getValue(), X方向长度) mesh_size ParamValidator.validate_mesh_size( widgetDict[mesh_field].getValue()) except ValueError as e: sendCommand(fprint(⚠️ {str(e)})) return # 终止后续流程效果用户输入abc、-5、时GUI弹出明确中文提示而非Python traceback。5.2 日志追踪用logging模块记录全流程故障时秒定位断点Abaqus默认不输出详细日志。我们在内核脚本开头加入import logging import os from datetime import datetime def setup_logger(): 创建带时间戳的日志文件 log_dir abaqus_logs if not os.path.exists(log_dir): os.makedirs(log_dir) log_file os.path.join(log_dir, frun_{datetime.now().strftime(%Y%m%d_%H%M%S)}.log) logging.basicConfig( levellogging.INFO, format%(asctime)s - %(levelname)s - %(message)s, handlers[ logging.FileHandler(log_file, encodingutf-8), logging.StreamHandler(sys.stdout) # 同时输出到控制台 ] ) logging.info(f 新会话启动 ) logging.info(f当前工作目录: {os.getcwd()}) logging.info(fAbaqus版本: {session.productInfo}) # 在core_script.py顶部调用 if __name__ __main__: setup_logger() logging.info(开始执行参数化建模...) create_parametric_cube(...) logging.info(建模完成准备提交作业...)日志价值当客户说“点了没反应”你只需索要abaqus_logs/下的最新log5秒内定位到是卡在seedPartInstance还是submit()。5.3 一键打包用pyinstaller生成独立exe告别环境配置最终交付物不应是.py文件而是双击即用的AnalysisTool.exe。步骤如下安装pyinstaller在Abaqus Python环境中# 进入Abaqus自带PythonWindows示例 C:\SIMULIA\Abaqus\2022\win64\tools\SMApy\python\python.exe -m pip install pyinstaller编写打包脚本build_tool.py# build_tool.py import PyInstaller.__main__ import os # 主GUI入口 main_script gui_launcher.py # Abaqus模块路径关键否则打包后找不到abaqus模块 abaqus_path rC:\SIMULIA\Abaqus\2022\win64\tools\SMApy\python PyInstaller.__main__.run([ --onefile, # 打包成单个exe --console, # 显示控制台调试用发布时换--windowed --name, Abaqus_Analysis_Tool, --add-data, f{abaqus_path};., # 包含Abaqus Python路径 --hidden-import, abaqus, --hidden-import, abaqusConstants, --hidden-import, caeModules, main_script, ])执行打包# 在Abaqus Python环境下运行 C:\SIMULIA\Abaqus\2022\win64\tools\SMApy\python\python.exe build_tool.py输出位于dist/Abaqus_Analysis_Tool.exe。注意事项--add-data必须指向Abaqus安装目录下的SMApy\python这是Abaqus Python解释器所在首次运行exe会慢需解压内置Python环境后续正常若报ModuleNotFoundError: No module named abaqus检查--add-data路径是否拼错或尝试--paths参数。6. 我坚持的3个实战习惯让Abaqus二次开发不再玄学而是可复制的工程能力做完上面所有你已经能做出一个功能完整的工具。但真正让我在多个项目中少走弯路的是三个刻进肌肉记忆的习惯。它们不写在任何手册里却是把“能跑”变成“敢交”的分水岭。6.1 习惯一所有mdb操作前先print(mdb.models.keys())像呼吸一样自然Abaqus的mdb对象是全局单例但它的状态极易被意外污染。比如你在GUI里删掉一个模型mdb.models里就没了但内核脚本如果还试图访问mdb.models[OldModel]就是KeyError。我见过太多人花两小时debug最后发现只是忘了mdb.Model(nameNewModel)。所以我的每个建模函数第一行都是print(f[DEBUG] 当前模型列表: {list(mdb.models.keys())}) if ParametricModel not in mdb.models.keys(): mdb.Model(nameParametricModel)这行打印在开发期是累赘但在客户现场就是后悔药——他一报错你让他打开Abaqus命令行粘贴这行立刻知道是环境问题还是代码问题。6.2 习惯二用job.writeInput()生成.inp文件当作“中间真相”来校验Abaqus内核脚本最终生成的是.inp文件它是纯文本、可读、可 diff 的。我从不信任“脚本跑完了”而是在job.submit()前加job.writeInput()用VS Code打开生成的.inp搜索*Part,*Material,*Boundary确认关键段落存在且参数正确甚至用abaqus jobtest.inp interactive手动运行.inp验证是否真能求解。 这招帮我揪出过所有“以为赋值成功其实变量名写错”的低级错误。.inp就是Abaqus世界的汇编语言看不懂它就别谈二次开发。6.3 习惯三GUI界面里永远放一个“Debug Info”按钮点击输出sys.path和abaqusVersion用户环境千奇百怪他装的是2022还是2023Python路径对不对有没有装额外包污染环境我的GUI底部永远有一个灰色小按钮def show_debug_info(button, widgetDict): import sys from abaqus import abaqusVersion info f Debug Info Abaqus版本: {abaqusVersion} Python路径: {sys.executable} sys.path: {sys.path[:3]}... # 只显示前3项防刷屏 工作目录: {os.getcwd()} sendCommand(fprint({info})) # 在GUI布局末尾添加 debug_btn AFXButton(aligner, Debug Info, targetshow_debug_info)客户一说“打不开”我让他点这个按钮截图发我30秒内判断是版本不兼容、路径错误还是他双击了错误的exe。这些习惯没有技术含量但把不确定性压缩到了最低。Abaqus二次开发从来不是炫技而是用确定性对抗复杂性。当你能把一个参数化分析流程从“我电脑上能跑”变成“发给客户他双击就出报告”你就已经超越了90%的同行。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站