pyfemtet 0.4.7__tar.gz → 0.4.8__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of pyfemtet might be problematic. Click here for more details.

Files changed (80) hide show
  1. {pyfemtet-0.4.7 → pyfemtet-0.4.8}/PKG-INFO +1 -1
  2. pyfemtet-0.4.8/pyfemtet/__init__.py +1 -0
  3. {pyfemtet-0.4.7 → pyfemtet-0.4.8}/pyfemtet/_test_util.py +21 -1
  4. {pyfemtet-0.4.7 → pyfemtet-0.4.8}/pyfemtet/opt/_femopt_core.py +20 -3
  5. {pyfemtet-0.4.7 → pyfemtet-0.4.8}/pyfemtet/opt/femprj_sample/cad_ex01_NX.py +4 -0
  6. {pyfemtet-0.4.7 → pyfemtet-0.4.8}/pyfemtet/opt/femprj_sample_jp/cad_ex01_NX_jp.py +4 -0
  7. {pyfemtet-0.4.7 → pyfemtet-0.4.8}/pyfemtet/opt/interface/_base.py +6 -0
  8. {pyfemtet-0.4.7 → pyfemtet-0.4.8}/pyfemtet/opt/interface/_femtet.py +159 -19
  9. {pyfemtet-0.4.7 → pyfemtet-0.4.8}/pyfemtet/opt/interface/_femtet_with_nx/_interface.py +35 -2
  10. {pyfemtet-0.4.7 → pyfemtet-0.4.8}/pyfemtet/opt/interface/_femtet_with_nx/update_model.py +36 -28
  11. {pyfemtet-0.4.7 → pyfemtet-0.4.8}/pyfemtet/opt/opt/_base.py +2 -0
  12. {pyfemtet-0.4.7 → pyfemtet-0.4.8}/pyfemtet/opt/visualization/_graphs.py +10 -13
  13. {pyfemtet-0.4.7 → pyfemtet-0.4.8}/pyfemtet/opt/visualization/_monitor.py +88 -10
  14. {pyfemtet-0.4.7 → pyfemtet-0.4.8}/pyproject.toml +1 -1
  15. pyfemtet-0.4.7/pyfemtet/__init__.py +0 -1
  16. {pyfemtet-0.4.7 → pyfemtet-0.4.8}/LICENSE +0 -0
  17. {pyfemtet-0.4.7 → pyfemtet-0.4.8}/README.md +0 -0
  18. {pyfemtet-0.4.7 → pyfemtet-0.4.8}/pyfemtet/FemtetPJTSample/NX_ex01/NX_ex01.femprj +0 -0
  19. {pyfemtet-0.4.7 → pyfemtet-0.4.8}/pyfemtet/FemtetPJTSample/NX_ex01/NX_ex01.prt +0 -0
  20. {pyfemtet-0.4.7 → pyfemtet-0.4.8}/pyfemtet/FemtetPJTSample/NX_ex01/NX_ex01.py +0 -0
  21. {pyfemtet-0.4.7 → pyfemtet-0.4.8}/pyfemtet/FemtetPJTSample/Sldworks_ex01/Sldworks_ex01.SLDPRT +0 -0
  22. {pyfemtet-0.4.7 → pyfemtet-0.4.8}/pyfemtet/FemtetPJTSample/Sldworks_ex01/Sldworks_ex01.femprj +0 -0
  23. {pyfemtet-0.4.7 → pyfemtet-0.4.8}/pyfemtet/FemtetPJTSample/Sldworks_ex01/Sldworks_ex01.py +0 -0
  24. {pyfemtet-0.4.7 → pyfemtet-0.4.8}/pyfemtet/FemtetPJTSample/_her_ex40_parametric.py +0 -0
  25. {pyfemtet-0.4.7 → pyfemtet-0.4.8}/pyfemtet/FemtetPJTSample/gau_ex08_parametric.femprj +0 -0
  26. {pyfemtet-0.4.7 → pyfemtet-0.4.8}/pyfemtet/FemtetPJTSample/gau_ex08_parametric.py +0 -0
  27. {pyfemtet-0.4.7 → pyfemtet-0.4.8}/pyfemtet/FemtetPJTSample/her_ex40_parametric.femprj +0 -0
  28. {pyfemtet-0.4.7 → pyfemtet-0.4.8}/pyfemtet/FemtetPJTSample/her_ex40_parametric.py +0 -0
  29. {pyfemtet-0.4.7 → pyfemtet-0.4.8}/pyfemtet/FemtetPJTSample/wat_ex14_parallel_parametric.py +0 -0
  30. {pyfemtet-0.4.7 → pyfemtet-0.4.8}/pyfemtet/FemtetPJTSample/wat_ex14_parametric.femprj +0 -0
  31. {pyfemtet-0.4.7 → pyfemtet-0.4.8}/pyfemtet/FemtetPJTSample/wat_ex14_parametric.py +0 -0
  32. {pyfemtet-0.4.7 → pyfemtet-0.4.8}/pyfemtet/core.py +0 -0
  33. {pyfemtet-0.4.7 → pyfemtet-0.4.8}/pyfemtet/dispatch_extensions.py +0 -0
  34. {pyfemtet-0.4.7 → pyfemtet-0.4.8}/pyfemtet/logger.py +0 -0
  35. {pyfemtet-0.4.7 → pyfemtet-0.4.8}/pyfemtet/opt/__init__.py +0 -0
  36. {pyfemtet-0.4.7 → pyfemtet-0.4.8}/pyfemtet/opt/_femopt.py +0 -0
  37. {pyfemtet-0.4.7 → pyfemtet-0.4.8}/pyfemtet/opt/femprj_sample/cad_ex01_NX.femprj +0 -0
  38. {pyfemtet-0.4.7 → pyfemtet-0.4.8}/pyfemtet/opt/femprj_sample/cad_ex01_NX.prt +0 -0
  39. {pyfemtet-0.4.7 → pyfemtet-0.4.8}/pyfemtet/opt/femprj_sample/cad_ex01_NX_test_result.reccsv +0 -0
  40. {pyfemtet-0.4.7 → pyfemtet-0.4.8}/pyfemtet/opt/femprj_sample/cad_ex01_SW.SLDPRT +0 -0
  41. {pyfemtet-0.4.7 → pyfemtet-0.4.8}/pyfemtet/opt/femprj_sample/cad_ex01_SW.femprj +0 -0
  42. {pyfemtet-0.4.7 → pyfemtet-0.4.8}/pyfemtet/opt/femprj_sample/cad_ex01_SW.py +0 -0
  43. {pyfemtet-0.4.7 → pyfemtet-0.4.8}/pyfemtet/opt/femprj_sample/cad_ex01_SW_test_result.reccsv +0 -0
  44. {pyfemtet-0.4.7 → pyfemtet-0.4.8}/pyfemtet/opt/femprj_sample/gal_ex58_parametric.femprj +0 -0
  45. {pyfemtet-0.4.7 → pyfemtet-0.4.8}/pyfemtet/opt/femprj_sample/gal_ex58_parametric.py +0 -0
  46. {pyfemtet-0.4.7 → pyfemtet-0.4.8}/pyfemtet/opt/femprj_sample/gal_ex58_parametric_test_result.reccsv +0 -0
  47. {pyfemtet-0.4.7 → pyfemtet-0.4.8}/pyfemtet/opt/femprj_sample/gau_ex08_parametric.femprj +0 -0
  48. {pyfemtet-0.4.7 → pyfemtet-0.4.8}/pyfemtet/opt/femprj_sample/gau_ex08_parametric.py +0 -0
  49. {pyfemtet-0.4.7 → pyfemtet-0.4.8}/pyfemtet/opt/femprj_sample/gau_ex08_parametric_test_result.reccsv +0 -0
  50. {pyfemtet-0.4.7 → pyfemtet-0.4.8}/pyfemtet/opt/femprj_sample/her_ex40_parametric.femprj +0 -0
  51. {pyfemtet-0.4.7 → pyfemtet-0.4.8}/pyfemtet/opt/femprj_sample/her_ex40_parametric.py +0 -0
  52. {pyfemtet-0.4.7 → pyfemtet-0.4.8}/pyfemtet/opt/femprj_sample/her_ex40_parametric_test_result.reccsv +0 -0
  53. {pyfemtet-0.4.7 → pyfemtet-0.4.8}/pyfemtet/opt/femprj_sample/paswat_ex1_parametric.femprj +0 -0
  54. {pyfemtet-0.4.7 → pyfemtet-0.4.8}/pyfemtet/opt/femprj_sample/paswat_ex1_parametric.py +0 -0
  55. {pyfemtet-0.4.7 → pyfemtet-0.4.8}/pyfemtet/opt/femprj_sample/paswat_ex1_parametric_parallel.py +0 -0
  56. {pyfemtet-0.4.7 → pyfemtet-0.4.8}/pyfemtet/opt/femprj_sample/paswat_ex1_parametric_test_result.reccsv +0 -0
  57. {pyfemtet-0.4.7 → pyfemtet-0.4.8}/pyfemtet/opt/femprj_sample/wat_ex14_parametric.femprj +0 -0
  58. {pyfemtet-0.4.7 → pyfemtet-0.4.8}/pyfemtet/opt/femprj_sample/wat_ex14_parametric.py +0 -0
  59. {pyfemtet-0.4.7 → pyfemtet-0.4.8}/pyfemtet/opt/femprj_sample/wat_ex14_parametric_test_result.reccsv +0 -0
  60. {pyfemtet-0.4.7 → pyfemtet-0.4.8}/pyfemtet/opt/femprj_sample_jp/cad_ex01_NX_jp.femprj +0 -0
  61. {pyfemtet-0.4.7 → pyfemtet-0.4.8}/pyfemtet/opt/femprj_sample_jp/cad_ex01_SW_jp.femprj +0 -0
  62. {pyfemtet-0.4.7 → pyfemtet-0.4.8}/pyfemtet/opt/femprj_sample_jp/cad_ex01_SW_jp.py +0 -0
  63. {pyfemtet-0.4.7 → pyfemtet-0.4.8}/pyfemtet/opt/femprj_sample_jp/gal_ex58_parametric_jp.femprj +0 -0
  64. {pyfemtet-0.4.7 → pyfemtet-0.4.8}/pyfemtet/opt/femprj_sample_jp/gal_ex58_parametric_jp.py +0 -0
  65. {pyfemtet-0.4.7 → pyfemtet-0.4.8}/pyfemtet/opt/femprj_sample_jp/gau_ex08_parametric_jp.femprj +0 -0
  66. {pyfemtet-0.4.7 → pyfemtet-0.4.8}/pyfemtet/opt/femprj_sample_jp/gau_ex08_parametric_jp.py +0 -0
  67. {pyfemtet-0.4.7 → pyfemtet-0.4.8}/pyfemtet/opt/femprj_sample_jp/her_ex40_parametric_jp.femprj +0 -0
  68. {pyfemtet-0.4.7 → pyfemtet-0.4.8}/pyfemtet/opt/femprj_sample_jp/her_ex40_parametric_jp.py +0 -0
  69. {pyfemtet-0.4.7 → pyfemtet-0.4.8}/pyfemtet/opt/femprj_sample_jp/paswat_ex1_parametric_jp.femprj +0 -0
  70. {pyfemtet-0.4.7 → pyfemtet-0.4.8}/pyfemtet/opt/femprj_sample_jp/paswat_ex1_parametric_jp.py +0 -0
  71. {pyfemtet-0.4.7 → pyfemtet-0.4.8}/pyfemtet/opt/femprj_sample_jp/paswat_ex1_parametric_parallel_jp.py +0 -0
  72. {pyfemtet-0.4.7 → pyfemtet-0.4.8}/pyfemtet/opt/femprj_sample_jp/wat_ex14_parametric_jp.femprj +0 -0
  73. {pyfemtet-0.4.7 → pyfemtet-0.4.8}/pyfemtet/opt/femprj_sample_jp/wat_ex14_parametric_jp.py +0 -0
  74. {pyfemtet-0.4.7 → pyfemtet-0.4.8}/pyfemtet/opt/interface/__init__.py +0 -0
  75. {pyfemtet-0.4.7 → pyfemtet-0.4.8}/pyfemtet/opt/interface/_femtet_parametric.py +0 -0
  76. {pyfemtet-0.4.7 → pyfemtet-0.4.8}/pyfemtet/opt/interface/_femtet_with_nx/__init__.py +0 -0
  77. {pyfemtet-0.4.7 → pyfemtet-0.4.8}/pyfemtet/opt/interface/_femtet_with_sldworks.py +0 -0
  78. {pyfemtet-0.4.7 → pyfemtet-0.4.8}/pyfemtet/opt/opt/__init__.py +0 -0
  79. {pyfemtet-0.4.7 → pyfemtet-0.4.8}/pyfemtet/opt/opt/_optuna.py +0 -0
  80. {pyfemtet-0.4.7 → pyfemtet-0.4.8}/pyfemtet/opt/visualization/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pyfemtet
