python集成ansys脚本实现参数更改

一、前言

在传统有限元仿真工作流中,存在两大痛点:

APDL 脚本参数修改依赖手动打开记事本修改数值,多工况批量仿真时重复工作量巨大,极易手输错误;

ANSYS GUI 交互式操作无法自动化批量计算、批量导出应力 / 位移 / 温度云图,仿真报告产出效率极低。

本文搭建一套完整离线自动化仿真链路:

FastAPI参数化服务(正则精准替换APDL)→ APDL仿真脚本 → Windows BAT批处理后台静默运行ANSYS → 自动输出PNG云图结果

同时对比两套参数替换方案:固定行号下标替换(简易版)、正则命令匹配替换(工业稳定版),适配批量工况仿真、多材料热应力耦合计算场景。

适用场景:结构热应力仿真、多垫片装配体批量参数迭代、离线无界面自动化仿真平台开发。

二、整体流程架构

整体执行链路分为三层,自上而下完整闭环:

上层:FastAPI Web 参数化服务

提供 HTTP 接口,两种参数绑定方案,自动修改 APDL 脚本内材料、压力、温度、尺寸等关键参数,持久化存储参数绑定规则。

中层:APDL 核心仿真脚本 dianpian.txt

实现 X_T 模型导入、多材料定义(含线膨胀系数热应力)、垫片几何建模、网格划分、温度场 + 压力载荷施加、约束设置、求解、后处理自动导出应力 / 位移 / 温度 PNG 云图。

底层:Windows BAT 批处理脚本

静默后台调用 ANSYS 241 batch 模式,无 GUI 运行仿真,输出运行日志 run.log,计算完成后窗口暂停查看报错。

流程执行顺序

调用 FastAPI 接口绑定 APDL 内需要迭代的参数(压力、温度、弹性模量等);

更新参数值,一键覆盖原始 APDL 脚本;

双击 bat 批处理文件启动 ANSYS 批量求解;

求解结束自动生成各类云图 PNG 图片,用于仿真报告。

三、底层:Windows BAT 批处理启动 ANSYS 批量仿真

首先明确有两部分,一个是window脚本,一个是ansys脚本,使用bat脚本驱动ansys脚本实现生成相关文件(比如云图等)

windows脚本

bash 复制代码
@echo off
cd /d "D:\Demo\ansysScriptChange"
"D:\Program Files\ANSYS Inc\v241\ansys\bin\winx64\ansys241.exe" -b -i dianpian.txt -o run.log
pause

ansys脚本文件

bash 复制代码
/batch
/FILNAME,file,0
~PARAIN,dianpian,X_T
/FACET,NORML
/REPLOT

!===== 自定义显示颜色 =====
/RGB,INDEX,100,100,100,  0
/RGB,INDEX, 80, 80, 80, 13
/RGB,INDEX, 60, 60, 60, 14
/RGB,INDEX,  0,  0,  0, 15
/REPLOT

/prep7
ET,1,SOLID187

!==================== 材料定义(新增线膨胀系数ALPX用于热应力计算) ====================
!材料1:原导入零件
MP,EX,1,110000
MP,PRXY,1,0.33
MP,DENS,1,0.000004540
MP,ALPX,1,8.6E-6     !线膨胀系数 1/℃

!材料2:垫片1 铝合金
MP,EX,2,70000
MP,PRXY,2,0.30
MP,DENS,2,0.000002710
MP,ALPX,2,23.0E-6

!材料3:垫片2 结构钢
MP,EX,3,210000
MP,PRXY,3,0.30
MP,DENS,3,0.000007850
MP,ALPX,3,11.5E-6

!材料4:垫片3 钛合金
MP,EX,4,95000
MP,PRXY,4,0.32
MP,DENS,4,0.000004430
MP,ALPX,4,9.4E-6

!原导入几何体赋材料1
VSEL, , , ,1
VATT,1,,1,0
VSEL,ALL

