├── README.md ├── examples └── example.py └── src └── PyWbUnit ├── Errors.py ├── _CoWbUnit.py ├── __init__.py └── cython_setup.py /README.md: -------------------------------------------------------------------------------- 1 | # PyWbUnit 2 | 3 | 本模块提供Python与Workbench联合仿真的支持:可通过Python启动Workbench,并向WB实时发送脚本代码执行,同时支持查询代码执行结果和脚本变量值。 4 | 5 | 说明:本模块只提供Python与Workbench中的ScriptEngine的双向数据接口,WB中仿真功能实现还需要通过其Python脚本开发实现,可以参考以下文章: 6 | 7 | - 《[基于Python的Workbench开发指南+案例解析](https://mp.weixin.qq.com/s?__biz=Mzg5MDMwNDIwMQ==&mid=2247483779&idx=1&sn=c6ccd8641b852364f0b87bef85f416e1&chksm=cfdfe225f8a86b33377a6bd9824c01d509772cdc0e86212be992634760dd0a715f9e0f7ff5a3&token=1162439082&lang=zh_CN#rd)》 8 | - 《[Workbench开发指南:仿真流程集成](https://mp.weixin.qq.com/s?__biz=Mzg5MDMwNDIwMQ==&mid=2247483922&idx=1&sn=1b3e9d5e36abd1afbeff37493c472194&chksm=cfdfe1b4f8a868a225c2bd12cc67dfb59a5f7593af9d840b906b4d5391074a1bbfb26b680e1f&token=1162439082&lang=zh_CN#rd)》 9 | - 《[Workbench开发指南:仿真模板开发](https://mp.weixin.qq.com/s?__biz=Mzg5MDMwNDIwMQ==&mid=2247483935&idx=1&sn=db6b79291216713f08104875b58906c5&chksm=cfdfe1b9f8a868afec0844cef84cb6f11b7a16e0d38fb7c305b7ce0ab6dddb35729298ca05de&token=1162439082&lang=zh_CN#rd)》 10 | - 《[SCDM二次开发快速入门|应用+技巧](https://mp.weixin.qq.com/s?__biz=Mzg5MDMwNDIwMQ==&mid=2247483810&idx=1&sn=f88dc36cbb1296e0b45bdeb6a2c83325&chksm=cfdfe204f8a86b12be2bb476ba19a57a1074e4df5f7eb05df82cb0c81858e7db52e5a5cc2562&token=1162439082&lang=zh_CN#rd)》 11 | - 《[轻松上手Mechanical脚本自动化](https://mp.weixin.qq.com/s?__biz=Mzg5MDMwNDIwMQ==&mid=2247484014&idx=1&sn=b122a0c8bcdde20c5632c04efb8cf1a4&chksm=cfdfe1c8f8a868de229aa8f3b05fb606dc00bf852d6de4336c9f148e4c786c540816072fb639&token=1162439082&lang=zh_CN#rd)》 12 | - 《[Fluent脚本自动化快速入门](https://mp.weixin.qq.com/s?__biz=Mzg5MDMwNDIwMQ==&mid=2247483965&idx=1&sn=6b197e9c067f07cf111f37345e4c4f4f&chksm=cfdfe19bf8a8688dd53c5c9a721646956f820ea90fb33de92314cb91f01a3728ca609cf01b1e&token=1162439082&lang=zh_CN#rd)》 13 | - 视频教程:《[Python语言在ANSYS的应用52讲-掌握SCDM脚本建模及ANSYS二次开发能力](https://www.fangzhenxiu.com/course/408360)》 14 | 15 | 16 | ## 安装使用 17 | 18 | 预编译的二进制库目前只支持Windows x64平台的Python3.7、3.8、3.9版本,安装方法如下: 19 | 20 | pip install PyWbUnit-0.3.0-cp37-none-win_amd64.whl 21 | pip install PyWbUnit-0.3.0-cp38-none-win_amd64.whl 22 | pip install PyWbUnit-0.3.0-cp39-none-win_amd64.whl 23 | 24 | ## API说明 25 | 26 | ```text 27 | Help on class CoWbUnitProcess in module PyWbUnit._CoWbUnit: 28 | 29 | class CoWbUnitProcess(builtins.object) 30 | | 31 | | CoWbUnitProcess(workDir=None, version=201, interactive=True) 32 | | 33 | | Unit class for co-simulation with Workbench using Python. 34 | | 35 | | >>> coWbUnit = CoWbUnitProcess() 36 | | >>> coWbUnit.initialize() 37 | | >>> command = 'GetTemplate(TemplateName="Static Structural", Solver="ANSYS").CreateSystem()' 38 | | >>> coWbUnit.execWbCommand(command) 39 | | >>> coWbUnit.execWbCommand('systems=GetAllSystems()') 40 | | >>> print(coWbUnit.queryWbVariable('systems')) 41 | | >>> coWbUnit.saveProject(r'D:/example.wbpj') 42 | | >>> coWbUnit.finalize() 43 | | 44 | | Methods defined here: 45 | | 46 | | __init__(self, workDir=None, version=201, interactive=True) 47 | | Constructor of CoWbUnitProcess. 48 | | :param workDir: str, the directory where the Workbench starts. 49 | | :param version: int, workbench version: 2019R1-190/2020R1-201/2021R1-211. 50 | | :param interactive: bool, whether to display the Workbench interface 51 | | 52 | | execWbCommand(self, command: 'str') -> 'str' 53 | | Send python script command to the Workbench for execution 54 | | :param command: str, python script command 55 | | :return: str, execution result 56 | | 57 | | exitWb(self) -> 'str' 58 | | `Exit` the current Workbench client process and close the Workbench server 59 | | :return: str 60 | | 61 | | finalize(self) 62 | | Exit the current workbench and close the TCP Server connection 63 | | :return: None 64 | | 65 | | initialize(self) -> None 66 | | Called before `execWbCommand`: Start the Workbench in the server 67 | | mode and open the TCP server port to create a socket connection 68 | | :return: None 69 | | 70 | | queryWbVariable(self, variable: 'str') 71 | | Query the value of `variable` in the workbench script environment 72 | | :param variable: str, script variable name 73 | | :return: str 74 | | 75 | | saveProject(self, filePath=None, overWrite=True) 76 | | Save the current workbench project file to `filePath` 77 | | If the Project has not been saved, using method: `saveProject()` 78 | | will raise `CommandFailedException` 79 | | :param filePath: Optional[str, None], if 80 | | :param overWrite: bool, Whether to overwrite the original project 81 | | :return: str, execution result 82 | | 83 | | terminate(self) 84 | | Terminates the current Workbench client process 85 | | :return: bool 86 | | 87 | | ---------------------------------------------------------------------- 88 | 89 | Help on class WbServerClient in module PyWbUnit._CoWbUnit: 90 | 91 | class WbServerClient(builtins.object) 92 | | 93 | | WbServerClient(aasKey: 'str') 94 | | 95 | | Client Class for the Workbench server connection 96 | | >>> aas_key = 'localhost:9000' 97 | | >>> wbClient = WbServerClient(aas_key) 98 | | >>> wbClient.execWbCommand('') 99 | | >>> print(wbClient.queryWbVariable('')) 100 | | 101 | | Methods defined here: 102 | | 103 | | __init__(self, aasKey: 'str') 104 | | 105 | | execWbCommand(self, command: 'str') -> 'str' 106 | | 107 | | queryWbVariable(self, variable) -> 'str' 108 | | 109 | | ---------------------------------------------------------------------- 110 | ``` 111 | ## 核心类实现 112 | 113 | ```python 114 | # -*- coding: utf-8 -*- 115 | from socket import * 116 | 117 | class WbServerClient: 118 | 119 | _suffix = '' 120 | _coding = 'US-ASCII' 121 | _buffer = 1024 122 | 123 | def __init__(self, aasKey: str): 124 | aasList = aasKey.split(':') 125 | self._address = (aasList[0], int(aasList[1])) 126 | 127 | def execWbCommand(self, command: str) -> str: 128 | sockCommand = command + self._suffix 129 | with socket(AF_INET, SOCK_STREAM) as sock: 130 | sock.connect(self._address) 131 | sock.sendall(sockCommand.encode(self._coding)) 132 | data = sock.recv(self._buffer).decode() 133 | return data 134 | 135 | def queryWbVariable(self, variable) -> str: 136 | self.execWbCommand("__variable__=" + variable + ".__repr__()") 137 | retValue = self.execWbCommand("Query,__variable__") 138 | return retValue[13:] 139 | ``` 140 | 141 | ## 使用方法 142 | 首先从PyWbUnit模块中导入CoWbUnitProcess类,详细文档说明可以通过help(CoWbUnitProcess)查看,以公众号文章:《[ANSYS中使用Python实现高效结构仿真](https://mp.weixin.qq.com/s?__biz=Mzg5MDMwNDIwMQ==&mid=2247484455&idx=1&sn=aac9501bb6fec23276353e4a27c10af9&chksm=cfdfe781f8a86e97bc5afb34678036318ce09d442d82cbeab195c8bdbaeb9e3e00606951469c&token=1162439082&lang=zh_CN#rd)》为例,演示如何使用PyWbUnit调用Workbench完成联合仿真的过程: 143 | 144 | ```python 145 | # -*- coding: utf-8 -*- 146 | from PyWbUnit import CoWbUnitProcess 147 | 148 | # 创建wb单元实例,指定ansys wb版本,如2020R1对应201 149 | coWbUnit = CoWbUnitProcess(workDir=r'E:/Workdata', version=201, interactive=True) 150 | coWbUnit.initialize() 151 | command = 'mechSys = GetTemplate(TemplateName="Static Structural", Solver="ANSYS").CreateSystem()' 152 | coWbUnit.execWbCommand(command) 153 | coWbUnit.execWbCommand('systems=GetAllSystems()') 154 | print(coWbUnit.queryWbVariable('systems')) 155 | materialScript = r'''# 创建静结构分析流程 156 | # 获得Engineering Data数据容器 157 | engData = mechSys.GetContainer(ComponentName="Engineering Data") 158 | # 封装创建材料的方法 159 | def CreateMaterial(name, density, *elastic): 160 | mat = engData.CreateMaterial(Name=name) 161 | mat.CreateProperty(Name="Density").SetData(Variables=["Density"], 162 | Values=[["%s [kg m^-3]" % density]]) 163 | elasticProp = mat.CreateProperty(Name="Elasticity", Behavior="Isotropic") 164 | elasticProp.SetData(Variables=["Young's Modulus"], Values=[["%s [MPa]" % elastic[0]]]) 165 | elasticProp.SetData(Variables=["Poisson's Ratio"], Values=[["%s" % elastic[1]]]) 166 | # 创建材料Steel,密度:7850kg/m3,杨氏模量:208e3MPa,泊松比:0.3 167 | CreateMaterial("Steel", 7850, 209.e3, 0.3)''' 168 | coWbUnit.execWbCommand(materialScript) 169 | coWbUnit.execWbCommand('geo=mechSys.GetContainer("Geometry")') 170 | coWbUnit.execWbCommand('geo.Edit(IsSpaceClaimGeometry=True)') 171 | geoScript = r'''# Python Script, API Version = V18 172 | # 创建悬臂梁实体区域 173 | BlockBody.Create(Point.Origin, Point.Create(MM(200), MM(25), MM(20))) 174 | GetRootPart().SetName("Beam") 175 | # 选择beam实体,用于后续材料赋予 176 | Selection.Create(GetRootPart().Bodies).CreateAGroup("ns_beamBody") 177 | # 通过坐标点选择面对象 178 | def GetFaceObjByPt(pt): 179 | for face in GetRootPart().GetDescendants[IDesignFace](): 180 | if face.Shape.ContainsPoint(pt): return face 181 | # 选择固定约束加载面 182 | fixSupFace = GetFaceObjByPt(Point.Create(0, MM(12.5), MM(10))) 183 | Selection.Create(fixSupFace).CreateAGroup("ns_fixSup") 184 | # 选择压力载荷加载面 185 | pressFace = GetFaceObjByPt(Point.Create(MM(50), MM(12.5), MM(20))) 186 | Selection.Create(pressFace).CreateAGroup("ns_press") 187 | ''' 188 | coWbUnit.execWbCommand(f'geo.SendCommand(Language="Python", Command={geoScript!r})') 189 | coWbUnit.execWbCommand(f'geo.Exit()') 190 | launchScript = r'''# 刷新Model Component数据 191 | modelComp = mechSys.GetComponent(Name="Model") 192 | modelComp.Refresh() 193 | # 获得Mechanical中Model的数据容器 194 | model = mechSys.GetContainer(ComponentName="Model") 195 | model.Edit()''' 196 | coWbUnit.execWbCommand(launchScript) 197 | mechScript = r"""# encoding: utf-8 198 | # 给定Named Selection名称获取子对象实例 199 | def GetLocByName(ns_name): 200 | for ns in Model.NamedSelections.Children: 201 | if ns.Name == ns_name: return ns 202 | # 指定材料 203 | matAss = Model.Materials.AddMaterialAssignment() 204 | matAss.Material = "Steel" 205 | matAss.Location = GetLocByName("ns_beamBody") 206 | # 划分网格 207 | mesh = Model.Mesh 208 | mesh.ElementSize = Quantity("10 [mm]") 209 | mesh.GenerateMesh() 210 | # 获得Analyses对象 211 | analysis = Model.Analyses[0] 212 | # 添加固定约束 213 | fixSup = analysis.AddFixedSupport() 214 | fixSup.Location= GetLocByName("ns_fixSup") 215 | # 加载压力载荷 216 | pressLoad = analysis.AddPressure() 217 | pressLoad.Location = GetLocByName("ns_press") 218 | pressLoad.Magnitude.Output.DiscreteValues = [Quantity("0.5 [MPa]")] 219 | Model.Solve() 220 | # 后处理操作 221 | solution = analysis.Solution 222 | misesResult = solution.AddEquivalentStress() 223 | misesResult.EvaluateAllResults() 224 | # 设置视角 225 | camera = ExtAPI.Graphics.Camera 226 | camera.UpVector = Vector3D(0,0,1) 227 | camera.SceneWidth = Quantity("150 [mm]") 228 | camera.SceneHeight = Quantity("120 [mm]") 229 | camera.FocalPoint = Point((0.08,0.0125,0), 'm') 230 | # 输出后处理云图 231 | misesResult.Activate() 232 | ExtAPI.Graphics.ExportImage("d:/mises.png")""" 233 | coWbUnit.execWbCommand(f'model.SendCommand(Language="Python", Command={mechScript!r})') 234 | coWbUnit.execWbCommand('model.Exit()') 235 | coWbUnit.saveProject(r"E:/Workdata/beam.wbpj") 236 | # 关闭wb单元实例 237 | coWbUnit.finalize() 238 | ``` 239 | 240 | ## 问题反馈 241 | 242 | 关注微信公众号:“ANSYS仿真与开发”,后台留言;或者邮件至:tguangs@163.com 243 | -------------------------------------------------------------------------------- /examples/example.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from PyWbUnit import CoWbUnitProcess 3 | 4 | coWbUnit = CoWbUnitProcess() 5 | coWbUnit.initialize() 6 | command = 'mechSys = GetTemplate(TemplateName="Static Structural", Solver="ANSYS").CreateSystem()' 7 | coWbUnit.execWbCommand(command) 8 | coWbUnit.execWbCommand('systems=GetAllSystems()') 9 | print(coWbUnit.queryWbVariable('systems')) 10 | materialScript = r'''# 创建静结构分析流程 11 | # 获得Engineering Data数据容器 12 | engData = mechSys.GetContainer(ComponentName="Engineering Data") 13 | # 封装创建材料的方法 14 | def CreateMaterial(name, density, *elastic): 15 | mat = engData.CreateMaterial(Name=name) 16 | mat.CreateProperty(Name="Density").SetData(Variables=["Density"], 17 | Values=[["%s [kg m^-3]" % density]]) 18 | elasticProp = mat.CreateProperty(Name="Elasticity", Behavior="Isotropic") 19 | elasticProp.SetData(Variables=["Young's Modulus"], Values=[["%s [MPa]" % elastic[0]]]) 20 | elasticProp.SetData(Variables=["Poisson's Ratio"], Values=[["%s" % elastic[1]]]) 21 | 22 | # 创建材料Steel,密度:7850kg/m3,杨氏模量:208e3MPa,泊松比:0.3 23 | CreateMaterial("Steel", 7850, 209.e3, 0.3)''' 24 | coWbUnit.execWbCommand(materialScript) 25 | coWbUnit.execWbCommand('geo=mechSys.GetContainer("Geometry")') 26 | coWbUnit.execWbCommand('geo.Edit(IsSpaceClaimGeometry=True)') 27 | geoScript = r'''# Python Script, API Version = V18 28 | # 创建悬臂梁实体区域 29 | BlockBody.Create(Point.Origin, Point.Create(MM(200), MM(25), MM(20))) 30 | GetRootPart().SetName("Beam") 31 | # 选择beam实体,用于后续材料赋予 32 | Selection.Create(GetRootPart().Bodies).CreateAGroup("ns_beamBody") 33 | # 通过坐标点选择面对象 34 | def GetFaceObjByPt(pt): 35 | for face in GetRootPart().GetDescendants[IDesignFace](): 36 | if face.Shape.ContainsPoint(pt): return face 37 | # 选择固定约束加载面 38 | fixSupFace = GetFaceObjByPt(Point.Create(0, MM(12.5), MM(10))) 39 | Selection.Create(fixSupFace).CreateAGroup("ns_fixSup") 40 | # 选择压力载荷加载面 41 | pressFace = GetFaceObjByPt(Point.Create(MM(50), MM(12.5), MM(20))) 42 | Selection.Create(pressFace).CreateAGroup("ns_press") 43 | ''' 44 | coWbUnit.execWbCommand(f'geo.SendCommand(Language="Python", Command={geoScript!r})') 45 | coWbUnit.execWbCommand(f'geo.Exit()') 46 | launchScript = r'''# 刷新Model Component数据 47 | modelComp = mechSys.GetComponent(Name="Model") 48 | modelComp.Refresh() 49 | # 获得Mechanical中Model的数据容器 50 | model = mechSys.GetContainer(ComponentName="Model") 51 | model.Edit()''' 52 | coWbUnit.execWbCommand(launchScript) 53 | mechScript = r"""# encoding: utf-8 54 | # 给定Named Selection名称获取子对象实例 55 | def GetLocByName(ns_name): 56 | for ns in Model.NamedSelections.Children: 57 | if ns.Name == ns_name: return ns 58 | # 指定材料 59 | matAss = Model.Materials.AddMaterialAssignment() 60 | matAss.Material = "Steel" 61 | matAss.Location = GetLocByName("ns_beamBody") 62 | # 划分网格 63 | mesh = Model.Mesh 64 | mesh.ElementSize = Quantity("10 [mm]") 65 | mesh.GenerateMesh() 66 | # 获得Analyses对象 67 | analysis = Model.Analyses[0] 68 | # 添加固定约束 69 | fixSup = analysis.AddFixedSupport() 70 | fixSup.Location= GetLocByName("ns_fixSup") 71 | # 加载压力载荷 72 | pressLoad = analysis.AddPressure() 73 | pressLoad.Location = GetLocByName("ns_press") 74 | pressLoad.Magnitude.Output.DiscreteValues = [Quantity("0.5 [MPa]")] 75 | Model.Solve() 76 | # 后处理操作 77 | solution = analysis.Solution 78 | misesResult = solution.AddEquivalentStress() 79 | misesResult.EvaluateAllResults() 80 | # 设置视角 81 | camera = ExtAPI.Graphics.Camera 82 | camera.UpVector = Vector3D(0,0,1) 83 | camera.SceneWidth = Quantity("150 [mm]") 84 | camera.SceneHeight = Quantity("120 [mm]") 85 | camera.FocalPoint = Point((0.08,0.0125,0), 'm') 86 | # 输出后处理云图 87 | misesResult.Activate() 88 | ExtAPI.Graphics.ExportImage("d:/mises.png")""" 89 | coWbUnit.execWbCommand(f'model.SendCommand(Language="Python", Command={mechScript!r})') 90 | coWbUnit.execWbCommand('model.Exit()') 91 | coWbUnit.saveProject(r"E:/Workdata/beam.wbpj") 92 | coWbUnit.finalize() 93 | -------------------------------------------------------------------------------- /src/PyWbUnit/Errors.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import re 3 | import sys 4 | 5 | 6 | class UnboundNameException(NameError): 7 | 8 | def __init__(self, msg): 9 | NameError.__init__(self, msg) 10 | 11 | 12 | class CommandArgumentException(ValueError): 13 | 14 | def __init__(self, msg): 15 | ValueError.__init__(self, msg) 16 | 17 | 18 | class MissingMemberException(AttributeError): 19 | 20 | def __init__(self, msg): 21 | AttributeError.__init__(self, msg) 22 | 23 | 24 | class CoWbUnitRuntimeError(RuntimeError): 25 | 26 | def __init__(self, msg): 27 | RuntimeError.__init__(self, msg) 28 | 29 | 30 | class CommandFailedException(RuntimeError): 31 | 32 | def __init__(self, msg): 33 | RuntimeError.__init__(self, msg) 34 | 35 | 36 | class TooManyArgumentsException(ValueError): 37 | 38 | def __init__(self, msg): 39 | ValueError.__init__(self, msg) 40 | 41 | 42 | def handleException(msg): 43 | try: 44 | errorType, message = re.search(r'(\w*Exception):\s*(\w+.*)', msg).groups() 45 | errorModule = sys.modules[__name__] 46 | return getattr(errorModule, errorType, Exception)(message.strip()) 47 | except AttributeError: 48 | return Exception(msg) 49 | -------------------------------------------------------------------------------- /src/PyWbUnit/_CoWbUnit.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import shutil 3 | from socket import * 4 | import subprocess 5 | import tempfile 6 | import os 7 | from pathlib import Path 8 | from typing import Union 9 | import time 10 | from Errors import (handleException, CoWbUnitRuntimeError) 11 | 12 | 13 | __all__ = ["CoWbUnitProcess", "WbServerClient", "__version__", 14 | "__author__"] 15 | 16 | __version__ = ".".join(("0", "3", "0")) 17 | __author__ = "tguangs@163.com" 18 | 19 | 20 | class CoWbUnitProcess(object): 21 | 22 | """Unit class for co-simulation with Workbench using Python. 23 | 24 | >>> coWbUnit = CoWbUnitProcess() 25 | >>> coWbUnit.initialize() 26 | >>> command = 'GetTemplate(TemplateName="Static Structural", Solver="ANSYS").CreateSystem()' 27 | >>> coWbUnit.execWbCommand(command) 28 | >>> coWbUnit.execWbCommand('systems=GetAllSystems()') 29 | >>> print(coWbUnit.queryWbVariable('systems')) 30 | >>> coWbUnit.saveProject(r'D:/example.wbpj') 31 | >>> coWbUnit.finalize() 32 | """ 33 | 34 | _aasName = "aaS_WbId.txt" 35 | 36 | def __init__(self, workDir=None, version=201, interactive=True): 37 | """ 38 | Constructor of CoWbUnitProcess. 39 | :param interactive: bool, whether to open the Workbench in interactive mode. 40 | :param workDir: str, the directory where the Workbench starts. 41 | :param version: int, workbench version: 2019R1-190/2020R1-201/2021R1-211. 42 | :param interactive: bool, whether to display the Workbench interface 43 | """ 44 | self._workDir = Path(workDir) if workDir else Path(".") 45 | if f"AWP_ROOT{version}" not in os.environ: 46 | raise CoWbUnitRuntimeError(f"ANSYS version: v{version} is not installed!") 47 | self._ansysDir = Path(os.environ[f"AWP_ROOT{version}"]) 48 | self._wbExe = self._ansysDir / "Framework" / "bin" / "Win64" / "runwb2.exe" 49 | self._interactive = interactive 50 | self._process = None 51 | self._coWbUnit = None 52 | 53 | def _start(self): 54 | aasFile = self._workDir / self._aasName 55 | self._clear_aasFile() 56 | stateOpt = fr'''-p "[9000:9200]" --server-write-connection-info "{aasFile}"''' 57 | if self._interactive: 58 | batchArgs = fr'"{self._wbExe}" -I {stateOpt}' 59 | else: 60 | batchArgs = fr'"{self._wbExe}" -s {stateOpt}' 61 | self._process = subprocess.Popen(batchArgs, cwd=str(self._workDir.absolute()), 62 | stdout=subprocess.PIPE, stderr=subprocess.STDOUT) 63 | 64 | def _clear_aasFile(self): 65 | aasFile = self._workDir / self._aasName 66 | if aasFile.exists(): aasFile.unlink() 67 | 68 | def initialize(self) -> None: 69 | """Called before `execWbCommand`: Start the Workbench in interactive 70 | mode and open the TCP server port to create a socket connection 71 | :return: None 72 | """ 73 | if self._coWbUnit is not None: 74 | raise RuntimeError("Workbench client already started!") 75 | self._start() 76 | self._coWbUnit = WbServerClient(self._readWbId()) 77 | 78 | def execWbCommand(self, command: str) -> str: 79 | """Send python script command to the Workbench for execution 80 | :param command: str, python script command 81 | :return: str, execution result 82 | """ 83 | if self._coWbUnit is None: 84 | raise CoWbUnitRuntimeError("Please initialize() first!") 85 | return self._coWbUnit.execWbCommand(command) 86 | 87 | def queryWbVariable(self, variable: str): 88 | """Query the value of `variable` in the workbench script environment 89 | :param variable: str, script variable name 90 | :return: str 91 | """ 92 | return self._coWbUnit.queryWbVariable(variable) 93 | 94 | def terminate(self): 95 | """Terminates the current Workbench client process 96 | :return: bool 97 | """ 98 | if self._process: 99 | try: 100 | self._process.terminate() 101 | tempDir = tempfile.mkdtemp() 102 | filePath = os.path.join(tempDir, "temp.wbpj") 103 | self.saveProject(filePath) 104 | self.finalize() 105 | while True: 106 | try: 107 | shutil.rmtree(tempDir) 108 | break 109 | except OSError: 110 | time.sleep(2) 111 | return True 112 | except PermissionError: 113 | return False 114 | 115 | def saveProject(self, filePath=None, overWrite=True): 116 | """Save the current workbench project file to `filePath` 117 | If the Project has not been saved, using method: `saveProject()` 118 | will raise `CommandFailedException` 119 | :param filePath: Optional[str, None], if 120 | :param overWrite: bool, Whether to overwrite the original project 121 | :return: str, execution result 122 | """ 123 | if filePath is None: 124 | return self.execWbCommand(f'Save(Overwrite={overWrite})') 125 | return self.execWbCommand(f'Save(FilePath={filePath!r}, Overwrite={overWrite})') 126 | 127 | def finalize(self): 128 | """ 129 | Exit the current workbench and close the TCP Server connection 130 | :return: None 131 | """ 132 | self.saveProject() 133 | self.exitWb() 134 | self._clear_aasFile() 135 | self._process = None 136 | self._coWbUnit = None 137 | 138 | def exitWb(self) -> str: 139 | """ 140 | `Exit` the current Workbench client process 141 | :return: str 142 | """ 143 | return self.execWbCommand("Exit") 144 | 145 | def _readWbId(self) -> Union[str, None]: 146 | if not self._process: return None 147 | aasFile = self._workDir / self._aasName 148 | while True: 149 | if not aasFile.exists(): 150 | time.sleep(0.5) 151 | continue 152 | with aasFile.open("r") as data: 153 | for line in data: 154 | if 'localhost' in line: 155 | return line 156 | 157 | 158 | class WbServerClient: 159 | 160 | """Client Class for the Workbench server connection 161 | >>> aas_key = 'localhost:9000' 162 | >>> wbClient = WbServerClient(aas_key) 163 | >>> wbClient.execWbCommand('') 164 | >>> print(wbClient.queryWbVariable('')) 165 | """ 166 | 167 | _suffix = '' 168 | _coding = 'UTF-8' 169 | _buffer = 1024 170 | 171 | def __init__(self, aasKey: str): 172 | aasList = aasKey.split(':') 173 | self._address = (aasList[0], int(aasList[1])) 174 | 175 | def execWbCommand(self, command: str) -> str: 176 | sockCommand = command + self._suffix 177 | 178 | with socket(AF_INET, SOCK_STREAM) as sock: 179 | sock.connect(self._address) 180 | sock.sendall(sockCommand.encode(self._coding)) 181 | data = sock.recv(self._buffer).decode() 182 | 183 | if data != '' and 'Exception:' in data: 184 | raise handleException(data) 185 | return data 186 | 187 | def queryWbVariable(self, variable) -> str: 188 | self.execWbCommand("__variable__=" + variable + ".__repr__()") 189 | retValue = self.execWbCommand("Query,__variable__") 190 | return retValue[13:] 191 | -------------------------------------------------------------------------------- /src/PyWbUnit/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Package : PyWbUnit 3 | # __init__.py Created on: 2021/06/14 4 | # Author : guangsheng.tian 5 | # Email : tguangs@163.com 6 | # 7 | # Copyright (C) 2019-2022 guangsheng.tian 8 | 9 | from ._CoWbUnit import (CoWbUnitProcess, WbServerClient, 10 | __version__, __author__) 11 | from . import Errors 12 | 13 | __all__ = ["CoWbUnitProcess", "WbServerClient", 14 | "__version__", "__author__", "Errors"] 15 | -------------------------------------------------------------------------------- /src/PyWbUnit/cython_setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from distutils.core import setup 3 | from Cython.Build import cythonize 4 | 5 | fileList = ["_CoWbUnit.py", "Errors.py"] 6 | 7 | setup( 8 | ext_modules=cythonize(fileList) 9 | ) 10 | --------------------------------------------------------------------------------