3
- Version: 0.4.7
3
+ Version: 0.4.8
4
4
  Summary: Design parameter optimization using Femtet.
5
5
  Home-page: https://github.com/pyfemtet/pyfemtet
6
6
  License: BSD-3-Clause
@@ -0,0 +1 @@
1
+ __version__ = "0.4.8"
@@ -86,10 +86,30 @@ def _get_obj_from_csv(csv_path):
86
86
  return out, columns
87
87
 
88
88
 
89
- def is_equal_result(csv1, csv2):
89
+ def is_equal_result(csv1, csv2, result_save_to=None):
90
90
  """Check the equality of two result csv files."""
91
91
  df1, columns1 = _get_obj_from_csv(csv1)
92
92
  df2, columns2 = _get_obj_from_csv(csv2)
93
+
94
+ if result_save_to is not None:
95
+ import datetime
96
+ with open(result_save_to, 'a', encoding='utf-8', newline='\n') as f:
97
+ name = os.path.basename(csv1)
98
+ content = [
99
+ f'===== result of {name} =====\n',
100
+ f'{datetime.datetime.now()}\n',
101
+ f'----- column numbers -----\n',
102
+ f'csv1: {len(columns1)} columns\n',
103
+ f'csv2: {len(columns2)} columns\n',
104
+ f'----- row numbers -----\n',
105
+ f'csv1: {len(df1)} columns\n',
106
+ f'csv2: {len(df2)} columns\n',
107
+ f'----- difference -----\n',
108
+ f'max difference ratio: {(np.abs(df1.values - df2.values) / np.abs(df2.values)).max() * 100}%\n',
109
+ '\n'
110
+ ]
111
+ f.writelines(content)
112
+
93
113
  assert len(columns1) == len(columns2), '結果 csv の column 数が異なります。'