!==================== 创建3个圆形圆环垫片 ====================
!垫片1:内R10,外R20,厚度2,Z:0~2
CYL4,0,0,10,,20,,0,2
!垫片2:内R15,外R25,厚度1.5,Z:5~6.5
CYL4,0,0,15,,25,,5,6.5
!垫片3:内R8,外R18,厚度3,Z:10~13

!垫片1(体2)→材料2
VSEL,S,VOLU,,2
VATT,2,,1,0
VSEL,ALL

!垫片2(体3)→材料3
VSEL,S,VOLU,,3
VATT,3,,1,0
VSEL,ALL

!垫片3(体4)→材料4
VSEL,S,VOLU,,4
VATT,4,,1,0
VSEL,ALL

!==================== 网格划分 ====================
SMRT,7
MSHAPE,1,3D
MSHKEY,0
VMESH,all

FINISH
/SOL
ALLSEL

!========= 温度载荷设置 =========
TREF,25        !参考温度(无应力初始温度)25℃
!可全局统一环境温度
BF,ALL,TEMP,300
!若需要各部件不同环境温度,取消上面BF全局命令,用下面分段设置
!VSEL,S,MAT,,1
!BF,ALL,TEMP,75
!VSEL,S,MAT,,2
!BF,ALL,TEMP,90
!VSEL,S,MAT,,3
!BF,ALL,TEMP,60
!VSEL,S,MAT,,4
!BF,ALL,TEMP,85
!ALLSEL

!========= 原模型约束(保留你原有8号面全约束) =========
DA,8,ALL,
ALLSEL

!==================== 各垫片分别约束+施加不同压力 ====================
!===== 垫片1:Z=0~2,底面Z=0全约束,上表面施加压力 5MPa =====
ASEL,S,LOC,Z,0
DA,ALL,ALL
ASEL,S,LOC,Z,2
SFA,ALL,1,PRES,2000
ALLSEL

!===== 垫片2:Z=5~6.5,底面Z=5全约束,上表面施加压力 12MPa =====
ASEL,S,LOC,Z,5
DA,ALL,ALL
ASEL,S,LOC,Z,6.5
SFA,ALL,1,PRES,12
ALLSEL

!===== 垫片3:Z=10~13,底面Z=10全约束,上表面施加压力 8MPa =====
ASEL,S,LOC,Z,10
DA,ALL,ALL
ASEL,S,LOC,Z,13
SFA,ALL,1,PRES,8
ALLSEL

SAVE
/SOLU
/STATUS,SOLU
SOLVE

!==================== 后处理输出云图 ====================
/POST1
set,last
/VIEW,1,1,2,3
/ANG,1
/REP,FAST
/VIEW,1,1,1,1
/ANG,1
/REP,FAST

!等效应力云图
/GRAPHICS,POWER
/SHOW,PNG,,0
/DSCALE,ALL,1.0
/EFACET,1
PLNSOL,S,EQV
/SHOW,CLOSE
/DEVICE,VECTOR,0
/RENAME,FILE000,PNG,,EQV,PNG

!总位移云图
/GRAPHICS,POWER
/SHOW,PNG,,0
/DSCALE,ALL,1.0
/EFACET,1
PLNSOL,U,SUM
/SHOW,CLOSE
/DEVICE,VECTOR,0
/RENAME,FILE000,PNG,,USUM,PNG

!可选:输出温度分布云图
/GRAPHICS,POWER
/SHOW,PNG,,0
PLNSOL,TEMP
/SHOW,CLOSE
/RENAME,FILE000,PNG,,TEMP,PNG

/EXIT,ALL

python函数实现参数替换(基础版 按照行数和下标 标定数据)

bash 复制代码
import json
import os
from typing import List, Dict, Any, Optional
from fastapi import FastAPI, Body
from pydantic import BaseModel

app = FastAPI(title="文本变量绑定替换服务")
BIND_CONFIG_PATH = "text_bind_config.json"

