osis-python 0.5.2__py3-none-any.whl → 0.5.4__py3-none-any.whl
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.2.dist-info → osis_python-0.5.4.dist-info}/METADATA +1 -1
- {osis_python-0.5.2.dist-info → osis_python-0.5.4.dist-info}/RECORD +19 -16
- pyosis/__init__.py +1 -1
- pyosis/core/all_func.py +2 -1
- pyosis/core/apdl_sync.py +13 -22
- pyosis/core/engine.py +48 -9
- pyosis/core/sync_apdl_cli.py +3 -3
- pyosis/display/__init__.py +8 -0
- pyosis/display/interface.py +103 -0
- pyosis/display/manager.py +115 -0
- pyosis/element/manager.py +9 -0
- pyosis/project/interface.py +12 -1
- pyosis/project/manager.py +30 -6
- pyosis/transfer/out_to_python.py +264 -95
- pyosis/transfer/parser.py +53 -53
- pyosis/transfer/routes.py +6 -0
- {osis_python-0.5.2.dist-info → osis_python-0.5.4.dist-info}/WHEEL +0 -0
- {osis_python-0.5.2.dist-info → osis_python-0.5.4.dist-info}/licenses/LICENSE +0 -0
- {osis_python-0.5.2.dist-info → osis_python-0.5.4.dist-info}/top_level.txt +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
osis_python-0.5.
|
|
2
|
-
pyosis/__init__.py,sha256=
|
|
1
|
+
osis_python-0.5.4.dist-info/licenses/LICENSE,sha256=T4mkSYliXdU9mYe-NfHkqg6GPhzoYaIQO_a630UzpS4,1088
|
|
2
|
+
pyosis/__init__.py,sha256=N9nzd0g89imrL0phXM-9CNHnHaggd9920UZPFTH96A0,501
|
|
3
3
|
pyosis/boundary/__init__.py,sha256=W5LfF-jugWa-YI8pnfELZaz0pXLa-D8TiGnBn12HGGQ,102
|
|
4
4
|
pyosis/boundary/interface.py,sha256=3MG3ZwOP_HM6WF6NTs8oyaTwucdCDa5CANifOdVZ9u8,8998
|
|
5
5
|
pyosis/boundary/manager.py,sha256=SZAGysQFju2yEbibTTY_y0YosDVLUvA5pq-IOPB3WN0,31839
|
|
@@ -9,16 +9,19 @@ pyosis/control/io.py,sha256=buaut3-W8kwJIe3PgjtgOU0JCSgDfOEd2cS3mxYsfuc,591
|
|
|
9
9
|
pyosis/control/manager.py,sha256=9vzNx6E9MVxU2DVNledeWv6UQaQNr3W9q5BJrqsnxzU,6795
|
|
10
10
|
pyosis/control/nonlinear.py,sha256=26W_2GhD_0cn3YQxJjgbPR1oEopJWyuFWGlgx0q023I,1604
|
|
11
11
|
pyosis/core/__init__.py,sha256=KvFYe6Iaain38GbuHT4s-gmxSWYzrnJDSCu8H_0FZBA,523
|
|
12
|
-
pyosis/core/all_func.py,sha256=
|
|
13
|
-
pyosis/core/apdl_sync.py,sha256=
|
|
12
|
+
pyosis/core/all_func.py,sha256=VhvAeMJMCQJ1redjqdkm7meTw1PJNDoRdmXXh2LzkVo,521
|
|
13
|
+
pyosis/core/apdl_sync.py,sha256=rW5Ot6FLpmUzpfMlrbemstnfhxD_0qtPrWgDfj9_4XY,5312
|
|
14
14
|
pyosis/core/basic_manager.py,sha256=S_IVCCGZXTEmNQ-0T4MNEG4xvHMyA3Y2kmeupHCjnHU,230
|
|
15
15
|
pyosis/core/build.py,sha256=imXSZ8tU1yxY-a3kgZ0BwQEQggsWlG19bxHhYbi6mus,80845
|
|
16
16
|
pyosis/core/client.py,sha256=0Fx--DW0aVvqnDUP0iUTPLvnDEPKfnA6jOE-nJVQJrU,2838
|
|
17
17
|
pyosis/core/command.py,sha256=dmI7UtdELTTqziHh4w2NmhsU4QagGPdu1q53NGCEQ_U,8559
|
|
18
|
-
pyosis/core/engine.py,sha256=
|
|
18
|
+
pyosis/core/engine.py,sha256=N5Xp8Z3y-e4_dI0Vg5UtDFMzJjqI8kVyGiIZyB64n3I,15132
|
|
19
19
|
pyosis/core/exceptions.py,sha256=x0I0Emooejy8wF1ZBWGz7Yc1N70MitsnhhQv9txJQj8,1093
|
|
20
20
|
pyosis/core/references.py,sha256=JQ8260aqVB5PhhGrqMt7W7fmrsmE69dFdrE6EyyLADg,2755
|
|
21
|
-
pyosis/core/sync_apdl_cli.py,sha256=
|
|
21
|
+
pyosis/core/sync_apdl_cli.py,sha256=xlRDhtR4fOebLvyVZT9joUp6Q0H55gIt0YDy9Cb4HB4,874
|
|
22
|
+
pyosis/display/__init__.py,sha256=POxWZ8I5Qanj4ETpHBZMhXNOMlsLMYRamXwP_JjSDUo,161
|
|
23
|
+
pyosis/display/interface.py,sha256=QCMFWG7Xxh8RDSbvdtnT3fyg90pnKAV-DX-LnL2xtUM,3133
|
|
24
|
+
pyosis/display/manager.py,sha256=wV_JMDFH25TkrdBn6mFZvmIcju62297sWR7nlKvBgos,3720
|
|
22
25
|
pyosis/dynamic/__init__.py,sha256=ThVGZWd8qryNJc99IPKJ7oA10hg1skiHTYvWPBelB2U,314
|
|
23
26
|
pyosis/dynamic/load_to_mass.py,sha256=xmSC16ug2C8HaY6rEwxfSX3puF5qjsUPsu44Skl8pyA,2940
|
|
24
27
|
pyosis/dynamic/manager.py,sha256=v5SyICAIimFcJwmHPL8AF49PrqsS8kNivz1OQaqTR10,25829
|
|
@@ -26,7 +29,7 @@ pyosis/dynamic/modal.py,sha256=__as3Nl7Ks2ntgcZwAA9UhvsvTfBl-1zSUOTsnPVf-Y,640
|
|
|
26
29
|
pyosis/dynamic/seismic.py,sha256=8DIh7xkUgl0UwweDq4WoYSNzMxpLnpDaM4W_7u3rg_A,6380
|
|
27
30
|
pyosis/element/__init__.py,sha256=fZgmxJiNhCe3Ki8vNEdZOhyinxwvlF6Jcu-DgD-c6YM,92
|
|
28
31
|
pyosis/element/interface.py,sha256=xLd2ljq0qW5uvqV0Lo2e6Ly7NShh_p2I5vQNdcA8P4Q,9872
|
|
29
|
-
pyosis/element/manager.py,sha256=
|
|
32
|
+
pyosis/element/manager.py,sha256=tjvKDjj7_mt6dsvd1ABobdUA1IxH7Nrfy-5SKKNkT5o,28776
|
|
30
33
|
pyosis/general/__init__.py,sha256=dyvge-GgUpT3BeDwm_Lss1xqNlxAQdt63WJ7QcuZ_O4,24
|
|
31
34
|
pyosis/general/interface.py,sha256=YRXQ-iXYVhiUBiUd7fpA0NOtL5VSueOHcobj625aQeU,5382
|
|
32
35
|
pyosis/geometry/__init__.py,sha256=mlAXlxaohcEcm8-6anyDo1fevh_IzYz2OXBn0_DIrCY,99
|
|
@@ -54,8 +57,8 @@ pyosis/post/design_check.py,sha256=vJzpu63yBjU4QDUIxvLOhUvqPrZ56q301ADCgHdu0eA,1
|
|
|
54
57
|
pyosis/post/display.py,sha256=BdoHetcM69Ez4s0-Zx3MreIcadxJPv2U2cx0E0PJTVw,2006
|
|
55
58
|
pyosis/post/manager.py,sha256=wj0nGIqdlRhu9zqWxZAAhNAt9FB6vX0-hVtIYcdC6zU,19927
|
|
56
59
|
pyosis/project/__init__.py,sha256=hYfnubp0bbHJK2tBBH12XDs3CeeO0d7whzILDxl4044,78
|
|
57
|
-
pyosis/project/interface.py,sha256=
|
|
58
|
-
pyosis/project/manager.py,sha256=
|
|
60
|
+
pyosis/project/interface.py,sha256=Iiku4SsBlBkH216Hrb2E3VrqzHnoLW6tlGwxfFDwbpU,1522
|
|
61
|
+
pyosis/project/manager.py,sha256=iVgSFQXsbIo-qTALN7bEltM7m5NWEBSB7ZoJ-FnmM58,3545
|
|
59
62
|
pyosis/property/__init__.py,sha256=xGdJItBRic3rd-6i5vyjGfA3KY-e9-WuOquIEnjPP_4,191
|
|
60
63
|
pyosis/property/component_thickness.py,sha256=jrbmc4TwulxakRXKJpyC0oDue5EKLDODcKZHObf2tTw,728
|
|
61
64
|
pyosis/property/coordinate.py,sha256=xFfuzXCzBWqYsDxHifpYCYgC_to9kC4LKD2yTX2CtvM,2668
|
|
@@ -96,11 +99,11 @@ pyosis/thickness/manager.py,sha256=ALJXBQEwIdIXmoYcjyvrYdh1mzNEygSf5Eo6y__0u6c,4
|
|
|
96
99
|
pyosis/transfer/__init__.py,sha256=OZVxVnjhDGe4fHC6pjtOaKPzAKuoDp3-XkNFzDWepsE,623
|
|
97
100
|
pyosis/transfer/generator.py,sha256=T3YLf4TrqAF4FFIa84opclmNDCw5WpvbNIsKC2rvKcU,3541
|
|
98
101
|
pyosis/transfer/matrix.py,sha256=sHw9X34-PUeY1EdoNklgK0LUHwbBPnhkhk0R9ME0XG0,3378
|
|
99
|
-
pyosis/transfer/out_to_python.py,sha256=
|
|
100
|
-
pyosis/transfer/parser.py,sha256=
|
|
101
|
-
pyosis/transfer/routes.py,sha256=
|
|
102
|
+
pyosis/transfer/out_to_python.py,sha256=WMzu1GaVvzvO1oq9ihyyp9dn2xY4s6ualW5M7CO98Cg,14464
|
|
103
|
+
pyosis/transfer/parser.py,sha256=R_aBlYJTe53hD1sX5PBv86bhj88NQwANFRN4pGYudQ8,6774
|
|
104
|
+
pyosis/transfer/routes.py,sha256=GAgrx71utq1tToxQKtfYeBwBvy7bktvFWEvWq3FPYIk,8267
|
|
102
105
|
pyosis/transfer/split.py,sha256=hpBe9D9WbjbKsovx4hM3X5RjTrruWNkuFmZt2ed0fME,1284
|
|
103
|
-
osis_python-0.5.
|
|
104
|
-
osis_python-0.5.
|
|
105
|
-
osis_python-0.5.
|
|
106
|
-
osis_python-0.5.
|
|
106
|
+
osis_python-0.5.4.dist-info/METADATA,sha256=KK6oUHASR6A6uzfKiE2BrL0Q6lOTSNBO9itDyoHs3aA,11330
|
|
107
|
+
osis_python-0.5.4.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
108
|
+
osis_python-0.5.4.dist-info/top_level.txt,sha256=6VDNySkQKwipRXcN8ayKVci4vAXi4oPyWLqFhNVcIfM,7
|
|
109
|
+
osis_python-0.5.4.dist-info/RECORD,,
|
pyosis/__init__.py
CHANGED
|
@@ -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.4"
|
|
11
11
|
__author__ = "CCCC Highway Consultant Co. Ltd." # 中交公路规划设计院 - 工程技术研究中心 - lzh - wjh - myf
|
|
12
12
|
|
|
13
13
|
from .core.engine import OSISEngine
|
pyosis/core/all_func.py
CHANGED
pyosis/core/apdl_sync.py
CHANGED
|
@@ -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
|
pyosis/core/engine.py
CHANGED
|
@@ -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
|
|
@@ -41,6 +41,7 @@ from ..stability.manager import StabilityManager, stability_manager
|
|
|
41
41
|
from ..dynamic.manager import DynamicManager, dynamic_manager
|
|
42
42
|
from ..post.manager import PostManager, post_manager
|
|
43
43
|
from ..result.manager import ResultManager, result_manager
|
|
44
|
+
from ..display.manager import DisplayManager, display_manager
|
|
44
45
|
from ..general.interface import (
|
|
45
46
|
osis_matrix,
|
|
46
47
|
output_result_for_calc_book,
|
|
@@ -78,6 +79,7 @@ class OSISEngine:
|
|
|
78
79
|
- dynamic: 动力分析管理器(荷载转换质量、模态、反应谱)
|
|
79
80
|
- post: 后处理管理器(荷载组合、规范验算、结果显示)
|
|
80
81
|
- result: 结果导出管理器(工况结果、包络结果、验算结果)
|
|
82
|
+
- display: 显示管理器(边界 / 荷载 / 钢束显隐等)
|
|
81
83
|
|
|
82
84
|
通用操作可直接通过 engine 调用:
|
|
83
85
|
- run(), solve(), replot(), clear(), clc()
|
|
@@ -114,6 +116,9 @@ class OSISEngine:
|
|
|
114
116
|
### 后处理 ###
|
|
115
117
|
self._post = post_manager
|
|
116
118
|
self._result = result_manager
|
|
119
|
+
|
|
120
|
+
### 显示 ###
|
|
121
|
+
self._display = display_manager
|
|
117
122
|
|
|
118
123
|
# OSIS端口与网址
|
|
119
124
|
if port is None:
|
|
@@ -228,6 +233,22 @@ class OSISEngine:
|
|
|
228
233
|
"""结果导出管理器(工况结果、包络结果、验算结果)"""
|
|
229
234
|
return self._result
|
|
230
235
|
|
|
236
|
+
### 显示 ###
|
|
237
|
+
|
|
238
|
+
@property
|
|
239
|
+
def display(self) -> DisplayManager:
|
|
240
|
+
"""显示管理器(边界 / 荷载 / 钢束显隐等)"""
|
|
241
|
+
return self._display
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def start(self) -> None:
|
|
245
|
+
"""开启 OSIS 软件"""
|
|
246
|
+
...
|
|
247
|
+
|
|
248
|
+
def close(self) -> None:
|
|
249
|
+
"""关闭 OSIS 软件"""
|
|
250
|
+
...
|
|
251
|
+
|
|
231
252
|
# ──────────────────────────────────────────
|
|
232
253
|
# 通用操作(直接暴露 general 和 core 的函数)
|
|
233
254
|
# ──────────────────────────────────────────
|
|
@@ -297,9 +318,14 @@ class OSISEngine:
|
|
|
297
318
|
# 便捷方法(委托给 project manager)
|
|
298
319
|
# ──────────────────────────────────────────
|
|
299
320
|
|
|
300
|
-
def save_project(self) -> None:
|
|
301
|
-
"""
|
|
302
|
-
|
|
321
|
+
def save_project(self, filepath: str = "") -> None:
|
|
322
|
+
"""保存项目
|
|
323
|
+
|
|
324
|
+
Args:
|
|
325
|
+
filepath: 工程文件路径。为空时使用当前项目路径;
|
|
326
|
+
非空时使用传入路径。
|
|
327
|
+
"""
|
|
328
|
+
self._project.save(filepath)
|
|
303
329
|
|
|
304
330
|
def new_project(self, type: int = 1, filepath: str = "") -> None:
|
|
305
331
|
"""新建项目
|
|
@@ -387,8 +413,21 @@ class OSISEngine:
|
|
|
387
413
|
return f"OSISEngine(project_path={self.project.get_directory()})"
|
|
388
414
|
|
|
389
415
|
def sync_apdl(
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
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)
|
pyosis/core/sync_apdl_cli.py
CHANGED
|
@@ -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
|
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""显示控制命令接口
|
|
2
|
+
|
|
3
|
+
控制边界、荷载、钢束显隐,以及视图方向、显示开关等。
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import Literal, Sequence, Union
|
|
7
|
+
|
|
8
|
+
from ..core import REGISTRY
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@REGISTRY.register("DispCtrl")
|
|
12
|
+
def osis_disp_ctrl(
|
|
13
|
+
eObject: Literal["bc", "boundary", "lg", "load", "td", "tendon"],
|
|
14
|
+
eType: str,
|
|
15
|
+
ids: Union[str, int, Sequence[Union[str, int]]],
|
|
16
|
+
bShow: Literal[0, 1] = 1,
|
|
17
|
+
):
|
|
18
|
+
"""控制边界 / 荷载 / 钢束的显示与隐藏。
|
|
19
|
+
|
|
20
|
+
格式: ``dispctrl,<Object>,<Type>,<Number/Name...>,<0|1>``
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
eObject (str): 对象类型
|
|
24
|
+
* ``bc`` / ``boundary`` = 边界
|
|
25
|
+
* ``lg`` / ``load`` = 荷载
|
|
26
|
+
* ``td`` / ``tendon`` = 钢束
|
|
27
|
+
eType (str): 子类型
|
|
28
|
+
* 边界: ``all``, ``general``, ``mstslv``, ``release``, ``elstcspt``,
|
|
29
|
+
``generalelstcspt``, ``rigid``, ``secfactor``
|
|
30
|
+
* 荷载: ``all``, ``gravity``, ``nforce``, ``line``, ``pointforce``,
|
|
31
|
+
``pointmoment``, ``displacement``, ``initial``, ``utemp``, ``gtemp``,
|
|
32
|
+
``pst``, ``cforce``, ``elementsurface``
|
|
33
|
+
* 钢束: 通常为 ``all``
|
|
34
|
+
ids: 编号或名称
|
|
35
|
+
* ``all`` = 全部
|
|
36
|
+
* 单个编号 / 名称,或多个组成的序列(如 ``[1, 2, "3to10"]``、``["T1", "T2"]``)
|
|
37
|
+
bShow (int): ``0`` = 隐藏,``1`` = 显示
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
tuple (bool, str): 是否成功,失败原因
|
|
41
|
+
|
|
42
|
+
Examples:
|
|
43
|
+
>>> # 显示全部边界
|
|
44
|
+
>>> osis_disp_ctrl("bc", "all", "all", 1)
|
|
45
|
+
>>> # 显示一般边界 1、2、3to10
|
|
46
|
+
>>> osis_disp_ctrl("bc", "general", [1, 2, "3to10"], 1)
|
|
47
|
+
>>> # 显示全部节点力荷载
|
|
48
|
+
>>> osis_disp_ctrl("lg", "nforce", "all", 1)
|
|
49
|
+
>>> # 显示名为 DeadLoad 的荷载
|
|
50
|
+
>>> osis_disp_ctrl("lg", "all", "DeadLoad", 1)
|
|
51
|
+
>>> # 隐藏钢束 1、2、3to10
|
|
52
|
+
>>> osis_disp_ctrl("td", "all", [1, 2, "3to10"], 0)
|
|
53
|
+
>>> # 显示钢束 T1、T2
|
|
54
|
+
>>> osis_disp_ctrl("td", "all", ["T1", "T2"], 1)
|
|
55
|
+
"""
|
|
56
|
+
pass
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@REGISTRY.register("/control")
|
|
60
|
+
def osis_control(
|
|
61
|
+
eAction: str,
|
|
62
|
+
eArg: str | None = None,
|
|
63
|
+
):
|
|
64
|
+
"""视图 / 界面控制命令。
|
|
65
|
+
|
|
66
|
+
格式: ``/control,<Action>[,<Arg>]``
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
eAction (str): 子命令
|
|
70
|
+
* ``view`` = 切换视图方向(配合 eArg)
|
|
71
|
+
* 其他如 ``quickCreateModel`` 等
|
|
72
|
+
eArg (str | None): 子命令参数
|
|
73
|
+
* 当 eAction 为 ``view`` 时: ``standard`` / ``top`` / ``right`` / ``front``
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
tuple (bool, str): 是否成功,失败原因
|
|
77
|
+
|
|
78
|
+
Examples:
|
|
79
|
+
>>> osis_control("view", "standard")
|
|
80
|
+
>>> osis_control("view", "top")
|
|
81
|
+
>>> osis_control("view", "right")
|
|
82
|
+
>>> osis_control("view", "front")
|
|
83
|
+
"""
|
|
84
|
+
pass
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@REGISTRY.register("Plsm")
|
|
88
|
+
def osis_plsm(bFlag: Literal[0, 1] = 1):
|
|
89
|
+
"""显示开关(Plsm)。
|
|
90
|
+
|
|
91
|
+
格式: ``plsm,<0|1>``
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
bFlag (int): ``0`` = 关,``1`` = 开
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
tuple (bool, str): 是否成功,失败原因
|
|
98
|
+
|
|
99
|
+
Examples:
|
|
100
|
+
>>> osis_plsm(1)
|
|
101
|
+
>>> osis_plsm(0)
|
|
102
|
+
"""
|
|
103
|
+
pass
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""显示管理器 - 控制显隐、视图方向与显示开关
|
|
2
|
+
|
|
3
|
+
设计理念:
|
|
4
|
+
- 隐藏底层命令接口细节,提供原生 Python 风格 API
|
|
5
|
+
- 无状态设计,操作直接提交到 OSIS
|
|
6
|
+
|
|
7
|
+
用法:
|
|
8
|
+
>>> from pyosis.display import display_manager
|
|
9
|
+
>>> display_manager.disp_ctrl("bc", "all", "all", 1)
|
|
10
|
+
>>> display_manager.set_view("top")
|
|
11
|
+
>>> display_manager.set_plsm(1)
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from typing import Literal, Sequence, Union
|
|
17
|
+
|
|
18
|
+
from .interface import osis_control, osis_disp_ctrl, osis_plsm
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class DisplayManager:
|
|
22
|
+
"""显示管理器
|
|
23
|
+
|
|
24
|
+
控制边界 / 荷载 / 钢束显隐、视图方向与显示开关。
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def disp_ctrl(
|
|
28
|
+
self,
|
|
29
|
+
obj: Literal["bc", "boundary", "lg", "load", "td", "tendon"],
|
|
30
|
+
type: str,
|
|
31
|
+
ids: Union[str, int, Sequence[Union[str, int]]],
|
|
32
|
+
show: Literal[0, 1] = 1,
|
|
33
|
+
) -> None:
|
|
34
|
+
"""控制边界 / 荷载 / 钢束的显示与隐藏。
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
obj: 对象类型
|
|
38
|
+
* ``bc`` / ``boundary`` = 边界
|
|
39
|
+
* ``lg`` / ``load`` = 荷载
|
|
40
|
+
* ``td`` / ``tendon`` = 钢束
|
|
41
|
+
type: 子类型(``all`` 或具体类型,见 ``osis_disp_ctrl`` 文档)
|
|
42
|
+
ids: 编号或名称(``all`` / 单个 / 序列)
|
|
43
|
+
show: ``0`` = 隐藏,``1`` = 显示
|
|
44
|
+
|
|
45
|
+
Raises:
|
|
46
|
+
RuntimeError: 操作失败时抛出异常
|
|
47
|
+
|
|
48
|
+
Examples:
|
|
49
|
+
>>> display_manager.disp_ctrl("bc", "all", "all", 1)
|
|
50
|
+
>>> display_manager.disp_ctrl("bc", "general", [1, 2, "3to10"], 1)
|
|
51
|
+
>>> display_manager.disp_ctrl("lg", "nforce", "all", 1)
|
|
52
|
+
>>> display_manager.disp_ctrl("td", "all", ["T1", "T2"], 1)
|
|
53
|
+
"""
|
|
54
|
+
ok, err = osis_disp_ctrl(obj, type, ids, show)
|
|
55
|
+
if not ok:
|
|
56
|
+
raise RuntimeError(f"显示控制失败: {err}")
|
|
57
|
+
|
|
58
|
+
def control(self, action: str, arg: str | None = None) -> None:
|
|
59
|
+
"""视图 / 界面控制(对应 ``/control,...``)。
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
action: 子命令,如 ``view``、``quickCreateModel``
|
|
63
|
+
arg: 子命令参数;``view`` 时为 ``standard`` / ``top`` / ``right`` / ``front``
|
|
64
|
+
|
|
65
|
+
Raises:
|
|
66
|
+
RuntimeError: 操作失败时抛出异常
|
|
67
|
+
"""
|
|
68
|
+
ok, err = osis_control(action, arg)
|
|
69
|
+
if not ok:
|
|
70
|
+
raise RuntimeError(f"界面控制失败: {err}")
|
|
71
|
+
|
|
72
|
+
def set_view(
|
|
73
|
+
self,
|
|
74
|
+
view: Literal["standard", "top", "right", "front"] = "standard",
|
|
75
|
+
) -> None:
|
|
76
|
+
"""切换视图方向。
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
view: ``standard`` / ``top`` / ``right`` / ``front``
|
|
80
|
+
|
|
81
|
+
Raises:
|
|
82
|
+
RuntimeError: 操作失败时抛出异常
|
|
83
|
+
|
|
84
|
+
Examples:
|
|
85
|
+
>>> display_manager.set_view("top")
|
|
86
|
+
>>> display_manager.set_view("front")
|
|
87
|
+
"""
|
|
88
|
+
self.control("view", view)
|
|
89
|
+
|
|
90
|
+
def set_plsm(self, enabled: Literal[0, 1] = 1) -> None:
|
|
91
|
+
"""显示开关(Plsm)。
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
enabled: ``0`` = 关,``1`` = 开
|
|
95
|
+
|
|
96
|
+
Raises:
|
|
97
|
+
RuntimeError: 操作失败时抛出异常
|
|
98
|
+
|
|
99
|
+
Examples:
|
|
100
|
+
>>> display_manager.set_plsm(1)
|
|
101
|
+
>>> display_manager.set_plsm(0)
|
|
102
|
+
"""
|
|
103
|
+
ok, err = osis_plsm(enabled)
|
|
104
|
+
if not ok:
|
|
105
|
+
raise RuntimeError(f"设置 Plsm 失败: {err}")
|
|
106
|
+
|
|
107
|
+
def __repr__(self) -> str:
|
|
108
|
+
return "DisplayManager()"
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
# ═════════════════════════════════════════════
|
|
112
|
+
# 全局单例
|
|
113
|
+
# ═════════════════════════════════════════════
|
|
114
|
+
|
|
115
|
+
display_manager = DisplayManager()
|
pyosis/element/manager.py
CHANGED
|
@@ -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: 创建的单元组对象
|
pyosis/project/interface.py
CHANGED
|
@@ -43,7 +43,8 @@ def save_project(filePath) -> Tuple[bool, str]:
|
|
|
43
43
|
保存项目
|
|
44
44
|
|
|
45
45
|
Args:
|
|
46
|
-
|
|
46
|
+
filePath: 工程文件路径。为空时由上层补全为当前工程路径;
|
|
47
|
+
非空时使用该路径。
|
|
47
48
|
|
|
48
49
|
Returns:
|
|
49
50
|
Tuple[bool, str]
|
|
@@ -62,3 +63,13 @@ def save_project_as(filePath) -> Tuple[bool, str]:
|
|
|
62
63
|
Tuple[bool, str]
|
|
63
64
|
"""
|
|
64
65
|
pass
|
|
66
|
+
|
|
67
|
+
@REGISTRY.register("/Close")
|
|
68
|
+
def close_osis() -> Tuple[bool, str]:
|
|
69
|
+
"""
|
|
70
|
+
关闭 OSIS 项目
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
Tuple[bool, str]
|
|
74
|
+
"""
|
|
75
|
+
pass
|
pyosis/project/manager.py
CHANGED
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
用法:
|
|
4
4
|
>>> from pyosis.project import project_manager
|
|
5
|
-
>>> project_manager.create(type=1, filepath="D:/Models/bridge.
|
|
6
|
-
>>> project_manager.open("D:/Models/bridge.
|
|
5
|
+
>>> project_manager.create(type=1, filepath="D:/Models/bridge.sis")
|
|
6
|
+
>>> project_manager.open("D:/Models/bridge.sis")
|
|
7
7
|
>>> project_manager.save()
|
|
8
|
-
>>> project_manager.
|
|
8
|
+
>>> project_manager.save("D:/Models/bridge.sis")
|
|
9
|
+
>>> project_manager.save_as("D:/Models/bridge_v2.sis")
|
|
10
|
+
>>> project_manager.close()
|
|
9
11
|
>>> project_manager.get_directory()
|
|
10
12
|
"""
|
|
11
13
|
|
|
@@ -16,6 +18,7 @@ from .interface import (
|
|
|
16
18
|
open_project,
|
|
17
19
|
save_project,
|
|
18
20
|
save_project_as,
|
|
21
|
+
close_osis,
|
|
19
22
|
get_project_directory,
|
|
20
23
|
)
|
|
21
24
|
|
|
@@ -53,13 +56,19 @@ class ProjectManager:
|
|
|
53
56
|
if not ok:
|
|
54
57
|
raise RuntimeError(f"打开项目失败: {err}")
|
|
55
58
|
|
|
56
|
-
def save(self) -> None:
|
|
57
|
-
"""
|
|
59
|
+
def save(self, filepath: str = "") -> None:
|
|
60
|
+
"""保存项目
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
filepath: 工程文件路径。为空时使用当前项目路径;
|
|
64
|
+
非空时使用传入路径。
|
|
58
65
|
|
|
59
66
|
Raises:
|
|
60
67
|
RuntimeError: 保存失败时抛出异常
|
|
61
68
|
"""
|
|
62
|
-
|
|
69
|
+
if not filepath:
|
|
70
|
+
filepath = self._current_project_file()
|
|
71
|
+
ok, err = save_project(filepath)
|
|
63
72
|
if not ok:
|
|
64
73
|
raise RuntimeError(f"保存项目失败: {err}")
|
|
65
74
|
|
|
@@ -76,6 +85,16 @@ class ProjectManager:
|
|
|
76
85
|
if not ok:
|
|
77
86
|
raise RuntimeError(f"另存为失败: {err}")
|
|
78
87
|
|
|
88
|
+
def close(self) -> None:
|
|
89
|
+
"""关闭 OSIS 项目
|
|
90
|
+
|
|
91
|
+
Raises:
|
|
92
|
+
RuntimeError: 关闭失败时抛出异常
|
|
93
|
+
"""
|
|
94
|
+
ok, err = close_osis()
|
|
95
|
+
if not ok:
|
|
96
|
+
raise RuntimeError(f"关闭软件失败: {err}")
|
|
97
|
+
|
|
79
98
|
def get_directory(self) -> str:
|
|
80
99
|
"""获取当前项目目录
|
|
81
100
|
|
|
@@ -90,6 +109,11 @@ class ProjectManager:
|
|
|
90
109
|
except Exception as e:
|
|
91
110
|
raise RuntimeError(f"获取项目目录失败: {e}")
|
|
92
111
|
|
|
112
|
+
def _current_project_file(self) -> str:
|
|
113
|
+
"""由当前项目目录推导工程文件路径(目录同名 .sis)。"""
|
|
114
|
+
directory = self.get_directory().rstrip("\\/")
|
|
115
|
+
return f"{directory}.sis"
|
|
116
|
+
|
|
93
117
|
def __repr__(self) -> str:
|
|
94
118
|
return "ProjectManager()"
|
|
95
119
|
|
pyosis/transfer/out_to_python.py
CHANGED
|
@@ -1,21 +1,53 @@
|
|
|
1
1
|
"""transfer 同步链路的纯文本处理工具:把 .out 文本转换为 pyosis prep 模块。
|
|
2
2
|
|
|
3
|
-
只负责文本解析与 Python
|
|
4
|
-
-
|
|
5
|
-
-
|
|
3
|
+
只负责文本解析与 Python 代码生成:
|
|
4
|
+
- 读 .out 源文件
|
|
5
|
+
- 用 parser 解析出命令流,并按 //--- MODULE_NAME --- 标记分桶
|
|
6
|
+
- 第一个模块标记之前的"前言"命令(典型为 clear/clc)写到 main.py
|
|
7
|
+
- 每个模块的命令写到 _1_control.py … _10_stage.py
|
|
6
8
|
|
|
7
|
-
|
|
9
|
+
不负责:
|
|
8
10
|
- .out 文件的获取(export_apdl) —— 由调用方管理
|
|
9
|
-
- prep 模块的执行(import + builder 调用) ——
|
|
11
|
+
- prep 模块的执行(import + builder 调用) —— 由 main.py 调度
|
|
10
12
|
"""
|
|
11
13
|
|
|
12
14
|
from __future__ import annotations
|
|
13
15
|
|
|
16
|
+
import inspect
|
|
17
|
+
import re
|
|
18
|
+
import sys
|
|
14
19
|
from collections import defaultdict
|
|
15
20
|
from pathlib import Path
|
|
21
|
+
from typing import Iterable
|
|
16
22
|
|
|
17
23
|
from pyosis.transfer.generator import generate_lines
|
|
18
|
-
from pyosis.transfer.parser import
|
|
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
|
+
})
|
|
19
51
|
|
|
20
52
|
# 模块文件名
|
|
21
53
|
MODULE_FILES = {
|
|
@@ -47,28 +79,6 @@ MODULE_BUILDERS = {
|
|
|
47
79
|
|
|
48
80
|
MODULE_ORDER = tuple(MODULE_FILES.keys())
|
|
49
81
|
|
|
50
|
-
|
|
51
|
-
# 按模块分桶
|
|
52
|
-
def _bucket_by_module(parsed: list[ParsedCommand]) -> dict[str, list[ParsedCommand]]:
|
|
53
|
-
buckets: dict[str, list[ParsedCommand]] = defaultdict(list)
|
|
54
|
-
for cmd in parsed:
|
|
55
|
-
buckets[cmd.module or "PREAMBLE"].append(cmd)
|
|
56
|
-
return buckets
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
# 合并 preamble 到 control
|
|
60
|
-
def _merge_preamble_into_control(
|
|
61
|
-
buckets: dict[str, list[ParsedCommand]],
|
|
62
|
-
) -> dict[str, list[ParsedCommand]]:
|
|
63
|
-
if "PREAMBLE" not in buckets:
|
|
64
|
-
return buckets
|
|
65
|
-
merged = dict(buckets)
|
|
66
|
-
preamble = merged.pop("PREAMBLE")
|
|
67
|
-
merged.setdefault("CONTROL", [])
|
|
68
|
-
merged["CONTROL"] = preamble + merged["CONTROL"]
|
|
69
|
-
return merged
|
|
70
|
-
|
|
71
|
-
|
|
72
82
|
# 模块职责一句话(给 AI 看的提示)
|
|
73
83
|
MODULE_PURPOSE = {
|
|
74
84
|
"CONTROL": "全局控制参数(重力、非线性、收缩徐变开关等)",
|
|
@@ -84,51 +94,199 @@ MODULE_PURPOSE = {
|
|
|
84
94
|
}
|
|
85
95
|
|
|
86
96
|
|
|
87
|
-
#
|
|
88
|
-
def
|
|
89
|
-
"""
|
|
90
|
-
|
|
91
|
-
例:
|
|
92
|
-
engine.load.get("防撞护栏右").create("LINE", ...) -> load.get+防撞护栏右
|
|
93
|
-
engine.element.group.create("0号块单元", ...) -> element.group.create+0号块单元
|
|
94
|
-
engine.element.create(1, "BEAM3D", ...) -> element.create+BEAM3D
|
|
95
|
-
engine.control.set_gravity_acceleration(9.8) -> control.set_gravity_acceleration+-
|
|
97
|
+
# 按模块分桶
|
|
98
|
+
def _split_by_module(text: str) -> tuple[list[ParsedCommand], dict[str, list[ParsedCommand]]]:
|
|
99
|
+
"""走一遍 .out 文本,把命令按 //--- MODULE_NAME --- 标记分桶。
|
|
96
100
|
|
|
97
|
-
|
|
101
|
+
Returns:
|
|
102
|
+
(preamble, {MODULE: [cmd, ...]})
|
|
103
|
+
- preamble: 第一个模块标记之前的命令(典型为 clear/clc)
|
|
104
|
+
- by_module: 每个模块的命令列表,按 .out 出现顺序
|
|
98
105
|
"""
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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":
|
|
112
202
|
return None
|
|
113
|
-
return f"{method}|{rest[1:j]}"
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
def _insert_group_comments(lines: list[str]) -> list[str]:
|
|
117
|
-
"""在 group key 变化处插入 `# ---- <method>: <name> ----` 注释。
|
|
118
203
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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()
|
|
122
278
|
out: list[str] = []
|
|
123
|
-
prev_key: str | None = None
|
|
124
279
|
for line in lines:
|
|
125
|
-
|
|
126
|
-
if
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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)
|
|
130
289
|
out.append(line)
|
|
131
|
-
prev_key = key if key is not None else prev_key
|
|
132
290
|
return out
|
|
133
291
|
|
|
134
292
|
|
|
@@ -139,7 +297,7 @@ def _write_prep_module(prep_dir: Path, module: str, lines: list[str]) -> Path:
|
|
|
139
297
|
生成顺序:
|
|
140
298
|
1. docstring(说明模块职责)
|
|
141
299
|
2. imports
|
|
142
|
-
3. builder 函数
|
|
300
|
+
3. builder 函数
|
|
143
301
|
4. if __name__ == "__main__": 单跑入口
|
|
144
302
|
"""
|
|
145
303
|
fname = MODULE_FILES[module]
|
|
@@ -151,12 +309,8 @@ def _write_prep_module(prep_dir: Path, module: str, lines: list[str]) -> Path:
|
|
|
151
309
|
doc = f"OSIS 命令流 {module} 模块 — 当前 .out 中无该段,无需调用。"
|
|
152
310
|
fn_body = [" pass"]
|
|
153
311
|
else:
|
|
154
|
-
doc =
|
|
155
|
-
|
|
156
|
-
f"由 pyosis.transfer.out_to_python 从 .out 自动生成。"
|
|
157
|
-
f"按调用特征分组(注释头 # ---- method: name ----),同组相邻命令共享同一上下文。"
|
|
158
|
-
)
|
|
159
|
-
annotated = _insert_group_comments(lines)
|
|
312
|
+
doc = f"OSIS 命令流 {module} 模块 — {purpose}"
|
|
313
|
+
annotated = _insert_method_doc_comments(lines)
|
|
160
314
|
fn_body = [f" {line}" for line in annotated]
|
|
161
315
|
|
|
162
316
|
body_lines = [
|
|
@@ -179,20 +333,21 @@ def _write_prep_module(prep_dir: Path, module: str, lines: list[str]) -> Path:
|
|
|
179
333
|
return path
|
|
180
334
|
|
|
181
335
|
|
|
182
|
-
def
|
|
183
|
-
"""
|
|
184
|
-
|
|
185
|
-
f"
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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]
|
|
190
345
|
|
|
191
346
|
body_lines = [
|
|
192
347
|
'"""main.py — 入口脚本,按 _1.._10 顺序依次执行 prep 模块。',
|
|
193
348
|
"",
|
|
194
|
-
"
|
|
195
|
-
"
|
|
349
|
+
"可直接 `python main.py`,或被 import 后调 main()。",
|
|
350
|
+
"engine 用 _0_engine 模块级单例,跟同包内的 prep 模块保持一致。",
|
|
196
351
|
'"""',
|
|
197
352
|
"",
|
|
198
353
|
"from __future__ import annotations",
|
|
@@ -203,37 +358,51 @@ def _write_main_py(prep_dir: Path) -> Path:
|
|
|
203
358
|
"# 让 main.py 不管从哪个目录跑都能 import 同目录的 prep modules",
|
|
204
359
|
"sys.path.insert(0, str(Path(__file__).resolve().parent))",
|
|
205
360
|
"",
|
|
206
|
-
"from _0_engine import engine
|
|
361
|
+
"from _0_engine import engine",
|
|
207
362
|
*import_lines,
|
|
208
363
|
"",
|
|
209
|
-
"def main(
|
|
210
|
-
|
|
211
|
-
|
|
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([
|
|
212
371
|
"",
|
|
213
372
|
'if __name__ == "__main__":',
|
|
214
373
|
" main()",
|
|
215
374
|
"",
|
|
216
|
-
]
|
|
375
|
+
])
|
|
376
|
+
|
|
217
377
|
path = prep_dir / "main.py"
|
|
218
378
|
path.write_text("\n".join(body_lines), encoding="utf-8")
|
|
219
379
|
return path
|
|
220
380
|
|
|
221
381
|
|
|
222
|
-
def write_prep_outputs(
|
|
223
|
-
"""
|
|
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,返回 (代码行数, 文件路径列表)。
|
|
224
384
|
|
|
225
|
-
|
|
385
|
+
每个标准模块都会写入文件;.out 中无对应命令时生成 pass stub,避免遗留旧 prep。
|
|
386
|
+
|
|
387
|
+
Args:
|
|
388
|
+
out_path: 源 .out 文件路径(由 apdl_sync 管理获取,这里只读取)
|
|
389
|
+
prep_dir: prep 输出目录(必须已存在)
|
|
226
390
|
"""
|
|
391
|
+
text = _read_out_text(out_path)
|
|
392
|
+
preamble, by_module = _split_by_module(text)
|
|
393
|
+
|
|
227
394
|
code_line_count = 0
|
|
228
395
|
prep_paths: list[Path] = []
|
|
229
|
-
buckets = _merge_preamble_into_control(_bucket_by_module(parsed))
|
|
230
396
|
|
|
231
397
|
for module in MODULE_ORDER:
|
|
232
|
-
cmds =
|
|
398
|
+
cmds = by_module.get(module, [])
|
|
233
399
|
lines = generate_lines(cmds) if cmds else []
|
|
234
400
|
code_line_count += len(lines)
|
|
235
401
|
prep_paths.append(_write_prep_module(prep_dir, module, lines))
|
|
236
402
|
|
|
403
|
+
# 前言(典型 clear/clc)放到 main.py 顶部;无前言时为空列表
|
|
404
|
+
preamble_lines = generate_lines(preamble) if preamble else []
|
|
405
|
+
|
|
237
406
|
(prep_dir / "_0_engine.py").write_text(
|
|
238
407
|
'"""模块级单例 OSISEngine,供同包内其他 prep 模块 import 使用。\n\n'
|
|
239
408
|
"不要在主代码里直接 new OSISEngine();通过 main.py 调度。\n"
|
|
@@ -242,5 +411,5 @@ def write_prep_outputs(parsed: list[ParsedCommand], prep_dir: Path) -> tuple[int
|
|
|
242
411
|
"engine = OSISEngine()\n",
|
|
243
412
|
encoding="utf-8",
|
|
244
413
|
)
|
|
245
|
-
prep_paths.append(_write_main_py(prep_dir))
|
|
246
|
-
return code_line_count, prep_paths
|
|
414
|
+
prep_paths.append(_write_main_py(prep_dir, preamble_lines))
|
|
415
|
+
return code_line_count, prep_paths
|
pyosis/transfer/parser.py
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
"""命令流解析器.
|
|
2
2
|
|
|
3
|
+
职责:把输入的一整段 OSIS 命令流文本解析成多条 ParsedCommand.
|
|
4
|
+
不做任何 .out 格式相关的事(比如识别 //--- CONTROL --- 这类模块标记),
|
|
5
|
+
那是 out_to_python 的职责.
|
|
6
|
+
|
|
3
7
|
支持:
|
|
4
8
|
- 行注释(//、#)
|
|
5
9
|
- 多行续行(行末逗号 / 下一行首字符为空白+逗号)
|
|
@@ -13,7 +17,7 @@ from __future__ import annotations
|
|
|
13
17
|
|
|
14
18
|
import re
|
|
15
19
|
from dataclasses import dataclass
|
|
16
|
-
from typing import List
|
|
20
|
+
from typing import Iterator, List
|
|
17
21
|
|
|
18
22
|
from .split import split_cmd, split_commands
|
|
19
23
|
|
|
@@ -23,24 +27,10 @@ _GLUED_TAIL_COMMANDS = ("SectionOffset",)
|
|
|
23
27
|
# 矩阵赋值命令正则表达式
|
|
24
28
|
MATRIX_ASSIGN_RE = re.compile(r"^(\w+)\[([\d,\s]+)\]\s*=\s*(.+)$")
|
|
25
29
|
# 注释行正则表达式
|
|
26
|
-
|
|
30
|
+
COMMENT_LINE_RE = re.compile(r"^\s*(//|#)")
|
|
27
31
|
# 空白行正则表达式
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
MODULE_PATTERN = re.compile(r"//-+\s*(\w+)\s*-*")
|
|
31
|
-
# 用于识别命令所属模块
|
|
32
|
-
KNOWN_MODULES = frozenset({
|
|
33
|
-
"CONTROL",
|
|
34
|
-
"PROPERTY",
|
|
35
|
-
"MATERIAL",
|
|
36
|
-
"SECTION",
|
|
37
|
-
"NODE",
|
|
38
|
-
"ELEMENT",
|
|
39
|
-
"BOUNDARY",
|
|
40
|
-
"LOADCASE",
|
|
41
|
-
"ANALYSIS",
|
|
42
|
-
"STAGE",
|
|
43
|
-
})
|
|
32
|
+
BLANK_RE = re.compile(r"^\s*$")
|
|
33
|
+
|
|
44
34
|
|
|
45
35
|
# 已解析的 OSIS 命令
|
|
46
36
|
@dataclass
|
|
@@ -54,7 +44,6 @@ class ParsedCommand:
|
|
|
54
44
|
matrix_name: str = ""
|
|
55
45
|
matrix_indices: tuple[int, ...] = ()
|
|
56
46
|
matrix_value: str = ""
|
|
57
|
-
module: str | None = None # "CONTROL" | "SECTION" | ... | None
|
|
58
47
|
|
|
59
48
|
|
|
60
49
|
def _join_continuation_lines(lines: List[str]) -> List[str]:
|
|
@@ -117,8 +106,33 @@ def _split_glued_command_fields(fields: List[str]) -> List[List[str]]:
|
|
|
117
106
|
return groups
|
|
118
107
|
|
|
119
108
|
|
|
109
|
+
# 去除一行内的 // 注释(保留 // 之前的有效命令部分)
|
|
110
|
+
def strip_inline_comment(line: str) -> str:
|
|
111
|
+
if "//" not in line:
|
|
112
|
+
return line
|
|
113
|
+
pos = line.find("//")
|
|
114
|
+
if pos == 0:
|
|
115
|
+
return ""
|
|
116
|
+
if line[pos - 1] in (" ", "\t"):
|
|
117
|
+
return line[:pos].rstrip()
|
|
118
|
+
return line
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
# 迭代"物理命令行":合并续行;不过滤注释/空行,留给调用方决定
|
|
122
|
+
def iter_physical_lines(text: str) -> Iterator[str]:
|
|
123
|
+
"""逐行 yield 物理命令行(已合并续行)。
|
|
124
|
+
|
|
125
|
+
不做任何注释/空行过滤——调用方按需处理:
|
|
126
|
+
- parse_text 会跳过 //、#、空行(以及 //--- MODULE_NAME --- 这种模块标记);
|
|
127
|
+
因为 parser 不识别模块,所以这条规则保持宽泛。
|
|
128
|
+
- out_to_python._split_by_module 同样跳过注释/空行,但用 MODULE_PATTERN
|
|
129
|
+
提前捕获模块标记以切换 current_module。
|
|
130
|
+
"""
|
|
131
|
+
yield from _join_continuation_lines(text.splitlines())
|
|
132
|
+
|
|
133
|
+
|
|
120
134
|
# 创建已解析的命令
|
|
121
|
-
def _make_parsed_command(fields: List[str], source: str
|
|
135
|
+
def _make_parsed_command(fields: List[str], source: str) -> ParsedCommand:
|
|
122
136
|
first = fields[0] if fields else ""
|
|
123
137
|
return ParsedCommand(
|
|
124
138
|
raw=source,
|
|
@@ -126,11 +140,11 @@ def _make_parsed_command(fields: List[str], source: str, module: str | None) ->
|
|
|
126
140
|
name=first,
|
|
127
141
|
source=source,
|
|
128
142
|
kind="normal",
|
|
129
|
-
module=module,
|
|
130
143
|
)
|
|
131
144
|
|
|
145
|
+
|
|
132
146
|
# 解析一条命令,返回已解析的命令列表
|
|
133
|
-
def _parse_one_command(source: str
|
|
147
|
+
def _parse_one_command(source: str) -> List[ParsedCommand]:
|
|
134
148
|
fields = split_cmd(source)
|
|
135
149
|
first = fields[0] if fields else ""
|
|
136
150
|
lower_first = first.lower()
|
|
@@ -143,7 +157,6 @@ def _parse_one_command(source: str, module:str|None) -> List[ParsedCommand]:
|
|
|
143
157
|
name=first,
|
|
144
158
|
source=source,
|
|
145
159
|
kind="matrix_dim",
|
|
146
|
-
module=module,
|
|
147
160
|
)
|
|
148
161
|
]
|
|
149
162
|
|
|
@@ -161,7 +174,6 @@ def _parse_one_command(source: str, module:str|None) -> List[ParsedCommand]:
|
|
|
161
174
|
matrix_name=assign_match.group(1),
|
|
162
175
|
matrix_indices=indices,
|
|
163
176
|
matrix_value=assign_match.group(3).strip(),
|
|
164
|
-
module=module,
|
|
165
177
|
)
|
|
166
178
|
]
|
|
167
179
|
|
|
@@ -171,46 +183,34 @@ def _parse_one_command(source: str, module:str|None) -> List[ParsedCommand]:
|
|
|
171
183
|
if not group:
|
|
172
184
|
continue
|
|
173
185
|
sub_source = ",".join(group)
|
|
174
|
-
result.append(_make_parsed_command(group, sub_source
|
|
186
|
+
result.append(_make_parsed_command(group, sub_source))
|
|
175
187
|
return result
|
|
176
188
|
|
|
177
189
|
|
|
190
|
+
# 解析一行物理命令为多条 ParsedCommand(处理 ; 分隔)
|
|
191
|
+
def _parse_physical_line(line: str) -> List[ParsedCommand]:
|
|
192
|
+
out: List[ParsedCommand] = []
|
|
193
|
+
for sub in split_commands(line):
|
|
194
|
+
out.extend(_parse_one_command(sub))
|
|
195
|
+
return out
|
|
196
|
+
|
|
197
|
+
|
|
178
198
|
# 解析 OSIS 命令流文本,返回已解析的命令列表
|
|
179
199
|
def parse_text(text: str) -> List[ParsedCommand]:
|
|
180
200
|
"""解析 OSIS 命令流文本。
|
|
181
201
|
|
|
182
202
|
Returns:
|
|
183
203
|
ParsedCommand 列表(不含注释、空行)。
|
|
204
|
+
不识别 //--- MODULE_NAME --- 这类 .out 模块标记,那是 out_to_python 的职责。
|
|
184
205
|
"""
|
|
185
|
-
lines = text.splitlines()
|
|
186
|
-
physical_lines = _join_continuation_lines(lines)
|
|
187
|
-
current_module: str | None = None
|
|
188
206
|
commands: List[ParsedCommand] = []
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
stripped = physical.strip()
|
|
192
|
-
# 识别模块标记
|
|
193
|
-
m = MODULE_PATTERN.match(stripped)
|
|
194
|
-
if m:
|
|
195
|
-
name = m.group(1).upper()
|
|
196
|
-
current_module = name if name in KNOWN_MODULES else None
|
|
207
|
+
for line in iter_physical_lines(text):
|
|
208
|
+
if COMMENT_LINE_RE.match(line):
|
|
197
209
|
continue
|
|
198
|
-
|
|
199
|
-
if _COMMENT_LINE_RE.match(physical):
|
|
200
|
-
continue
|
|
201
|
-
if _BLANK_RE.match(physical):
|
|
210
|
+
if BLANK_RE.match(line):
|
|
202
211
|
continue
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
if hash_pos == 0:
|
|
206
|
-
continue
|
|
207
|
-
if hash_pos > 0 and physical[hash_pos - 1] in (" ", "\t"):
|
|
208
|
-
physical = physical[:hash_pos].rstrip()
|
|
209
|
-
if not physical:
|
|
212
|
+
line = strip_inline_comment(line)
|
|
213
|
+
if BLANK_RE.match(line):
|
|
210
214
|
continue
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
for cmd in _parse_one_command(sub, module=current_module):
|
|
214
|
-
commands.append(cmd)
|
|
215
|
-
|
|
216
|
-
return commands
|
|
215
|
+
commands.extend(_parse_physical_line(line))
|
|
216
|
+
return commands
|
pyosis/transfer/routes.py
CHANGED
|
@@ -173,6 +173,12 @@ ROUTES = {
|
|
|
173
173
|
"/Open": "engine.open_project",
|
|
174
174
|
"/Save": "engine.save_project",
|
|
175
175
|
"/SaveAs": "engine.project.save_as",
|
|
176
|
+
"/Close": "engine.close",
|
|
177
|
+
|
|
178
|
+
# ─── DISPLAY ───
|
|
179
|
+
"DispCtrl": "engine.display.disp_ctrl",
|
|
180
|
+
"/control": "engine.display.control",
|
|
181
|
+
"Plsm": "engine.display.set_plsm",
|
|
176
182
|
|
|
177
183
|
# ─── GENERAL ───
|
|
178
184
|
"Clear": "engine.clear",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|