94
114
  assert len(df1) == len(df2), '結果 csv の row 数が異なります。'
95
115
  assert (np.abs(df1.values - df2.values) / np.abs(df2.values)).max() <= 0.01, '前回の結果と 1% を超える相違があります。'
@@ -14,7 +14,7 @@ import numpy as np
14
14
  import pandas as pd
15
15
  from scipy.stats.qmc import LatinHypercube
16
16
  from optuna._hypervolume import WFG
17
- from dask.distributed import Lock
17
+ from dask.distributed import Lock, get_client
18
18
 
19
19
  # win32com
20
20
  from win32com.client import constants, Constants
@@ -532,7 +532,17 @@ class History:
532
532
 
533
533
  return columns, metadata
534
534
 
535
- def record(self, parameters, objectives, constraints, obj_values, cns_values, message):
535
+ def record(
536
+ self,
537
+ parameters,
538
+ objectives,
539
+ constraints,
540
+ obj_values,
541
+ cns_values,
542
+ message,
543
+ postprocess_func,
544
+ postprocess_args,
545
+ ):
536
546
  """Records the optimization results in the history.
537
547
 
538
548
  Record only. NOT save.
@@ -544,7 +554,8 @@ class History:
544
554
  obj_values (list): The objective values.
545
555
  cns_values (list): The constraint values.
546
556
  message (str): Additional information or messages related to the optimization results.
547
-
557
+ postprocess_func (Callable): fem method to call after solving. i.e. save result file. Must take trial(int) for 1st argument.
558
+ postprocess_args (dict): arguments for `postprocess_func`. i.e. create binary data of result file in the worker process.
548
559
  """
549
560
 
550
561
  # create row
@@ -591,6 +602,12 @@ class History:
591
602
  self._calc_hypervolume(objectives) # update self.local_data
592
603
  self.actor_data = self.local_data
593
604
 
605
+ # save file
606
+ if postprocess_args is not None:
607
+ trial = self.local_data['trial'].values[-1]
608
+ client = get_client() # always returns valid client
609
+ client.run_on_scheduler(postprocess_func, trial, **postprocess_args)
610
+
594
611
  def _calc_non_domi(self, objectives):
595
612
 
596
613
  # 目的関数の履歴を取り出してくる
@@ -107,6 +107,10 @@ if __name__ == '__main__':
107
107
  fem = FemtetWithNXInterface(
108
108
  prt_path='cad_ex01_NX.prt',
109
109
  open_result_with_gui=False, # To calculate von Mises stress, set this argument to False. See Femtet Macro Help.
110
+ export_curves=False,
111
+ export_surfaces=False,
112
+ export_solids=True,
113
+ export_flattened_assembly=False,
110
114
  )
111
115
 
112
116
  # Initialize the FEMOpt object.
@@ -103,6 +103,10 @@ if __name__ == '__main__':
103
103
  fem = FemtetWithNXInterface(
104
104
  prt_path='cad_ex01_NX.prt',
105
105
  open_result_with_gui=False,
106
+ export_curves=False,
107
+ export_surfaces=False,
108
+ export_solids=True,
109
+ export_flattened_assembly=False,
106
110
  )
107
111
 
108
112
  # FEMOpt オブジェクトの初期化 (最適化問題とFemtetとの接続を行います)
@@ -63,6 +63,12 @@ class FEMInterface(ABC):
63
63
  """Preprocessing after launching a dask worker and before run optimization (if implemented in concrete class)."""
64
64
  pass
65
65
 