# 全局存储绑定实例
class TextVariableBinder:
    def __init__(self, file_path: str):
        self.target_file_path = file_path
        self.bind_mapping: List[Dict[str, Any]] = self._load_bind_config()

    def _load_bind_config(self) -> List[Dict[str, Any]]:
        if not os.path.exists(BIND_CONFIG_PATH):
            return []
        with open(BIND_CONFIG_PATH, "r", encoding="utf-8") as f:
            return json.load(f)

    def _save_bind_config(self):
        with open(BIND_CONFIG_PATH, "w", encoding="utf-8") as f:
            json.dump(self.bind_mapping, f, ensure_ascii=False, indent=2)

    def _get_file_lines(self) -> List[str]:
        with open(self.target_file_path, "r", encoding="utf-8") as f:
            return f.readlines()

    def bind_variable(self, var_name: str, line_no: int, start_idx: int, end_idx: int, original_text: str) -> Dict[str, Any]:
        for item in self.bind_mapping:
            if item["var_name"] == var_name:
                return {"code": 400, "msg": f"变量{var_name}已存在", "data": None}
        lines = self._get_file_lines()
        if line_no < 0 or line_no >= len(lines):
            return {"code": 400, "msg": f"行号{line_no}超出范围", "data": None}
        line_content = lines[line_no]
        if start_idx < 0 or end_idx > len(line_content):
            return {"code": 400, "msg": "下标超出该行文本范围", "data": None}
        real_text = line_content[start_idx:end_idx]
        if real_text != original_text:
            return {"code": 400, "msg": f"原始文本不匹配,文件内实际:{real_text}", "data": None}
        bind_item = {
            "var_name": var_name,
            "file_path": self.target_file_path,
            "line_no": line_no,
            "start_idx": start_idx,
            "end_idx": end_idx,
            "origin_text": original_text,
            "current_value": original_text
        }
        self.bind_mapping.append(bind_item)
        self._save_bind_config()
        return {"code": 200, "msg": "绑定成功", "data": bind_item}

    def update_variable_value(self, var_name: str, new_value: str) -> Dict[str, Any]:
        target = None
        for item in self.bind_mapping:
            if item["var_name"] == var_name:
                target = item
                break
        if not target:
            return {"code": 404, "msg": f"变量{var_name}不存在", "data": None}
        target["current_value"] = new_value
        self._save_bind_config()
        return {"code": 200, "msg": "变量更新成功", "data": {"var_name": var_name, "new_value": new_value}}

    def get_all_bind_info(self) -> Dict[str, Any]:
        return {"code": 200, "msg": "查询成功", "data": self.bind_mapping}

    def read_target_file(self) -> Dict[str, Any]:
        try:
            lines = self._get_file_lines()
            return {"code": 200, "msg": "读取成功", "data": {"line_count": len(lines), "lines": lines}}
        except Exception as e:
            return {"code": 500, "msg": f"读取失败:{str(e)}", "data": None}

    def save_modified_file(self, output_path: str) -> Dict[str, Any]:
        try:
            lines = self._get_file_lines()
            sorted_binds = sorted(self.bind_mapping, key=lambda x: x["line_no"], reverse=True)
            for bind in sorted_binds:
                ln = bind["line_no"]
                s = bind["start_idx"]
                e = bind["end_idx"]
                val = bind["current_value"]
                origin_line = lines[ln]
                new_line = origin_line[:s] + val + origin_line[e:]
                lines[ln] = new_line
            with open(output_path, "w", encoding="utf-8") as f:
                f.writelines(lines)
            return {"code": 200, "msg": "文件保存完成", "data": {"save_path": output_path}}
        except Exception as e:
            return {"code": 500, "msg": f"保存失败:{str(e)}", "data": None}

    def clear_all_bind(self) -> Dict[str, Any]:
        self.bind_mapping = []
        self._save_bind_config()
        return {"code": 200, "msg": "全部绑定已清空", "data": None}
      # 【修改】直接覆盖原始文件,不需要传入输出路径
    def overwrite_origin_file(self) -> Dict[str, Any]:
        try:
            lines = self._get_file_lines()
            # 行号倒序替换,防止下标偏移
            sorted_binds = sorted(self.bind_mapping, key=lambda x: x["line_no"], reverse=True)
            for bind in sorted_binds:
                ln = bind["line_no"]
                s = bind["start_idx"]
                e = bind["end_idx"]
                val = bind["current_value"]
                origin_line = lines[ln]
                new_line = origin_line[:s] + val + origin_line[e:]
                lines[ln] = new_line
            # 直接写入原文件,覆盖
            with open(self.target_file_path, "w", encoding="utf-8") as f:
                f.writelines(lines)
            return {"code": 200, "msg": f"已直接覆盖原始文件:{self.target_file_path}", "data": {"origin_file": self.target_file_path}}
        except Exception as e:
            return {"code": 500, "msg": f"覆盖原文件失败:{str(e)}", "data": None}    

