osis-python 0.5.3__tar.gz → 0.5.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {osis_python-0.5.3/src/osis_python.egg-info → osis_python-0.5.5}/PKG-INFO +1 -1
- {osis_python-0.5.3 → osis_python-0.5.5/src/osis_python.egg-info}/PKG-INFO +1 -1
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/__init__.py +1 -1
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/boundary/interface.py +3 -3
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/boundary/manager.py +1 -1
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/core/apdl_sync.py +13 -22
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/core/engine.py +28 -10
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/core/sync_apdl_cli.py +3 -3
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/element/manager.py +9 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/project/interface.py +1 -1
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/project/manager.py +1 -1
- osis_python-0.5.5/src/pyosis/transfer/out_to_python.py +415 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/transfer/parser.py +53 -53
- osis_python-0.5.3/src/pyosis/transfer/out_to_python.py +0 -246
- {osis_python-0.5.3 → osis_python-0.5.5}/LICENSE +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/README.md +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/pyproject.toml +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/setup.cfg +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/osis_python.egg-info/SOURCES.txt +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/osis_python.egg-info/dependency_links.txt +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/osis_python.egg-info/requires.txt +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/osis_python.egg-info/top_level.txt +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/boundary/__init__.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/control/__init__.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/control/control.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/control/io.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/control/manager.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/control/nonlinear.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/core/__init__.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/core/all_func.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/core/basic_manager.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/core/build.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/core/client.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/core/command.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/core/exceptions.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/core/references.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/display/__init__.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/display/interface.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/display/manager.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/dynamic/__init__.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/dynamic/load_to_mass.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/dynamic/manager.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/dynamic/modal.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/dynamic/seismic.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/element/__init__.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/element/interface.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/general/__init__.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/general/interface.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/geometry/__init__.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/geometry/interface.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/geometry/manager.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/live/__init__.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/live/analysis.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/live/grade.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/live/lane.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/live/manager.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/load/__init__.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/load/loadcase.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/load/manager.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/load/static.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/load/tendon.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/material/__init__.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/material/interface.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/material/manager.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/node/__init__.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/node/interface.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/node/manager.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/post/__init__.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/post/combine.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/post/design_check.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/post/display.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/post/manager.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/project/__init__.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/property/__init__.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/property/component_thickness.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/property/coordinate.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/property/creep_shrink.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/property/damping.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/property/manager.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/property/pu_curve.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/quick_building/__init__.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/quick_building/interface.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/result/__init__.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/result/check.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/result/env.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/result/loadcase.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/result/manager.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/result/result.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/section/__init__.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/section/common.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/section/composite.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/section/manager.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/section/numeric.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/section/param.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/section/rebar.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/section/rib.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/section/steel.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/settlement/__init__.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/settlement/interface.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/settlement/manager.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/stability/__init__.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/stability/buckling.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/stability/manager.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/stage/__init__.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/stage/define.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/stage/manager.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/stage/overall.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/thickness/__init__.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/thickness/interface.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/thickness/manager.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/transfer/__init__.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/transfer/generator.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/transfer/matrix.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/transfer/routes.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/src/pyosis/transfer/split.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/tests/test_delete_occupied.py +0 -0
- {osis_python-0.5.3 → osis_python-0.5.5}/tests/test_project_manager.py +0 -0
|
@@ -7,7 +7,7 @@ A library for extending OSIS functionality.
|
|
|
7
7
|
A library that allows users to use CAE with great flexibility.
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
|
-
__version__ = "0.5.
|
|
10
|
+
__version__ = "0.5.5"
|
|
11
11
|
__author__ = "CCCC Highway Consultant Co. Ltd." # 中交公路规划设计院 - 工程技术研究中心 - lzh - wjh - myf
|
|
12
12
|
|
|
13
13
|
from .core.engine import OSISEngine
|
|
@@ -224,11 +224,11 @@ def osis_assign_boundary(nBd: int=1, eOP: Literal["a", "s", "r", "aa", "ra"]="a"
|
|
|
224
224
|
pass
|
|
225
225
|
|
|
226
226
|
@REGISTRY.register("BdGrp")
|
|
227
|
-
def osis_boundary_group(strName: str, eOP: Literal["c", "a", "s", "r", "aa", "ra", "m", "d"], param:
|
|
227
|
+
def osis_boundary_group(strName: str, eOP: Literal["c", "a", "s", "r", "aa", "ra", "m", "d"], *param: str | int):
|
|
228
228
|
'''
|
|
229
229
|
## 添加或移除边界组
|
|
230
230
|
pyosis.boundary.osis_boundary_group
|
|
231
|
-
|
|
231
|
+
|
|
232
232
|
Args:
|
|
233
233
|
strName (str): 边界组名
|
|
234
234
|
eOP (str): 操作
|
|
@@ -240,7 +240,7 @@ def osis_boundary_group(strName: str, eOP: Literal["c", "a", "s", "r", "aa", "ra
|
|
|
240
240
|
* ra = 移除全部
|
|
241
241
|
* m = 修改组名
|
|
242
242
|
* d = 删除
|
|
243
|
-
param
|
|
243
|
+
param: 待操作的编号,支持的格式:*, *to*; *by*,仅用于替换。
|
|
244
244
|
例子:[2,3,5,"8to10"] ["2by3","5by6","8by10"] 重合的编号自动忽略
|
|
245
245
|
|
|
246
246
|
Returns:
|
|
@@ -396,7 +396,7 @@ class BoundaryGroup:
|
|
|
396
396
|
|
|
397
397
|
def _execute(self, operation: Literal["c", "a", "s", "r", "aa", "ra", "m", "d"], *param: str | int) -> None:
|
|
398
398
|
"""执行边界组底层操作(内部使用)"""
|
|
399
|
-
ok, err = osis_boundary_group(self.name, operation,
|
|
399
|
+
ok, err = osis_boundary_group(self.name, operation, *param)
|
|
400
400
|
if not ok:
|
|
401
401
|
raise RuntimeError(f"边界组操作 {self.name} ({operation}) 失败: {err}")
|
|
402
402
|
|
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
4. 有变化 → parse → 写 prep → 存本次 hash
|
|
8
8
|
|
|
9
9
|
用法:
|
|
10
|
-
|
|
11
|
-
if
|
|
12
|
-
print(
|
|
10
|
+
changed = engine.sync_apdl()
|
|
11
|
+
if changed:
|
|
12
|
+
print("命令流已变化,已写 prep")
|
|
13
13
|
"""
|
|
14
14
|
|
|
15
15
|
from __future__ import annotations
|
|
@@ -21,7 +21,6 @@ from dataclasses import dataclass
|
|
|
21
21
|
from pathlib import Path
|
|
22
22
|
from typing import TYPE_CHECKING
|
|
23
23
|
|
|
24
|
-
from ..transfer import parse_text
|
|
25
24
|
from ..transfer.out_to_python import write_prep_outputs
|
|
26
25
|
|
|
27
26
|
if TYPE_CHECKING:
|
|
@@ -39,19 +38,9 @@ _TIME_LINE_PATTERN = re.compile(
|
|
|
39
38
|
)
|
|
40
39
|
|
|
41
40
|
|
|
42
|
-
@dataclass
|
|
43
|
-
class ApdlSyncResult:
|
|
44
|
-
"""APDL 同步结果。"""
|
|
45
|
-
|
|
46
|
-
changed: bool = False
|
|
47
|
-
|
|
48
|
-
def summary(self) -> str:
|
|
49
|
-
return "命令流已变化,已写 prep" if self.changed else "命令流未变化"
|
|
50
|
-
|
|
51
|
-
|
|
52
41
|
@dataclass
|
|
53
42
|
class ApdlSessionStore:
|
|
54
|
-
"""记录最近一次同步的 .out 路径与 hash
|
|
43
|
+
"""记录最近一次同步的 .out 路径与 hash(供调试/展示)。"""
|
|
55
44
|
|
|
56
45
|
last_path: str = ""
|
|
57
46
|
last_hash: str = ""
|
|
@@ -162,11 +151,14 @@ def perform_apdl_sync(
|
|
|
162
151
|
engine: "OSISEngine",
|
|
163
152
|
path: str | None = None,
|
|
164
153
|
*,
|
|
165
|
-
force_export: bool =
|
|
166
|
-
) ->
|
|
154
|
+
force_export: bool = False,
|
|
155
|
+
) -> bool | None:
|
|
167
156
|
"""执行 APDL 同步:export → 比 hash → 有变化则写 prep(不执行 prep)。
|
|
168
157
|
|
|
169
|
-
|
|
158
|
+
Returns:
|
|
159
|
+
True - 命令流已变化,已写 prep
|
|
160
|
+
False - 命令流未变化
|
|
161
|
+
None - 未获取到项目路径(无法同步)
|
|
170
162
|
"""
|
|
171
163
|
if _get_project_dir(engine) is None:
|
|
172
164
|
return None
|
|
@@ -186,12 +178,11 @@ def perform_apdl_sync(
|
|
|
186
178
|
session.update_file(str(out_path), file_hash)
|
|
187
179
|
|
|
188
180
|
if file_hash == last_hash:
|
|
189
|
-
return
|
|
181
|
+
return False
|
|
190
182
|
|
|
191
|
-
|
|
192
|
-
write_prep_outputs(parsed, prep_dir)
|
|
183
|
+
write_prep_outputs(out_path, prep_dir)
|
|
193
184
|
|
|
194
185
|
# 未执行 prep,OSIS 未变
|
|
195
186
|
_save_persisted_hash(state_path, out_path, file_hash)
|
|
196
187
|
|
|
197
|
-
return
|
|
188
|
+
return True
|
|
@@ -22,7 +22,7 @@ from __future__ import annotations
|
|
|
22
22
|
import os
|
|
23
23
|
from typing import Any
|
|
24
24
|
|
|
25
|
-
from .apdl_sync import
|
|
25
|
+
from .apdl_sync import ApdlSessionStore, perform_apdl_sync
|
|
26
26
|
from ..material.manager import MaterialManager, material_manager
|
|
27
27
|
from ..section.manager import SectionManager, section_manager
|
|
28
28
|
from ..node.manager import NodeManager, node_manager
|
|
@@ -240,6 +240,15 @@ class OSISEngine:
|
|
|
240
240
|
"""显示管理器(边界 / 荷载 / 钢束显隐等)"""
|
|
241
241
|
return self._display
|
|
242
242
|
|
|
243
|
+
|
|
244
|
+
def start(self) -> None:
|
|
245
|
+
"""开启 OSIS 软件"""
|
|
246
|
+
...
|
|
247
|
+
|
|
248
|
+
def close(self) -> None:
|
|
249
|
+
"""关闭 OSIS 软件"""
|
|
250
|
+
...
|
|
251
|
+
|
|
243
252
|
# ──────────────────────────────────────────
|
|
244
253
|
# 通用操作(直接暴露 general 和 core 的函数)
|
|
245
254
|
# ──────────────────────────────────────────
|
|
@@ -335,10 +344,6 @@ class OSISEngine:
|
|
|
335
344
|
"""
|
|
336
345
|
self._project.open(filepath)
|
|
337
346
|
|
|
338
|
-
def close(self) -> None:
|
|
339
|
-
"""关闭 OSIS 软件"""
|
|
340
|
-
self._project.close()
|
|
341
|
-
|
|
342
347
|
# ──────────────────────────────────────────
|
|
343
348
|
# 便捷方法(委托给 control manager)
|
|
344
349
|
# ──────────────────────────────────────────
|
|
@@ -408,8 +413,21 @@ class OSISEngine:
|
|
|
408
413
|
return f"OSISEngine(project_path={self.project.get_directory()})"
|
|
409
414
|
|
|
410
415
|
def sync_apdl(
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
+
self,
|
|
417
|
+
path: str | None = None,
|
|
418
|
+
*,
|
|
419
|
+
force_export: bool = False,
|
|
420
|
+
) -> bool | None:
|
|
421
|
+
"""通过 APDL 导出从 OSIS 同步数据。OSIS 界面操作后调用。
|
|
422
|
+
|
|
423
|
+
Args:
|
|
424
|
+
path: 自定义 .out 文件路径,None 用项目下默认 _pyosis_sync.out。
|
|
425
|
+
force_export: False(默认)若 .out 已存在则直接复用,仅当 hash 与上次不一致才写 prep;
|
|
426
|
+
True 时总是重新执行 export_apdl。
|
|
427
|
+
|
|
428
|
+
Returns:
|
|
429
|
+
True - 命令流已变化,已写 prep
|
|
430
|
+
False - 命令流未变化
|
|
431
|
+
None - 未获取到项目路径
|
|
432
|
+
"""
|
|
433
|
+
return perform_apdl_sync(self, path, force_export=force_export)
|
|
@@ -20,16 +20,16 @@ from .engine import OSISEngine
|
|
|
20
20
|
def main() -> int:
|
|
21
21
|
engine = OSISEngine()
|
|
22
22
|
try:
|
|
23
|
-
|
|
23
|
+
changed = perform_apdl_sync(engine)
|
|
24
24
|
except Exception as exc:
|
|
25
25
|
print(f"sync_apdl 失败: {exc}", file=sys.stderr)
|
|
26
26
|
return 1
|
|
27
27
|
|
|
28
|
-
if
|
|
28
|
+
if changed is None:
|
|
29
29
|
print("跳过:未获取到项目路径")
|
|
30
30
|
return 0
|
|
31
31
|
|
|
32
|
-
print(
|
|
32
|
+
print("命令流已变化,已写 prep" if changed else "命令流未变化")
|
|
33
33
|
return 0
|
|
34
34
|
|
|
35
35
|
|
|
@@ -298,6 +298,15 @@ class ElementGroupManager:
|
|
|
298
298
|
Args:
|
|
299
299
|
name: 单元组名称
|
|
300
300
|
op: 操作
|
|
301
|
+
* c = 创建
|
|
302
|
+
* a = 添加
|
|
303
|
+
* s = 替换
|
|
304
|
+
* r = 移除
|
|
305
|
+
* aa = 添加全部
|
|
306
|
+
* ra = 移除全部
|
|
307
|
+
* m = 修改组名
|
|
308
|
+
* d = 删除
|
|
309
|
+
*param (str | int): 待操作的编号,支持的格式:*,*to*;*by*,仅用于替换。例子:2,3,5,8to10,2by3,5by6,8by10 重合的编号自动忽略
|
|
301
310
|
|
|
302
311
|
Returns:
|
|
303
312
|
ElementGroup: 创建的单元组对象
|
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
"""transfer 同步链路的纯文本处理工具:把 .out 文本转换为 pyosis prep 模块。
|
|
2
|
+
|
|
3
|
+
只负责文本解析与 Python 代码生成:
|
|
4
|
+
- 读 .out 源文件
|
|
5
|
+
- 用 parser 解析出命令流,并按 //--- MODULE_NAME --- 标记分桶
|
|
6
|
+
- 第一个模块标记之前的"前言"命令(典型为 clear/clc)写到 main.py
|
|
7
|
+
- 每个模块的命令写到 _1_control.py … _10_stage.py
|
|
8
|
+
|
|
9
|
+
不负责:
|
|
10
|
+
- .out 文件的获取(export_apdl) —— 由调用方管理
|
|
11
|
+
- prep 模块的执行(import + builder 调用) —— 由 main.py 调度
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import inspect
|
|
17
|
+
import re
|
|
18
|
+
import sys
|
|
19
|
+
from collections import defaultdict
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
from typing import Iterable
|
|
22
|
+
|
|
23
|
+
from pyosis.transfer.generator import generate_lines
|
|
24
|
+
from pyosis.transfer.parser import (
|
|
25
|
+
BLANK_RE,
|
|
26
|
+
COMMENT_LINE_RE,
|
|
27
|
+
ParsedCommand,
|
|
28
|
+
_parse_physical_line,
|
|
29
|
+
iter_physical_lines,
|
|
30
|
+
strip_inline_comment,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
# 匹配链式调用中的 .get(...) 部分(如 engine.section.get("k").set_offset(...))
|
|
34
|
+
_CHAIN_GET_RE = re.compile(r"""\.get\((?:"[^"]*"|'[^']*'|[^()]*)\)""")
|
|
35
|
+
|
|
36
|
+
# .out 中模块标记行的正则: //----- CONTROL ----- 之类
|
|
37
|
+
MODULE_PATTERN = re.compile(r"^\s*//-+\s*(\w+)\s*-*")
|
|
38
|
+
# 已知的模块名(用于过滤 .out 中的非模块横线注释,例如 //--- VERSION: x.xx ---//)
|
|
39
|
+
KNOWN_MODULES = frozenset({
|
|
40
|
+
"CONTROL",
|
|
41
|
+
"PROPERTY",
|
|
42
|
+
"MATERIAL",
|
|
43
|
+
"SECTION",
|
|
44
|
+
"NODE",
|
|
45
|
+
"ELEMENT",
|
|
46
|
+
"BOUNDARY",
|
|
47
|
+
"LOADCASE",
|
|
48
|
+
"ANALYSIS",
|
|
49
|
+
"STAGE",
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
# 模块文件名
|
|
53
|
+
MODULE_FILES = {
|
|
54
|
+
"CONTROL": "_1_control.py",
|
|
55
|
+
"PROPERTY": "_2_property.py",
|
|
56
|
+
"MATERIAL": "_3_material.py",
|
|
57
|
+
"SECTION": "_4_section.py",
|
|
58
|
+
"NODE": "_5_node.py",
|
|
59
|
+
"ELEMENT": "_6_element.py",
|
|
60
|
+
"BOUNDARY": "_7_boundary.py",
|
|
61
|
+
"LOADCASE": "_8_loadcase.py",
|
|
62
|
+
"ANALYSIS": "_9_analysis.py",
|
|
63
|
+
"STAGE": "_10_stage.py",
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
# 模块生成函数名
|
|
67
|
+
MODULE_BUILDERS = {
|
|
68
|
+
"CONTROL": "setup_control",
|
|
69
|
+
"PROPERTY": "build_property",
|
|
70
|
+
"MATERIAL": "build_materials",
|
|
71
|
+
"SECTION": "build_sections",
|
|
72
|
+
"NODE": "build_nodes",
|
|
73
|
+
"ELEMENT": "build_elements",
|
|
74
|
+
"BOUNDARY": "build_boundaries",
|
|
75
|
+
"LOADCASE": "build_loadcases",
|
|
76
|
+
"ANALYSIS": "build_analysis",
|
|
77
|
+
"STAGE": "build_stages",
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
MODULE_ORDER = tuple(MODULE_FILES.keys())
|
|
81
|
+
|
|
82
|
+
# 模块职责一句话(给 AI 看的提示)
|
|
83
|
+
MODULE_PURPOSE = {
|
|
84
|
+
"CONTROL": "全局控制参数(重力、非线性、收缩徐变开关等)",
|
|
85
|
+
"PROPERTY": "几何属性(坐标系、收缩徐变特性、钢束线型等)",
|
|
86
|
+
"MATERIAL": "材料定义(混凝土、钢筋、钢绞线)",
|
|
87
|
+
"SECTION": "截面定义(标准截面 + 加厚/变化截面)",
|
|
88
|
+
"NODE": "节点坐标",
|
|
89
|
+
"ELEMENT": "单元(梁/弹簧)创建 + 分组",
|
|
90
|
+
"BOUNDARY": "边界条件(支座、约束自由度)",
|
|
91
|
+
"LOADCASE": "荷载工况(自重、二期、预应力、温度、沉降)",
|
|
92
|
+
"ANALYSIS": "分析设置(活载等级、车道)",
|
|
93
|
+
"STAGE": "施工阶段(激活/钝化、体系转换)",
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
# 按模块分桶
|
|
98
|
+
def _split_by_module(text: str) -> tuple[list[ParsedCommand], dict[str, list[ParsedCommand]]]:
|
|
99
|
+
"""走一遍 .out 文本,把命令按 //--- MODULE_NAME --- 标记分桶。
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
(preamble, {MODULE: [cmd, ...]})
|
|
103
|
+
- preamble: 第一个模块标记之前的命令(典型为 clear/clc)
|
|
104
|
+
- by_module: 每个模块的命令列表,按 .out 出现顺序
|
|
105
|
+
"""
|
|
106
|
+
preamble: list[ParsedCommand] = []
|
|
107
|
+
by_module: dict[str, list[ParsedCommand]] = defaultdict(list)
|
|
108
|
+
current: str | None = None
|
|
109
|
+
|
|
110
|
+
for line in iter_physical_lines(text):
|
|
111
|
+
m = MODULE_PATTERN.match(line)
|
|
112
|
+
if m:
|
|
113
|
+
name = m.group(1).upper()
|
|
114
|
+
current = name if name in KNOWN_MODULES else None
|
|
115
|
+
continue
|
|
116
|
+
if COMMENT_LINE_RE.match(line) or BLANK_RE.match(line):
|
|
117
|
+
continue
|
|
118
|
+
line = strip_inline_comment(line)
|
|
119
|
+
if BLANK_RE.match(line):
|
|
120
|
+
continue
|
|
121
|
+
cmds = _parse_physical_line(line)
|
|
122
|
+
if current is None:
|
|
123
|
+
preamble.extend(cmds)
|
|
124
|
+
else:
|
|
125
|
+
by_module[current].extend(cmds)
|
|
126
|
+
|
|
127
|
+
return preamble, dict(by_module)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
# 读取 .out 文件内容(容错编码)
|
|
131
|
+
def _read_out_text(path: Path) -> str:
|
|
132
|
+
for encoding in ("gbk", "utf-8", "gb2312", "gb18030", "latin-1"):
|
|
133
|
+
try:
|
|
134
|
+
return path.read_text(encoding=encoding)
|
|
135
|
+
except UnicodeDecodeError:
|
|
136
|
+
continue
|
|
137
|
+
raise RuntimeError(f"无法解码 .out 文件: {path}")
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
# ──────────────────────────────────────────────────────────────────────
|
|
141
|
+
# 方法 docstring 注释:在每个被调用方法的第一次出现处,插入其 docstring 第一行
|
|
142
|
+
# ──────────────────────────────────────────────────────────────────────
|
|
143
|
+
|
|
144
|
+
_ENGINE_INSTANCE: object | None = None
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _get_engine() -> object:
|
|
148
|
+
"""懒加载一个 OSISEngine 实例,只读属性,不会真正连 OSIS。"""
|
|
149
|
+
global _ENGINE_INSTANCE
|
|
150
|
+
if _ENGINE_INSTANCE is None:
|
|
151
|
+
from pyosis.core.engine import OSISEngine
|
|
152
|
+
_ENGINE_INSTANCE = OSISEngine()
|
|
153
|
+
return _ENGINE_INSTANCE
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _strip_outer_args(line: str) -> str:
|
|
157
|
+
"""剥离最外层调用的实参列表,返回接收者表达式。
|
|
158
|
+
|
|
159
|
+
取的是最后一个顶层 `(`,这样链式调用也能保留中间的 .get(...)。
|
|
160
|
+
engine.control.set_gravity_acceleration(9.8) → engine.control.set_gravity_acceleration
|
|
161
|
+
engine.section.get(1).set_offset("Middle", 0) → engine.section.get(1).set_offset
|
|
162
|
+
engine.matrix("M", [[1]]) → engine.matrix
|
|
163
|
+
"""
|
|
164
|
+
depth = 0
|
|
165
|
+
last_paren = -1
|
|
166
|
+
in_str = False
|
|
167
|
+
sc: str | None = None
|
|
168
|
+
for i, ch in enumerate(line):
|
|
169
|
+
if in_str:
|
|
170
|
+
if ch == sc and (i == 0 or line[i - 1] != "\\"):
|
|
171
|
+
in_str = False
|
|
172
|
+
continue
|
|
173
|
+
if ch in ('"', "'"):
|
|
174
|
+
in_str = True
|
|
175
|
+
sc = ch
|
|
176
|
+
continue
|
|
177
|
+
if ch == "(":
|
|
178
|
+
if depth == 0:
|
|
179
|
+
last_paren = i
|
|
180
|
+
depth += 1
|
|
181
|
+
elif ch == ")":
|
|
182
|
+
depth -= 1
|
|
183
|
+
return line[:last_paren] if last_paren >= 0 else line
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def _doc_first_line(doc: str | None) -> str:
|
|
187
|
+
if not doc:
|
|
188
|
+
return ""
|
|
189
|
+
return doc.strip().split("\n", 1)[0].strip()
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def _resolve_chain_target(head: str, engine: object) -> object | None:
|
|
193
|
+
"""从 'engine.X.method'(已折叠 .get)解析出真正的可调用对象。
|
|
194
|
+
|
|
195
|
+
1) 直接 getattr 链:engine.section.create → SectionManager.create;engine.matrix → OSISEngine.matrix
|
|
196
|
+
2) 链式调用:head 折叠后是 engine.section.set_offset(或嵌套的 engine.live.case.include),
|
|
197
|
+
中间某一层管理器没有这个属性。从该层往前找带 .get 的管理器,用其返回类型注解
|
|
198
|
+
(Section / LiveCase / TendonShape 等)找到方法本体。
|
|
199
|
+
"""
|
|
200
|
+
parts = head.split(".")
|
|
201
|
+
if len(parts) < 2 or parts[0] != "engine":
|
|
202
|
+
return None
|
|
203
|
+
|
|
204
|
+
# 1) 直接 getattr 链
|
|
205
|
+
try:
|
|
206
|
+
obj = engine
|
|
207
|
+
for p in parts[1:]:
|
|
208
|
+
obj = getattr(obj, p)
|
|
209
|
+
if callable(obj):
|
|
210
|
+
return obj
|
|
211
|
+
except AttributeError:
|
|
212
|
+
pass
|
|
213
|
+
|
|
214
|
+
if len(parts) < 3:
|
|
215
|
+
return None
|
|
216
|
+
method_name = parts[-1]
|
|
217
|
+
|
|
218
|
+
# 2) 从尾往头试每一层,看它是不是一个带 .get 的管理器
|
|
219
|
+
for cut in range(len(parts) - 1, 1, -1):
|
|
220
|
+
try:
|
|
221
|
+
mgr_obj = engine
|
|
222
|
+
for p in parts[1:cut]:
|
|
223
|
+
mgr_obj = getattr(mgr_obj, p)
|
|
224
|
+
except AttributeError:
|
|
225
|
+
continue
|
|
226
|
+
get_method = type(mgr_obj).__dict__.get("get")
|
|
227
|
+
if get_method is None:
|
|
228
|
+
continue
|
|
229
|
+
try:
|
|
230
|
+
sig = inspect.signature(get_method)
|
|
231
|
+
except (ValueError, TypeError):
|
|
232
|
+
continue
|
|
233
|
+
ann = sig.return_annotation
|
|
234
|
+
if ann is inspect.Parameter.empty:
|
|
235
|
+
continue
|
|
236
|
+
if isinstance(ann, str):
|
|
237
|
+
mod = sys.modules.get(get_method.__module__)
|
|
238
|
+
if mod is None:
|
|
239
|
+
continue
|
|
240
|
+
try:
|
|
241
|
+
ann = eval(ann, vars(mod))
|
|
242
|
+
except Exception:
|
|
243
|
+
continue
|
|
244
|
+
candidates: list[type] = []
|
|
245
|
+
if isinstance(ann, type):
|
|
246
|
+
candidates.append(ann)
|
|
247
|
+
else:
|
|
248
|
+
for arg in getattr(ann, "__args__", ()):
|
|
249
|
+
if isinstance(arg, type):
|
|
250
|
+
candidates.append(arg)
|
|
251
|
+
else:
|
|
252
|
+
for inner in getattr(arg, "__args__", ()):
|
|
253
|
+
if isinstance(inner, type):
|
|
254
|
+
candidates.append(inner)
|
|
255
|
+
for cand in candidates:
|
|
256
|
+
target = vars(cand).get(method_name)
|
|
257
|
+
if target is not None and callable(target):
|
|
258
|
+
return target
|
|
259
|
+
return None
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def _method_doc_for(line: str) -> str:
|
|
263
|
+
"""取这一行生成代码所调用方法的 docstring 第一行。非 engine 调用返空串。"""
|
|
264
|
+
s = line.strip()
|
|
265
|
+
if not s.startswith("engine."):
|
|
266
|
+
return ""
|
|
267
|
+
head = _CHAIN_GET_RE.sub("", _strip_outer_args(s))
|
|
268
|
+
# head 形如 'engine.section.set_offset' 或 'engine.control.set_gravity_acceleration'
|
|
269
|
+
target = _resolve_chain_target(head, _get_engine())
|
|
270
|
+
if target is None:
|
|
271
|
+
return ""
|
|
272
|
+
return _doc_first_line(getattr(target, "__doc__", None))
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def _insert_method_doc_comments(lines: list[str]) -> list[str]:
|
|
276
|
+
"""在每个被调用的独特方法的第一次出现处,插入一行 # <docstring 第一行>。"""
|
|
277
|
+
seen: set[str] = set()
|
|
278
|
+
out: list[str] = []
|
|
279
|
+
for line in lines:
|
|
280
|
+
s = line.strip()
|
|
281
|
+
if s.startswith("engine."):
|
|
282
|
+
# 用与解析时相同的 head 作为去重键,保证多次调用同一方法只注一次
|
|
283
|
+
head = _CHAIN_GET_RE.sub("", _strip_outer_args(s))
|
|
284
|
+
if head not in seen:
|
|
285
|
+
doc = _method_doc_for(line)
|
|
286
|
+
if doc:
|
|
287
|
+
out.append(f"# {doc}")
|
|
288
|
+
seen.add(head)
|
|
289
|
+
out.append(line)
|
|
290
|
+
return out
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
# 写入 prep 模块
|
|
294
|
+
def _write_prep_module(prep_dir: Path, module: str, lines: list[str]) -> Path:
|
|
295
|
+
"""写入 prep 模块;lines 为空时写 pass stub。
|
|
296
|
+
|
|
297
|
+
生成顺序:
|
|
298
|
+
1. docstring(说明模块职责)
|
|
299
|
+
2. imports
|
|
300
|
+
3. builder 函数
|
|
301
|
+
4. if __name__ == "__main__": 单跑入口
|
|
302
|
+
"""
|
|
303
|
+
fname = MODULE_FILES[module]
|
|
304
|
+
builder = MODULE_BUILDERS[module]
|
|
305
|
+
empty = not lines
|
|
306
|
+
purpose = MODULE_PURPOSE.get(module, module)
|
|
307
|
+
|
|
308
|
+
if empty:
|
|
309
|
+
doc = f"OSIS 命令流 {module} 模块 — 当前 .out 中无该段,无需调用。"
|
|
310
|
+
fn_body = [" pass"]
|
|
311
|
+
else:
|
|
312
|
+
doc = f"OSIS 命令流 {module} 模块 — {purpose}"
|
|
313
|
+
annotated = _insert_method_doc_comments(lines)
|
|
314
|
+
fn_body = [f" {line}" for line in annotated]
|
|
315
|
+
|
|
316
|
+
body_lines = [
|
|
317
|
+
f'"""{doc}"""',
|
|
318
|
+
"",
|
|
319
|
+
"from __future__ import annotations",
|
|
320
|
+
"",
|
|
321
|
+
"from pyosis.core.engine import OSISEngine",
|
|
322
|
+
"",
|
|
323
|
+
f"def {builder}(engine: OSISEngine) -> None:",
|
|
324
|
+
*fn_body,
|
|
325
|
+
"",
|
|
326
|
+
'if __name__ == "__main__":',
|
|
327
|
+
" from _0_engine import engine",
|
|
328
|
+
f" {builder}(engine)",
|
|
329
|
+
"",
|
|
330
|
+
]
|
|
331
|
+
path = prep_dir / fname
|
|
332
|
+
path.write_text("\n".join(body_lines), encoding="utf-8")
|
|
333
|
+
return path
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def _iter_module_call_lines() -> Iterable[str]:
|
|
337
|
+
"""main.py 中依次调用各模块 builder 的语句(4 空格缩进)。"""
|
|
338
|
+
for mod in MODULE_ORDER:
|
|
339
|
+
yield f" {MODULE_BUILDERS[mod]}(engine)"
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def _write_main_py(prep_dir: Path, preamble_lines: list[str]) -> Path:
|
|
343
|
+
"""写入 main.py:先执行 preamble(典型为 clear/clc),再依次执行 _1.._10 builder。"""
|
|
344
|
+
import_lines = [f"from {Path(MODULE_FILES[mod]).stem} import {MODULE_BUILDERS[mod]}" for mod in MODULE_ORDER]
|
|
345
|
+
|
|
346
|
+
body_lines = [
|
|
347
|
+
'"""main.py — 入口脚本,按 _1.._10 顺序依次执行 prep 模块。',
|
|
348
|
+
"",
|
|
349
|
+
"可直接 `python main.py`,或被 import 后调 main()。",
|
|
350
|
+
"engine 用 _0_engine 模块级单例,跟同包内的 prep 模块保持一致。",
|
|
351
|
+
'"""',
|
|
352
|
+
"",
|
|
353
|
+
"from __future__ import annotations",
|
|
354
|
+
"",
|
|
355
|
+
"import sys",
|
|
356
|
+
"from pathlib import Path",
|
|
357
|
+
"",
|
|
358
|
+
"# 让 main.py 不管从哪个目录跑都能 import 同目录的 prep modules",
|
|
359
|
+
"sys.path.insert(0, str(Path(__file__).resolve().parent))",
|
|
360
|
+
"",
|
|
361
|
+
"from _0_engine import engine",
|
|
362
|
+
*import_lines,
|
|
363
|
+
"",
|
|
364
|
+
"def main() -> None:",
|
|
365
|
+
]
|
|
366
|
+
for line in preamble_lines:
|
|
367
|
+
body_lines.append(f" {line}")
|
|
368
|
+
body_lines.append("")
|
|
369
|
+
body_lines.extend(_iter_module_call_lines())
|
|
370
|
+
body_lines.extend([
|
|
371
|
+
"",
|
|
372
|
+
'if __name__ == "__main__":',
|
|
373
|
+
" main()",
|
|
374
|
+
"",
|
|
375
|
+
])
|
|
376
|
+
|
|
377
|
+
path = prep_dir / "main.py"
|
|
378
|
+
path.write_text("\n".join(body_lines), encoding="utf-8")
|
|
379
|
+
return path
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
def write_prep_outputs(out_path: Path, prep_dir: Path) -> tuple[int, list[Path]]:
|
|
383
|
+
"""读 .out 源文件,生成 _0_engine.py + _1~_10 prep 模块 + main.py,返回 (代码行数, 文件路径列表)。
|
|
384
|
+
|
|
385
|
+
每个标准模块都会写入文件;.out 中无对应命令时生成 pass stub,避免遗留旧 prep。
|
|
386
|
+
|
|
387
|
+
Args:
|
|
388
|
+
out_path: 源 .out 文件路径(由 apdl_sync 管理获取,这里只读取)
|
|
389
|
+
prep_dir: prep 输出目录(必须已存在)
|
|
390
|
+
"""
|
|
391
|
+
text = _read_out_text(out_path)
|
|
392
|
+
preamble, by_module = _split_by_module(text)
|
|
393
|
+
|
|
394
|
+
code_line_count = 0
|
|
395
|
+
prep_paths: list[Path] = []
|
|
396
|
+
|
|
397
|
+
for module in MODULE_ORDER:
|
|
398
|
+
cmds = by_module.get(module, [])
|
|
399
|
+
lines = generate_lines(cmds) if cmds else []
|
|
400
|
+
code_line_count += len(lines)
|
|
401
|
+
prep_paths.append(_write_prep_module(prep_dir, module, lines))
|
|
402
|
+
|
|
403
|
+
# 前言(典型 clear/clc)放到 main.py 顶部;无前言时为空列表
|
|
404
|
+
preamble_lines = generate_lines(preamble) if preamble else []
|
|
405
|
+
|
|
406
|
+
(prep_dir / "_0_engine.py").write_text(
|
|
407
|
+
'"""模块级单例 OSISEngine,供同包内其他 prep 模块 import 使用。\n\n'
|
|
408
|
+
"不要在主代码里直接 new OSISEngine();通过 main.py 调度。\n"
|
|
409
|
+
'"""\n'
|
|
410
|
+
"from pyosis.core.engine import OSISEngine\n\n"
|
|
411
|
+
"engine = OSISEngine()\n",
|
|
412
|
+
encoding="utf-8",
|
|
413
|
+
)
|
|
414
|
+
prep_paths.append(_write_main_py(prep_dir, preamble_lines))
|
|
415
|
+
return code_line_count, prep_paths
|