66
+ def postprocess_func(self, trial: int, *args, dask_scheduler=None, **kwargs):
67
+ pass
68
+
69
+ def create_postprocess_args(self):
70
+ pass
71
+
66
72
 
67
73
  class NoFEM(FEMInterface):
68
74
  """Interface with no FEM for debug."""
@@ -59,9 +59,10 @@ class FemtetInterface(FEMInterface):
59
59
  self,
60
60
  femprj_path=None,
61
61
  model_name=None,
62
- connect_method='auto',
63
- strictly_pid_specify=True,
64
- allow_without_project=False,
62
+ connect_method='auto', # dask worker では __init__ の中で 'new' にするので super() の引数にしない。(しても意味がない)
63
+ save_pdt='all', # 'all' or None
64
+ strictly_pid_specify=True, # dask worker では True にしたいので super() の引数にしない。
65
+ allow_without_project=False, # main でのみ True を許容したいので super() の引数にしない。
65
66
  open_result_with_gui=True,
66
67
  parametric_output_indexes_use_as_objective=None,
67
68
  **kwargs # 継承されたクラスからの引数
@@ -80,6 +81,7 @@ class FemtetInterface(FEMInterface):
80
81
  self.allow_without_project = allow_without_project
81
82
  self.original_femprj_path = self.femprj_path
82
83
  self.open_result_with_gui = open_result_with_gui
84
+ self.save_pdt = save_pdt
83
85
 
84
86
  # その他のメンバーの宣言や初期化
85
87
  self.Femtet = None
@@ -106,6 +108,15 @@ class FemtetInterface(FEMInterface):
106
108
  # 開かれたモデルに応じて femprj_path と model を更新する
107
109
  self._connect_and_open_femtet()
108
110
 
111
+ # original_fem_prj が None なら必ず
112
+ # dask worker でないプロセスがオリジナルファイルを開いている
113
+ if self.original_femprj_path is None:
114
+ # dask worker でなければ original のはず
115
+ try:
116
+ worker = get_worker()
117
+ except ValueError:
118
+ self.original_femprj_path = self.femprj_path
119
+
109
120
  # 接続した Femtet の種類に応じて del 時に quit するかどうか決める
110
121
  self.quit_when_destruct = self.connected_method == 'new'
111
122
 
@@ -117,26 +128,13 @@ class FemtetInterface(FEMInterface):
117
128
  model_name=self.model_name,
118
129
  open_result_with_gui=self.open_result_with_gui,
119
130
  parametric_output_indexes_use_as_objective=self.parametric_output_indexes_use_as_objective,
131
+ save_pdt=self.save_pdt,
120
132
  **kwargs
121
133
  )
122
134
 
123
135
  def __del__(self):
124
136
  if self.quit_when_destruct:
125
- try:
126
- # 強制終了 TODO: 自動保存を回避する
127
- hwnd = self.Femtet.hWnd
128
- pid = _get_pid(hwnd)
129
- util.close_femtet(hwnd, 1, True)
130
- start = time()
131
- while psutil.pid_exists(pid):
132
- if time() - start > 30: # 30 秒経っても存在するのは何かおかしい
133
- os.kill(pid, signal.SIGKILL)
134
- break
135
- sleep(1)
136
- sleep(1)
137
-
138
- except (AttributeError, OSError): # already dead
139
- pass
137
+ self.quit()
140
138
  # CoUninitialize() # Win32 exception occurred releasing IUnknown at 0x0000022427692748
141
139
 
142
140
  def _connect_new_femtet(self):
@@ -626,8 +624,25 @@ class FemtetInterface(FEMInterface):
626
624
  print('================')
627
625
  input('終了するには Enter を押してください。')
628
626
  raise e
627
+
629
628
  else:
630
- util.close_femtet(self.Femtet.hWnd, timeout, force)
629
+ hwnd = self.Femtet.hWnd
630
+
631
+ # terminate
632
+ util.close_femtet(hwnd, timeout, force)
633
+
634
+ try:
635
+ pid = _get_pid(hwnd)
636
+ start = time()
637
+ while psutil.pid_exists(pid):
638
+ if time() - start > 30: # 30 秒経っても存在するのは何かおかしい
639
+ logger.error('Femtet の終了に失敗しました。')
640
+ break
641
+ sleep(1)
642
+ sleep(1)
643
+
644
+ except (AttributeError, OSError): # already dead
645
+ pass
631
646
 
632
647
  def _setup_before_parallel(self, client):
