pyfemtet 0.3.12__py3-none-any.whl → 0.4.2__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.
Potentially problematic release.
This version of pyfemtet might be problematic. Click here for more details.
- pyfemtet/FemtetPJTSample/NX_ex01/NX_ex01.py +61 -32
- pyfemtet/FemtetPJTSample/Sldworks_ex01/Sldworks_ex01.py +62 -40
- pyfemtet/FemtetPJTSample/gau_ex08_parametric.py +26 -23
- pyfemtet/FemtetPJTSample/her_ex40_parametric.femprj +0 -0
- pyfemtet/FemtetPJTSample/her_ex40_parametric.py +58 -46
- pyfemtet/FemtetPJTSample/wat_ex14_parallel_parametric.py +31 -29
- pyfemtet/FemtetPJTSample/wat_ex14_parametric.femprj +0 -0
- pyfemtet/FemtetPJTSample/wat_ex14_parametric.py +30 -28
- pyfemtet/__init__.py +1 -1
- pyfemtet/core.py +14 -0
- pyfemtet/dispatch_extensions.py +5 -0
- pyfemtet/opt/__init__.py +22 -2
- pyfemtet/opt/_femopt.py +544 -0
- pyfemtet/opt/_femopt_core.py +732 -0
- pyfemtet/opt/interface/__init__.py +15 -0
- pyfemtet/opt/interface/_base.py +71 -0
- pyfemtet/opt/{interface.py → interface/_femtet.py} +121 -408
- pyfemtet/opt/interface/_femtet_with_nx/__init__.py +3 -0
- pyfemtet/opt/interface/_femtet_with_nx/_interface.py +128 -0
- pyfemtet/opt/interface/_femtet_with_sldworks.py +174 -0
- pyfemtet/opt/opt/__init__.py +8 -0
- pyfemtet/opt/opt/_base.py +202 -0
- pyfemtet/opt/opt/_optuna.py +246 -0
- pyfemtet/opt/visualization/__init__.py +7 -0
- pyfemtet/opt/visualization/_graphs.py +222 -0
- pyfemtet/opt/visualization/_monitor.py +1149 -0
- {pyfemtet-0.3.12.dist-info → pyfemtet-0.4.2.dist-info}/METADATA +6 -5
- pyfemtet-0.4.2.dist-info/RECORD +38 -0
- {pyfemtet-0.3.12.dist-info → pyfemtet-0.4.2.dist-info}/WHEEL +1 -1
- pyfemtet-0.4.2.dist-info/entry_points.txt +3 -0
- pyfemtet/opt/base.py +0 -1490
- pyfemtet/opt/monitor.py +0 -474
- pyfemtet-0.3.12.dist-info/RECORD +0 -26
- /pyfemtet/opt/{_FemtetWithNX → interface/_femtet_with_nx}/update_model.py +0 -0
- {pyfemtet-0.3.12.dist-info → pyfemtet-0.4.2.dist-info}/LICENSE +0 -0
|
@@ -1,95 +1,39 @@
|
|
|
1
|
+
from typing import Optional, List
|
|
2
|
+
|
|
1
3
|
import os
|
|
2
|
-
import re
|
|
3
4
|
import sys
|
|
4
5
|
from time import sleep, time
|
|
5
|
-
import json
|
|
6
|
-
import subprocess
|
|
7
6
|
import signal
|
|
8
|
-
from abc import ABC, abstractmethod
|
|
9
7
|
|
|
10
8
|
import pandas as pd
|
|
11
9
|
import psutil
|
|
10
|
+
from dask.distributed import get_worker
|
|
11
|
+
|
|
12
12
|
from pywintypes import com_error
|
|
13
13
|
from pythoncom import CoInitialize, CoUninitialize
|
|
14
|
-
from win32com.client import constants
|
|
15
|
-
from dask.distributed import get_worker
|
|
16
|
-
from tqdm import trange
|
|
14
|
+
from win32com.client import constants
|
|
17
15
|
from femtetutils import util
|
|
18
16
|
|
|
19
|
-
from
|
|
17
|
+
from pyfemtet.core import (
|
|
20
18
|
ModelError,
|
|
21
19
|
MeshError,
|
|
22
20
|
SolveError,
|
|
21
|
+
_version,
|
|
23
22
|
)
|
|
24
|
-
from
|
|
23
|
+
from pyfemtet.dispatch_extensions import (
|
|
25
24
|
dispatch_femtet,
|
|
26
25
|
dispatch_specific_femtet,
|
|
27
26
|
launch_and_dispatch_femtet,
|
|
28
27
|
_get_pid,
|
|
28
|
+
_get_pids,
|
|
29
29
|
DispatchExtensionException,
|
|
30
30
|
)
|
|
31
|
-
|
|
32
|
-
import logging
|
|
33
|
-
from ..logger import get_logger
|
|
34
|
-
logger = get_logger('FEM')
|
|
35
|
-
logger.setLevel(logging.INFO)
|
|
36
|
-
|
|
37
|
-
here, me = os.path.split(__file__)
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
class FEMInterface(ABC):
|
|
41
|
-
"""Abstract base class for the interface with FEM software."""
|
|
42
|
-
|
|
43
|
-
def __init__(
|
|
44
|
-
self,
|
|
45
|
-
**kwargs
|
|
46
|
-
):
|
|
47
|
-
"""Stores information necessary to restore FEMInterface instance in a subprocess.
|
|
48
|
-
|
|
49
|
-
The concrete class should call super().__init__() with the desired arguments when restoring.
|
|
50
|
-
|
|
51
|
-
Args:
|
|
52
|
-
**kwargs: keyword arguments for FEMInterface (re)constructor.
|
|
53
|
-
|
|
54
|
-
"""
|
|
55
|
-
# restore のための情報保管
|
|
56
|
-
self.kwargs = kwargs
|
|
57
|
-
|
|
58
|
-
@abstractmethod
|
|
59
|
-
def update(self, parameters: pd.DataFrame) -> None:
|
|
60
|
-
"""Updates the FEM analysis based on the proposed parameters."""
|
|
61
|
-
raise NotImplementedError('update() must be implemented.')
|
|
62
|
-
|
|
63
|
-
def check_param_value(self, param_name) -> float or None:
|
|
64
|
-
"""Checks the value of a parameter in the FEM model (if implemented in concrete class)."""
|
|
65
|
-
if False:
|
|
66
|
-
raise RuntimeError(f"{param_name} doesn't exist on FEM model.")
|
|
67
|
-
|
|
68
|
-
def update_parameter(self, parameters: pd.DataFrame) -> None:
|
|
69
|
-
"""Updates only FEM variables (if implemented in concrete class)."""
|
|
70
|
-
pass
|
|
71
|
-
|
|
72
|
-
def setup_before_parallel(self, client) -> None:
|
|
73
|
-
"""Preprocessing before launching a dask worker (if implemented in concrete class).
|
|
74
|
-
|
|
75
|
-
Args:
|
|
76
|
-
client: dask client.
|
|
77
|
-
i.e. you can update associated files by
|
|
78
|
-
`client.upload_file(file_path)`
|
|
79
|
-
The file will be saved to dask-scratch-space directory
|
|
80
|
-
without any directory structure.
|
|
81
|
-
|
|
82
|
-
"""
|
|
83
|
-
pass
|
|
84
|
-
|
|
85
|
-
def setup_after_parallel(self):
|
|
86
|
-
"""Preprocessing after launching a dask worker and before run optimization (if implemented in concrete class)."""
|
|
87
|
-
pass
|
|
31
|
+
from pyfemtet.opt.interface import FEMInterface, logger
|
|
88
32
|
|
|
89
33
|
|
|
90
34
|
class FemtetInterface(FEMInterface):
|
|
91
|
-
"""Concrete class for the interface with Femtet
|
|
92
|
-
|
|
35
|
+
"""Concrete class for the interface with Femtet.
|
|
36
|
+
|
|
93
37
|
Args:
|
|
94
38
|
femprj_path (str or None, optional): The path to the .femprj file. Defaults to None.
|
|
95
39
|
model_name (str or None, optional): The name of the analysis model. Defaults to None.
|
|
@@ -100,15 +44,13 @@ class FemtetInterface(FEMInterface):
|
|
|
100
44
|
Even if you specify ``strictly_pid_specify=True`` on the constructor,
|
|
101
45
|
**the connection behavior is like** ``strictly_pid_specify=False`` **in parallel processing**
|
|
102
46
|
because of its large overhead.
|
|
103
|
-
So you should close all Femtet processes before running FEMOpt.
|
|
47
|
+
So you should close all Femtet processes before running FEMOpt.optimize()
|
|
104
48
|
if ``n_parallel`` >= 2.
|
|
105
49
|
|
|
106
|
-
|
|
107
|
-
|
|
108
50
|
Tip:
|
|
109
51
|
If you search for information about the method to connect python and Femtet, see :func:`connect_femtet`.
|
|
110
|
-
|
|
111
|
-
"""
|
|
52
|
+
|
|
53
|
+
"""
|
|
112
54
|
|
|
113
55
|
def __init__(
|
|
114
56
|
self,
|
|
@@ -116,6 +58,7 @@ class FemtetInterface(FEMInterface):
|
|
|
116
58
|
model_name=None,
|
|
117
59
|
connect_method='auto',
|
|
118
60
|
strictly_pid_specify=True,
|
|
61
|
+
allow_without_project=False,
|
|
119
62
|
**kwargs # 継承されたクラスからの引数
|
|
120
63
|
):
|
|
121
64
|
|
|
@@ -129,9 +72,12 @@ class FemtetInterface(FEMInterface):
|
|
|
129
72
|
self.femprj_path = os.path.abspath(femprj_path)
|
|
130
73
|
self.model_name = model_name
|
|
131
74
|
self.connect_method = connect_method
|
|
75
|
+
self.allow_without_project = allow_without_project
|
|
76
|
+
self.original_femprj_path = self.femprj_path
|
|
132
77
|
|
|
133
78
|
# その他のメンバーの宣言や初期化
|
|
134
79
|
self.Femtet = None
|
|
80
|
+
self.femtet_pid = 0
|
|
135
81
|
self.quit_when_destruct = False
|
|
136
82
|
self.connected_method = 'unconnected'
|
|
137
83
|
self.parameters = None
|
|
@@ -151,7 +97,7 @@ class FemtetInterface(FEMInterface):
|
|
|
151
97
|
|
|
152
98
|
# femprj_path と model に基づいて Femtet を開き、
|
|
153
99
|
# 開かれたモデルに応じて femprj_path と model を更新する
|
|
154
|
-
self.
|
|
100
|
+
self._connect_and_open_femtet()
|
|
155
101
|
|
|
156
102
|
# 接続した Femtet の種類に応じて del 時に quit するかどうか決める
|
|
157
103
|
self.quit_when_destruct = self.connected_method == 'new'
|
|
@@ -180,25 +126,24 @@ class FemtetInterface(FEMInterface):
|
|
|
180
126
|
sleep(1)
|
|
181
127
|
sleep(1)
|
|
182
128
|
|
|
183
|
-
except AttributeError: # already dead
|
|
129
|
+
except (AttributeError, OSError): # already dead
|
|
184
130
|
pass
|
|
185
131
|
# CoUninitialize() # Win32 exception occurred releasing IUnknown at 0x0000022427692748
|
|
186
132
|
|
|
187
133
|
def _connect_new_femtet(self):
|
|
188
134
|
logger.info('└ Try to launch and connect new Femtet process.')
|
|
189
135
|
|
|
190
|
-
self.Femtet,
|
|
136
|
+
self.Femtet, self.femtet_pid = launch_and_dispatch_femtet(strictly_pid_specify=self.strictly_pid_specify)
|
|
191
137
|
|
|
192
138
|
self.connected_method = 'new'
|
|
193
139
|
|
|
194
|
-
|
|
195
140
|
def _connect_existing_femtet(self, pid: int or None = None):
|
|
196
141
|
logger.info('└ Try to connect existing Femtet process.')
|
|
197
142
|
# 既存の Femtet を探して Dispatch する。
|
|
198
143
|
if pid is None:
|
|
199
|
-
self.Femtet,
|
|
144
|
+
self.Femtet, self.femtet_pid = dispatch_femtet(timeout=5)
|
|
200
145
|
else:
|
|
201
|
-
self.Femtet,
|
|
146
|
+
self.Femtet, self.femtet_pid = dispatch_specific_femtet(pid, timeout=5)
|
|
202
147
|
self.connected_method = 'existing'
|
|
203
148
|
|
|
204
149
|
def connect_femtet(self, connect_method: str = 'auto', pid: int or None = None):
|
|
@@ -309,7 +254,7 @@ class FemtetInterface(FEMInterface):
|
|
|
309
254
|
# 2. API 実行時に成功失敗を示す戻り値を返し、ShowLastError で例外にアクセスできる状態になる
|
|
310
255
|
|
|
311
256
|
# Gaudi コマンドなら Gaudi.Activate する
|
|
312
|
-
logger.debug(' '*print_indent + f'Femtet API:{fun.__name__}, args:{args}, kwargs:{kwargs}')
|
|
257
|
+
logger.debug(' ' * print_indent + f'Femtet API:{fun.__name__}, args:{args}, kwargs:{kwargs}')
|
|
313
258
|
if is_Gaudi_method: # Optimizer は Gogh に触らないので全部にこれをつけてもいい気がする
|
|
314
259
|
try:
|
|
315
260
|
self._call_femtet_api(
|
|
@@ -317,7 +262,7 @@ class FemtetInterface(FEMInterface):
|
|
|
317
262
|
False, # None 以外なら何でもいい
|
|
318
263
|
Exception,
|
|
319
264
|
'解析モデルのオープンに失敗しました',
|
|
320
|
-
print_indent=print_indent+1
|
|
265
|
+
print_indent=print_indent + 1
|
|
321
266
|
)
|
|
322
267
|
except com_error:
|
|
323
268
|
# Gaudi へのアクセスだけで com_error が生じうる
|
|
@@ -332,8 +277,8 @@ class FemtetInterface(FEMInterface):
|
|
|
332
277
|
if ret_for_check_idx is None:
|
|
333
278
|
returns = ret_if_failed
|
|
334
279
|
else:
|
|
335
|
-
returns = [ret_if_failed]*(ret_for_check_idx+1)
|
|
336
|
-
logger.debug(' '*print_indent + f'Femtet API result:{returns}')
|
|
280
|
+
returns = [ret_if_failed] * (ret_for_check_idx + 1)
|
|
281
|
+
logger.debug(' ' * print_indent + f'Femtet API result:{returns}')
|
|
337
282
|
|
|
338
283
|
# チェックすべき値の抽出
|
|
339
284
|
if ret_for_check_idx is None:
|
|
@@ -383,12 +328,12 @@ class FemtetInterface(FEMInterface):
|
|
|
383
328
|
ret_for_check_idx,
|
|
384
329
|
args,
|
|
385
330
|
kwargs,
|
|
386
|
-
recourse_depth+1,
|
|
387
|
-
print_indent+1
|
|
331
|
+
recourse_depth + 1,
|
|
332
|
+
print_indent + 1
|
|
388
333
|
)
|
|
389
334
|
|
|
390
335
|
def femtet_is_alive(self) -> bool:
|
|
391
|
-
"""Returns connected femtet process is
|
|
336
|
+
"""Returns connected femtet process is existing or not."""
|
|
392
337
|
return _get_pid(self.Femtet.hWnd) > 0
|
|
393
338
|
|
|
394
339
|
def open(self, femprj_path: str, model_name: str or None = None) -> None:
|
|
@@ -412,7 +357,7 @@ class FemtetInterface(FEMInterface):
|
|
|
412
357
|
if not result:
|
|
413
358
|
self.Femtet.ShowLastError()
|
|
414
359
|
|
|
415
|
-
def
|
|
360
|
+
def _connect_and_open_femtet(self):
|
|
416
361
|
"""Connects to a Femtet process and open the femprj.
|
|
417
362
|
|
|
418
363
|
This function is for establishing a connection with Femtet and opening the specified femprj file.
|
|
@@ -448,28 +393,48 @@ class FemtetInterface(FEMInterface):
|
|
|
448
393
|
# femprj が指定されていない
|
|
449
394
|
else:
|
|
450
395
|
# かつ new だと解析すべき femprj がわからないのでエラー
|
|
451
|
-
if
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
396
|
+
if (
|
|
397
|
+
(self.connect_method == 'new')
|
|
398
|
+
and (not self.allow_without_project)
|
|
399
|
+
):
|
|
400
|
+
raise RuntimeError(
|
|
401
|
+
'femprj_path を指定せず Femtet の connect_method に "new" を指定する場合、"allow_without_project" 引数を True に設定してください。')
|
|
402
|
+
# さらに auto の場合は Femtet が存在しなければ new と同じ挙動になるので同様の処理
|
|
403
|
+
if (
|
|
404
|
+
(self.connect_method == 'auto')
|
|
405
|
+
and (len(_get_pids(process_name='Femtet.exe')) == 0)
|
|
406
|
+
and (not self.allow_without_project)
|
|
407
|
+
):
|
|
408
|
+
raise RuntimeError(
|
|
409
|
+
'femprj_path を指定せず Femtet の connect_method を指定しない(又は "auto" に指定する)場合、Femtet を起動して処理したい .femprj ファイルを開いた状態にしてください。')
|
|
410
|
+
self.connect_femtet(self.connect_method)
|
|
455
411
|
|
|
456
412
|
# 最終的に接続した Femtet の femprj_path と model を インスタンスに戻す
|
|
457
413
|
self.femprj_path = self.Femtet.Project
|
|
458
414
|
self.model_name = self.Femtet.AnalysisModelName
|
|
459
415
|
|
|
460
416
|
def check_param_value(self, param_name):
|
|
461
|
-
"""
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
417
|
+
"""Check param_name is set in femprj file or not.
|
|
418
|
+
|
|
419
|
+
Note:
|
|
420
|
+
This function works with Femtet version 2023.1.1 and above.
|
|
421
|
+
Otherwise, no check is performed.
|
|
422
|
+
|
|
423
|
+
"""
|
|
424
|
+
if self._version() >= _version(2023, 1, 1):
|
|
425
|
+
variable_names = self.Femtet.GetVariableNames_py()
|
|
426
|
+
if variable_names is not None:
|
|
427
|
+
if param_name in variable_names:
|
|
428
|
+
return self.Femtet.GetVariableValue(param_name)
|
|
429
|
+
message = f'Femtet 解析モデルに変数 {param_name} がありません.'
|
|
430
|
+
message += f'現在のモデルに設定されている変数は {variable_names} です.'
|
|
431
|
+
message += '大文字・小文字の区別に注意してください.'
|
|
432
|
+
raise RuntimeError(message)
|
|
433
|
+
else:
|
|
434
|
+
return None
|
|
435
|
+
|
|
436
|
+
def update_parameter(self, parameters: 'pd.DataFrame', with_warning=False):
|
|
437
|
+
"""Update parameter of femprj."""
|
|
473
438
|
self.parameters = parameters.copy()
|
|
474
439
|
|
|
475
440
|
# 変数更新のための処理
|
|
@@ -481,23 +446,48 @@ class FemtetInterface(FEMInterface):
|
|
|
481
446
|
error_message='解析モデルが開かれていません',
|
|
482
447
|
)
|
|
483
448
|
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
449
|
+
if self._version() >= _version(2023, 1, 1):
|
|
450
|
+
# Femtet の設計変数の更新
|
|
451
|
+
existing_variable_names = self._call_femtet_api(
|
|
452
|
+
fun=self.Femtet.GetVariableNames_py,
|
|
453
|
+
ret_if_failed=False, # 意味がない
|
|
454
|
+
if_error=ModelError, # 生きてるのに失敗した場合
|
|
455
|
+
error_message=f'GetVariableNames_py に失敗しました。',
|
|
456
|
+
is_Gaudi_method=True,
|
|
457
|
+
)
|
|
492
458
|
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
459
|
+
# 変数を含まないプロジェクトである場合
|
|
460
|
+
if existing_variable_names is None:
|
|
461
|
+
if with_warning:
|
|
462
|
+
return ['解析モデルに変数が含まれていません。']
|
|
463
|
+
else:
|
|
464
|
+
return None
|
|
465
|
+
|
|
466
|
+
# update
|
|
467
|
+
warnings = []
|
|
468
|
+
for i, row in parameters.iterrows():
|
|
469
|
+
name = row['name']
|
|
470
|
+
value = row['value']
|
|
471
|
+
if name in existing_variable_names:
|
|
472
|
+
self._call_femtet_api(
|
|
473
|
+
fun=self.Femtet.UpdateVariable,
|
|
474
|
+
ret_if_failed=False,
|
|
475
|
+
if_error=ModelError, # 生きてるのに失敗した場合
|
|
476
|
+
error_message=f'変数の更新に失敗しました:変数{name}, 値{value}',
|
|
477
|
+
is_Gaudi_method=True,
|
|
478
|
+
args=(name, value),
|
|
479
|
+
)
|
|
480
|
+
else:
|
|
481
|
+
msg = f'変数 {name} は 解析モデル {self.model_name} に含まれていません。無視されます。'
|
|
482
|
+
warnings.append(msg)
|
|
483
|
+
logger.warn(msg)
|
|
496
484
|
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
485
|
+
else:
|
|
486
|
+
# update without parameter check
|
|
487
|
+
warnings = []
|
|
488
|
+
for i, row in parameters.iterrows():
|
|
489
|
+
name = row['name']
|
|
490
|
+
value = row['value']
|
|
501
491
|
self._call_femtet_api(
|
|
502
492
|
fun=self.Femtet.UpdateVariable,
|
|
503
493
|
ret_if_failed=False,
|
|
@@ -506,19 +496,20 @@ class FemtetInterface(FEMInterface):
|
|
|
506
496
|
is_Gaudi_method=True,
|
|
507
497
|
args=(name, value),
|
|
508
498
|
)
|
|
509
|
-
else:
|
|
510
|
-
logger.warn(f'変数 {name} は .femprj に含まれていません。無視されます。')
|
|
511
499
|
|
|
512
500
|
# ここでは ReExecute しない
|
|
513
|
-
|
|
501
|
+
if with_warning:
|
|
502
|
+
return warnings
|
|
503
|
+
else:
|
|
504
|
+
return None
|
|
514
505
|
|
|
515
|
-
def update_model(self, parameters: 'pd.DataFrame') ->
|
|
506
|
+
def update_model(self, parameters: 'pd.DataFrame', with_warning=False) -> Optional[List[str]]:
|
|
516
507
|
"""Updates the analysis model only."""
|
|
517
508
|
|
|
518
509
|
self.parameters = parameters.copy()
|
|
519
510
|
|
|
520
511
|
# 変数の更新
|
|
521
|
-
self.update_parameter(parameters)
|
|
512
|
+
warnings = self.update_parameter(parameters, with_warning)
|
|
522
513
|
|
|
523
514
|
# 設計変数に従ってモデルを再構築
|
|
524
515
|
self._call_femtet_api(
|
|
@@ -538,8 +529,11 @@ class FemtetInterface(FEMInterface):
|
|
|
538
529
|
is_Gaudi_method=True,
|
|
539
530
|
)
|
|
540
531
|
|
|
532
|
+
if with_warning:
|
|
533
|
+
return warnings or []
|
|
534
|
+
|
|
541
535
|
def solve(self) -> None:
|
|
542
|
-
"""Execute FEM analysis
|
|
536
|
+
"""Execute FEM analysis."""
|
|
543
537
|
# # メッシュを切る
|
|
544
538
|
self._call_femtet_api(
|
|
545
539
|
self.Femtet.Gaudi.Mesh,
|
|
@@ -578,292 +572,11 @@ class FemtetInterface(FEMInterface):
|
|
|
578
572
|
"""Force to terminate connected Femtet."""
|
|
579
573
|
util.close_femtet(self.Femtet.hWnd, timeout, force)
|
|
580
574
|
|
|
581
|
-
def
|
|
575
|
+
def _setup_before_parallel(self, client):
|
|
582
576
|
client.upload_file(
|
|
583
577
|
self.kwargs['femprj_path'],
|
|
584
578
|
False
|
|
585
579
|
)
|
|
586
580
|
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
"""Interface with no FEM for debug."""
|
|
590
|
-
def update(self, parameters: pd.DataFrame) -> None:
|
|
591
|
-
pass
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
class FemtetWithNXInterface(FemtetInterface):
|
|
595
|
-
"""Femtet with NX interface class.
|
|
596
|
-
|
|
597
|
-
Args:
|
|
598
|
-
prt_path: The path to the prt file.
|
|
599
|
-
|
|
600
|
-
For details of The other arguments, see ``FemtetInterface``.
|
|
601
|
-
|
|
602
|
-
"""
|
|
603
|
-
|
|
604
|
-
_JOURNAL_PATH = os.path.abspath(os.path.join(here, '_FemtetWithNX/update_model.py'))
|
|
605
|
-
|
|
606
|
-
def __init__(
|
|
607
|
-
self,
|
|
608
|
-
prt_path,
|
|
609
|
-
femprj_path=None,
|
|
610
|
-
model_name=None,
|
|
611
|
-
connect_method='auto',
|
|
612
|
-
strictly_pid_specify=True,
|
|
613
|
-
):
|
|
614
|
-
|
|
615
|
-
# check NX installation
|
|
616
|
-
self.run_journal_path = os.path.join(os.environ.get('UGII_BASE_DIR'), 'NXBIN', 'run_journal.exe')
|
|
617
|
-
if not os.path.isfile(self.run_journal_path):
|
|
618
|
-
raise FileNotFoundError(r'"%UGII_BASE_DIR%\NXBIN\run_journal.exe" が見つかりませんでした。環境変数 UGII_BASE_DIR 又は NX のインストール状態を確認してください。')
|
|
619
|
-
|
|
620
|
-
# 引数の処理
|
|
621
|
-
# dask サブプロセスのときは prt_path を worker space から取るようにする
|
|
622
|
-
try:
|
|
623
|
-
worker = get_worker()
|
|
624
|
-
space = worker.local_directory
|
|
625
|
-
self.prt_path = os.path.join(space, os.path.basename(prt_path))
|
|
626
|
-
except ValueError: # get_worker に失敗した場合
|
|
627
|
-
self.prt_path = os.path.abspath(prt_path)
|
|
628
|
-
|
|
629
|
-
# FemtetInterface の設定 (femprj_path, model_name の更新など)
|
|
630
|
-
# + restore 情報の上書き
|
|
631
|
-
super().__init__(
|
|
632
|
-
femprj_path=femprj_path,
|
|
633
|
-
model_name=model_name,
|
|
634
|
-
connect_method=connect_method,
|
|
635
|
-
strictly_pid_specify=strictly_pid_specify,
|
|
636
|
-
prt_path=self.prt_path,
|
|
637
|
-
)
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
def check_param_value(self, name):
|
|
641
|
-
"""Override FemtetInterface.check_param_value().
|
|
642
|
-
|
|
643
|
-
Do nothing because the parameter can be registered
|
|
644
|
-
to not only .femprj but also .prt.
|
|
645
|
-
|
|
646
|
-
"""
|
|
647
|
-
pass
|
|
648
|
-
|
|
649
|
-
def setup_before_parallel(self, client):
|
|
650
|
-
client.upload_file(
|
|
651
|
-
self.kwargs['prt_path'],
|
|
652
|
-
False
|
|
653
|
-
)
|
|
654
|
-
super().setup_before_parallel(client)
|
|
655
|
-
|
|
656
|
-
def update_model(self, parameters: 'pd.DataFrame') -> None:
|
|
657
|
-
"""Update .x_t"""
|
|
658
|
-
|
|
659
|
-
self.parameters = parameters.copy()
|
|
660
|
-
|
|
661
|
-
# Femtet が参照している x_t パスを取得する
|
|
662
|
-
x_t_path = self.Femtet.Gaudi.LastXTPath
|
|
663
|
-
|
|
664
|
-
# 前のが存在するならば消しておく
|
|
665
|
-
if os.path.isfile(x_t_path):
|
|
666
|
-
os.remove(x_t_path)
|
|
667
|
-
|
|
668
|
-
# 変数の json 文字列を作る
|
|
669
|
-
tmp_dict = {}
|
|
670
|
-
for i, row in parameters.iterrows():
|
|
671
|
-
tmp_dict[row['name']] = row['value']
|
|
672
|
-
str_json = json.dumps(tmp_dict)
|
|
673
|
-
|
|
674
|
-
# NX journal を使ってモデルを編集する
|
|
675
|
-
env = os.environ.copy()
|
|
676
|
-
subprocess.run(
|
|
677
|
-
[self.run_journal_path, self._JOURNAL_PATH, '-args', self.prt_path, str_json, x_t_path],
|
|
678
|
-
env=env,
|
|
679
|
-
shell=True,
|
|
680
|
-
cwd=os.path.dirname(self.prt_path)
|
|
681
|
-
)
|
|
682
|
-
|
|
683
|
-
# この時点で x_t ファイルがなければ NX がモデル更新に失敗しているはず
|
|
684
|
-
if not os.path.isfile(x_t_path):
|
|
685
|
-
raise ModelError
|
|
686
|
-
|
|
687
|
-
# モデルの再インポート
|
|
688
|
-
self._call_femtet_api(
|
|
689
|
-
self.Femtet.Gaudi.ReExecute,
|
|
690
|
-
False,
|
|
691
|
-
ModelError, # 生きてるのに失敗した場合
|
|
692
|
-
error_message=f'モデル再構築に失敗しました.',
|
|
693
|
-
is_Gaudi_method=True,
|
|
694
|
-
)
|
|
695
|
-
|
|
696
|
-
# 処理を確定
|
|
697
|
-
self._call_femtet_api(
|
|
698
|
-
self.Femtet.Redraw,
|
|
699
|
-
False, # 戻り値は常に None なのでこの変数に意味はなく None 以外なら何でもいい
|
|
700
|
-
ModelError, # 生きてるのに失敗した場合
|
|
701
|
-
error_message=f'モデル再構築に失敗しました.',
|
|
702
|
-
is_Gaudi_method=True,
|
|
703
|
-
)
|
|
704
|
-
|
|
705
|
-
# femprj モデルの変数も更新
|
|
706
|
-
super().update_model(parameters)
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
class FemtetWithSolidworksInterface(FemtetInterface):
|
|
710
|
-
|
|
711
|
-
# 定数の宣言
|
|
712
|
-
swThisConfiguration = 1 # https://help.solidworks.com/2023/english/api/swconst/SOLIDWORKS.Interop.swconst~SOLIDWORKS.Interop.swconst.swInConfigurationOpts_e.html
|
|
713
|
-
swAllConfiguration = 2
|
|
714
|
-
swSpecifyConfiguration = 3 # use with ConfigName argument
|
|
715
|
-
swSaveAsCurrentVersion = 0
|
|
716
|
-
swSaveAsOptions_Copy = 2 #
|
|
717
|
-
swSaveAsOptions_Silent = 1 # https://help.solidworks.com/2021/english/api/swconst/solidworks.interop.swconst~solidworks.interop.swconst.swsaveasoptions_e.html
|
|
718
|
-
swSaveWithReferencesOptions_None = 0 # https://help-solidworks-com.translate.goog/2023/english/api/swconst/SolidWorks.Interop.swconst~SolidWorks.Interop.swconst.swSaveWithReferencesOptions_e.html?_x_tr_sl=auto&_x_tr_tl=ja&_x_tr_hl=ja&_x_tr_pto=wapp
|
|
719
|
-
swDocPART = 1 # https://help.solidworks.com/2023/english/api/swconst/SOLIDWORKS.Interop.swconst~SOLIDWORKS.Interop.swconst.swDocumentTypes_e.html
|
|
720
|
-
|
|
721
|
-
def __init__(
|
|
722
|
-
self,
|
|
723
|
-
sldprt_path,
|
|
724
|
-
femprj_path=None,
|
|
725
|
-
model_name=None,
|
|
726
|
-
connect_method='auto',
|
|
727
|
-
strictly_pid_specify=True,
|
|
728
|
-
):
|
|
729
|
-
# 引数の処理
|
|
730
|
-
# dask サブプロセスのときは space 直下の sldprt_path を参照する
|
|
731
|
-
try:
|
|
732
|
-
worker = get_worker()
|
|
733
|
-
space = worker.local_directory
|
|
734
|
-
self.sldprt_path = os.path.join(space, os.path.basename(sldprt_path))
|
|
735
|
-
except ValueError: # get_worker に失敗した場合
|
|
736
|
-
self.sldprt_path = os.path.abspath(sldprt_path)
|
|
737
|
-
|
|
738
|
-
# FemtetInterface の設定 (femprj_path, model_name の更新など)
|
|
739
|
-
# + restore 情報の上書き
|
|
740
|
-
super().__init__(
|
|
741
|
-
femprj_path=femprj_path,
|
|
742
|
-
model_name=model_name,
|
|
743
|
-
connect_method=connect_method,
|
|
744
|
-
strictly_pid_specify=strictly_pid_specify,
|
|
745
|
-
sldprt_path=self.sldprt_path,
|
|
746
|
-
)
|
|
747
|
-
|
|
748
|
-
def initialize_sldworks_connection(self):
|
|
749
|
-
# SolidWorks を捕まえ、ファイルを開く
|
|
750
|
-
self.swApp = DispatchEx('SLDWORKS.Application')
|
|
751
|
-
self.swApp.Visible = True
|
|
752
|
-
|
|
753
|
-
# open model
|
|
754
|
-
self.swApp.OpenDoc(self.sldprt_path, self.swDocPART)
|
|
755
|
-
self.swModel = self.swApp.ActiveDoc
|
|
756
|
-
self.swEqnMgr = self.swModel.GetEquationMgr
|
|
757
|
-
self.nEquation = self.swEqnMgr.GetCount
|
|
758
|
-
|
|
759
|
-
def check_param_value(self, param_name):
|
|
760
|
-
"""Override FemtetInterface.check_param_value().
|
|
761
|
-
|
|
762
|
-
Do nothing because the parameter can be registered
|
|
763
|
-
to not only .femprj but also .SLDPRT.
|
|
764
|
-
|
|
765
|
-
"""
|
|
766
|
-
pass
|
|
767
|
-
|
|
768
|
-
def setup_before_parallel(self, client):
|
|
769
|
-
client.upload_file(
|
|
770
|
-
self.kwargs['sldprt_path'],
|
|
771
|
-
False
|
|
772
|
-
)
|
|
773
|
-
super().setup_before_parallel(client)
|
|
774
|
-
|
|
775
|
-
def setup_after_parallel(self):
|
|
776
|
-
CoInitialize()
|
|
777
|
-
self.initialize_sldworks_connection()
|
|
778
|
-
|
|
779
|
-
def update_model(self, parameters: pd.DataFrame):
|
|
780
|
-
"""Update .x_t"""
|
|
781
|
-
|
|
782
|
-
self.parameters = parameters.copy()
|
|
783
|
-
|
|
784
|
-
# Femtet が参照している x_t パスを取得する
|
|
785
|
-
x_t_path = self.Femtet.Gaudi.LastXTPath
|
|
786
|
-
|
|
787
|
-
# 前のが存在するならば消しておく
|
|
788
|
-
if os.path.isfile(x_t_path):
|
|
789
|
-
os.remove(x_t_path)
|
|
790
|
-
|
|
791
|
-
# solidworks のモデルの更新
|
|
792
|
-
self.update_sw_model(parameters)
|
|
793
|
-
|
|
794
|
-
# export as x_t
|
|
795
|
-
self.swModel.SaveAs(x_t_path)
|
|
796
|
-
|
|
797
|
-
# 30 秒待っても x_t ができてなければエラー(COM なので)
|
|
798
|
-
timeout = 30
|
|
799
|
-
start = time()
|
|
800
|
-
while True:
|
|
801
|
-
if os.path.isfile(x_t_path):
|
|
802
|
-
break
|
|
803
|
-
if time()-start > timeout:
|
|
804
|
-
raise ModelError('モデル再構築に失敗しました')
|
|
805
|
-
sleep(1)
|
|
806
|
-
|
|
807
|
-
# モデルの再インポート
|
|
808
|
-
self._call_femtet_api(
|
|
809
|
-
self.Femtet.Gaudi.ReExecute,
|
|
810
|
-
False,
|
|
811
|
-
ModelError, # 生きてるのに失敗した場合
|
|
812
|
-
error_message=f'モデル再構築に失敗しました.',
|
|
813
|
-
is_Gaudi_method=True,
|
|
814
|
-
)
|
|
815
|
-
|
|
816
|
-
# 処理を確定
|
|
817
|
-
self._call_femtet_api(
|
|
818
|
-
self.Femtet.Redraw,
|
|
819
|
-
False, # 戻り値は常に None なのでこの変数に意味はなく None 以外なら何でもいい
|
|
820
|
-
ModelError, # 生きてるのに失敗した場合
|
|
821
|
-
error_message=f'モデル再構築に失敗しました.',
|
|
822
|
-
is_Gaudi_method=True,
|
|
823
|
-
)
|
|
824
|
-
|
|
825
|
-
# femprj モデルの変数も更新
|
|
826
|
-
super().update_model(parameters)
|
|
827
|
-
|
|
828
|
-
def update_sw_model(self, parameters: pd.DataFrame):
|
|
829
|
-
"""Update .sldprt"""
|
|
830
|
-
# df を dict に変換
|
|
831
|
-
user_param_dict = {}
|
|
832
|
-
for i, row in parameters.iterrows():
|
|
833
|
-
user_param_dict[row['name']] = row['value']
|
|
834
|
-
|
|
835
|
-
# プロパティを退避
|
|
836
|
-
buffer_aso = self.swEqnMgr.AutomaticSolveOrder
|
|
837
|
-
buffer_ar = self.swEqnMgr.AutomaticRebuild
|
|
838
|
-
self.swEqnMgr.AutomaticSolveOrder = False
|
|
839
|
-
self.swEqnMgr.AutomaticRebuild = False
|
|
840
|
-
|
|
841
|
-
for i in range(self.nEquation):
|
|
842
|
-
# name, equation の取得
|
|
843
|
-
current_equation = self.swEqnMgr.Equation(i)
|
|
844
|
-
current_name = self._get_name_from_equation(current_equation)
|
|
845
|
-
# 対象なら処理
|
|
846
|
-
if current_name in list(user_param_dict.keys()):
|
|
847
|
-
new_equation = f'"{current_name}" = {user_param_dict[current_name]}'
|
|
848
|
-
self.swEqnMgr.Equation(i, new_equation)
|
|
849
|
-
|
|
850
|
-
# 式の計算
|
|
851
|
-
# noinspection PyStatementEffect
|
|
852
|
-
self.swEqnMgr.EvaluateAll # always returns -1
|
|
853
|
-
|
|
854
|
-
# プロパティをもとに戻す
|
|
855
|
-
self.swEqnMgr.AutomaticSolveOrder = buffer_aso
|
|
856
|
-
self.swEqnMgr.AutomaticRebuild = buffer_ar
|
|
857
|
-
|
|
858
|
-
# 更新する(ここで失敗はしうる)
|
|
859
|
-
result = self.swModel.EditRebuild3 # モデル再構築
|
|
860
|
-
if not result:
|
|
861
|
-
raise ModelError('モデル再構築に失敗しました')
|
|
862
|
-
|
|
863
|
-
def _get_name_from_equation(self, equation:str):
|
|
864
|
-
pattern = r'^\s*"(.+?)"\s*$'
|
|
865
|
-
matched = re.match(pattern, equation.split('=')[0])
|
|
866
|
-
if matched:
|
|
867
|
-
return matched.group(1)
|
|
868
|
-
else:
|
|
869
|
-
return None
|
|
581
|
+
def _version(self):
|
|
582
|
+
return _version(Femtet=self.Femtet)
|