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.

Files changed (35) hide show
  1. pyfemtet/FemtetPJTSample/NX_ex01/NX_ex01.py +61 -32
  2. pyfemtet/FemtetPJTSample/Sldworks_ex01/Sldworks_ex01.py +62 -40
  3. pyfemtet/FemtetPJTSample/gau_ex08_parametric.py +26 -23
  4. pyfemtet/FemtetPJTSample/her_ex40_parametric.femprj +0 -0
  5. pyfemtet/FemtetPJTSample/her_ex40_parametric.py +58 -46
  6. pyfemtet/FemtetPJTSample/wat_ex14_parallel_parametric.py +31 -29
  7. pyfemtet/FemtetPJTSample/wat_ex14_parametric.femprj +0 -0
  8. pyfemtet/FemtetPJTSample/wat_ex14_parametric.py +30 -28
  9. pyfemtet/__init__.py +1 -1
  10. pyfemtet/core.py +14 -0
  11. pyfemtet/dispatch_extensions.py +5 -0
  12. pyfemtet/opt/__init__.py +22 -2
  13. pyfemtet/opt/_femopt.py +544 -0
  14. pyfemtet/opt/_femopt_core.py +732 -0
  15. pyfemtet/opt/interface/__init__.py +15 -0
  16. pyfemtet/opt/interface/_base.py +71 -0
  17. pyfemtet/opt/{interface.py → interface/_femtet.py} +121 -408
  18. pyfemtet/opt/interface/_femtet_with_nx/__init__.py +3 -0
  19. pyfemtet/opt/interface/_femtet_with_nx/_interface.py +128 -0
  20. pyfemtet/opt/interface/_femtet_with_sldworks.py +174 -0
  21. pyfemtet/opt/opt/__init__.py +8 -0
  22. pyfemtet/opt/opt/_base.py +202 -0
  23. pyfemtet/opt/opt/_optuna.py +246 -0
  24. pyfemtet/opt/visualization/__init__.py +7 -0
  25. pyfemtet/opt/visualization/_graphs.py +222 -0
  26. pyfemtet/opt/visualization/_monitor.py +1149 -0
  27. {pyfemtet-0.3.12.dist-info → pyfemtet-0.4.2.dist-info}/METADATA +6 -5
  28. pyfemtet-0.4.2.dist-info/RECORD +38 -0
  29. {pyfemtet-0.3.12.dist-info → pyfemtet-0.4.2.dist-info}/WHEEL +1 -1
  30. pyfemtet-0.4.2.dist-info/entry_points.txt +3 -0
  31. pyfemtet/opt/base.py +0 -1490
  32. pyfemtet/opt/monitor.py +0 -474
  33. pyfemtet-0.3.12.dist-info/RECORD +0 -26
  34. /pyfemtet/opt/{_FemtetWithNX → interface/_femtet_with_nx}/update_model.py +0 -0
  35. {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, DispatchEx
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 ..core import (
17
+ from pyfemtet.core import (
20
18
  ModelError,
21
19
  MeshError,
22
20
  SolveError,
21
+ _version,
23
22
  )
24
- from ..dispatch_extensions import (
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 software.
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.main()
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.connect_and_open_femtet()
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, _ = launch_and_dispatch_femtet(strictly_pid_specify=self.strictly_pid_specify)
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, _ = dispatch_femtet(timeout=5)
144
+ self.Femtet, self.femtet_pid = dispatch_femtet(timeout=5)
200
145
  else:
201
- self.Femtet, _ = dispatch_specific_femtet(pid, timeout=5)
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 exsiting or not."""
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 connect_and_open_femtet(self):
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 self.connect_method == 'new':
452
- RuntimeError('Femtet の connect_method "new" を用いる場合、femprj_path を指定してください。')
453
- # 開いている Femtet と接続する
454
- self.connect_femtet('existing')
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
- """See :func:`FEMInterface.check_param_value`"""
462
- variable_names = self.Femtet.GetVariableNames()
463
- if variable_names is not None:
464
- if param_name in variable_names:
465
- return self.Femtet.GetVariableValue(param_name)
466
- message = f'Femtet 解析モデルに変数 {param_name} がありません.'
467
- message += f'現在のモデルに設定されている変数は {variable_names} です.'
468
- message += '大文字・小文字の区別に注意してください.'
469
- raise RuntimeError(message)
470
-
471
- def update_parameter(self, parameters: 'pd.DataFrame'):
472
- """See :func:`FEMInterface.update_parameter`"""
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
- # Femtet の設計変数の更新
485
- existing_variable_names = self._call_femtet_api(
486
- fun=self.Femtet.GetVariableNames_py,
487
- ret_if_failed=False, # 意味がない
488
- if_error=ModelError, # 生きてるのに失敗した場合
489
- error_message=f'GetVariableNames_py に失敗しました。',
490
- is_Gaudi_method=True,
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
- if existing_variable_names is None:
495
- return
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
- for i, row in parameters.iterrows():
498
- name = row['name']
499
- value = row['value']
500
- if name in existing_variable_names:
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
- pass
501
+ if with_warning:
502
+ return warnings
503
+ else:
504
+ return None
514
505
 
515
- def update_model(self, parameters: 'pd.DataFrame') -> None:
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 (with updated model)."""
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 setup_before_parallel(self, client):
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
- class NoFEM(FEMInterface):
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)