633
648
  client.upload_file(
@@ -637,3 +652,128 @@ class FemtetInterface(FEMInterface):
637
652
 
638
653
  def _version(self):
639
654
  return _version(Femtet=self.Femtet)
655
+
656
+ def create_postprocess_args(self):
657
+ file_content = self.create_result_file_content()
658
+ jpg_content = self.create_jpg_content()
659
+
660
+ out = dict(
661
+ original_femprj_path=self.original_femprj_path,
662
+ model_name=self.model_name,
663
+ pdt_file_content=file_content,
664
+ jpg_file_content=jpg_content,
665
+ )
666
+ return out
667
+
668
+ @staticmethod
669
+ def postprocess_func(
670
+ trial: int,
671
+ original_femprj_path: str,
672
+ model_name: str,
673
+ pdt_file_content=None,
674
+ jpg_file_content=None,
675
+ dask_scheduler=None
676
+ ):
677
+ result_dir = original_femprj_path.replace('.femprj', '.Results')
678
+ if pdt_file_content is not None:
679
+ pdt_path = os.path.join(result_dir, model_name + f'_trial{trial}.pdt')
680
+ with open(pdt_path, 'wb') as f:
681
+ f.write(pdt_file_content)
682
+
683
+ if jpg_file_content is not None:
684
+ jpg_path = os.path.join(result_dir, model_name + f'_trial{trial}.jpg')
685
+ with open(jpg_path, 'wb') as f:
686
+ f.write(jpg_file_content)
687
+
688
+ def create_result_file_content(self):
689
+ """Called after solve"""
690
+ if self.save_pdt == 'all':
691
+ # save to worker space
692
+ result_dir = self.femprj_path.replace('.femprj', '.Results')
693
+ pdt_path = os.path.join(result_dir, self.model_name + '.pdt')
694
+ succeed = self.Femtet.SavePDT(pdt_path, True)
695
+
696
+ # convert .pdt to ByteIO
697
+ if succeed:
698
+ with open(pdt_path, 'rb') as f:
699
+ content = f.read()
700
+ return content
701
+
702
+ else:
703
+ raise Exception('pdt ファイルの保存でエラーが発生しました。')
704
+
705
+ else:
706
+ return None
707
+
708
+ def create_jpg_content(self):
709
+ result_dir = self.femprj_path.replace('.femprj', '.Results')
710
+ jpg_path = os.path.join(result_dir, self.model_name + '.jpg')
711
+
712
+ # モデル表示画面の設定
713
+ self.Femtet.SetWindowSize(200, 200)
714
+ self.Femtet.Fit()
715
+ self.Femtet.ViewNumeric.SetCoord(1, 1, 1)
716
+
717
+ # ---モデルの画面を保存---
718
+ self.Femtet.Redraw() # 再描画
719
+ succeed = self.Femtet.SavePicture(jpg_path, 200, 200, 80)
720
+
721
+ self.Femtet.RedrawMode = True # 逐一の描画をオン
722
+
723
+ if not succeed:
724
+ raise Exception('jpg ファイルの保存でエラーが発生しました。')
725
+
726
+ if not os.path.exists(jpg_path):
727
+ raise Exception('保存した jpg ファイルが見つかりませんでした。')
728
+
729
+ with open(jpg_path, 'rb') as f:
730
+ content = f.read()
731
+
732
+ return content
733
+
734
+
735
+ from win32com.client import Dispatch, constants
736
+
737
+
738
+ class _UnPicklableNoFEM(FemtetInterface):
739
+
740
+
741
+ original_femprj_path = 'dummy'
742
+ model_name = 'dummy'
743
+ parametric_output_indexes_use_as_objective = None
744
+ kwargs = dict()
745
+ Femtet = None
746
+ quit_when_destruct = False
747
+
748
+ def __init__(self):
749
+ CoInitialize()
750
+ self.unpicklable_member = Dispatch('FemtetMacro.Femtet')
751
+ self.cns = constants
752
+
753
+ def _setup_before_parallel(self, *args, **kwargs):
754
+ pass
755
+
756
+ def check_param_value(self, *args, **kwargs):
757
+ pass
758
+
759
+ def update_parameter(self, *args, **kwargs):
760
+ pass
761
+
762
+ def update(self, *args, **kwargs):
763
+ pass
764
+
765
+ def create_result_file_content(self):
766
+ """Called after solve"""
767
+
768
+ # save to worker space
769
+ with open(__file__, 'rb') as f:
770
+ content = f.read()
771
+
772
+ return content
773
+
774
+ def create_file_path(self, trial: int):
775
+ # return path of scheduler environment
776
+ here = os.path.dirname(__file__)
777
+ pdt_path = os.path.join(here, f'trial{trial}.pdt')
778
+ return pdt_path
779
+
@@ -17,6 +17,10 @@ class FemtetWithNXInterface(FemtetInterface):
17
17
 
18
18
  Args:
19
19
  prt_path: The path to the prt file.
20
+ export_curves(bool or None): Parasolid export setting of NX. If None, PyFemtet does not change the setting of NX. Defaults to None.
21
+ export_surfaces(bool or None): Parasolid export setting of NX. If None, PyFemtet does not change the setting of NX. Defaults to None.
22
+ export_solids(bool or None): Parasolid export setting of NX. If None, PyFemtet does not change the setting of NX. Defaults to None.
23
+ export_flattened_assembly(bool or None): Parasolid export setting of NX. If None, PyFemtet does not change the setting of NX. Defaults to None.
20
24
 
21
25
  For details of The other arguments, see ``FemtetInterface``.
22
26
 
@@ -27,9 +31,12 @@ class FemtetWithNXInterface(FemtetInterface):
27
31
  def __init__(
28
32
  self,
29
33
  prt_path,
34
+ export_curves: bool or None = None,
35
+ export_surfaces: bool or None = None,
36
+ export_solids: bool or None = None,
37
+ export_flattened_assembly: bool or None = None,
30
38
  **kwargs
31
39
  ):
32
-
33
40
  # check NX installation
34
41
  self.run_journal_path = os.path.join(os.environ.get('UGII_BASE_DIR'), 'NXBIN', 'run_journal.exe')
35
42
  if not os.path.isfile(self.run_journal_path):
@@ -45,10 +52,19 @@ class FemtetWithNXInterface(FemtetInterface):
45
52
  except ValueError: # get_worker に失敗した場合
46
53
  self.prt_path = os.path.abspath(prt_path)
47
54
 
55
+ self.export_curves = export_curves
56
+ self.export_surfaces = export_surfaces
57
+ self.export_solids = export_solids
58
+ self.export_flattened_assembly = export_flattened_assembly
59
+
48
60
  # FemtetInterface の設定 (femprj_path, model_name の更新など)
49
61
  # + restore 情報の上書き
50
62
  super().__init__(
51
63
  prt_path=self.prt_path,
64
+ export_curves=self.export_curves,
65
+ export_surfaces=self.export_surfaces,
66
+ export_solids=self.export_solids,
67
+ export_flattened_assembly=self.export_flattened_assembly,
52
68
  **kwargs
53
69
  )
54
70
 
@@ -86,10 +102,27 @@ class FemtetWithNXInterface(FemtetInterface):
86
102
  tmp_dict[row['name']] = row['value']
87
103
  str_json = json.dumps(tmp_dict)
88
104
 
105
+ # create dumped json of export settings
106
+ tmp_dict = dict(
107
+ include_curves=self.export_curves,
108
+ include_surfaces=self.export_surfaces,
109
+ include_solids=self.export_solids,
110
+ flatten_assembly=self.export_flattened_assembly,
111
+ )
112
+ dumped_json_export_settings = json.dumps(tmp_dict)
113
+
89
114
  # NX journal を使ってモデルを編集する
90
115
  env = os.environ.copy()
91
116
  subprocess.run(
92
- [self.run_journal_path, self._JOURNAL_PATH, '-args', self.prt_path, str_json, x_t_path],
117
+ [
118
+ self.run_journal_path, # run_journal.exe
119
+ self._JOURNAL_PATH, # update_model.py
120
+ '-args',
121
+ self.prt_path,
122
+ str_json,
123
+ x_t_path,
124
+ dumped_json_export_settings,
125
+ ],
93
126
  env=env,
94
127
  shell=True,
95
128
  cwd=os.path.dirname(self.prt_path)
@@ -1,35 +1,34 @@
1
1
  import os
2
2
  import sys
3
3
  import json
4
- import NXOpen
5
-
4
+ from xml.etree.ElementInclude import include
6
5
 
7
- def main(prtPath:str, parameters:'dict as str', x_tPath:str = None):
8
- '''
9
- .prt ファイルのパスを受け取り、parameters に指定された変数を更新し、
10
- x_tPath が None のときは.prt と同じディレクトリに
11
- .x_t ファイルをエクスポートする
6
+ import NXOpen
12
7
 
13
- Parameters
14
- ----------
15
- prtPath : str
16
- DESCRIPTION.
17
- parameters : 'dict as str'
18
- DESCRIPTION.
19
8
 
20
- Returns
21
- -------
22
- None.
9
+ def main(
10
+ prtPath: str,
11
+ parameters: str, # dumped json
12
+ x_tPath: str,
13
+ dumped_json_export_settings: str,
14
+ ):
15
+ """Update the parameter of .prt file and export to .x_t file."""
23
16
 
24
- '''
25
17
  # 保存先の設定
26
- prtPath = os.path.abspath(prtPath) # 一応
18
+ prtPath = os.path.abspath(prtPath)
27
19
  if x_tPath is None:
28
20
  x_tPath = os.path.splitext(prtPath)[0] + '.x_t'
29
21
 
30
22
  # 辞書の作成
31
23
  parameters = json.loads(parameters)
32
-
24
+
25
+ # export 設定
26
+ settings = json.loads(dumped_json_export_settings)
27
+ include_curves = settings['include_curves']
28
+ include_surfaces = settings['include_surfaces']
29
+ include_solids = settings['include_solids']
30
+ flatten_assembly = settings['flatten_assembly']
31
+
33
32
  # session の取得とパートを開く
34
33
  theSession = NXOpen.Session.GetSession()
35
34
  theSession.Parts.OpenActiveDisplay(prtPath, NXOpen.DisplayPartOption.AllowAdditional)
@@ -40,7 +39,6 @@ def main(prtPath:str, parameters:'dict as str', x_tPath:str = None):
40
39
  displayPart = theSession.Parts.Display
41
40
 
42
41
  # 式を更新
43
- unit_mm = workPart.UnitCollection.FindObject("MilliMeter")
44
42
  for k, v in parameters.items():
45
43
  try:
46
44
  exp = workPart.Expressions.FindObject(k)
@@ -48,14 +46,15 @@ def main(prtPath:str, parameters:'dict as str', x_tPath:str = None):
48
46
  print(f'├ 変数{k}は .prt ファイルに含まれていません。無視されます。')
49
47
  continue
50
48
 
51
- workPart.Expressions.EditWithUnits(exp, unit_mm, str(v))
49
+ workPart.Expressions.Edit(exp, str(v))
52
50
  # 式の更新を適用
53
51
  id1 = theSession.NewestVisibleUndoMark
54
52
  try:
55
53
  nErrs1 = theSession.UpdateManager.DoUpdate(id1)
56
54
  # 更新に失敗
57
55
  except NXOpen.NXException as e:
58
- print(' 形状が破綻しました。操作を取り消します。')
56
+ print(f' ERROR! {e}')
57
+ print(f'└ 形状が破綻しました。操作を取り消します。')
59
58
  return None
60
59
 
61
60
  print('│ model 更新に成功しました。')
@@ -64,19 +63,28 @@ def main(prtPath:str, parameters:'dict as str', x_tPath:str = None):
64
63
  # parasolid のエクスポート
65
64
  parasolidExporter1 = theSession.DexManager.CreateParasolidExporter()
66
65
 
67
- parasolidExporter1.ObjectTypes.Curves = False
68
- parasolidExporter1.ObjectTypes.Surfaces = False
69
- parasolidExporter1.ObjectTypes.Solids = True
66
+ if include_curves is not None:
67
+ parasolidExporter1.ObjectTypes.Curves = include_curves
68
+
69
+ if include_surfaces is not None:
70
+ parasolidExporter1.ObjectTypes.Surfaces = include_surfaces
71
+
72
+ if include_solids is not None:
73
+ parasolidExporter1.ObjectTypes.Solids = include_solids
74
+
75
+ if flatten_assembly is not None:
76
+ parasolidExporter1.FlattenAssembly = flatten_assembly
70
77
 
71
78
  parasolidExporter1.InputFile = prtPath
72
79
  parasolidExporter1.ParasolidVersion = NXOpen.ParasolidExporter.ParasolidVersionOption.Current
73
80
  parasolidExporter1.OutputFile = x_tPath
74
81
 
75
82
  parasolidExporter1.Commit()
76
-
77
83
  parasolidExporter1.Destroy()
78
- except:
79
- print('└ parasolid 更新に失敗しました。')
84
+
85
+ except Exception as e:
86
+ print(f'├ ERROR! {e}')
87
+ print(f'└ parasolid 更新に失敗しました。')
80
88
  return None
81
89
 
82
90
  print('└ parasolid 更新が正常に終了しました。')
@@ -88,6 +88,8 @@ class AbstractOptimizer(ABC):
88
88
  y,
89
89
  c,
90
90
  self.message,
91
+ postprocess_func=self.fem.postprocess_func,
92
+ postprocess_args=self.fem.create_postprocess_args(),
91
93
  )
92
94
 
93
95
  logger.debug('history.record end')
@@ -2,9 +2,6 @@ import plotly.graph_objs as go
2
2
  import plotly.express as px
3
3
 
4
4
 
5
- _CUSTOM_DATA_DICT = {'trial': 0} # 連番
6
-
7
-
8
5
  class _ColorSet:
9
6
  non_domi = {True: '#007bff', False: '#6c757d'} # color
10
7
 
@@ -49,7 +46,7 @@ def update_hypervolume_plot(history, df):
49
46
  x="trial",
50
47
  y="hypervolume",
51
48
  markers=True,
52
- custom_data=_CUSTOM_DATA_DICT.keys(),
49
+ custom_data=['trial'],
53
50
  )
