from coperniFUS import *
from coperniFUS.modules.interfaces.trimesh_interfaces import TrimeshHandler, TrimeshScriptHandler, StlHandler
from coperniFUS.modules.armatures.base_armature import Armature
[docs]
class STLMeshArmature(Armature):
_DEFAULT_PARAMS = {
'visible': False,
'tooltip_on_armature': False,
'rgba_color': (.6, .6, .6, .7),
'glline_width': 5,
'armature_config_csts': {},
'uneval_armature_config_dict': {
'_armature_joints': {
'udialysis FUS holder': {
'translation_0': {
'args': ['x', -18e-3],
'_is_editable': False},
'translation_1': {
'args': ['z', -10e-3],
'_is_editable': False},
}
},
'_stl_mesh': {
'file_path': 'None',
'transform_str': None,
'ignore_plane_slicing': True,
'ignore_anatomical_landmarks_calibration': True,
'gl_mesh_shader': None,
'gl_mesh_drawEdges': True,
'gl_mesh_drawFaces': False,
'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, armature_display_name, parent_viewer, stereotax_frame_instance, **kwargs) -> None:
super().__init__(armature_display_name, parent_viewer, stereotax_frame_instance, **kwargs)
# Reset aramatures configuration dicts with default ones
# self.armature_config_csts = self._DEFAULT_PARAMS['armature_config_csts']
# self.uneval_armature_config_dict = self._DEFAULT_PARAMS['uneval_armature_config_dict']
self.mesh_handler = StlHandler(parent_viewer)
# --- Armature specific public attributes ---
[docs]
def add_render(self):
""" Called when populating the viewer with the module rendered objects """
super().add_render()
if '_stl_mesh' in self.armature_config_dict and self.armature_config_dict['_stl_mesh']['file_path'] is not None:
if self.parent_viewer.assets_dir_path is not None:
stl_fpath = self.parent_viewer.assets_dir_path / self.armature_config_dict['_stl_mesh']['file_path']
else:
stl_fpath = self.armature_config_dict['_stl_mesh']['file_path']
if pathlib.Path(stl_fpath).exists():
self._is_render_uptodate # Init hash
self.mesh_handler.stl_item_name = pathlib.Path(stl_fpath).stem
self.mesh_handler.set_user_param('file_path', str(stl_fpath))
# Set StlHandler gl parameters
armature_dict_stl_params = self.uneval_armature_config_dict['_stl_mesh']
for stl_param_key in self.mesh_handler._DEFAULT_PARAMS.keys():
if stl_param_key in armature_dict_stl_params and stl_param_key != 'file_path':
self.mesh_handler.set_user_param(stl_param_key, armature_dict_stl_params[stl_param_key])
self._update_stl_item_transform_matrix()
if self.mesh_handler.stl_glitem != None or self.visible is False:
self.mesh_handler.delete_rendered_object()
else:
self.mesh_handler.add_rendered_object()
[docs]
def update_render(self, force_update=False):
""" Called on render view updates """
if not self._is_render_uptodate or force_update:
super().update_render(force_update=True)
if self.visible is True:
if self.mesh_handler.stl_glitem is None:
self.add_render()
self._update_stl_item_transform_matrix()
self.mesh_handler.update_rendered_object() #ignore_plane_slicing=True)
else:
self.delete_render()
[docs]
def delete_render(self):
""" Called on deletion of the module rendered objects """
super().delete_render()
self.mesh_handler.delete_rendered_object()
# --- Optionnal armature methods ---
# --- Armature specific attributes ---
def _update_stl_item_transform_matrix(self):
if 'transform_str' in self.armature_config_dict['_stl_mesh'] and self.armature_config_dict['_stl_mesh']['transform_str'] is not None:
transforms_matrices = af_tr_from_str.transform_matrices_from_str(self.armature_config_dict['_stl_mesh']['transform_str'])
else:
transforms_matrices = [np.eye(4)]
stl_item_tmat = np.eye(4)
for tr_mat in transforms_matrices:
stl_item_tmat = stl_item_tmat @ tr_mat
stl_item_tmat = stl_item_tmat @ self.end_transform_mat
self.mesh_handler.stl_item_tmat = stl_item_tmat
[docs]
class TrimeshScriptArmature(Armature):
_DEFAULT_PARAMS = {
'visible': False,
'tooltip_on_armature': False,
'rgba_color': (.6, .6, .6, .7),
'glline_width': 5,
'armature_config_csts': {},
'uneval_armature_config_dict': {
'_stl_mesh': {
'file_path': 'None',
'transform_str': None,
'ignore_plane_slicing': True,
'ignore_anatomical_landmarks_calibration': True,
'gl_mesh_shader': None,
'gl_mesh_drawEdges': True,
'gl_mesh_drawFaces': False,
'gl_mesh_edgeColor': (.5, .5, .5, .7),
'gl_mesh_glOptions': 'opaque',
'gl_mesh_smooth': False,
'gl_mesh_edgeWidth': 5,
},
'_trimesh_script': """
outer_circle = trimesh.path.creation.circle(radius=tube_diameter/2, segments=64)
inner_circle = trimesh.path.creation.circle(radius=tube_diameter/2 - tube_thickness, segments=64)
path_2d = outer_circle + inner_circle
extrusion = path_2d.extrude(tube_length)
mesh = extrusion.to_mesh()
tmat = trimesh.transformations.compose_matrix(translate=[0, 0, z_offset])
mesh.apply_transform(tmat)
""",
'_trimesh_script_coords': {
'tube_diameter': {
'args': ['diameter', 0.01],
'_is_editable': True,
'_edit_increment': 0.0005,
'_param_label': 'Tube diameter',
'_color': 'grey',
'_unit': 'm'
},
'tube_thickness': {
'args': ['diameter', 0.001],
'_is_editable': True,
'_edit_increment': 0.0005,
'_param_label': 'Tube thickness',
'_color': 'grey',
'_unit': 'm'
},
'tube_length': {
'args': ['x', 0.02],
'_is_editable': True,
'_edit_increment': 0.0005,
'_param_label': 'Tube length',
'_color': 'z_BLUE',
'_unit': 'm'
},
'z_offset': {
'args': ['z', -0.01],
'_is_editable': True,
'_edit_increment': 0.0005,
'_param_label': 'Tube offset',
'_color': 'grey',
'_unit': 'm'
},
},
},
}
""" Default configuration parameters used when a parameter value is not yet cached """
def __init__(self, armature_display_name, parent_viewer, stereotax_frame_instance, **kwargs) -> None:
super().__init__(armature_display_name, parent_viewer, stereotax_frame_instance, **kwargs)
# Reset aramatures configuration dicts with default ones
# self.armature_config_csts = self._DEFAULT_PARAMS['armature_config_csts']
# self.uneval_armature_config_dict = self._DEFAULT_PARAMS['uneval_armature_config_dict']
self.mesh_handler = TrimeshScriptHandler(parent_viewer)
# --- Armature specific public attributes ---
[docs]
def add_render(self):
""" Called when populating the viewer with the module rendered objects """
super().add_render()
if '_trimesh_script' in self.armature_config_dict:
if '_trimesh_script_coords' in self.armature_config_dict:
self.mesh_handler.trimesh_script_constants_dict = {mask_param: mask_param_value['args'][1] for (mask_param, mask_param_value) in self.armature_config_dict['_trimesh_script_coords'].items()}
self.mesh_handler.trimesh_script = self.armature_config_dict['_trimesh_script']
self.mesh_handler.stl_item_name = 'trimesh_scripted_mesh'
self._is_render_uptodate # Init hash
self._update_stl_item_transform_matrix()
# Set StlHandler gl parameters
armature_dict_stl_params = self.uneval_armature_config_dict['_stl_mesh']
for stl_param_key in self.mesh_handler._DEFAULT_PARAMS.keys():
if stl_param_key in armature_dict_stl_params:
self.mesh_handler.set_user_param(stl_param_key, armature_dict_stl_params[stl_param_key])
if self.mesh_handler.stl_glitem != None or self.visible is False:
self.mesh_handler.delete_rendered_object()
else:
self.mesh_handler.add_rendered_object()
[docs]
def update_render(self, force_update=False):
""" Called on render view updates """
if not self._is_render_uptodate or force_update:
super().update_render(force_update=True)
if self.visible is True:
if self.mesh_handler.stl_glitem is None:
self.add_render()
self._update_stl_item_transform_matrix()
self.mesh_handler.update_rendered_object()
else:
self.delete_render()
[docs]
def delete_render(self):
""" Called on deletion of the module rendered objects """
super().delete_render()
self.mesh_handler.delete_rendered_object()
# --- Optionnal armature methods ---
# --- Armature specific attributes ---
def _update_stl_item_transform_matrix(self):
if 'transform_str' in self.armature_config_dict['_stl_mesh'] and self.armature_config_dict['_stl_mesh']['transform_str'] is not None:
transforms_matrices = af_tr_from_str.transform_matrices_from_str(self.armature_config_dict['_stl_mesh']['transform_str'])
else:
transforms_matrices = [np.eye(4)]
stl_item_tmat = np.eye(4)
for tr_mat in transforms_matrices:
stl_item_tmat = stl_item_tmat @ tr_mat
stl_item_tmat = stl_item_tmat @ self.end_transform_mat
self.mesh_handler.stl_item_tmat = stl_item_tmat
[docs]
class STLMeshBooleanArmature(STLMeshArmature):
_DEFAULT_PARAMS = {
'visible': False,
'tooltip_on_armature': False,
'rgba_color': (.6, .6, .6, .7),
'glline_width': 5,
'armature_config_csts': {},
'uneval_armature_config_dict': {
'_stl_mesh': {
'file_path': 'Pohl2013_coarse.stl',
'transform_str': 'S.11 Rz90deg Tz-31.9mm Tx46mm Ry3.7deg',
'ignore_plane_slicing': False,
'ignore_anatomical_landmarks_calibration': False,
'gl_mesh_shader': 'boneShader',
'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': 2
},
'_boolean_mask': {
'transform_str': 'Tz2mm',
'ignore_plane_slicing': True,
'_boolean_operations': ['difference', ['_stl_mesh', '_boolean_mask']],
'_mask_preview_gl_options': {
'gl_mesh_shader': None,
'gl_mesh_drawEdges': True,
'gl_mesh_drawFaces': False,
'gl_mesh_edgeColor': (0.5, 0, 0, 1),
'gl_mesh_glOptions': 'opaque',
'gl_mesh_smooth': False,
'gl_mesh_edgeWidth': 5
},
'_boolean_mask_trimesh_script': """
a_AP_bregref = a_AP - bregma_AP
b_ML_bregref = b_ML - bregma_ML
c_AP_bregref = c_AP - bregma_AP
d_ML_bregref = d_ML - bregma_ML
mid_ML = (b_ML_bregref + d_ML_bregref) / 2
corner_radius = abs(b_ML_bregref - d_ML_bregref) / 2
path_2d_dict = {
'entities': [
{'type': 'Arc', 'points': [0, 1, 2], 'closed': False},
{'type': 'Line', 'points': [2, 3], 'closed': False},
{'type': 'Arc', 'points': [3, 4, 5], 'closed': False},
{'type': 'Arc', 'points': [5, 6, 7], 'closed': False},
{'type': 'Line', 'points': [7, 8], 'closed': False},
{'type': 'Arc', 'points': [8, 9, 0], 'closed': False}
],
'vertices': [
[a_AP_bregref, mid_ML],
[a_AP_bregref - corner_radius * (1 - np.cos(np.pi/4)), mid_ML + corner_radius * np.cos(np.pi/4)],
[a_AP_bregref - corner_radius, d_ML_bregref],
[c_AP_bregref + corner_radius, d_ML_bregref],
[c_AP_bregref + corner_radius * (1 - np.cos(np.pi/4)), mid_ML + corner_radius * np.cos(np.pi/4)],
[c_AP_bregref, mid_ML],
[c_AP_bregref + corner_radius * (1 - np.cos(np.pi/4)), mid_ML - corner_radius * np.cos(np.pi/4)],
[c_AP_bregref + corner_radius, b_ML_bregref],
[a_AP_bregref - corner_radius, b_ML_bregref],
[a_AP_bregref - corner_radius * (1 - np.cos(np.pi/4)), mid_ML - corner_radius * np.cos(np.pi/4)],
]
}
path_2d_from_dict = trimesh.path.exchange.load.load_path(
dict_to_path(path_2d_dict)
)
extrusion = path_2d_from_dict.extrude(extrude_length)
mesh = extrusion.to_mesh()
""",
'_boolean_mask_coords': {
'bregma_AP': {
'args': ['x', 0.0684],
'_is_editable': True,
'_edit_increment': 0.0005,
'_param_label': 'Bregma AP',
'_color': 'grey',
'_unit': 'm'
},
'bregma_ML': {
'args': ['x', 0.019100000000000002],
'_is_editable': True,
'_edit_increment': 0.0005,
'_param_label': 'Bregma ML',
'_color': 'grey',
'_unit': 'm'
},
'a_AP': {
'args': ['x', 0.0673],
'_is_editable': True,
'_edit_increment': 0.0001,
'_param_label': 'Ac. window (a)',
'_color': 'x_RED',
'_unit': 'm'
},
'b_ML': {
'args': ['y', 0.014400000000000001],
'_is_editable': True,
'_edit_increment': 0.0001,
'_param_label': 'Ac. window (b)',
'_color': 'y_GREEN',
'_unit': 'm'
},
'c_AP': {
'args': ['x', 0.06],
'_is_editable': True,
'_edit_increment': 0.0001,
'_param_label': 'Ac. window (c)',
'_color': 'x_RED',
'_unit': 'm'
},
'd_ML': {
'args': ['y', 0.0185],
'_is_editable': True,
'_edit_increment': 0.0001,
'_param_label': 'Ac. window (d)',
'_color': 'y_GREEN',
'_unit': 'm'
},
'extrude_length': {
'args': ['x', -0.004],
'_is_editable': True,
'_edit_increment': 0.0005,
'_param_label': 'Ac. window extrude length',
'_color': 'grey',
'_unit': 'm'
}
}
}
}
}
""" Default configuration parameters used when a parameter value is not yet cached """
def __init__(self, armature_display_name, parent_viewer, stereotax_frame_instance, **kwargs) -> None:
super().__init__(armature_display_name, parent_viewer, stereotax_frame_instance, **kwargs)
# Reset aramatures configuration dicts with default ones
# self.armature_config_csts = self._DEFAULT_PARAMS['armature_config_csts']
# self.uneval_armature_config_dict = self._DEFAULT_PARAMS['uneval_armature_config_dict']
self.bool_mask_mesh_handler = TrimeshHandler(parent_viewer)
self._bmask_mesh = None
self._current_mesh_bmask_params = None
@property
def bmask_mesh(self): # TODO use trimesh scripted interface
""" Mesh object to be used as a boolean mask.
The mesh is defined as a trimesh script in armature_config_dict['_boolean_mask']['_boolean_mask_trimesh_script']. Uppon execution, the script should define a 'mesh' object.
"""
has_been_updated = False
if '_boolean_mask' in self.armature_config_dict and '_boolean_mask_trimesh_script' in self.armature_config_dict['_boolean_mask']:
# Retreive boolean mask param values
if '_boolean_mask_coords' in self.armature_config_dict['_boolean_mask']:
bool_mask_params = {mask_param: mask_param_value['args'][1] for (mask_param, mask_param_value) in self.armature_config_dict['_boolean_mask']['_boolean_mask_coords'].items()}
else:
bool_mask_params = {}
# Reset mesh if parameters have been updated
if self._current_mesh_bmask_params != bool_mask_params:
self._bmask_mesh = None
if self._bmask_mesh is None:
accessible_globals_names = [
'trimesh', 'np',
'dict_to_path_patched' # Depricated in v0.1.1
]
accessible_globals = {
accessible_glob_name: globals()[accessible_glob_name]
for accessible_glob_name in accessible_globals_names
}
accessible_globals = {
**accessible_globals, **bool_mask_params,
'dict_to_path': trimesh.path.exchange.misc.dict_to_path
}
# run trimesh script
try:
exec(self.armature_config_dict['_boolean_mask']['_boolean_mask_trimesh_script'], accessible_globals)
self._bmask_mesh = accessible_globals['mesh']
self._current_mesh_bmask_params = bool_mask_params
has_been_updated = True
except Exception as e:
self._bmask_mesh = None
self._current_mesh_bmask_params = None
has_been_updated = False
self.parent_viewer.show_error_popup(f"Error in {self.armature_display_name} _boolean_mask_trimesh_script", f'{type(e).__name__}: {str(e)}')
else:
self._bmask_mesh = None
return (self._bmask_mesh, has_been_updated)
[docs]
def compute_boolean_operation(self):
""" Supported boolean operations:
- difference
- intersection
- union
"""
def compute_b_operation(boperator_str, boolean_meshes_names, bool_mesh_index):
b_mesh = None
b_meshes = []
for b_mesh_name in boolean_meshes_names:
if b_mesh_name == '_stl_mesh':
self.mesh_handler.stl_item_mesh_processed = None # Reset boolean operations
b_mesh = self.mesh_handler.stl_item_mesh
elif b_mesh_name == '_boolean_mask':
self.bool_mask_mesh_handler.stl_item_mesh_processed = None # Reset boolean operations
b_mesh = self.bool_mask_mesh_handler.stl_item_mesh
elif b_mesh_name in self.stereotax_frame_instance.armatures_objects:
arma_obj = self.stereotax_frame_instance.armatures_objects[b_mesh_name]
if hasattr(arma_obj, 'mesh_handler') and arma_obj.mesh_handler.stl_item_mesh is not None:
b_mesh = copy.deepcopy(arma_obj.mesh_handler.stl_item_mesh)
else:
raise ValueError(f'Unsupported boolean mesh from armature -> {b_mesh_name}')
else:
raise ValueError(f'Unsupported boolean mesh -> {b_mesh_name}')
if b_mesh is not None:
b_mesh = ensure_mesh_is_a_volume_manifold(b_mesh)
b_meshes.append(b_mesh)
else:
available_stl_armatures_formated = "\n\t".join([arma_obj_name for arma_obj_name, arma_obj in self.stereotax_frame_instance.armatures_objects.items() if '_stl_mesh' in arma_obj.armature_config_dict])
warnings.warn(f'Skipping {b_mesh_name} as it does not exist -> Please make sure that the mesh has been succesfully loaded or computed in the case of trimesh operations.\nAvaiblable meshes are:\n\t_stl_mesh\n\t_boolean_mask{available_stl_armatures_formated}')
if boperator_str in b_operators:
boolean_computed_mesh = b_operators[boperator_str](b_meshes, engine='manifold')
boolean_computed_mesh.bool_mesh_index = bool_mesh_index # Add index attribute (acoustic simulations material assignement)
else:
raise ValueError('Invalid boolean operator')
return boolean_computed_mesh
print('Applying boolean operation...')
b_operators = {
'difference': trimesh.boolean.difference,
'intersection': trimesh.boolean.intersection,
'union': trimesh.boolean.union
}
boolean_operations = self.armature_config_dict['_boolean_mask']['_boolean_operations']
if isinstance(boolean_operations, list) and len(boolean_operations) == 2:
boperator_str, boolean_meshes_names = boolean_operations
boolean_computed_meshes = compute_b_operation(boperator_str, boolean_meshes_names, bool_mesh_index=0)
elif isinstance(boolean_operations, dict):
boolean_computed_meshes = []
for op_ii, (boperator_str, boolean_meshes_names) in boolean_operations.items():
boolean_computed_meshes.append(compute_b_operation(boperator_str, boolean_meshes_names, bool_mesh_index=op_ii))
else:
raise ValueError('Invalid boolean operation instruction')
# Add mesh(es) to viewer
if self.mesh_handler.stl_item_name is None: # If mesh_handler did not exist
# Assign available mesh_handler name
mesh_item_index = 0
mesh_item_name_formater = lambda ii: f'boolean_op_result_mesh_{ii}'
mesh_handler_child_attributes = [child_att for child_att in self.parent_viewer.cache.get_attr_unique_childs('mesh_handler') if 'boolean_op_result_mesh' in child_att]
while mesh_item_name_formater(mesh_item_index) in mesh_handler_child_attributes:
mesh_item_index += 1
self.mesh_handler.stl_item_name = mesh_item_name_formater(mesh_item_index)
# Set StlHandler gl parameters
armature_dict_stl_params = self.uneval_armature_config_dict['_stl_mesh']
for stl_param_key in self.mesh_handler._DEFAULT_PARAMS.keys():
if stl_param_key in armature_dict_stl_params:
self.mesh_handler.set_user_param(stl_param_key, armature_dict_stl_params[stl_param_key])
self.mesh_handler.stl_item_mesh_processed = boolean_computed_meshes
self.mesh_handler.add_rendered_object()
else:
self.mesh_handler.stl_item_mesh_processed = boolean_computed_meshes
self.mesh_handler.update_rendered_object()
print('Done')
# --- Required armature attributes ---
[docs]
def add_render(self):
""" Called when populating the viewer with the module rendered objects """
super().add_render()
if '_boolean_mask' in self.armature_config_dict:
if self.bmask_mesh is not None:
self.bool_mask_mesh_handler.stl_item_name = 'boolean_mask_mesh'
self.bool_mask_mesh_handler.raw_stl_item_mesh = self.bmask_mesh[0]
self._is_render_uptodate # Init hash
self._update_boolean_mask_transform_matrix()
# Set StlHandler gl parameters
armature_dict_bmask_params = self.uneval_armature_config_dict['_boolean_mask']['_mask_preview_gl_options']
for bmask_param_key in self.bool_mask_mesh_handler._DEFAULT_PARAMS.keys():
if bmask_param_key in armature_dict_bmask_params:
self.bool_mask_mesh_handler.set_user_param(bmask_param_key, armature_dict_bmask_params[bmask_param_key])
if self.bool_mask_mesh_handler.stl_glitem != None or self.visible is False:
self.bool_mask_mesh_handler.delete_rendered_object()
else:
self.bool_mask_mesh_handler.add_rendered_object()
[docs]
def update_render(self, force_update=False):
""" Called on render view updates """
if not self._is_render_uptodate or force_update:
super().update_render(force_update=True)
if self.visible is True:
if self.bool_mask_mesh_handler.stl_glitem is None:
self.add_render()
self._update_boolean_mask_transform_matrix()
if self.bmask_mesh[1]: # Check if the mesh has been updated
self.bool_mask_mesh_handler.raw_stl_item_mesh = self.bmask_mesh[0]
self.bool_mask_mesh_handler.update_rendered_object() #ignore_plane_slicing=True)
else:
self.delete_render()
[docs]
def delete_render(self):
""" Called on deletion of the module rendered objects """
super().delete_render()
self.bool_mask_mesh_handler.delete_rendered_object()
# --- Optionnal armature methods ---
# --- Armature specific attributes ---
def _update_boolean_mask_transform_matrix(self):
if ('_boolean_mask' in self.armature_config_dict) and ('transform_str' in self.armature_config_dict['_boolean_mask']) and (self.armature_config_dict['_boolean_mask']['transform_str'] is not None):
transforms_matrices = af_tr_from_str.transform_matrices_from_str(self.armature_config_dict['_boolean_mask']['transform_str'])
else:
transforms_matrices = [np.eye(4)]
bmask_tmat = self.end_transform_mat
for tr_mat in transforms_matrices:
bmask_tmat = bmask_tmat @ tr_mat
self.bool_mask_mesh_handler.stl_item_tmat = bmask_tmat
[docs]
class STLMeshConvexHull(STLMeshArmature): # Armature
_DEFAULT_PARAMS = {
'visible': False,
'tooltip_on_armature': False,
'rgba_color': (.6, .6, .6, .7),
'glline_width': 5,
'armature_config_csts': {},
'uneval_armature_config_dict': {
'_stl_mesh': {
'file_path': 'None',
'transform_str': 'S1 Rx0deg Tz0mm',
'ignore_plane_slicing': False,
'gl_mesh_shader': None,
'gl_mesh_drawEdges': True,
'gl_mesh_drawFaces': False,
'gl_mesh_edgeColor': (.5, .5, .5, .7),
'gl_mesh_glOptions': 'opaque',
'gl_mesh_smooth': False,
'gl_mesh_edgeWidth': 5},
'_convex_hull': {
'_src_mesh': 'Skull acoustic window',
'ignore_plane_slicing': True,
'_mask_preview_gl_options': {
'gl_mesh_shader': None,
'gl_mesh_drawEdges': True,
'gl_mesh_drawFaces': False,
'gl_mesh_edgeColor': (.5, 0, 0, 1),
'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, armature_display_name, parent_viewer, stereotax_frame_instance, **kwargs) -> None:
super().__init__(armature_display_name, parent_viewer, stereotax_frame_instance, **kwargs)
# Reset aramatures configuration dicts with default ones
# self.armature_config_csts = self._DEFAULT_PARAMS['armature_config_csts']
# self.uneval_armature_config_dict = self._DEFAULT_PARAMS['uneval_armature_config_dict']
self._hull_mesh_handler = TrimeshHandler(parent_viewer)
self._hull_mesh = None
# --- Armature specific public attributes ---
[docs]
def compute_convex_hull(self):
""" Implementaion of https://trimesh.org/trimesh.base.html#trimesh.base.Trimesh.convex_hull """
convex_hull_src_mesh_name = self.armature_config_dict['_convex_hull']['_src_mesh']
scr_mesh = None
if convex_hull_src_mesh_name == '_stl_mesh':
self.mesh_handler.stl_item_mesh_processed = None # Reset previous trimesh operations
scr_mesh = self.mesh_handler.stl_item_mesh
elif convex_hull_src_mesh_name in self.stereotax_frame_instance.armatures_objects:
arma_obj = self.stereotax_frame_instance.armatures_objects[convex_hull_src_mesh_name]
if hasattr(arma_obj, 'mesh_handler') and arma_obj.mesh_handler.stl_item_mesh is not None:
scr_mesh = copy.deepcopy(arma_obj.mesh_handler.stl_item_mesh)
else:
raise ValueError(f'Convex Hull: Unsupported mesh from armature -> {convex_hull_src_mesh_name}\nMake sure that mesh_handler.stl_item_mesh is valid on the targetted armature.')
else:
raise ValueError(f'Convex Hull: Unsupported mesh -> {convex_hull_src_mesh_name}')
if scr_mesh is not None:
convex_hull_mesh = scr_mesh.convex_hull
else:
raise ValueError(f'Convex Hull: Unsupported mesh -> {convex_hull_src_mesh_name}')
# Add mesh to viewer
if self.mesh_handler.stl_item_name is None: # If mesh_handler did not exist
# Assign available mesh_handler name
mesh_item_index = 0
mesh_item_name_formater = lambda ii: f'convex_hull_result_mesh_{ii}'
mesh_handler_child_attributes = [child_att for child_att in self.parent_viewer.cache.get_attr_unique_childs('mesh_handler') if 'convex_hull_result_mesh' in child_att]
while mesh_item_name_formater(mesh_item_index) in mesh_handler_child_attributes:
mesh_item_index += 1
self.mesh_handler.stl_item_name = mesh_item_name_formater(mesh_item_index)
# Set StlHandler gl parameters
armature_dict_stl_params = self.uneval_armature_config_dict['_stl_mesh']
for stl_param_key in self.mesh_handler._DEFAULT_PARAMS.keys():
if stl_param_key in armature_dict_stl_params:
self.mesh_handler.set_user_param(stl_param_key, armature_dict_stl_params[stl_param_key])
self.mesh_handler.stl_item_mesh_processed = convex_hull_mesh
self.mesh_handler.add_rendered_object()
else:
self.mesh_handler.stl_item_mesh_processed = convex_hull_mesh
self.mesh_handler.update_rendered_object()
# --- Optionnal armature methods ---