# 全局工具实例(操作目标文件 test.txt)
binder = TextVariableBinder(file_path="dianpian.txt")

# 请求体模型
class BindReq(BaseModel):
    var_name: str
    line_no: int
    start_idx: int
    end_idx: int
    original_text: str

class UpdateVarReq(BaseModel):
    var_name: str
    new_value: str

class SaveFileReq(BaseModel):
    output_path: str

# 接口1:绑定变量
@app.post("/api/bind")
def bind_api(req: BindReq = Body()):
    return binder.bind_variable(
        var_name=req.var_name,
        line_no=req.line_no,
        start_idx=req.start_idx,
        end_idx=req.end_idx,
        original_text=req.original_text
    )

# 接口2:更新变量值
@app.post("/api/update_var")
def update_var_api(req: UpdateVarReq = Body()):
    return binder.update_variable_value(var_name=req.var_name, new_value=req.new_value)

# 接口3:查询所有绑定
@app.get("/api/all_bind")
def get_all_bind_api():
    return binder.get_all_bind_info()

# 接口4:读取原始文件
@app.get("/api/read_file")
def read_file_api():
    return binder.read_target_file()

# 接口5:导出替换后的文件
@app.post("/api/save_file")
def save_file_api(req: SaveFileReq = Body()):
    return binder.save_modified_file(output_path=req.output_path)


# 【新增】覆盖原文件接口,无入参
@app.post("/api/overwrite_origin")
def overwrite_origin_api():
    return binder.overwrite_origin_file()

    
# 接口6:清空所有绑定
@app.post("/api/clear_bind")
def clear_bind_api():
    return binder.clear_all_bind()

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app="main:app", host="127.0.0.1", port=8000, reload=True)

python函数实现参数替换(按照正则表达式精准匹配)

bash 复制代码
import json
import os
import re
from typing import List, Dict, Any
from fastapi import FastAPI, Body
from pydantic import BaseModel

# 服务初始化
app = FastAPI(title="APDL脚本正则参数绑定工具", version="1.0")
CONFIG_FILE = "bind_config.json"
TARGET_APDL_FILE = "dianpian.txt"