54
51
 
55
52
  fig.update_layout(
@@ -67,13 +64,17 @@ def update_default_figure(history, df):
67
64
  obj_names = history.obj_names
68
65
 
69
66
  if len(obj_names) == 0:
70
- return go.Figure()
67
+ fig = go.Figure()
71
68
 
72
69
  elif len(obj_names) == 1:
73
- return update_single_objective_plot(history, df)
70
+ fig = update_single_objective_plot(history, df)
74
71
 
75
72
  elif len(obj_names) >= 2:
76
- return update_multi_objective_pairplot(history, df)
73
+ fig = update_multi_objective_pairplot(history, df)
74
+
75
+ fig.update_traces(hoverinfo="none", hovertemplate=None)
76
+
77
+ return fig
77
78
 
78
79
 
79
80
  def update_single_objective_plot(history, df):
@@ -97,7 +98,7 @@ def update_single_objective_plot(history, df):
97
98
  _ls.feasible['label']: False,
98
99
  'trial': True,
99
100
  },
100
- custom_data=_CUSTOM_DATA_DICT.keys(),
101
+ custom_data=['trial'],
101
102
  )
102
103
 
103
104
  fig.add_trace(
@@ -143,11 +144,7 @@ def update_multi_objective_pairplot(history, df):
143
144
  _ls.feasible[True]: _ss.feasible[True],
144
145
  _ls.feasible[False]: _ss.feasible[False],
145
146
  },
