diff --git a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/analysis/extract_data.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/analysis/extract_data.py index b4daaf8..eea08fa 100644 --- a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/analysis/extract_data.py +++ b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/analysis/extract_data.py @@ -1,5 +1,6 @@ import typing as typ +import enum import bpy import jax import jax.numpy as jnp @@ -15,7 +16,12 @@ log = logger.get(__name__) class ExtractDataNode(base.MaxwellSimNode): - """Node for extracting data from particular objects.""" + """Node for extracting data from particular objects. + + Attributes: + extract_filter: Identifier for data to extract from the input. + + """ node_type = ct.NodeType.ExtractData bl_label = 'Extract' @@ -32,11 +38,10 @@ class ExtractDataNode(base.MaxwellSimNode): #################### # - Properties #################### - extract_filter: bpy.props.StringProperty( - name='Extract Filter', - description='Data to extract from the input', - search=lambda self, _, edit_text: self.search_extract_filters(edit_text), - update=lambda self, context: self.on_prop_changed('extract_filter', context), + extract_filter: enum.Enum = bl_cache.BLField( + None, + prop_ui=True, + enum_cb=lambda self, _: self.search_extract_filters(), ) # Sim Data @@ -49,41 +54,30 @@ class ExtractDataNode(base.MaxwellSimNode): #################### # - Computed Properties #################### - @bl_cache.cached_bl_property(persist=False) + @property def has_sim_data(self) -> bool: - return ( - self.active_socket_set == 'Sim Data' - and self.inputs['Sim Data'].is_linked - and self.sim_data_monitor_nametype - ) + return self.active_socket_set == 'Sim Data' and self.sim_data_monitor_nametype - @bl_cache.cached_bl_property(persist=False) + @property def has_monitor_data(self) -> bool: - return ( - self.active_socket_set == 'Monitor Data' - and self.inputs['Monitor Data'].is_linked - and self.monitor_data_type - ) + return self.active_socket_set == 'Monitor Data' and self.monitor_data_type #################### # - Extraction Filter Search #################### - def search_extract_filters(self, edit_text: str) -> list[tuple[str, str, str]]: + def search_extract_filters(self) -> list[ct.BLEnumElement]: if self.has_sim_data: return [ - ( - monitor_name, - monitor_type.removesuffix('Data'), + (monitor_name, monitor_name, monitor_type.removesuffix('Data'), '', i) + for i, (monitor_name, monitor_type) in enumerate( + self.sim_data_monitor_nametype.items() ) - for monitor_name, monitor_type in self.sim_data_monitor_nametype.items() - if edit_text == '' or edit_text.lower() in monitor_name.lower() ] if self.has_monitor_data: return [ - (component_name, f'ā„‚ {component_name[1]}-Pol') - for component_name in self.monitor_data_components - if (edit_text == '' or edit_text.lower() in component_name.lower()) + (component_name, component_name, f'ā„‚ {component_name[1]}-Pol', '', i) + for i, component_name in enumerate(self.monitor_data_components) ] return [] @@ -92,7 +86,7 @@ class ExtractDataNode(base.MaxwellSimNode): # - UI #################### def draw_props(self, _: bpy.types.Context, col: bpy.types.UILayout) -> None: - col.prop(self, 'extract_filter', text='') + col.prop(self, self.blfields['extract_filter'], text='') def draw_info(self, _: bpy.types.Context, col: bpy.types.UILayout) -> None: if self.has_sim_data or self.has_monitor_data: @@ -108,7 +102,9 @@ class ExtractDataNode(base.MaxwellSimNode): row = col.row() box = row.box() grid = box.grid_flow(row_major=True, columns=2, even_columns=True) - for name, desc in self.search_extract_filters(edit_text=''): + for name, desc in [ + (name, desc) for idname, name, desc, *_ in self.search_extract_filters() + ]: grid.label(text=name) grid.label(text=desc if desc else '') @@ -120,6 +116,7 @@ class ExtractDataNode(base.MaxwellSimNode): prop_name='active_socket_set', input_sockets={'Sim Data', 'Monitor Data'}, input_sockets_optional={'Sim Data': True, 'Monitor Data': True}, + run_on_init=True, ) def on_sim_data_changed(self, input_sockets: dict): if input_sockets['Sim Data'] is not None: @@ -130,6 +127,8 @@ class ExtractDataNode(base.MaxwellSimNode): 'Sim Data' ].monitor_data.items() } + elif self.sim_data_monitor_nametype: + self.sim_data_monitor_nametype = {} if input_sockets['Monitor Data'] is not None: # Monitor Data Type @@ -168,18 +167,14 @@ class ExtractDataNode(base.MaxwellSimNode): 'Htheta', 'Hphi', ] + else: + if self.monitor_data_type: + self.monitor_data_type = '' + if self.monitor_data_components: + self.monitor_data_components = [] # Invalidate Computed Property Caches - self.has_sim_data = bl_cache.Signal.InvalidateCache - self.has_monitor_data = bl_cache.Signal.InvalidateCache - - # Reset Extraction Filter - ## The extraction filter that was set before may not be valid anymore. - ## If so, simply remove it. - if self.extract_filter not in [ - el[0] for el in self.search_extract_filters(edit_text='') - ]: - self.extract_filter = '' + self.extract_filter = bl_cache.Signal.ResetEnumItems #################### # - Output: Value @@ -225,16 +220,22 @@ class ExtractDataNode(base.MaxwellSimNode): kind=ct.FlowKind.Info, props={'monitor_data_type', 'extract_filter'}, input_sockets={'Monitor Data'}, + input_socket_kinds={'Monitor Data': ct.FlowKind.Value}, ) def compute_extracted_data_info( self, props: dict, input_sockets: dict - ) -> ct.InfoFlow: # noqa: PLR0911 - if input_sockets['Monitor Data'] is None or not props['extract_filter']: + ) -> ct.InfoFlow: + # Retrieve XArray + if ( + input_sockets['Monitor Data'] is not None + and props['extract_filter'] != 'NONE' + ): + xarr = getattr(input_sockets['Monitor Data'], props['extract_filter']) + else: return ct.InfoFlow() - xarr = getattr(input_sockets['Monitor Data'], props['extract_filter']) - - # XYZF: Field / Permittivity / FieldProjectionCartesian + # Compute InfoFlow from XArray + ## XYZF: Field / Permittivity / FieldProjectionCartesian if props['monitor_data_type'] in { 'Field', 'Permittivity', @@ -257,7 +258,7 @@ class ExtractDataNode(base.MaxwellSimNode): }, ) - # XYZT: FieldTime + ## XYZT: FieldTime if props['monitor_data_type'] == 'FieldTime': return ct.InfoFlow( dim_names=['x', 'y', 'z', 't'], @@ -276,7 +277,7 @@ class ExtractDataNode(base.MaxwellSimNode): }, ) - # F: Flux + ## F: Flux if props['monitor_data_type'] == 'Flux': return ct.InfoFlow( dim_names=['f'], @@ -289,7 +290,7 @@ class ExtractDataNode(base.MaxwellSimNode): }, ) - # T: FluxTime + ## T: FluxTime if props['monitor_data_type'] == 'FluxTime': return ct.InfoFlow( dim_names=['t'], @@ -302,7 +303,7 @@ class ExtractDataNode(base.MaxwellSimNode): }, ) - # RThetaPhiF: FieldProjectionAngle + ## RThetaPhiF: FieldProjectionAngle if props['monitor_data_type'] == 'FieldProjectionAngle': return ct.InfoFlow( dim_names=['r', 'theta', 'phi', 'f'], @@ -328,7 +329,7 @@ class ExtractDataNode(base.MaxwellSimNode): }, ) - # UxUyRF: FieldProjectionKSpace + ## UxUyRF: FieldProjectionKSpace if props['monitor_data_type'] == 'FieldProjectionKSpace': return ct.InfoFlow( dim_names=['ux', 'uy', 'r', 'f'], @@ -352,7 +353,7 @@ class ExtractDataNode(base.MaxwellSimNode): }, ) - # OrderxOrderyF: Diffraction + ## OrderxOrderyF: Diffraction if props['monitor_data_type'] == 'Diffraction': return ct.InfoFlow( dim_names=['orders_x', 'orders_y', 'f'], diff --git a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/analysis/math/filter_math.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/analysis/math/filter_math.py index 2147fd6..66a21be 100644 --- a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/analysis/math/filter_math.py +++ b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/analysis/math/filter_math.py @@ -43,8 +43,8 @@ class FilterMathNode(base.MaxwellSimNode): prop_ui=True, enum_cb=lambda self, _: self.search_operations() ) - dim: str = bl_cache.BLField( - '', prop_ui=True, str_cb=lambda self, _, edit_text: self.search_dims(edit_text) + dim: enum.Enum = bl_cache.BLField( + None, prop_ui=True, enum_cb=lambda self, _: self.search_dims() ) dim_names: list[str] = bl_cache.BLField([]) @@ -64,22 +64,24 @@ class FilterMathNode(base.MaxwellSimNode): ('FIX', 'del a | iā‰ˆv', 'Fix Coordinate'), ] - return items + return [(*item, '', i) for i, item in enumerate(items)] #################### # - Dim Search #################### - def search_dims(self, edit_text: str) -> list[tuple[str, str, str]]: + def search_dims(self) -> list[ct.BLEnumElement]: if self.dim_names: dims = [ - (dim_name, dim_name) - for dim_name in self.dim_names - if edit_text == '' or edit_text.lower() in dim_name.lower() + (dim_name, dim_name, dim_name, '', i) + for i, dim_name in enumerate(self.dim_names) ] # Squeeze: Dimension Must Have Length=1 + ## We must also correct the "NUMBER" of the enum. if self.operation == 'SQUEEZE': - return [dim for dim in dims if self.dim_lens[dim[0]] == 1] + filtered_dims = [dim for dim in dims if self.dim_lens[dim[0]] == 1] + return [(*dim[:-1], i) for i, dim in enumerate(filtered_dims)] + return dims return [] @@ -107,6 +109,7 @@ class FilterMathNode(base.MaxwellSimNode): input_sockets={'Data'}, input_socket_kinds={'Data': ct.FlowKind.Info}, input_sockets_optional={'Data': True}, + run_on_init=True, ) def on_any_change(self, props: dict, input_sockets: dict): # Set Dimension Names from InfoFlow @@ -121,7 +124,7 @@ class FilterMathNode(base.MaxwellSimNode): self.dim_lens = {} # Reset String Searcher - self.dim = bl_cache.Signal.ResetStrSearch + self.dim = bl_cache.Signal.ResetEnumItems @events.on_value_changed( prop_name='dim', @@ -132,10 +135,7 @@ class FilterMathNode(base.MaxwellSimNode): ) def on_dim_change(self, props: dict, input_sockets: dict): # Add/Remove Input Socket "Value" - if ( - props['active_socket_set'] == 'By Dim Value' - and props['dim'] in input_sockets['Data'].dim_names - ): + if props['active_socket_set'] == 'By Dim Value' and props['dim'] != 'NONE': # Get Current and Wanted Socket Defs current_socket_def = self.loose_input_sockets.get('Value') wanted_socket_def = sockets.SOCKET_DEFS[ @@ -167,7 +167,7 @@ class FilterMathNode(base.MaxwellSimNode): # Compute Bound/Free Parameters func_args = [int] if props['active_socket_set'] == 'By Dim Value' else [] - if props['dim']: + if props['dim'] != 'NONE': axis = info.dim_names.index(props['dim']) else: msg = 'Dimension cannot be empty' @@ -225,7 +225,7 @@ class FilterMathNode(base.MaxwellSimNode): # Compute Bound/Free Parameters ## Empty Dimension -> Empty InfoFlow - if props['dim']: + if props['dim'] != 'NONE': axis = info.dim_names.index(props['dim']) else: return ct.InfoFlow() @@ -272,7 +272,7 @@ class FilterMathNode(base.MaxwellSimNode): in [ ('By Dim Value', 'FIX'), ] - and props['dim'] + and props['dim'] != 'NONE' and input_sockets['Value'] is not None ): # Compute IDX Corresponding to Coordinate Value diff --git a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/base.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/base.py index c5f2ac7..85f0afc 100644 --- a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/base.py +++ b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/base.py @@ -1034,6 +1034,16 @@ class MaxwellSimNode(bpy.types.Node): ## Blender will automatically add .001 so that `self.name` is unique. self.sim_node_name = self.name + # Event Methods + ## Run any 'DataChanged' methods with 'run_on_init' set. + ## -> Copying a node _arguably_ re-initializes the new node. + for event_method in [ + event_method + for event_method in self.event_methods_by_event[ct.FlowEvent.DataChanged] + if event_method.callback_info.run_on_init + ]: + event_method(self) + def free(self) -> None: """Cleans various instance-associated data up, so the node can be cleanly deleted. diff --git a/src/blender_maxwell/nodeps/utils/image_ops.py b/src/blender_maxwell/nodeps/utils/image_ops.py new file mode 100644 index 0000000..e69de29