# 绑定配置管理核心类
class APDLParamBinder:
    def __init__(self):
        self.bind_list: List[Dict] = self._load_config()

    # 加载本地绑定配置
    def _load_config(self) -> List[Dict]:
        if not os.path.exists(CONFIG_FILE):
            return []
        with open(CONFIG_FILE, "r", encoding="utf-8") as f:
            return json.load(f)

    # 持久化保存配置
    def _save_config(self):
        with open(CONFIG_FILE, "w", encoding="utf-8") as f:
            json.dump(self.bind_list, f, ensure_ascii=False, indent=2)

    # 读取APDL文件所有行
    def _read_apdl_lines(self) -> List[str]:
        with open(TARGET_APDL_FILE, "r", encoding="utf-8") as f:
            return f.readlines()

    # 核心:正则绑定参数(完全独立,无任何旧行号下标逻辑)
    def bind_param(self, var_name: str, cmd_match: str, key_match: str) -> Dict[str, Any]:
        """
        :param var_name: 自定义变量名
        :param cmd_match: 定位行的命令片段,如 SFA,ALL
        :param key_match: 行内匹配关键字,如 PRES,
        匹配规则:key_match后截取 逗号/空格/换行前的内容
        """
        # 校验变量重复
        for item in self.bind_list:
            if item["var_name"] == var_name:
                return {"code": 400, "msg": f"变量 {var_name} 已存在", "data": None}

        lines = self._read_apdl_lines()
        target_line = None
        line_index = -1

        # 1. 匹配包含cmd_match的行
        for idx, line in enumerate(lines):
            if cmd_match in line:
                target_line = line
                line_index = idx
                break
        if line_index == -1:
            return {"code": 404, "msg": f"未找到包含 [{cmd_match}] 的脚本行", "data": None}

        # 2. 正则精准捕获key后第一个参数,遇到逗号/空格立即停止
        escape_key = re.escape(key_match)
        pattern = re.compile(rf"{escape_key}([^, \t\r\n]+)")
        match_result = pattern.search(target_line)
        if not match_result:
            return {"code": 400, "msg": f"当前行不存在 {key_match} 后参数", "data": None}

        origin_value = match_result.group(1)
        start_pos = match_result.start(1)
        end_pos = match_result.end(1)

        # 存入绑定列表(仅存储正则计算出的下标,外部无需手动传入)
        bind_info = {
            "var_name": var_name,
            "apdl_file": TARGET_APDL_FILE,
            "line_idx": line_index,
            "char_start": start_pos,
            "char_end": end_pos,
            "origin_val": origin_value,
            "current_val": origin_value,
            "cmd_rule": cmd_match,
            "key_rule": key_match
        }
        self.bind_list.append(bind_info)
        self._save_config()
        return {"code": 200, "msg": "参数绑定成功", "data": bind_info}

    # 更新已绑定变量的值
    def update_param(self, var_name: str, new_val: str) -> Dict[str, Any]:
        target = None
        for item in self.bind_list:
            if item["var_name"] == var_name:
                target = item
                break
        if not target:
            return {"code": 404, "msg": f"变量 {var_name} 不存在", "data": None}
        target["current_val"] = new_val.strip()
        self._save_config()
        return {"code": 200, "msg": "变量更新完成", "data": {"var": var_name, "new_value": new_val.strip()}}

    # 生成修改后的APDL文件并覆盖原文件
    def overwrite_apdl(self) -> Dict[str, Any]:
        try:
            lines = self._read_apdl_lines()
            # 倒序替换,防止字符下标偏移
            sorted_binds = sorted(self.bind_list, key=lambda x: x["line_idx"], reverse=True)
            for bind in sorted_binds:
                ln = bind["line_idx"]
                s = bind["char_start"]
                e = bind["char_end"]
                new_text = bind["current_val"]
                raw_line = lines[ln]
                new_line = raw_line[:s] + new_text + raw_line[e:]
                lines[ln] = new_line
            # 覆盖原始文件
            with open(TARGET_APDL_FILE, "w", encoding="utf-8") as f:
                f.writelines(lines)
            return {"code": 200, "msg": "已覆盖原始APDL文件", "data": {"file": TARGET_APDL_FILE}}
        except Exception as err:
            return {"code": 500, "msg": f"写入失败: {str(err)}", "data": None}

    # 导出另存为新文件
    def export_new_apdl(self, save_path: str) -> Dict[str, Any]:
        try:
            lines = self._read_apdl_lines()
            sorted_binds = sorted(self.bind_list, key=lambda x: x["line_idx"], reverse=True)
            for bind in sorted_binds:
                ln = bind["line_idx"]
                s = bind["char_start"]
                e = bind["char_end"]
                new_text = bind["current_val"]
                raw_line = lines[ln]
                new_line = raw_line[:s] + new_text + raw_line[e:]
                lines[ln] = new_line
            with open(save_path, "w", encoding="utf-8") as f:
                f.writelines(lines)
            return {"code": 200, "msg": "文件导出成功", "data": {"save_path": save_path}}
        except Exception as err:
            return {"code": 500, "msg": f"导出失败: {str(err)}", "data": None}

    # 查询所有绑定规则
    def get_all_binds(self) -> Dict[str, Any]:
        return {"code": 200, "msg": "查询成功", "data": self.bind_list}

    # 清空全部绑定配置
    def clear_all_binds(self) -> Dict[str, Any]:
        self.bind_list = []
        self._save_config()
        return {"code": 200, "msg": "所有绑定规则已清空", "data": None}