146
- hover_data={
147
- _ls.feasible['label']: False,
148
- 'trial': True,
149
- },
150
- custom_data=_CUSTOM_DATA_DICT.keys(),
147
+ custom_data=['trial'],
151
148
  category_orders={
152
149
  _ls.feasible['label']: (_ls.feasible[False], _ls.feasible[True]),
153
150
  _ls.non_domi['label']: (_ls.non_domi[False], _ls.non_domi[True]),
@@ -1,4 +1,5 @@
1
1
  import os
2
+ import base64
2
3
  from typing import Optional, List
3
4
 
4
5
  import json
@@ -6,10 +7,11 @@ import webbrowser
6
7
  from time import sleep
7
8
  from threading import Thread
8
9
 
10
+ import numpy as np
9
11
  import pandas as pd
10
12
  import psutil
11
13
  from plotly.graph_objects import Figure
12
- from dash import Dash, html, dcc, Output, Input, State, callback_context, no_update
14
+ from dash import Dash, html, dcc, Output, Input, State, callback_context, no_update, dash_table
13
15
  from dash.exceptions import PreventUpdate
14
16
  import dash_bootstrap_components as dbc
15
17
 
@@ -18,7 +20,6 @@ from pyfemtet.opt.interface import FemtetInterface
18
20
  from pyfemtet.opt.visualization._graphs import (
19
21
  update_default_figure,
20
22
  update_hypervolume_plot,
21
- _CUSTOM_DATA_DICT
22
23
  )
23
24
 
24
25
 
@@ -28,6 +29,8 @@ logger = get_logger('viz')
28
29
  logger.setLevel(logging.INFO)
29
30
 
30
31
 
32
+ here = os.path.dirname(__file__)
33
+
31
34
  DBC_COLUMN_STYLE_CENTER = {
32
35
  'display': 'flex',
33
36
  'justify-content': 'center',
@@ -458,7 +461,7 @@ class FemtetControl:
458
461
  points_dicts = selection_data['points']
459
462
  for points_dict in points_dicts:
460
463
  logger.debug(points_dict)
461
- trial = points_dict['customdata'][_CUSTOM_DATA_DICT['trial']]
464
+ trial = points_dict['customdata'][0]
462
465
  logger.debug(trial)
463
466
  index = trial - 1
464
467
  names = [name for name in home.monitor.local_df.columns if name.startswith('prm_')]
@@ -556,6 +559,7 @@ class HomePageBase:
556
559
  ID_GRAPH_TABS = 'home-graph-tabs'
557
560
  ID_GRAPH_CARD_BODY = 'home-graph-card-body'
558
561
  ID_GRAPH = 'home-graph'
562
+ ID_GRAPH_TOOLTIP = 'home-graph-hover-tooltip'
559
563
  ID_SELECTION_DATA = 'home-selection-data'
560
564
 
561
565
  # selection data attribute
@@ -577,7 +581,6 @@ class HomePageBase:
577
581
  self.monitor = monitor
578
582
  self.app: Dash = monitor.app
579
583
  self.history = monitor.history
580
- self.df = monitor.local_df
581
584
  self.setup_graph_card()
582
585
  self.setup_contents()
583
586
  self.setup_layout()
@@ -616,7 +619,12 @@ class HomePageBase:
616
619
  children=[
617
620
  # Loading : child が Output である callback について、
618
621
  # それが発火してから return するまでの間 Spinner が出てくる
619
- dcc.Loading(dcc.Graph(id=self.ID_GRAPH)),
622
+ html.Div([
623
+ dcc.Loading(
624
+ dcc.Graph(id=self.ID_GRAPH, clear_on_unhover=True, figure=self.get_fig_by_tab_id(default_tab)),
625
+ ),
626
+ dcc.Tooltip(id=self.ID_GRAPH_TOOLTIP),
627
+ ]),
620
628
  ],
621
629
  id=self.ID_GRAPH_CARD_BODY,
622
630
  ),
@@ -633,7 +641,7 @@ class HomePageBase:
633
641
  Output(self.ID_GRAPH, 'figure'),
634
642
  ],
635
643
  [
636
- Input(self.ID_GRAPH_CARD_BODY, 'children'),
644
+ Input(self.ID_DUMMY, 'children'),
637
645
  ],
