from coperniFUS import *
from coperniFUS.modules.interfaces.trimesh_interfaces import StlHandler
import pyqtgraph as pg
import functools
[docs]
class StlHandlerGUI(StlHandler):
_DEFAULT_PARAMS = {
'file_path': 'None',
'ignore_anatomical_landmarks_calibration': True,
'ignore_plane_slicing': False,
'stl_item_transforms_str' : 'S1 Rx0deg Tz0um',
'gl_mesh_shader': 'viewNormalColor',
'gl_mesh_drawEdges': False,
'gl_mesh_drawFaces': True,
'gl_mesh_edgeColor': (.5, .5, .5, .7),
'gl_mesh_glOptions': 'opaque',
'gl_mesh_smooth': False,
'gl_mesh_edgeWidth': 5,
}
""" Default configuration parameters used when a parameter value is not yet cached """
def __init__(self, parent_viewer, **kwargs) -> None:
super().__init__(parent_viewer, **kwargs)
# --- Module specific public attributes ---
@property
def stl_item_tmat(self):
""" Holds the atlas volume affine transformation matrix """
if self._stl_item_tmat is None:
# Compute transform matrix from transforms str
transforms_matrices = af_tr_from_str.transform_matrices_from_str(
self.get_user_param('stl_item_transforms_str')
)
self._stl_item_tmat = af_tr.scale_mat(1)
for tr_mat in transforms_matrices:
self._stl_item_tmat = self._stl_item_tmat @ tr_mat
# Apply anatomical landmark calibration transformation if enabled
if not self.get_user_param('ignore_anatomical_landmarks_calibration'):
anatomically_calibrated_stl_item_tmat = self._stl_item_tmat @ self.parent_viewer.anat_calib.landmarks_calib_tmat
else:
anatomically_calibrated_stl_item_tmat = self._stl_item_tmat
return anatomically_calibrated_stl_item_tmat
@stl_item_tmat.setter
def stl_item_tmat(self, value):
if value is not None:
if value.shape != (4, 4):
raise ValueError('Transformation matrix should be of shape (4, 4)')
self._stl_item_tmat = value
self.stl_item_mesh = None # Reset processed stl mesh to apply transform
# --- Required module attributes ---
[docs]
def init_dock(self):
""" Called on GUI setup to add a module dock """
# Setting up dock layout
self.dock = pyqtw.QDockWidget('STL Handler', self.parent_viewer)
self.parent_viewer.addDockWidget(pyqtc.Qt.DockWidgetArea.LeftDockWidgetArea, self.dock)
self.dock_widget = pyqtw.QWidget(self.dock)
self.dock.setWidget(self.dock_widget)
self.dock_layout = pyqtw.QGridLayout()
self.dock_widget.setLayout(self.dock_layout)
# Import button
self.select_stl_file_btn = pyqtw.QPushButton('Select STL file')
self.select_stl_file_btn.clicked.connect(self._import_stl)
self.dock_layout.addWidget(self.select_stl_file_btn, 0, 0, 1, 1) # Y, X, w, h
# Transform matrix str editor
self.stl_item_transform_editor = descriptive_line_edit(str(self.get_user_param('stl_item_transforms_str')), 'Mesh transform')
self.stl_item_transform_editor.editingFinished.connect(functools.partial(self.parse_editor, self.stl_item_transform_editor, 'stl_item_transforms_str', '', 'str'))
self.stl_item_transform_editor.editingFinished.connect(self._on_stl_item_transform_editing_finished)
self.stl_item_transform_editor.setEnabled(False)
self.dock_layout.addWidget(self.stl_item_transform_editor, 1, 0, 1, 1) # Y, X, w, h
self.stl_item_transform_editor.setToolTip('STL mesh transformations<br> - S0.5: Apply a 0.5 scaling factor (Use Sx to scale along x)<br> - Ty1mm: 1mm translation along y<br> - Rz90deg: Rotate by 90 degrees around z axis')
# Resize to most comptact height
self.dock.adjustSize()
self.dock.setMinimumHeight(self.dock.sizeHint().height())
self.dock.setMaximumHeight(self.dock.sizeHint().height())
[docs]
def add_rendered_object(self,):
""" Called when populating the viewer with the module rendered objects """
if self.stl_item_name is None:
self.stl_item_name = self.parent_viewer.cache.get_attr(['mesh_handler', 'last_used_stl_item_name'])
super().add_rendered_object()
if isinstance(self.stl_glitem, pg.opengl.items.GLMeshItem.GLMeshItem) or (isinstance(self.stl_glitem, list) and len(self.stl_glitem) > 0):
self._update_item_transform_editor()
self.stl_item_transform_editor.setEnabled(True)
# Setup dock button for image deletion
self.select_stl_file_btn.setText('Remove STL')
self.select_stl_file_btn.clicked.disconnect()
self.select_stl_file_btn.clicked.connect(self.delete_rendered_object)
[docs]
def delete_rendered_object(self):
""" Called on deletion of the module rendered objects """
if self.stl_glitem is not None:
self.parent_viewer.cache.set_attr(['mesh_handler', 'last_used_stl_item_name'], None)
super().delete_rendered_object()
self.select_stl_file_btn.setText('Select STL file')
self.select_stl_file_btn.clicked.disconnect()
self.select_stl_file_btn.clicked.connect(self._import_stl)
self.stl_item_transform_editor.setText(str(self._DEFAULT_PARAMS['stl_item_transforms_str']))
self.stl_item_transform_editor.setEnabled(False)
# --- Module specific attributes ---
[docs]
def parse_editor(self, src_editor, param_name, unit='', param_type='float'): # TODO move to base Module?
if param_type == 'int':
edited_value = int(src_editor.text())
elif param_type == 'float':
edited_text = src_editor.text().replace(' ', '') # remove spaces
edited_text_nounit = edited_text[:-len(unit)]
edited_value = si_parse(edited_text_nounit.replace('u', 'ยต'))
else: # raw str
edited_value = src_editor.text()
self.set_user_param(param_name, edited_value)
self.parent_viewer.update_rendered_view()
def _on_stl_item_transform_editing_finished(self):
self.stl_item_tmat = None
def _update_item_transform_editor(self):
self.stl_item_transform_editor.setText(str(self.get_user_param('stl_item_transforms_str')))
def _import_stl(self):
import_path = pyqtw.QFileDialog.getOpenFileName(parent=self.parent_viewer, caption=self.parent_viewer.tr("Select an STL"), filter=self.parent_viewer.tr('STL file (*.stl)'))
if import_path[0] == '':
self.parent_viewer.statusBar().showMessage('Invalid STL file path', self.parent_viewer._STATUS_BAR_MSG_TIMEOUT)
else:
self.raw_stl_item_mesh = None # Reset before reload
self.stl_item_name = pathlib.Path(import_path[0]).stem
self.set_user_param('file_path', import_path[0])
if self.raw_stl_item_mesh is None: # If import has failed
self.parent_viewer.statusBar().showMessage('STL file import fail', self.parent_viewer._STATUS_BAR_MSG_TIMEOUT)
self.set_user_param('file_path') # Reset to default
self.stl_item_name = None
else: # On success -> save for future use
self.add_rendered_object()
self.parent_viewer.cache.set_attr(
['mesh_handler', 'last_used_stl_item_name'],
self.stl_item_name)