# 全局实例
binder = APDLParamBinder()

# ==================== 请求体模型 ====================
class BindRequest(BaseModel):
    var_name: str
    cmd_match: str
    key_match: str

class UpdateRequest(BaseModel):
    var_name: str
    new_val: str

class ExportRequest(BaseModel):
    save_path: str

# ==================== 接口定义 ====================
# 1. 正则绑定参数接口
@app.post("/api/bind")
def api_bind(req: BindRequest = Body()):
    return binder.bind_param(req.var_name, req.cmd_match, req.key_match)

# 2. 更新变量值接口
@app.post("/api/update")
def api_update(req: UpdateRequest = Body()):
    return binder.update_param(req.var_name, req.new_val)

# 3. 查询全部绑定
@app.get("/api/list")
def api_list():
    return binder.get_all_binds()

# 4. 覆盖原始APDL文件
@app.post("/api/overwrite")
def api_overwrite():
    return binder.overwrite_apdl()

# 5. 导出新文件
@app.post("/api/export")
def api_export(req: ExportRequest = Body()):
    return binder.export_new_apdl(req.save_path)

# 6. 清空绑定配置
@app.post("/api/clear")
def api_clear():
    return binder.clear_all_binds()

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app="main:app", host="127.0.0.1", port=8000)

出现更改两次把换行符替换掉,所以优化代码

bash 复制代码
import json
import os
import re
from typing import List, Dict, Any
from fastapi import FastAPI, Body
from pydantic import BaseModel

app = FastAPI(title="APDL脚本正则参数绑定工具", version="1.0")
CONFIG_FILE = "bind_config.json"
TARGET_APDL_FILE = "dianpian.txt"

