pyfemtet 0.4.10__py3-none-any.whl → 0.4.11__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/__init__.py +1 -1
- pyfemtet/opt/_femopt.py +9 -3
- pyfemtet/opt/interface/_femtet.py +32 -10
- pyfemtet/opt/interface/_femtet_parametric.py +5 -25
- pyfemtet/opt/opt/_base.py +9 -1
- pyfemtet/opt/opt/_optuna.py +4 -0
- pyfemtet/opt/visualization/__init__.py +0 -7
- pyfemtet/opt/visualization/_create_wrapped_components.py +93 -0
- pyfemtet/opt/visualization/base.py +254 -0
- pyfemtet/opt/visualization/complex_components/__init__.py +0 -0
- pyfemtet/opt/visualization/complex_components/alert_region.py +71 -0
- pyfemtet/opt/visualization/complex_components/control_femtet.py +195 -0
- pyfemtet/opt/visualization/{_graphs.py → complex_components/main_figure_creator.py} +13 -49
- pyfemtet/opt/visualization/complex_components/main_graph.py +263 -0
- pyfemtet/opt/visualization/process_monitor/__init__.py +0 -0
- pyfemtet/opt/visualization/process_monitor/application.py +201 -0
- pyfemtet/opt/visualization/process_monitor/pages.py +276 -0
- pyfemtet/opt/visualization/result_viewer/.gitignore +1 -0
- pyfemtet/opt/visualization/result_viewer/__init__.py +0 -0
- pyfemtet/opt/visualization/result_viewer/application.py +44 -0
- pyfemtet/opt/visualization/result_viewer/pages.py +692 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/tutorial.csv +18 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14.jpg +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14.log +81 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14.pdt +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_heatflow.csv +28 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_heatflow_el.csv +22 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial1.jpg +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial1.pdt +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial10.jpg +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial10.pdt +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial11.jpg +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial11.pdt +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial12.jpg +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial12.pdt +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial13.jpg +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial13.pdt +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial14.jpg +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial14.pdt +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial15.jpg +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial15.pdt +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial2.jpg +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial2.pdt +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial3.jpg +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial3.pdt +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial4.jpg +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial4.pdt +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial5.jpg +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial5.pdt +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial6.jpg +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial6.pdt +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial7.jpg +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial7.pdt +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial8.jpg +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial8.pdt +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial9.jpg +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial9.pdt +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.femprj +0 -0
- pyfemtet/opt/visualization/wrapped_components/__init__.py +0 -0
- pyfemtet/opt/visualization/wrapped_components/dbc.py +1518 -0
- pyfemtet/opt/visualization/wrapped_components/dcc.py +609 -0
- pyfemtet/opt/visualization/wrapped_components/html.py +3570 -0
- pyfemtet/opt/visualization/wrapped_components/str_enum.py +43 -0
- {pyfemtet-0.4.10.dist-info → pyfemtet-0.4.11.dist-info}/METADATA +1 -1
- pyfemtet-0.4.11.dist-info/RECORD +136 -0
- {pyfemtet-0.4.10.dist-info → pyfemtet-0.4.11.dist-info}/entry_points.txt +1 -1
- pyfemtet/opt/visualization/_monitor.py +0 -1227
- pyfemtet/opt/visualization/result_viewer.py +0 -13
- pyfemtet-0.4.10.dist-info/RECORD +0 -83
- {pyfemtet-0.4.10.dist-info → pyfemtet-0.4.11.dist-info}/LICENSE +0 -0
- {pyfemtet-0.4.10.dist-info → pyfemtet-0.4.11.dist-info}/WHEEL +0 -0
pyfemtet/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.4.
|
|
1
|
+
__version__ = "0.4.11"
|
pyfemtet/opt/_femopt.py
CHANGED
|
@@ -13,7 +13,7 @@ from dask.distributed import LocalCluster, Client
|
|
|
13
13
|
# pyfemtet relative
|
|
14
14
|
from pyfemtet.opt.interface import FEMInterface, FemtetInterface
|
|
15
15
|
from pyfemtet.opt.opt import AbstractOptimizer, OptunaOptimizer
|
|
16
|
-
from pyfemtet.opt.visualization.
|
|
16
|
+
from pyfemtet.opt.visualization.process_monitor.application import main as process_monitor_main
|
|
17
17
|
from pyfemtet.opt._femopt_core import (
|
|
18
18
|
_check_bound,
|
|
19
19
|
_is_access_gogh,
|
|
@@ -574,6 +574,12 @@ def _start_monitor_server(
|
|
|
574
574
|
host=None,
|
|
575
575
|
port=None,
|
|
576
576
|
):
|
|
577
|
-
|
|
578
|
-
|
|
577
|
+
process_monitor_main(
|
|
578
|
+
history,
|
|
579
|
+
status,
|
|
580
|
+
worker_addresses,
|
|
581
|
+
worker_status_list,
|
|
582
|
+
host,
|
|
583
|
+
port,
|
|
584
|
+
)
|
|
579
585
|
return 'Exit monitor server process gracefully'
|
|
@@ -3,15 +3,19 @@ from typing import Optional, List
|
|
|
3
3
|
import os
|
|
4
4
|
import sys
|
|
5
5
|
from time import sleep, time
|
|
6
|
-
import signal
|
|
7
6
|
|
|
8
7
|
import pandas as pd
|
|
9
8
|
import psutil
|
|
10
9
|
from dask.distributed import get_worker
|
|
11
10
|
|
|
12
|
-
|
|
11
|
+
# noinspection PyUnresolvedReferences
|
|
12
|
+
from pywintypes import com_error, error
|
|
13
|
+
# noinspection PyUnresolvedReferences
|
|
13
14
|
from pythoncom import CoInitialize, CoUninitialize
|
|
15
|
+
# noinspection PyUnresolvedReferences
|
|
14
16
|
from win32com.client import constants
|
|
17
|
+
import win32con
|
|
18
|
+
import win32gui
|
|
15
19
|
from femtetutils import util
|
|
16
20
|
|
|
17
21
|
from pyfemtet.core import (
|
|
@@ -31,6 +35,10 @@ from pyfemtet.dispatch_extensions import (
|
|
|
31
35
|
from pyfemtet.opt.interface import FEMInterface, logger
|
|
32
36
|
|
|
33
37
|
|
|
38
|
+
def post_activate_message(hwnd):
|
|
39
|
+
win32gui.PostMessage(hwnd, win32con.WM_ACTIVATE, win32con.WA_ACTIVE, 0)
|
|
40
|
+
|
|
41
|
+
|
|
34
42
|
class FemtetInterface(FEMInterface):
|
|
35
43
|
"""Concrete class for the interface with Femtet.
|
|
36
44
|
|
|
@@ -113,7 +121,7 @@ class FemtetInterface(FEMInterface):
|
|
|
113
121
|
if self.original_femprj_path is None:
|
|
114
122
|
# dask worker でなければ original のはず
|
|
115
123
|
try:
|
|
116
|
-
|
|
124
|
+
_ = get_worker()
|
|
117
125
|
except ValueError:
|
|
118
126
|
self.original_femprj_path = self.femprj_path
|
|
119
127
|
|
|
@@ -252,7 +260,7 @@ class FemtetInterface(FEMInterface):
|
|
|
252
260
|
|
|
253
261
|
"""
|
|
254
262
|
|
|
255
|
-
# FIXME: Gaudi へのアクセスなど、self.Femtet.Gaudi.
|
|
263
|
+
# FIXME: Gaudi へのアクセスなど、self.Femtet.Gaudi.SomeFunc() のような場合、この関数を呼び出す前に Gaudi へのアクセスの時点で com_error が起こる
|
|
256
264
|
# FIXME: => 文字列で渡して eval() すればよい。
|
|
257
265
|
|
|
258
266
|
if args is None:
|
|
@@ -282,9 +290,16 @@ class FemtetInterface(FEMInterface):
|
|
|
282
290
|
|
|
283
291
|
# API を実行
|
|
284
292
|
try:
|
|
285
|
-
|
|
286
|
-
|
|
293
|
+
# 解析結果を開いた状態で Gaudi.Activate して ReExecute する場合、ReExecute の前後にアクティブ化イベントが必要
|
|
294
|
+
if fun.__name__ == 'ReExecute':
|
|
295
|
+
post_activate_message(self.Femtet.hWnd) # can raise pywintypes.error
|
|
296
|
+
returns = fun(*args, **kwargs)
|
|
297
|
+
post_activate_message(self.Femtet.hWnd)
|
|
298
|
+
else:
|
|
299
|
+
returns = fun(*args, **kwargs)
|
|
300
|
+
except (com_error, error):
|
|
287
301
|
# パターン 2 エラーが生じたことは確定なのでエラーが起こるよう returns を作る
|
|
302
|
+
# com_error ではなく error の場合はおそらく Femtet が落ちている
|
|
288
303
|
if ret_for_check_idx is None:
|
|
289
304
|
returns = return_value_if_failed
|
|
290
305
|
else:
|
|
@@ -665,6 +680,13 @@ class FemtetInterface(FEMInterface):
|
|
|
665
680
|
)
|
|
666
681
|
return out
|
|
667
682
|
|
|
683
|
+
@staticmethod
|
|
684
|
+
def create_pdt_path(femprj_path, model_name, trial):
|
|
685
|
+
result_dir = femprj_path.replace('.femprj', '.Results')
|
|
686
|
+
pdt_path = os.path.join(result_dir, model_name + f'_trial{trial}.pdt')
|
|
687
|
+
return pdt_path
|
|
688
|
+
|
|
689
|
+
# noinspection PyMethodOverriding
|
|
668
690
|
@staticmethod
|
|
669
691
|
def postprocess_func(
|
|
670
692
|
trial: int,
|
|
@@ -676,7 +698,7 @@ class FemtetInterface(FEMInterface):
|
|
|
676
698
|
):
|
|
677
699
|
result_dir = original_femprj_path.replace('.femprj', '.Results')
|
|
678
700
|
if pdt_file_content is not None:
|
|
679
|
-
pdt_path =
|
|
701
|
+
pdt_path = FemtetInterface.create_pdt_path(original_femprj_path, model_name, trial)
|
|
680
702
|
with open(pdt_path, 'wb') as f:
|
|
681
703
|
f.write(pdt_file_content)
|
|
682
704
|
|
|
@@ -710,13 +732,12 @@ class FemtetInterface(FEMInterface):
|
|
|
710
732
|
jpg_path = os.path.join(result_dir, self.model_name + '.jpg')
|
|
711
733
|
|
|
712
734
|
# モデル表示画面の設定
|
|
713
|
-
self.Femtet.SetWindowSize(
|
|
735
|
+
self.Femtet.SetWindowSize(600, 600)
|
|
714
736
|
self.Femtet.Fit()
|
|
715
|
-
self.Femtet.ViewNumeric.SetCoord(1, 1, 1)
|
|
716
737
|
|
|
717
738
|
# ---モデルの画面を保存---
|
|
718
739
|
self.Femtet.Redraw() # 再描画
|
|
719
|
-
succeed = self.Femtet.SavePicture(jpg_path,
|
|
740
|
+
succeed = self.Femtet.SavePicture(jpg_path, 600, 600, 80)
|
|
720
741
|
|
|
721
742
|
self.Femtet.RedrawMode = True # 逐一の描画をオン
|
|
722
743
|
|
|
@@ -745,6 +766,7 @@ class _UnPicklableNoFEM(FemtetInterface):
|
|
|
745
766
|
Femtet = None
|
|
746
767
|
quit_when_destruct = False
|
|
747
768
|
|
|
769
|
+
# noinspection PyMissingConstructor
|
|
748
770
|
def __init__(self):
|
|
749
771
|
CoInitialize()
|
|
750
772
|
self.unpicklable_member = Dispatch('FemtetMacro.Femtet')
|
|
@@ -1,36 +1,15 @@
|
|
|
1
|
+
from femtetutils import util
|
|
1
2
|
from pyfemtet.dispatch_extensions import _get_pid
|
|
2
3
|
|
|
3
|
-
import winreg
|
|
4
4
|
import ctypes
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
def _get_dll(
|
|
8
|
-
|
|
9
|
-
dll_path =
|
|
7
|
+
def _get_dll():
|
|
8
|
+
femtet_exe_path = util.get_femtet_exe_path()
|
|
9
|
+
dll_path = femtet_exe_path.replace('Femtet.exe', 'ParametricIF.dll')
|
|
10
10
|
return ctypes.cdll.LoadLibrary(dll_path)
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
def _get_femtet_major_version(Femtet):
|
|
14
|
-
from pyfemtet.core import _version
|
|
15
|
-
version_int8 = _version(Femtet=Femtet)
|
|
16
|
-
return str(version_int8)[0:4]
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
def _get_parametric_dll_path(femtet_major_version) -> str:
|
|
20
|
-
key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, r'SOFTWARE\FemtetInfo\InstallVersion\x64')
|
|
21
|
-
_, nValues, _ = winreg.QueryInfoKey(key)
|
|
22
|
-
for i in range(nValues):
|
|
23
|
-
name, data, _ = winreg.EnumValue(key, i)
|
|
24
|
-
if name == str(femtet_major_version):
|
|
25
|
-
winreg.CloseKey(key)
|
|
26
|
-
import os
|
|
27
|
-
dll_path = os.path.join(data, 'Program', 'ParametricIF.dll')
|
|
28
|
-
return dll_path
|
|
29
|
-
# ここまで来ていたら失敗
|
|
30
|
-
winreg.CloseKey(key)
|
|
31
|
-
raise RuntimeError('パラメトリック解析機能へのアクセスに失敗しました')
|
|
32
|
-
|
|
33
|
-
|
|
34
13
|
def _get_dll_with_set_femtet(Femtet):
|
|
35
14
|
dll = _get_dll(Femtet)
|
|
36
15
|
pid = _get_pid(Femtet.hWnd)
|
|
@@ -40,6 +19,7 @@ def _get_dll_with_set_femtet(Femtet):
|
|
|
40
19
|
|
|
41
20
|
|
|
42
21
|
def _get_prm_result_names(Femtet):
|
|
22
|
+
"""Used by pyfemtet-opt-gui"""
|
|
43
23
|
out = []
|
|
44
24
|
|
|
45
25
|
# load dll and set target femtet
|
pyfemtet/opt/opt/_base.py
CHANGED
|
@@ -65,10 +65,15 @@ class AbstractOptimizer(ABC):
|
|
|
65
65
|
|
|
66
66
|
# x の更新
|
|
67
67
|
self.parameters['value'] = x
|
|
68
|
+
logger.info(f'Start calculation with input: {x}')
|
|
68
69
|
|
|
69
70
|
# FEM の更新
|
|
70
71
|
logger.debug('fem.update() start')
|
|
71
|
-
|
|
72
|
+
try:
|
|
73
|
+
self.fem.update(self.parameters)
|
|
74
|
+
except Exception as e:
|
|
75
|
+
logger.warning('An exception has occurred during FEM update.')
|
|
76
|
+
raise e
|
|
72
77
|
|
|
73
78
|
# y, _y, c の更新
|
|
74
79
|
logger.debug('calculate y start')
|
|
@@ -93,6 +98,9 @@ class AbstractOptimizer(ABC):
|
|
|
93
98
|
)
|
|
94
99
|
|
|
95
100
|
logger.debug('history.record end')
|
|
101
|
+
|
|
102
|
+
logger.info(f'End calculation with output: {_y}')
|
|
103
|
+
|
|
96
104
|
return np.array(y), np.array(_y), np.array(c)
|
|
97
105
|
|
|
98
106
|
def _reconstruct_fem(self, skip_reconstruct=False):
|
pyfemtet/opt/opt/_optuna.py
CHANGED
|
@@ -18,6 +18,10 @@ from pyfemtet.core import MeshError, ModelError, SolveError
|
|
|
18
18
|
# filter warnings
|
|
19
19
|
import warnings
|
|
20
20
|
from optuna.exceptions import ExperimentalWarning
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
optuna.logging.set_verbosity(optuna.logging.ERROR)
|
|
24
|
+
|
|
21
25
|
warnings.filterwarnings('ignore', category=ExperimentalWarning)
|
|
22
26
|
|
|
23
27
|
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""Create autocompletable components"""
|
|
2
|
+
import os
|
|
3
|
+
import inspect
|
|
4
|
+
|
|
5
|
+
from dash.development.base_component import Component
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
# noinspection PyUnresolvedReferences
|
|
9
|
+
from dash import html, dcc, dash_table
|
|
10
|
+
# noinspection PyUnresolvedReferences
|
|
11
|
+
import dash_bootstrap_components as dbc
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
here, me = os.path.split(__file__)
|
|
15
|
+
COMPONENT_FILE_DIR = os.path.join(here, 'wrapped_components')
|
|
16
|
+
indent = ' '
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def create(module_name: str) -> str:
|
|
20
|
+
header = '''# auto created module
|
|
21
|
+
from pyfemtet.opt.visualization.wrapped_components.str_enum import StrEnum
|
|
22
|
+
# from enum import StrEnum
|
|
23
|
+
import dash
|
|
24
|
+
import dash_bootstrap_components
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
'''
|
|
28
|
+
path = os.path.join(COMPONENT_FILE_DIR, module_name.replace('.py', '') + '.py')
|
|
29
|
+
with open(path, 'w', newline='\n') as f:
|
|
30
|
+
f.write(header)
|
|
31
|
+
return path
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def append(html_class, module_path: str):
|
|
35
|
+
print('==========')
|
|
36
|
+
print(html_class)
|
|
37
|
+
|
|
38
|
+
# get property names
|
|
39
|
+
init_signature = inspect.signature(html_class.__init__)
|
|
40
|
+
props = [param.name for param in init_signature.parameters.values() if (param.name != 'self') and (param.name != 'args') and (param.name != 'kwargs')]
|
|
41
|
+
|
|
42
|
+
# create class definition
|
|
43
|
+
class_definition = f'class {html_class.__name__}({html_class.__module__}):\n' # library specific
|
|
44
|
+
|
|
45
|
+
# create id property
|
|
46
|
+
class_definition += indent + 'def _dummy(self):\n'
|
|
47
|
+
class_definition += indent*2 + '# noinspection PyAttributeOutsideInit\n'
|
|
48
|
+
class_definition += indent*2 + 'self.id = None\n\n'
|
|
49
|
+
|
|
50
|
+
# create Prop attribute
|
|
51
|
+
class_definition += indent + 'class Prop(StrEnum):\n'
|
|
52
|
+
for available_property in props:
|
|
53
|
+
property_definition = f'{available_property} = "{available_property}"'
|
|
54
|
+
try:
|
|
55
|
+
exec(property_definition)
|
|
56
|
+
except (SyntaxError, TypeError):
|
|
57
|
+
continue
|
|
58
|
+
class_definition += indent*2 + f'{property_definition}\n'
|
|
59
|
+
|
|
60
|
+
if class_definition.endswith(':\n'):
|
|
61
|
+
return
|
|
62
|
+
|
|
63
|
+
class_definition += '\n\n'
|
|
64
|
+
|
|
65
|
+
with open(module_path, 'a', newline='\n') as f:
|
|
66
|
+
f.write(class_definition)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def sub(module_name):
|
|
70
|
+
# glob component classes
|
|
71
|
+
module_path = create(module_name)
|
|
72
|
+
class_names = dir(eval(module_name))
|
|
73
|
+
for class_name in class_names:
|
|
74
|
+
cls = eval(f'{module_name}.{class_name}')
|
|
75
|
+
if not inspect.isclass(cls):
|
|
76
|
+
continue
|
|
77
|
+
if issubclass(cls, Component):
|
|
78
|
+
append(cls, module_path)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def main():
|
|
82
|
+
# html
|
|
83
|
+
sub('html')
|
|
84
|
+
|
|
85
|
+
# dcc
|
|
86
|
+
sub('dcc')
|
|
87
|
+
|
|
88
|
+
# dbc
|
|
89
|
+
sub('dbc')
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
if __name__ == '__main__':
|
|
93
|
+
main()
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
# type hint
|
|
2
|
+
from dash.development.base_component import Component
|
|
3
|
+
|
|
4
|
+
# application
|
|
5
|
+
from dash import Dash
|
|
6
|
+
import webbrowser
|
|
7
|
+
|
|
8
|
+
# callback
|
|
9
|
+
from dash import Output, Input # , State, no_update, callback_context
|
|
10
|
+
# from dash.exceptions import PreventUpdate
|
|
11
|
+
|
|
12
|
+
# components
|
|
13
|
+
# from dash import html, dcc
|
|
14
|
+
import dash_bootstrap_components
|
|
15
|
+
|
|
16
|
+
from pyfemtet.opt._femopt_core import History
|
|
17
|
+
from pyfemtet.opt.visualization.wrapped_components import html, dcc, dbc
|
|
18
|
+
|
|
19
|
+
# the others
|
|
20
|
+
from abc import ABC, abstractmethod
|
|
21
|
+
import logging
|
|
22
|
+
import psutil
|
|
23
|
+
from pyfemtet.logger import get_logger
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
dash_logger = logging.getLogger('werkzeug')
|
|
27
|
+
dash_logger.setLevel(logging.ERROR)
|
|
28
|
+
|
|
29
|
+
logger = get_logger('viewer')
|
|
30
|
+
logger.setLevel(logging.ERROR)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class AbstractPage(ABC):
|
|
34
|
+
"""Define content."""
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
=================
|
|
38
|
+
|~:8080/rel_url | <---- page.rel_url
|
|
39
|
+
=================
|
|
40
|
+
page. | | -------- |
|
|
41
|
+
title -->home| | | |
|
|
42
|
+
| | | <-------- sub_page.layout
|
|
43
|
+
| | -------- |
|
|
44
|
+
| ^ | |<--- page.layout
|
|
45
|
+
==|==============
|
|
46
|
+
|
|
|
47
|
+
sidebar
|
|
48
|
+
|
|
49
|
+
"""
|
|
50
|
+
def __init__(self, title='base-page', rel_url='/', application=None):
|
|
51
|
+
self.layout: Component = None
|
|
52
|
+
self.rel_url = rel_url
|
|
53
|
+
self.title = title
|
|
54
|
+
self.application: PyFemtetApplicationBase = application
|
|
55
|
+
self.subpages = []
|
|
56
|
+
self.setup_component()
|
|
57
|
+
self.setup_layout()
|
|
58
|
+
|
|
59
|
+
def add_subpage(self, subpage: 'AbstractPage'):
|
|
60
|
+
subpage.setup_component()
|
|
61
|
+
subpage.setup_layout()
|
|
62
|
+
self.subpages.append(subpage)
|
|
63
|
+
|
|
64
|
+
def set_application(self, app):
|
|
65
|
+
self.application = app
|
|
66
|
+
|
|
67
|
+
@abstractmethod
|
|
68
|
+
def setup_component(self):
|
|
69
|
+
# noinspection PyAttributeOutsideInit
|
|
70
|
+
self._component = html.Div('This is a abstract page.')
|
|
71
|
+
|
|
72
|
+
@abstractmethod
|
|
73
|
+
def setup_layout(self):
|
|
74
|
+
self.layout = self._component
|
|
75
|
+
|
|
76
|
+
def setup_callback(self):
|
|
77
|
+
# app = self.application.app
|
|
78
|
+
|
|
79
|
+
for subpage in self.subpages:
|
|
80
|
+
subpage.set_application(self.application)
|
|
81
|
+
subpage.setup_callback()
|
|
82
|
+
|
|
83
|
+
# @app.callback(...)
|
|
84
|
+
# def do_something():
|
|
85
|
+
# return ...
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _unused_port_number(start=49152):
|
|
89
|
+
# "LISTEN" 状態のポート番号をリスト化
|
|
90
|
+
used_ports = [conn.laddr.port for conn in psutil.net_connections() if conn.status == 'LISTEN']
|
|
91
|
+
port = start
|
|
92
|
+
for port in range(start, 65535 + 1):
|
|
93
|
+
# 未使用のポート番号ならreturn
|
|
94
|
+
if port not in set(used_ports):
|
|
95
|
+
break
|
|
96
|
+
if port != start:
|
|
97
|
+
logger.warning(f'Specified port "{start}" seems to be used. Port "{port}" is used instead.')
|
|
98
|
+
return port
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class SidebarApplicationBase:
|
|
102
|
+
""""""
|
|
103
|
+
""" Define entire layout and callback.
|
|
104
|
+
+------+--------+
|
|
105
|
+
| side | con- |
|
|
106
|
+
| bar | tent |
|
|
107
|
+
+------+--------+
|
|
108
|
+
│ └─ pages (dict(href: str = layout: Component))
|
|
109
|
+
└──────── nav_links (dict(order: float) = NavLink)
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
# port
|
|
113
|
+
DEFAULT_PORT = 49152
|
|
114
|
+
|
|
115
|
+
# members for sidebar application
|
|
116
|
+
SIDEBAR_STYLE = {
|
|
117
|
+
"position": "fixed",
|
|
118
|
+
"top": 0,
|
|
119
|
+
"left": 0,
|
|
120
|
+
"bottom": 0,
|
|
121
|
+
"width": "16rem",
|
|
122
|
+
"padding": "2rem 1rem",
|
|
123
|
+
"background-color": "#f8f9fa",
|
|
124
|
+
}
|
|
125
|
+
CONTENT_STYLE = {
|
|
126
|
+
"margin-left": "18rem",
|
|
127
|
+
"margin-right": "2rem",
|
|
128
|
+
"padding": "2rem 1rem",
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
def __init__(self, title=None, subtitle=None):
|
|
132
|
+
# define app
|
|
133
|
+
self.title = title if title is not None else 'App'
|
|
134
|
+
self.subtitle = subtitle if title is not None else ''
|
|
135
|
+
self.app = Dash(
|
|
136
|
+
__name__,
|
|
137
|
+
external_stylesheets=[dash_bootstrap_components.themes.BOOTSTRAP],
|
|
138
|
+
title=title,
|
|
139
|
+
update_title=None,
|
|
140
|
+
)
|
|
141
|
+
self.pages = dict()
|
|
142
|
+
self.nav_links = dict()
|
|
143
|
+
self.page_objects = []
|
|
144
|
+
|
|
145
|
+
def add_page(self, page: AbstractPage, order: int = None):
|
|
146
|
+
page.set_application(self)
|
|
147
|
+
self.page_objects.append(page)
|
|
148
|
+
self.pages[page.rel_url] = page.layout
|
|
149
|
+
if order is None:
|
|
150
|
+
order = len(self.pages)
|
|
151
|
+
self.nav_links[order] = dbc.NavLink(page.title, href=page.rel_url, active="exact")
|
|
152
|
+
|
|
153
|
+
def setup_callback(self):
|
|
154
|
+
for page in self.page_objects:
|
|
155
|
+
page.setup_callback()
|
|
156
|
+
|
|
157
|
+
def _setup_layout(self):
|
|
158
|
+
# setup sidebar
|
|
159
|
+
# https://dash-bootstrap-components.opensource.faculty.ai/examples/simple-sidebar/
|
|
160
|
+
|
|
161
|
+
# sidebar に表示される順に並び替え
|
|
162
|
+
ordered_items = sorted(self.nav_links.items(), key=lambda x: x[0])
|
|
163
|
+
ordered_links = [value for key, value in ordered_items]
|
|
164
|
+
|
|
165
|
+
# sidebar と contents から app 全体の layout を作成
|
|
166
|
+
sidebar = html.Div(
|
|
167
|
+
[
|
|
168
|
+
html.H2(self.title, className='display-4'),
|
|
169
|
+
html.Hr(),
|
|
170
|
+
html.P(self.subtitle, className='lead'),
|
|
171
|
+
dbc.Nav(ordered_links, vertical=True, pills=True),
|
|
172
|
+
],
|
|
173
|
+
style=self.SIDEBAR_STYLE,
|
|
174
|
+
)
|
|
175
|
+
content = html.Div(id="page-content", style=self.CONTENT_STYLE)
|
|
176
|
+
self.app.layout = html.Div([dcc.Location(id="url"), sidebar, content])
|
|
177
|
+
|
|
178
|
+
# sidebar によるページ遷移のための callback
|
|
179
|
+
@self.app.callback(Output(content.id, "children"), [Input("url", "pathname")])
|
|
180
|
+
def switch_page_content(pathname):
|
|
181
|
+
if pathname in list(self.pages.keys()):
|
|
182
|
+
return self.pages[pathname]
|
|
183
|
+
|
|
184
|
+
else:
|
|
185
|
+
return html.Div(
|
|
186
|
+
[
|
|
187
|
+
html.H1("404: Not found", className="text-danger"),
|
|
188
|
+
html.Hr(),
|
|
189
|
+
html.P(f"The pathname {pathname} was not recognised..."),
|
|
190
|
+
],
|
|
191
|
+
className="p-3 bg-light rounded-3",
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
def run(self, host='localhost', port=None, debug=False):
|
|
195
|
+
self._setup_layout()
|
|
196
|
+
port = port or self.DEFAULT_PORT
|
|
197
|
+
# port を検証
|
|
198
|
+
port = _unused_port_number(port)
|
|
199
|
+
# ブラウザを起動
|
|
200
|
+
if host == '0.0.0.0':
|
|
201
|
+
webbrowser.open(f'http://localhost:{str(port)}')
|
|
202
|
+
else:
|
|
203
|
+
webbrowser.open(f'http://{host}:{str(port)}')
|
|
204
|
+
self.app.run(debug=debug, host=host, port=port)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
class PyFemtetApplicationBase(SidebarApplicationBase):
|
|
208
|
+
""""""
|
|
209
|
+
"""
|
|
210
|
+
+------+--------+
|
|
211
|
+
| side | con- |
|
|
212
|
+
| bar | tent |
|
|
213
|
+
+--^---+--^-----+
|
|
214
|
+
│ └─ pages (dict(href: str = layout: Component))
|
|
215
|
+
└──────── nav_links (dict(order: float) = NavLink)
|
|
216
|
+
|
|
217
|
+
Accessible members:
|
|
218
|
+
- history: History
|
|
219
|
+
└ local_df: pd.DataFrame
|
|
220
|
+
- app: Dash
|
|
221
|
+
|
|
222
|
+
"""
|
|
223
|
+
|
|
224
|
+
def __init__(
|
|
225
|
+
self,
|
|
226
|
+
title=None,
|
|
227
|
+
subtitle=None,
|
|
228
|
+
history: History = None,
|
|
229
|
+
):
|
|
230
|
+
# register arguments
|
|
231
|
+
self.history = history # include actor
|
|
232
|
+
super().__init__(title, subtitle)
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def check_page_layout(page_cls: type):
|
|
236
|
+
home_page = page_cls() # required
|
|
237
|
+
application = PyFemtetApplicationBase(title='test-app')
|
|
238
|
+
application.add_page(home_page, 0)
|
|
239
|
+
application.run(debug=True)
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
if __name__ == '__main__':
|
|
243
|
+
|
|
244
|
+
# template
|
|
245
|
+
g_home_page = AbstractPage('home-page') # required
|
|
246
|
+
g_page1 = AbstractPage('page-1', '/page-1')
|
|
247
|
+
|
|
248
|
+
g_application = SidebarApplicationBase(title='test-app')
|
|
249
|
+
g_application.add_page(g_home_page, 0)
|
|
250
|
+
g_application.add_page(g_page1, 1)
|
|
251
|
+
|
|
252
|
+
# g_application.setup_callback()
|
|
253
|
+
|
|
254
|
+
g_application.run(debug=True)
|
|
File without changes
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# type hint
|
|
2
|
+
from typing import List
|
|
3
|
+
from dash.development.base_component import Component
|
|
4
|
+
|
|
5
|
+
# callback
|
|
6
|
+
from dash import Output, Input, State, no_update, callback_context
|
|
7
|
+
from dash.exceptions import PreventUpdate
|
|
8
|
+
|
|
9
|
+
# components
|
|
10
|
+
from pyfemtet.opt.visualization.wrapped_components import html, dcc, dbc
|
|
11
|
+
|
|
12
|
+
from pyfemtet.opt.visualization.base import AbstractPage, logger
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class AlertRegion(AbstractPage):
|
|
16
|
+
|
|
17
|
+
def setup_component(self):
|
|
18
|
+
# alert
|
|
19
|
+
# noinspection PyAttributeOutsideInit
|
|
20
|
+
self.alert_region = dbc.CardBody(children=[], id='alerts-card-body')
|
|
21
|
+
|
|
22
|
+
# clear alert
|
|
23
|
+
# noinspection PyAttributeOutsideInit
|
|
24
|
+
self.clear_alert_button = dbc.Button(
|
|
25
|
+
children='Clear messages',
|
|
26
|
+
id='clear-messages-button',
|
|
27
|
+
color='secondary',
|
|
28
|
+
outline=True,
|
|
29
|
+
className="position-relative",
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
def setup_layout(self):
|
|
33
|
+
self.layout = dbc.Card(
|
|
34
|
+
[
|
|
35
|
+
dbc.CardHeader(
|
|
36
|
+
children=self.clear_alert_button,
|
|
37
|
+
className='d-flex', # align right
|
|
38
|
+
style={'justify-content': 'end'}, # align right
|
|
39
|
+
),
|
|
40
|
+
self.alert_region,
|
|
41
|
+
]
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
def setup_callback(self):
|
|
45
|
+
app = self.application.app
|
|
46
|
+
|
|
47
|
+
# ===== clear alerts ==-==
|
|
48
|
+
@app.callback(
|
|
49
|
+
Output(self.alert_region.id, 'children', allow_duplicate=True),
|
|
50
|
+
Input(self.clear_alert_button.id, self.clear_alert_button.Prop.n_clicks),
|
|
51
|
+
prevent_initial_call=True, # required if allow_duplicate=True
|
|
52
|
+
)
|
|
53
|
+
def clear_alerts(_):
|
|
54
|
+
return []
|
|
55
|
+
|
|
56
|
+
def create_alerts(self, msg, color='secondary', current_alerts=None) -> List[Component]:
|
|
57
|
+
|
|
58
|
+
if current_alerts is None:
|
|
59
|
+
current_alerts = []
|
|
60
|
+
|
|
61
|
+
new_alert = dbc.Alert(
|
|
62
|
+
msg,
|
|
63
|
+
id=f'alert-{len(current_alerts) + 1}',
|
|
64
|
+
dismissable=True,
|
|
65
|
+
color=color,
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
new_alerts = [new_alert]
|
|
69
|
+
new_alerts.extend(current_alerts)
|
|
70
|
+
|
|
71
|
+
return new_alerts
|