pyvcad-rendering 1.0.0__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.
- pyvcad_rendering/__init__.py +2 -0
- pyvcad_rendering/compiler_input_ui/__init__.py +13 -0
- pyvcad_rendering/compiler_input_ui/color_inkjet_ui.py +9 -0
- pyvcad_rendering/compiler_input_ui/direct_material_inkjet_progress_ui.py +137 -0
- pyvcad_rendering/compiler_input_ui/direct_material_inkjet_ui.py +112 -0
- pyvcad_rendering/compiler_input_ui/finite_element_mesh_progress_ui.py +101 -0
- pyvcad_rendering/compiler_input_ui/finite_element_mesh_ui.py +230 -0
- pyvcad_rendering/compiler_input_ui/gcvf_inkjet_progress_ui.py +137 -0
- pyvcad_rendering/compiler_input_ui/gcvf_inkjet_ui.py +119 -0
- pyvcad_rendering/compiler_input_ui/meshes_progress_ui.py +140 -0
- pyvcad_rendering/compiler_input_ui/meshes_ui.py +83 -0
- pyvcad_rendering/compiler_input_ui/myerson_inkjet_progress_ui.py +118 -0
- pyvcad_rendering/compiler_input_ui/myerson_inkjet_ui.py +106 -0
- pyvcad_rendering/compiler_input_ui/vat_photo_progress_ui.py +116 -0
- pyvcad_rendering/compiler_input_ui/vat_photo_ui.py +156 -0
- pyvcad_rendering/export.py +14 -0
- pyvcad_rendering/export_frame.py +217 -0
- pyvcad_rendering/render.py +14 -0
- pyvcad_rendering/render_frame.py +408 -0
- pyvcad_rendering/vtk_pipeline_wx.py +595 -0
- pyvcad_rendering-1.0.0.dist-info/METADATA +249 -0
- pyvcad_rendering-1.0.0.dist-info/RECORD +24 -0
- pyvcad_rendering-1.0.0.dist-info/WHEEL +4 -0
- pyvcad_rendering-1.0.0.dist-info/licenses/LICENSE +185 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from .gcvf_inkjet_ui import GCVFInkjetPanel
|
|
2
|
+
from .gcvf_inkjet_progress_ui import GCVFInkjetProgressPanel
|
|
3
|
+
from .direct_material_inkjet_ui import DirectMaterialInkjetPanel
|
|
4
|
+
from .direct_material_inkjet_progress_ui import DirectMaterialInkjetProgressPanel
|
|
5
|
+
from .color_inkjet_ui import ColorInkjetPanel
|
|
6
|
+
from .myerson_inkjet_ui import MyersonInkjetPanel
|
|
7
|
+
from .myerson_inkjet_progress_ui import MyersonInkjetProgressPanel
|
|
8
|
+
from .meshes_ui import MeshesPanel
|
|
9
|
+
from .meshes_progress_ui import MeshesProgressPanel
|
|
10
|
+
from .finite_element_mesh_ui import FiniteElementMeshPanel
|
|
11
|
+
from .finite_element_mesh_progress_ui import FiniteElementMeshProgressPanel
|
|
12
|
+
from .vat_photo_ui import VatPhotoPanel
|
|
13
|
+
from .vat_photo_progress_ui import VatPhotoProgressPanel
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import wx
|
|
2
|
+
import wx.lib.newevent
|
|
3
|
+
import threading
|
|
4
|
+
import time
|
|
5
|
+
import os
|
|
6
|
+
import subprocess
|
|
7
|
+
import sys
|
|
8
|
+
import numpy as np
|
|
9
|
+
from wx.lib.newevent import NewEvent
|
|
10
|
+
import pyvcad as pv
|
|
11
|
+
import pyvcad_compilers as pvc
|
|
12
|
+
|
|
13
|
+
# Custom event for progress updates
|
|
14
|
+
UpdateProgressEvent, EVT_UPDATE_PROGRESS = NewEvent()
|
|
15
|
+
|
|
16
|
+
class DirectMaterialCompilerWorker(threading.Thread):
|
|
17
|
+
def __init__(self, parent, root, voxel_size, material_defs, output_directory, file_prefix, liquid_keepout, liquid_keepout_distance):
|
|
18
|
+
super().__init__()
|
|
19
|
+
self.parent = parent
|
|
20
|
+
self.root = root
|
|
21
|
+
self.voxel_size = voxel_size
|
|
22
|
+
self.material_defs = material_defs
|
|
23
|
+
self.output_directory = output_directory
|
|
24
|
+
self.file_prefix = file_prefix
|
|
25
|
+
self.liquid_keepout = liquid_keepout
|
|
26
|
+
self.liquid_keepout_distance = liquid_keepout_distance
|
|
27
|
+
self._running = True
|
|
28
|
+
|
|
29
|
+
def run(self):
|
|
30
|
+
try:
|
|
31
|
+
voxel_size_vec3 = pv.Vec3(self.voxel_size[0], self.voxel_size[1], self.voxel_size[2])
|
|
32
|
+
compiler = pvc.DirectMaterialCompiler(
|
|
33
|
+
root=self.root,
|
|
34
|
+
voxel_size=voxel_size_vec3,
|
|
35
|
+
material_defs=self.material_defs,
|
|
36
|
+
output_directory=self.output_directory,
|
|
37
|
+
file_prefix=self.file_prefix,
|
|
38
|
+
liquid_keep_out=self.liquid_keepout,
|
|
39
|
+
liquid_keep_out_distance=self.liquid_keepout_distance
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
def progress_callback(progress):
|
|
43
|
+
if not self._running:
|
|
44
|
+
# This is a simplistic way to stop; the compiler might not support interruption.
|
|
45
|
+
raise Exception("Export cancelled")
|
|
46
|
+
evt = UpdateProgressEvent(progress=int(progress * 100), error=None, finished=False)
|
|
47
|
+
wx.PostEvent(self.parent, evt)
|
|
48
|
+
|
|
49
|
+
compiler.setProgressCallback(progress_callback)
|
|
50
|
+
compiler.compile()
|
|
51
|
+
|
|
52
|
+
if self._running:
|
|
53
|
+
evt = UpdateProgressEvent(progress=100, error=None, finished=True)
|
|
54
|
+
wx.PostEvent(self.parent, evt)
|
|
55
|
+
|
|
56
|
+
except Exception as e:
|
|
57
|
+
if self._running:
|
|
58
|
+
evt = UpdateProgressEvent(error=str(e), finished=True)
|
|
59
|
+
wx.PostEvent(self.parent, evt)
|
|
60
|
+
|
|
61
|
+
def stop(self):
|
|
62
|
+
self._running = False
|
|
63
|
+
|
|
64
|
+
class DirectMaterialInkjetProgressPanel(wx.Panel):
|
|
65
|
+
def __init__(self, parent, export_options):
|
|
66
|
+
super().__init__(parent)
|
|
67
|
+
self.export_options = export_options
|
|
68
|
+
self.worker = None
|
|
69
|
+
self.start_time = None
|
|
70
|
+
|
|
71
|
+
sizer = wx.BoxSizer(wx.VERTICAL)
|
|
72
|
+
sizer.Add(wx.StaticText(self, label="Exporting PNG Stack..."), 0, wx.ALL, 5)
|
|
73
|
+
|
|
74
|
+
self.progress_bar = wx.Gauge(self, range=100, style=wx.GA_HORIZONTAL)
|
|
75
|
+
sizer.Add(self.progress_bar, 0, wx.ALL | wx.EXPAND, 5)
|
|
76
|
+
|
|
77
|
+
self.elapsed_time_label = wx.StaticText(self, label="Elapsed time: 00:00:00")
|
|
78
|
+
sizer.Add(self.elapsed_time_label, 0, wx.ALL, 5)
|
|
79
|
+
|
|
80
|
+
self.time_estimate_label = wx.StaticText(self, label="Estimated time remaining: N/A")
|
|
81
|
+
sizer.Add(self.time_estimate_label, 0, wx.ALL, 5)
|
|
82
|
+
|
|
83
|
+
self.SetSizer(sizer)
|
|
84
|
+
|
|
85
|
+
self.Bind(EVT_UPDATE_PROGRESS, self.on_update_progress)
|
|
86
|
+
self.update_timer = wx.Timer(self)
|
|
87
|
+
self.Bind(wx.EVT_TIMER, self.on_update_timer, self.update_timer)
|
|
88
|
+
|
|
89
|
+
def start_export(self):
|
|
90
|
+
self.start_time = time.time()
|
|
91
|
+
self.update_timer.Start(1000)
|
|
92
|
+
|
|
93
|
+
voxel_size_mm = self.export_options['voxel_size'] / 1000.0
|
|
94
|
+
|
|
95
|
+
self.worker = DirectMaterialCompilerWorker(
|
|
96
|
+
self,
|
|
97
|
+
self.export_options['root'],
|
|
98
|
+
np.array([voxel_size_mm[0], voxel_size_mm[1], voxel_size_mm[2]]),
|
|
99
|
+
self.export_options['materials'],
|
|
100
|
+
self.export_options['output_directory'],
|
|
101
|
+
self.export_options['file_prefix'],
|
|
102
|
+
self.export_options['liquid_keepout'],
|
|
103
|
+
self.export_options['liquid_keepout_distance']
|
|
104
|
+
)
|
|
105
|
+
self.worker.start()
|
|
106
|
+
|
|
107
|
+
def on_update_progress(self, event):
|
|
108
|
+
if event.error:
|
|
109
|
+
self.update_timer.Stop()
|
|
110
|
+
wx.MessageBox(f"An error occurred during export: {event.error}", "Error", wx.OK | wx.ICON_ERROR)
|
|
111
|
+
self.GetParent().GetParent().FindWindowByLabel("Back").Enable(True)
|
|
112
|
+
elif event.finished:
|
|
113
|
+
self.update_timer.Stop()
|
|
114
|
+
self.progress_bar.SetValue(100)
|
|
115
|
+
elapsed_seconds = time.time() - self.start_time
|
|
116
|
+
elapsed_time_str = time.strftime('%H:%M:%S', time.gmtime(elapsed_seconds))
|
|
117
|
+
self.elapsed_time_label.SetLabel(f"Elapsed time: {elapsed_time_str}")
|
|
118
|
+
|
|
119
|
+
main_frame = self.GetTopLevelParent()
|
|
120
|
+
main_frame.on_export_complete(self.export_options['output_directory'], elapsed_time_str)
|
|
121
|
+
else:
|
|
122
|
+
self.progress_bar.SetValue(event.progress)
|
|
123
|
+
|
|
124
|
+
def on_update_timer(self, event):
|
|
125
|
+
elapsed_seconds = time.time() - self.start_time
|
|
126
|
+
self.elapsed_time_label.SetLabel(f"Elapsed time: {time.strftime('%H:%M:%S', time.gmtime(elapsed_seconds))}")
|
|
127
|
+
|
|
128
|
+
progress = self.progress_bar.GetValue()
|
|
129
|
+
if progress > 0:
|
|
130
|
+
remaining_seconds = (elapsed_seconds * (100 - progress)) / progress
|
|
131
|
+
self.time_estimate_label.SetLabel(f"Estimated time remaining: {time.strftime('%H:%M:%S', time.gmtime(remaining_seconds))}")
|
|
132
|
+
|
|
133
|
+
def __del__(self):
|
|
134
|
+
if self.worker and self.worker.is_alive():
|
|
135
|
+
self.worker.stop()
|
|
136
|
+
self.worker.join()
|
|
137
|
+
self.update_timer.Stop()
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import wx
|
|
2
|
+
import numpy as np
|
|
3
|
+
|
|
4
|
+
class DirectMaterialInkjetPanel(wx.Panel):
|
|
5
|
+
def __init__(self, parent, root, materials):
|
|
6
|
+
super().__init__(parent)
|
|
7
|
+
self.root = root
|
|
8
|
+
self.materials = materials
|
|
9
|
+
|
|
10
|
+
min_bounds, max_bounds = self.root.bounding_box()
|
|
11
|
+
self.min_bounds = np.array([min_bounds.x, min_bounds.y, min_bounds.z])
|
|
12
|
+
self.max_bounds = np.array([max_bounds.x, max_bounds.y, max_bounds.z])
|
|
13
|
+
|
|
14
|
+
sizer = wx.BoxSizer(wx.VERTICAL)
|
|
15
|
+
|
|
16
|
+
# Path
|
|
17
|
+
path_label = wx.StaticText(self, label="Path:")
|
|
18
|
+
sizer.Add(path_label, 0, wx.ALL, 5)
|
|
19
|
+
|
|
20
|
+
path_sizer = wx.BoxSizer(wx.HORIZONTAL)
|
|
21
|
+
self.path_ctrl = wx.TextCtrl(self)
|
|
22
|
+
path_sizer.Add(self.path_ctrl, 1, wx.EXPAND | wx.ALL, 5)
|
|
23
|
+
browse_btn = wx.Button(self, label="Browse")
|
|
24
|
+
browse_btn.Bind(wx.EVT_BUTTON, self.on_browse)
|
|
25
|
+
path_sizer.Add(browse_btn, 0, wx.ALL, 5)
|
|
26
|
+
sizer.Add(path_sizer, 0, wx.EXPAND)
|
|
27
|
+
|
|
28
|
+
# Filename
|
|
29
|
+
filename_label = wx.StaticText(self, label="Filename:")
|
|
30
|
+
sizer.Add(filename_label, 0, wx.ALL, 5)
|
|
31
|
+
|
|
32
|
+
filename_sizer = wx.BoxSizer(wx.HORIZONTAL)
|
|
33
|
+
self.filename_ctrl = wx.TextCtrl(self)
|
|
34
|
+
filename_sizer.Add(self.filename_ctrl, 1, wx.EXPAND | wx.ALL, 5)
|
|
35
|
+
extension_label = wx.StaticText(self, label="XX.png")
|
|
36
|
+
filename_sizer.Add(extension_label, 0, wx.ALL, 5)
|
|
37
|
+
sizer.Add(filename_sizer, 0, wx.EXPAND)
|
|
38
|
+
|
|
39
|
+
# Liquid Keepout
|
|
40
|
+
self.liquid_keepout_check = wx.CheckBox(self, label="Enable Liquid Keepout")
|
|
41
|
+
self.liquid_keepout_check.Bind(wx.EVT_CHECKBOX, self.on_toggle_keepout)
|
|
42
|
+
sizer.Add(self.liquid_keepout_check, 0, wx.ALL, 5)
|
|
43
|
+
|
|
44
|
+
self.keepout_distance_ctrl = wx.SpinCtrlDouble(self, min=0.0, max=100.0, inc=0.1)
|
|
45
|
+
self.keepout_distance_ctrl.SetValue(0.0)
|
|
46
|
+
self.keepout_distance_ctrl.Enable(False)
|
|
47
|
+
sizer.Add(self.keepout_distance_ctrl, 0, wx.ALL | wx.EXPAND, 5)
|
|
48
|
+
|
|
49
|
+
# Voxel Size
|
|
50
|
+
voxel_size_label = wx.StaticText(self, label="Voxel Size (XYZ) in microns:")
|
|
51
|
+
sizer.Add(voxel_size_label, 0, wx.ALL, 5)
|
|
52
|
+
|
|
53
|
+
voxel_sizer = wx.BoxSizer(wx.HORIZONTAL)
|
|
54
|
+
self.voxel_size_x = wx.SpinCtrlDouble(self, min=1.0, max=100000.0, initial=42.3)
|
|
55
|
+
self.voxel_size_y = wx.SpinCtrlDouble(self, min=1.0, max=100000.0, initial=84.6)
|
|
56
|
+
self.voxel_size_z = wx.SpinCtrlDouble(self, min=1.0, max=100000.0, initial=27.0)
|
|
57
|
+
voxel_sizer.Add(self.voxel_size_x, 1, wx.EXPAND | wx.ALL, 5)
|
|
58
|
+
voxel_sizer.Add(self.voxel_size_y, 1, wx.EXPAND | wx.ALL, 5)
|
|
59
|
+
voxel_sizer.Add(self.voxel_size_z, 1, wx.EXPAND | wx.ALL, 5)
|
|
60
|
+
sizer.Add(voxel_sizer, 0, wx.EXPAND)
|
|
61
|
+
|
|
62
|
+
for ctrl in [self.voxel_size_x, self.voxel_size_y, self.voxel_size_z]:
|
|
63
|
+
ctrl.Bind(wx.EVT_SPINCTRLDOUBLE, self.on_voxel_size_change)
|
|
64
|
+
|
|
65
|
+
# Sampling Info
|
|
66
|
+
self.sampling_info_label = wx.StaticText(self, label="")
|
|
67
|
+
self.update_sampling_info()
|
|
68
|
+
sizer.Add(self.sampling_info_label, 0, wx.ALL, 5)
|
|
69
|
+
|
|
70
|
+
self.SetSizer(sizer)
|
|
71
|
+
|
|
72
|
+
def on_browse(self, event):
|
|
73
|
+
with wx.DirDialog(self, "Choose a directory:",
|
|
74
|
+
style=wx.DD_DEFAULT_STYLE) as dirDialog:
|
|
75
|
+
if dirDialog.ShowModal() == wx.ID_CANCEL:
|
|
76
|
+
return
|
|
77
|
+
self.path_ctrl.SetValue(dirDialog.GetPath())
|
|
78
|
+
|
|
79
|
+
def on_toggle_keepout(self, event):
|
|
80
|
+
self.keepout_distance_ctrl.Enable(event.IsChecked())
|
|
81
|
+
|
|
82
|
+
def on_voxel_size_change(self, event):
|
|
83
|
+
self.update_sampling_info()
|
|
84
|
+
|
|
85
|
+
def update_sampling_info(self):
|
|
86
|
+
size = self.max_bounds - self.min_bounds
|
|
87
|
+
voxel_size_mm = np.array([
|
|
88
|
+
self.voxel_size_x.GetValue() / 1000.0,
|
|
89
|
+
self.voxel_size_y.GetValue() / 1000.0,
|
|
90
|
+
self.voxel_size_z.GetValue() / 1000.0
|
|
91
|
+
])
|
|
92
|
+
|
|
93
|
+
total_voxels = (size[0] / voxel_size_mm[0]) * (size[1] / voxel_size_mm[1]) * (size[2] / voxel_size_mm[2])
|
|
94
|
+
|
|
95
|
+
sampling_info = (f"Sample Space Size: {size[0]:.2f} x {size[1]:.2f} x {size[2]:.2f} mm\n"
|
|
96
|
+
f"Total Voxels: {total_voxels:,.0f}")
|
|
97
|
+
self.sampling_info_label.SetLabel(sampling_info)
|
|
98
|
+
|
|
99
|
+
def get_export_options(self):
|
|
100
|
+
return {
|
|
101
|
+
"output_directory": self.path_ctrl.GetValue(),
|
|
102
|
+
"file_prefix": self.filename_ctrl.GetValue(),
|
|
103
|
+
"liquid_keepout": self.liquid_keepout_check.IsChecked(),
|
|
104
|
+
"liquid_keepout_distance": self.keepout_distance_ctrl.GetValue(),
|
|
105
|
+
"voxel_size": np.array([
|
|
106
|
+
self.voxel_size_x.GetValue(),
|
|
107
|
+
self.voxel_size_y.GetValue(),
|
|
108
|
+
self.voxel_size_z.GetValue()
|
|
109
|
+
]),
|
|
110
|
+
"root": self.root,
|
|
111
|
+
"materials": self.materials
|
|
112
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import wx
|
|
2
|
+
import wx.lib.newevent
|
|
3
|
+
import threading
|
|
4
|
+
import time
|
|
5
|
+
import os
|
|
6
|
+
import subprocess
|
|
7
|
+
import sys
|
|
8
|
+
import numpy as np
|
|
9
|
+
from wx.lib.newevent import NewEvent
|
|
10
|
+
|
|
11
|
+
# Custom event for progress updates
|
|
12
|
+
UpdateProgressEvent, EVT_UPDATE_PROGRESS = NewEvent()
|
|
13
|
+
|
|
14
|
+
class FiniteElementMeshCompilerWorker(threading.Thread):
|
|
15
|
+
def __init__(self, parent, export_options):
|
|
16
|
+
super().__init__()
|
|
17
|
+
self.parent = parent
|
|
18
|
+
self.export_options = export_options
|
|
19
|
+
self._running = True
|
|
20
|
+
|
|
21
|
+
def run(self):
|
|
22
|
+
try:
|
|
23
|
+
# This is where you would call the actual compiler
|
|
24
|
+
# For now, we simulate the progress
|
|
25
|
+
for i in range(101):
|
|
26
|
+
if not self._running:
|
|
27
|
+
break
|
|
28
|
+
time.sleep(0.05)
|
|
29
|
+
evt = UpdateProgressEvent(progress=i, error=None, finished=(i == 100))
|
|
30
|
+
wx.PostEvent(self.parent, evt)
|
|
31
|
+
except Exception as e:
|
|
32
|
+
evt = UpdateProgressEvent(error=str(e), finished=True)
|
|
33
|
+
wx.PostEvent(self.parent, evt)
|
|
34
|
+
|
|
35
|
+
def stop(self):
|
|
36
|
+
self._running = False
|
|
37
|
+
|
|
38
|
+
class FiniteElementMeshProgressPanel(wx.Panel):
|
|
39
|
+
def __init__(self, parent, export_options):
|
|
40
|
+
super().__init__(parent)
|
|
41
|
+
self.export_options = export_options
|
|
42
|
+
self.worker = None
|
|
43
|
+
self.start_time = None
|
|
44
|
+
|
|
45
|
+
sizer = wx.BoxSizer(wx.VERTICAL)
|
|
46
|
+
sizer.Add(wx.StaticText(self, label="Exporting INP File..."), 0, wx.ALL, 5)
|
|
47
|
+
|
|
48
|
+
self.progress_bar = wx.Gauge(self, range=100, style=wx.GA_HORIZONTAL)
|
|
49
|
+
sizer.Add(self.progress_bar, 0, wx.ALL | wx.EXPAND, 5)
|
|
50
|
+
|
|
51
|
+
self.elapsed_time_label = wx.StaticText(self, label="Elapsed time: 00:00:00")
|
|
52
|
+
sizer.Add(self.elapsed_time_label, 0, wx.ALL, 5)
|
|
53
|
+
|
|
54
|
+
self.time_estimate_label = wx.StaticText(self, label="Estimated time remaining: N/A")
|
|
55
|
+
sizer.Add(self.time_estimate_label, 0, wx.ALL, 5)
|
|
56
|
+
|
|
57
|
+
self.SetSizer(sizer)
|
|
58
|
+
|
|
59
|
+
self.Bind(EVT_UPDATE_PROGRESS, self.on_update_progress)
|
|
60
|
+
self.update_timer = wx.Timer(self)
|
|
61
|
+
self.Bind(wx.EVT_TIMER, self.on_update_timer, self.update_timer)
|
|
62
|
+
|
|
63
|
+
def start_export(self):
|
|
64
|
+
self.start_time = time.time()
|
|
65
|
+
self.update_timer.Start(1000)
|
|
66
|
+
|
|
67
|
+
self.worker = FiniteElementMeshCompilerWorker(self, self.export_options)
|
|
68
|
+
self.worker.start()
|
|
69
|
+
|
|
70
|
+
def on_update_progress(self, event):
|
|
71
|
+
if event.error:
|
|
72
|
+
self.update_timer.Stop()
|
|
73
|
+
wx.MessageBox(f"An error occurred during export: {event.error}", "Error", wx.OK | wx.ICON_ERROR)
|
|
74
|
+
self.GetParent().GetParent().FindWindowByLabel("Back").Enable(True)
|
|
75
|
+
elif event.finished:
|
|
76
|
+
self.update_timer.Stop()
|
|
77
|
+
self.progress_bar.SetValue(100)
|
|
78
|
+
elapsed_seconds = time.time() - self.start_time
|
|
79
|
+
elapsed_time_str = time.strftime('%H:%M:%S', time.gmtime(elapsed_seconds))
|
|
80
|
+
self.elapsed_time_label.SetLabel(f"Elapsed time: {elapsed_time_str}")
|
|
81
|
+
|
|
82
|
+
main_frame = self.GetTopLevelParent()
|
|
83
|
+
main_frame.on_export_complete(self.export_options['file_path'], elapsed_time_str)
|
|
84
|
+
else:
|
|
85
|
+
self.progress_bar.SetValue(event.progress)
|
|
86
|
+
|
|
87
|
+
def on_update_timer(self, event):
|
|
88
|
+
elapsed_seconds = time.time() - self.start_time
|
|
89
|
+
self.elapsed_time_label.SetLabel(f"Elapsed time: {time.strftime('%H:%M:%S', time.gmtime(elapsed_seconds))}")
|
|
90
|
+
|
|
91
|
+
progress = self.progress_bar.GetValue()
|
|
92
|
+
if progress > 0:
|
|
93
|
+
remaining_seconds = (elapsed_seconds * (100 - progress)) / progress
|
|
94
|
+
self.time_estimate_label.SetLabel(f"Estimated time remaining: {time.strftime('%H:%M:%S', time.gmtime(remaining_seconds))}")
|
|
95
|
+
|
|
96
|
+
def __del__(self):
|
|
97
|
+
if self.worker and self.worker.is_alive():
|
|
98
|
+
self.worker.stop()
|
|
99
|
+
self.worker.join()
|
|
100
|
+
self.update_timer.Stop()
|
|
101
|
+
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import wx
|
|
2
|
+
import numpy as np
|
|
3
|
+
|
|
4
|
+
class FiniteElementMeshPanel(wx.Panel):
|
|
5
|
+
def __init__(self, parent, root, materials):
|
|
6
|
+
super().__init__(parent)
|
|
7
|
+
self.root = root
|
|
8
|
+
self.materials = materials
|
|
9
|
+
|
|
10
|
+
sizer = wx.BoxSizer(wx.VERTICAL)
|
|
11
|
+
|
|
12
|
+
# Path
|
|
13
|
+
path_label = wx.StaticText(self, label="Path:")
|
|
14
|
+
sizer.Add(path_label, 0, wx.ALL, 5)
|
|
15
|
+
|
|
16
|
+
path_sizer = wx.BoxSizer(wx.HORIZONTAL)
|
|
17
|
+
self.path_ctrl = wx.TextCtrl(self)
|
|
18
|
+
path_sizer.Add(self.path_ctrl, 1, wx.EXPAND | wx.ALL, 5)
|
|
19
|
+
browse_btn = wx.Button(self, label="Browse")
|
|
20
|
+
browse_btn.Bind(wx.EVT_BUTTON, self.on_browse)
|
|
21
|
+
path_sizer.Add(browse_btn, 0, wx.ALL, 5)
|
|
22
|
+
sizer.Add(path_sizer, 0, wx.EXPAND)
|
|
23
|
+
|
|
24
|
+
# Element Type
|
|
25
|
+
element_type_label = wx.StaticText(self, label="Element Type:")
|
|
26
|
+
sizer.Add(element_type_label, 0, wx.ALL, 5)
|
|
27
|
+
self.element_type_combo = wx.ComboBox(self, choices=["Tetrahedron (C3D4)", "Brick (C3D8)"], style=wx.CB_READONLY)
|
|
28
|
+
self.element_type_combo.SetSelection(0)
|
|
29
|
+
self.element_type_combo.Bind(wx.EVT_COMBOBOX, self.on_element_type_change)
|
|
30
|
+
sizer.Add(self.element_type_combo, 0, wx.ALL | wx.EXPAND, 5)
|
|
31
|
+
|
|
32
|
+
# Brick Options
|
|
33
|
+
self.brick_group_box = wx.StaticBox(self, label="Brick Element (C3D8) Options")
|
|
34
|
+
self.brick_group_box.Hide()
|
|
35
|
+
brick_sizer = wx.StaticBoxSizer(self.brick_group_box, wx.VERTICAL)
|
|
36
|
+
|
|
37
|
+
brick_grid = wx.GridSizer(2, 3, 5, 5)
|
|
38
|
+
brick_grid.Add(wx.StaticText(self.brick_group_box, label="X Cells:"), 0, wx.ALIGN_CENTER_VERTICAL)
|
|
39
|
+
brick_grid.Add(wx.StaticText(self.brick_group_box, label="Y Cells:"), 0, wx.ALIGN_CENTER_VERTICAL)
|
|
40
|
+
brick_grid.Add(wx.StaticText(self.brick_group_box, label="Z Cells:"), 0, wx.ALIGN_CENTER_VERTICAL)
|
|
41
|
+
self.x_dim_ctrl = wx.TextCtrl(self.brick_group_box)
|
|
42
|
+
self.y_dim_ctrl = wx.TextCtrl(self.brick_group_box)
|
|
43
|
+
self.z_dim_ctrl = wx.TextCtrl(self.brick_group_box)
|
|
44
|
+
brick_grid.Add(self.x_dim_ctrl, 1, wx.EXPAND)
|
|
45
|
+
brick_grid.Add(self.y_dim_ctrl, 1, wx.EXPAND)
|
|
46
|
+
brick_grid.Add(self.z_dim_ctrl, 1, wx.EXPAND)
|
|
47
|
+
brick_sizer.Add(brick_grid, 0, wx.EXPAND | wx.ALL, 5)
|
|
48
|
+
|
|
49
|
+
self.num_elements_label = wx.StaticText(self.brick_group_box, label="Number of Elements: 0")
|
|
50
|
+
brick_sizer.Add(self.num_elements_label, 0, wx.ALL, 5)
|
|
51
|
+
|
|
52
|
+
self.use_volumetric_dither_check = wx.CheckBox(self.brick_group_box, label="Use Volumetric Dither")
|
|
53
|
+
brick_sizer.Add(self.use_volumetric_dither_check, 0, wx.ALL, 5)
|
|
54
|
+
|
|
55
|
+
for ctrl in [self.x_dim_ctrl, self.y_dim_ctrl, self.z_dim_ctrl]:
|
|
56
|
+
ctrl.Bind(wx.EVT_TEXT, self.on_brick_dim_change)
|
|
57
|
+
|
|
58
|
+
sizer.Add(brick_sizer, 0, wx.EXPAND | wx.ALL, 5)
|
|
59
|
+
|
|
60
|
+
# Tetrahedral Options
|
|
61
|
+
self.tetra_group_box = wx.StaticBox(self, label="Tetrahedron Element (C3D4) Options")
|
|
62
|
+
tetra_sizer = wx.StaticBoxSizer(self.tetra_group_box, wx.VERTICAL)
|
|
63
|
+
|
|
64
|
+
# Voxel Size
|
|
65
|
+
voxel_size_sizer = wx.BoxSizer(wx.HORIZONTAL)
|
|
66
|
+
voxel_size_sizer.Add(wx.StaticText(self.tetra_group_box, label="Sample Size (mm):"), 1, wx.ALIGN_CENTER_VERTICAL | wx.ALL, 5)
|
|
67
|
+
self.voxel_size_ctrl = wx.SpinCtrlDouble(self.tetra_group_box, min=0.000001, max=100.0, initial=0.1, inc=0.000001)
|
|
68
|
+
self.voxel_size_ctrl.SetDigits(6)
|
|
69
|
+
voxel_size_sizer.Add(self.voxel_size_ctrl, 1, wx.EXPAND | wx.ALL, 5)
|
|
70
|
+
auto_compute_btn = wx.Button(self.tetra_group_box, label="Auto-compute")
|
|
71
|
+
auto_compute_btn.Bind(wx.EVT_BUTTON, self.on_auto_compute)
|
|
72
|
+
voxel_size_sizer.Add(auto_compute_btn, 0, wx.ALL, 5)
|
|
73
|
+
tetra_sizer.Add(voxel_size_sizer, 0, wx.EXPAND)
|
|
74
|
+
|
|
75
|
+
# Variable Mesh Size
|
|
76
|
+
self.use_variable_mesh_check = wx.CheckBox(self.tetra_group_box, label="Use Gradient-based Variable Mesh Size")
|
|
77
|
+
self.use_variable_mesh_check.Bind(wx.EVT_CHECKBOX, self.on_toggle_variable_mesh)
|
|
78
|
+
tetra_sizer.Add(self.use_variable_mesh_check, 0, wx.ALL, 5)
|
|
79
|
+
|
|
80
|
+
# Cell Size (non-variable)
|
|
81
|
+
self.cell_size_sizer = wx.BoxSizer(wx.HORIZONTAL)
|
|
82
|
+
self.cell_size_sizer.Add(wx.StaticText(self.tetra_group_box, label="Cell Size:"), 1, wx.ALIGN_CENTER_VERTICAL | wx.ALL, 5)
|
|
83
|
+
self.cell_size_ctrl = wx.SpinCtrlDouble(self.tetra_group_box, min=0.000001, max=100.0, initial=0.1, inc=0.000001)
|
|
84
|
+
self.cell_size_ctrl.SetDigits(6)
|
|
85
|
+
self.cell_size_sizer.Add(self.cell_size_ctrl, 1, wx.EXPAND | wx.ALL, 5)
|
|
86
|
+
tetra_sizer.Add(self.cell_size_sizer, 0, wx.EXPAND)
|
|
87
|
+
|
|
88
|
+
# Variable Mesh Options
|
|
89
|
+
self.variable_mesh_group = wx.StaticBox(self.tetra_group_box, label="Variable Mesh Size Options")
|
|
90
|
+
self.variable_mesh_group.Hide()
|
|
91
|
+
variable_mesh_sizer = wx.StaticBoxSizer(self.variable_mesh_group, wx.VERTICAL)
|
|
92
|
+
|
|
93
|
+
min_cell_sizer = wx.BoxSizer(wx.HORIZONTAL)
|
|
94
|
+
min_cell_sizer.Add(wx.StaticText(self.variable_mesh_group, label="Minimum Cell Size:"), 1, wx.ALIGN_CENTER_VERTICAL | wx.ALL, 5)
|
|
95
|
+
self.min_cell_size_ctrl = wx.SpinCtrlDouble(self.variable_mesh_group, min=0.000001, max=100.0, inc=0.000001)
|
|
96
|
+
self.min_cell_size_ctrl.SetDigits(6)
|
|
97
|
+
min_cell_sizer.Add(self.min_cell_size_ctrl, 1, wx.EXPAND | wx.ALL, 5)
|
|
98
|
+
variable_mesh_sizer.Add(min_cell_sizer, 0, wx.EXPAND)
|
|
99
|
+
|
|
100
|
+
max_cell_sizer = wx.BoxSizer(wx.HORIZONTAL)
|
|
101
|
+
max_cell_sizer.Add(wx.StaticText(self.variable_mesh_group, label="Maximum Cell Size:"), 1, wx.ALIGN_CENTER_VERTICAL | wx.ALL, 5)
|
|
102
|
+
self.max_cell_size_ctrl = wx.SpinCtrlDouble(self.variable_mesh_group, min=0.000001, max=100.0, inc=0.000001)
|
|
103
|
+
self.max_cell_size_ctrl.SetDigits(6)
|
|
104
|
+
max_cell_sizer.Add(self.max_cell_size_ctrl, 1, wx.EXPAND | wx.ALL, 5)
|
|
105
|
+
variable_mesh_sizer.Add(max_cell_sizer, 0, wx.EXPAND)
|
|
106
|
+
|
|
107
|
+
variable_mesh_sizer.Add(wx.StaticText(self.variable_mesh_group, label="Cell Size Mapping Expression:"), 0, wx.ALL, 5)
|
|
108
|
+
self.variable_cell_size_expr_ctrl = wx.TextCtrl(self.variable_mesh_group, style=wx.TE_MULTILINE)
|
|
109
|
+
self.variable_cell_size_expr_ctrl.SetValue("min_cell + h * (max_cell - min_cell)")
|
|
110
|
+
variable_mesh_sizer.Add(self.variable_cell_size_expr_ctrl, 1, wx.EXPAND | wx.ALL, 5)
|
|
111
|
+
tetra_sizer.Add(variable_mesh_sizer, 0, wx.EXPAND | wx.ALL, 5)
|
|
112
|
+
|
|
113
|
+
# Facet Angle
|
|
114
|
+
facet_angle_sizer = wx.BoxSizer(wx.HORIZONTAL)
|
|
115
|
+
facet_angle_sizer.Add(wx.StaticText(self.tetra_group_box, label="Facet Angle (deg):"), 1, wx.ALIGN_CENTER_VERTICAL | wx.ALL, 5)
|
|
116
|
+
self.facet_angle_slider = wx.Slider(self.tetra_group_box, value=30, minValue=0, maxValue=90)
|
|
117
|
+
self.facet_angle_label = wx.StaticText(self.tetra_group_box, label="30")
|
|
118
|
+
self.facet_angle_slider.Bind(wx.EVT_SLIDER, lambda evt: self.facet_angle_label.SetLabel(str(self.facet_angle_slider.GetValue())))
|
|
119
|
+
facet_angle_sizer.Add(self.facet_angle_slider, 1, wx.EXPAND | wx.ALL, 5)
|
|
120
|
+
facet_angle_sizer.Add(self.facet_angle_label, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALL, 5)
|
|
121
|
+
tetra_sizer.Add(facet_angle_sizer, 0, wx.EXPAND)
|
|
122
|
+
|
|
123
|
+
# Facet Size
|
|
124
|
+
facet_size_sizer = wx.BoxSizer(wx.HORIZONTAL)
|
|
125
|
+
facet_size_sizer.Add(wx.StaticText(self.tetra_group_box, label="Facet Size (mm):"), 1, wx.ALIGN_CENTER_VERTICAL | wx.ALL, 5)
|
|
126
|
+
self.facet_size_ctrl = wx.SpinCtrlDouble(self.tetra_group_box, min=0.000001, max=100.0, inc=0.000001)
|
|
127
|
+
self.facet_size_ctrl.SetDigits(6)
|
|
128
|
+
facet_size_sizer.Add(self.facet_size_ctrl, 1, wx.EXPAND | wx.ALL, 5)
|
|
129
|
+
tetra_sizer.Add(facet_size_sizer, 0, wx.EXPAND)
|
|
130
|
+
|
|
131
|
+
# Facet Distance
|
|
132
|
+
facet_dist_sizer = wx.BoxSizer(wx.HORIZONTAL)
|
|
133
|
+
facet_dist_sizer.Add(wx.StaticText(self.tetra_group_box, label="Facet Distance (mm):"), 1, wx.ALIGN_CENTER_VERTICAL | wx.ALL, 5)
|
|
134
|
+
self.facet_dist_ctrl = wx.SpinCtrlDouble(self.tetra_group_box, min=0.000001, max=100.0, inc=0.000001)
|
|
135
|
+
self.facet_dist_ctrl.SetDigits(6)
|
|
136
|
+
facet_dist_sizer.Add(self.facet_dist_ctrl, 1, wx.EXPAND | wx.ALL, 5)
|
|
137
|
+
tetra_sizer.Add(facet_dist_sizer, 0, wx.EXPAND)
|
|
138
|
+
|
|
139
|
+
# Cell Radius/Edge Ratio
|
|
140
|
+
cell_ratio_sizer = wx.BoxSizer(wx.HORIZONTAL)
|
|
141
|
+
cell_ratio_sizer.Add(wx.StaticText(self.tetra_group_box, label="Cell Radius/Edge Ratio:"), 1, wx.ALIGN_CENTER_VERTICAL | wx.ALL, 5)
|
|
142
|
+
self.cell_ratio_ctrl = wx.SpinCtrl(self.tetra_group_box, min=1, max=10, initial=2)
|
|
143
|
+
cell_ratio_sizer.Add(self.cell_ratio_ctrl, 1, wx.EXPAND | wx.ALL, 5)
|
|
144
|
+
tetra_sizer.Add(cell_ratio_sizer, 0, wx.EXPAND)
|
|
145
|
+
|
|
146
|
+
sizer.Add(tetra_sizer, 0, wx.EXPAND | wx.ALL, 5)
|
|
147
|
+
|
|
148
|
+
self.on_auto_compute(None)
|
|
149
|
+
self.SetSizerAndFit(sizer)
|
|
150
|
+
|
|
151
|
+
def on_browse(self, event):
|
|
152
|
+
with wx.FileDialog(self, "Save INP File", wildcard="INP files (*.inp)|*.inp",
|
|
153
|
+
style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT) as fileDialog:
|
|
154
|
+
if fileDialog.ShowModal() == wx.ID_CANCEL:
|
|
155
|
+
return
|
|
156
|
+
pathname = fileDialog.GetPath()
|
|
157
|
+
if not pathname.lower().endswith(".inp"):
|
|
158
|
+
pathname += ".inp"
|
|
159
|
+
self.path_ctrl.SetValue(pathname)
|
|
160
|
+
|
|
161
|
+
def on_element_type_change(self, event):
|
|
162
|
+
is_tetra = self.element_type_combo.GetSelection() == 0
|
|
163
|
+
self.tetra_group_box.Show(is_tetra)
|
|
164
|
+
self.brick_group_box.Show(not is_tetra)
|
|
165
|
+
self.GetParent().Layout()
|
|
166
|
+
|
|
167
|
+
def on_brick_dim_change(self, event):
|
|
168
|
+
try:
|
|
169
|
+
x = int(self.x_dim_ctrl.GetValue())
|
|
170
|
+
y = int(self.y_dim_ctrl.GetValue())
|
|
171
|
+
z = int(self.z_dim_ctrl.GetValue())
|
|
172
|
+
self.num_elements_label.SetLabel(f"Number of Elements: {x*y*z:,.0f}")
|
|
173
|
+
except ValueError:
|
|
174
|
+
self.num_elements_label.SetLabel("Number of Elements: Invalid Input")
|
|
175
|
+
|
|
176
|
+
def on_toggle_variable_mesh(self, event):
|
|
177
|
+
use_variable = self.use_variable_mesh_check.IsChecked()
|
|
178
|
+
self.variable_mesh_group.Show(use_variable)
|
|
179
|
+
self.cell_size_sizer.Show(not use_variable)
|
|
180
|
+
self.GetParent().Layout()
|
|
181
|
+
|
|
182
|
+
def on_auto_compute(self, event):
|
|
183
|
+
if event: # Only show message box on button click
|
|
184
|
+
reply = wx.MessageBox("This will overwrite the current values. Are you sure you want to continue?", "Overwrite Values?", wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION)
|
|
185
|
+
if reply == wx.NO:
|
|
186
|
+
return
|
|
187
|
+
|
|
188
|
+
voxel_size = self.voxel_size_ctrl.GetValue()
|
|
189
|
+
self.voxel_size_ctrl.SetIncrement(voxel_size / 10.0)
|
|
190
|
+
|
|
191
|
+
self.facet_size_ctrl.SetValue(voxel_size)
|
|
192
|
+
self.facet_size_ctrl.SetIncrement(voxel_size / 10.0)
|
|
193
|
+
|
|
194
|
+
self.facet_dist_ctrl.SetValue(voxel_size / 10.0)
|
|
195
|
+
self.facet_dist_ctrl.SetIncrement(voxel_size / 100.0)
|
|
196
|
+
|
|
197
|
+
self.cell_size_ctrl.SetValue(voxel_size)
|
|
198
|
+
self.cell_size_ctrl.SetIncrement(voxel_size / 10.0)
|
|
199
|
+
|
|
200
|
+
self.min_cell_size_ctrl.SetValue(voxel_size)
|
|
201
|
+
self.min_cell_size_ctrl.SetIncrement(voxel_size / 20.0)
|
|
202
|
+
|
|
203
|
+
self.max_cell_size_ctrl.SetValue(voxel_size * 10)
|
|
204
|
+
self.max_cell_size_ctrl.SetIncrement(voxel_size / 20.0)
|
|
205
|
+
|
|
206
|
+
def get_export_options(self):
|
|
207
|
+
options = {"file_path": self.path_ctrl.GetValue()}
|
|
208
|
+
if self.element_type_combo.GetSelection() == 0: # Tetra
|
|
209
|
+
options.update({
|
|
210
|
+
"element_type": "tetra",
|
|
211
|
+
"voxel_size": self.voxel_size_ctrl.GetValue(),
|
|
212
|
+
"facet_angle": self.facet_angle_slider.GetValue(),
|
|
213
|
+
"facet_size": self.facet_size_ctrl.GetValue(),
|
|
214
|
+
"facet_distance": self.facet_dist_ctrl.GetValue(),
|
|
215
|
+
"cell_radius_edge_ratio": self.cell_ratio_ctrl.GetValue(),
|
|
216
|
+
"use_variable_mesh_size": self.use_variable_mesh_check.IsChecked(),
|
|
217
|
+
"cell_size": self.cell_size_ctrl.GetValue(),
|
|
218
|
+
"min_cell_size": self.min_cell_size_ctrl.GetValue(),
|
|
219
|
+
"max_cell_size": self.max_cell_size_ctrl.GetValue(),
|
|
220
|
+
"variable_cell_size_expression": self.variable_cell_size_expr_ctrl.GetValue(),
|
|
221
|
+
})
|
|
222
|
+
else: # Brick
|
|
223
|
+
options.update({
|
|
224
|
+
"element_type": "brick",
|
|
225
|
+
"x_dim": int(self.x_dim_ctrl.GetValue()),
|
|
226
|
+
"y_dim": int(self.y_dim_ctrl.GetValue()),
|
|
227
|
+
"z_dim": int(self.z_dim_ctrl.GetValue()),
|
|
228
|
+
"use_volumetric_dither": self.use_volumetric_dither_check.IsChecked(),
|
|
229
|
+
})
|
|
230
|
+
return options
|