class APDLParamBinder:
    def __init__(self):
        self.bind_list: List[Dict] = self._load_config()

    def _load_config(self) -> List[Dict]:
        if not os.path.exists(CONFIG_FILE):
            return []
        with open(CONFIG_FILE, "r", encoding="utf-8") as f:
            return json.load(f)

    def _save_config(self):
        with open(CONFIG_FILE, "w", encoding="utf-8") as f:
            json.dump(self.bind_list, f, ensure_ascii=False, indent=2)

    def _read_apdl_lines(self) -> List[str]:
        with open(TARGET_APDL_FILE, "r", encoding="utf-8") as f:
            return f.readlines()

    # 内部工具:单次根据cmd+key实时匹配获取位置与原值
    def _get_target_pos(self, lines: List[str], cmd_match: str, key_match: str):
        line_index = -1
        target_line = ""
        for idx, line in enumerate(lines):
            if cmd_match in line:
                line_index = idx
                target_line = line
                break
        if line_index == -1:
            return None, None, None, None
        esc_key = re.escape(key_match)
        pat = re.compile(rf"{esc_key}([^, \t\r\n]+)")
        m = pat.search(target_line)
        if not m:
            return None, None, None, None
        val = m.group(1)
        s = m.start(1)
        e = m.end(1)
        return line_index, s, e, val

    def bind_param(self, var_name: str, cmd_match: str, key_match: str) -> Dict[str, Any]:
        for item in self.bind_list:
            if item["var_name"] == var_name:
                return {"code": 400, "msg": f"变量 {var_name} 已存在", "data": None}
        lines = self._read_apdl_lines()
        line_idx, s, e, origin_val = self._get_target_pos(lines, cmd_match, key_match)
        if line_idx is None:
            return {"code": 404, "msg": f"未匹配 {cmd_match} + {key_match}", "data": None}
        # 不再存储静态下标,只存匹配规则,彻底解决偏移问题
        bind_info = {
            "var_name": var_name,
            "cmd_match": cmd_match,
            "key_match": key_match,
            "origin_val": origin_val,
            "current_val": origin_val
        }
        self.bind_list.append(bind_info)
        self._save_config()
        return {"code": 200, "msg": "参数绑定成功", "data": bind_info}

    def update_param(self, var_name: str, new_val: str) -> Dict[str, Any]:
        target = None
        for item in self.bind_list:
            if item["var_name"] == var_name:
                target = item
                break
        if not target:
            return {"code": 404, "msg": f"变量 {var_name} 不存在", "data": None}
        # 修复:去掉strip(),保留原始输入,不自动删换行空格
        target["current_val"] = new_val
        self._save_config()
        return {"code": 200, "msg": "变量更新完成", "data": {"var": var_name, "new_value": new_val}}

    def overwrite_apdl(self) -> Dict[str, Any]:
        try:
            lines = self._read_apdl_lines()
            # 每次写入都实时重新正则定位,不依赖旧下标
            for bind in self.bind_list:
                ln, s, e, _ = self._get_target_pos(lines, bind["cmd_match"], bind["key_match"])
                if ln is None or s is None or e is None:
                    continue
                raw_line = lines[ln]
                new_line = raw_line[:s] + bind["current_val"] + raw_line[e:]
                lines[ln] = new_line
            with open(TARGET_APDL_FILE, "w", encoding="utf-8") as f:
                f.writelines(lines)
            return {"code": 200, "msg": "已覆盖原始APDL文件", "data": {"file": TARGET_APDL_FILE}}
        except Exception as err:
            return {"code": 500, "msg": f"写入失败: {str(err)}", "data": None}

    def export_new_apdl(self, save_path: str) -> Dict[str, Any]:
        try:
            lines = self._read_apdl_lines()
            for bind in self.bind_list:
                ln, s, e, _ = self._get_target_pos(lines, bind["cmd_match"], bind["key_match"])
                if ln is None or s is None or e is None:
                    continue
                raw_line = lines[ln]
                new_line = raw_line[:s] + bind["current_val"] + raw_line[e:]
                lines[ln] = new_line
            with open(save_path, "w", encoding="utf-8") as f:
                f.writelines(lines)
            return {"code": 200, "msg": "文件导出成功", "data": {"save_path": save_path}}
        except Exception as err:
            return {"code": 500, "msg": f"导出失败: {str(err)}", "data": None}

    def get_all_binds(self) -> Dict[str, Any]:
        return {"code": 200, "msg": "查询成功", "data": self.bind_list}

    def clear_all_binds(self) -> Dict[str, Any]:
        self.bind_list = []
        self._save_config()
        return {"code": 200, "msg": "所有绑定规则已清空", "data": None}

binder = APDLParamBinder()

# 请求体
class BindRequest(BaseModel):
    var_name: str
    cmd_match: str
    key_match: str

class UpdateRequest(BaseModel):
    var_name: str
    new_val: str

class ExportRequest(BaseModel):
    save_path: str

# 接口
@app.post("/api/bind")
def api_bind(req: BindRequest = Body()):
    return binder.bind_param(req.var_name, req.cmd_match, req.key_match)

@app.post("/api/update")
def api_update(req: UpdateRequest = Body()):
    return binder.update_param(req.var_name, req.new_val)

@app.get("/api/list")
def api_list():
    return binder.get_all_binds()

@app.post("/api/overwrite")
def api_overwrite():
    return binder.overwrite_apdl()

@app.post("/api/export")
def api_export(req: ExportRequest = Body()):
    return binder.export_new_apdl(req.save_path)

@app.post("/api/clear")
def api_clear():
    return binder.clear_all_binds()

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app="main:app", host="127.0.0.1", port=8000)