638
646
  [
639
647
  State(self.ID_GRAPH_TABS, 'active_tab'),
@@ -678,6 +686,72 @@ class HomePageBase:
678
686
  logger.debug(f'on_select: {selected_data}')
679
687
  return [selected_data]
680
688
 
689
+ # ホバーに画像を表示する callback
690
+ @self.monitor.app.callback(
691
+ Output(self.ID_GRAPH_TOOLTIP, "show"),
692
+ Output(self.ID_GRAPH_TOOLTIP, "bbox"),
693
+ Output(self.ID_GRAPH_TOOLTIP, "children"),
694
+ Input(self.ID_GRAPH, "hoverData"),
695
+ )
696
+ def display_hover(hoverData):
697
+ if hoverData is None:
698
+ return False, no_update, no_update
699
+
700
+ pt = hoverData["points"][0]
701
+ bbox = pt["bbox"]
702
+
703
+ # get row of the history
704
+ trial = pt['customdata'][0]
705
+ row = self.monitor.local_df[self.monitor.local_df['trial'] == trial]
706
+
707
+ # === create hovered data ===
708
+ # get encoded image from history.additional_metadata
709
+ img_url = None
710
+
711
+ # Femtet specified processing
712
+ metadata = self.history.metadata
713
+ if metadata[0] != '':
714
+ # get img path
715
+ d = json.loads(metadata[0])
716
+ femprj_path = d['femprj_path']
717
+ model_name = d['model_name']
718
+ femprj_result_dir = femprj_path.replace('.femprj', '.Results')
719
+ img_path = os.path.join(femprj_result_dir, f'{model_name}_trial{trial}.jpg')
720
+ if os.path.exists(img_path):
721
+ # create encoded image
722
+ with open(img_path, 'rb') as f:
723
+ content = f.read()
724
+ encoded_image = base64.b64encode(content).decode('utf-8')
725
+ img_url = 'data:image/jpeg;base64, ' + encoded_image
726
+ html_img = html.Img(src=img_url, style={"width": "200px"}) if img_url is not None else html.Div()
727
+
728
+ # parameters
729
+ pd.options.display.float_format = '{:.4e}'.format
730
+ parameters = row.iloc[:, np.where(np.array(metadata) == 'prm')[0]]
731
+ names = parameters.columns
732
+ values = [f'{value:.3e}' for value in parameters.values.ravel()]
733
+ data = pd.DataFrame(dict(
734
+ name=names, value=values
735
+ ))
736
+
737
+ # descript result
738
+ desc = html.Div([
739
+ html.H3(f"trial{trial}", style={"color": "darkblue"}),
740
+ dash_table.DataTable(
741
+ columns=[{'name': col, 'id': col} for col in data.columns],
742
+ data=data.to_dict('records')
743
+ ),
744
+ ])
745
+
746
+ # make output
747
+ children = html.Div([
748
+ html.Div(html_img, style={'display': 'inline-block', 'margin-right': '10px', 'vertical-align': 'top'}),
749
+ html.Div(desc, style={'display': 'inline-block', 'margin-right': '10px'})
750
+ ])
751
+
752
+ return True, bbox, children
753
+
754
+
681
755
  def get_fig_by_tab_id(self, tab_id):
682
756
  if tab_id in self.graphs.keys():
683
757
  fig_func = self.graphs[tab_id]['fig_func']
@@ -712,8 +786,8 @@ class ResultViewerAppHomePage(HomePageBase):
712
786
  '---\n'
713
787
  '- 最適化の結果分析画面です。\n'
714
788
  '- 凡例をクリックすると、対応する要素の表示/非表示を切り替えます。\n'
715
- '- ブラウザを使用しますが、ネットワーク通信は行いません。\n'
716
- '- ブラウザを閉じてもプログラムは終了しません。'
789
+ '- ブラウザを使用しますが、解析結果のインターネット通信は行いません。\n'
790
+ '- ブラウザを閉じてもプログラムは終了しません。\n'
717
791
  ' - コマンドプロンプトを閉じるかコマンドプロンプトに `CTRL+C` を入力してプログラムを終了してください。\n'
718
792
  )
719
793
  self.contents.children = [
@@ -753,9 +827,10 @@ class ProcessMonitorAppHomePage(HomePageBase):
753
827
  note = dcc.Markdown(
754
828
  '---\n'
755
829
  '- 最適化の結果分析画面です。\n'
756
- '- ブラウザを使用しますが、ネットワーク通信は行いません。\n'
830
+ '- ブラウザを使用しますが、解析結果のインターネット通信は行いません。\n'
757
831
  '- この画面を閉じても最適化は中断されません。\n'
758
832
  f'- この画面を再び開くにはブラウザのアドレスバーに「localhost:{self.monitor.DEFAULT_PORT}」と入力して下さい。\n'
833
+ '- __マウスオーバーで表示されるデータを見る場合は、「自動更新を一時停止する」ボタンを押してください。__\n'
759
834
  )
760
835
 
761
836
  self.contents.children = [
@@ -832,7 +907,10 @@ class ProcessMonitorAppHomePage(HomePageBase):
832
907
  # 1. interval => figure を更新する
833
908
  if (len(self.monitor.local_df) > 0) and (active_tab_id is not None):
834
909
  fig = self.get_fig_by_tab_id(active_tab_id)
835
- ret[card_body] = dcc.Graph(figure=fig, id=self.ID_GRAPH) # list にせんとダメかも
910
+ ret[card_body] = [
911
+ dcc.Graph(figure=fig, id=self.ID_GRAPH, clear_on_unhover=True),
912
+ dcc.Tooltip(id=self.ID_GRAPH_TOOLTIP),
913
+ ]
836
914
 
837
915
  # 3. btn toggle => (toggle の children を切替) and (interval を切替)
838
916
  if toggle_n_clicks % 2 == 1:
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "pyfemtet"
3
- version = "0.4.7" # ignored by versioning plugin
3
+ version = "0.4.8" # ignored by versioning plugin
4
4
  description = "Design parameter optimization using Femtet."
5
5
  authors = ["kazuma.naito <kazuma.naito@murata.com>"]
6
6
  readme = "README.md"
@@ -1 +0,0 @@
1
- __version__ = "0.4.7"
File without changes
File without changes
File without changes
File without changes