fix: BLFields in FilterMath, bug fixes.

main
Sofus Albert Høgsbro Rose 2024-04-23 09:30:26 +02:00
parent 44d61b5639
commit f09b58e0e7
Signed by: so-rose
GPG Key ID: AD901CB0F3701434
6 changed files with 100 additions and 62 deletions

View File

@ -24,7 +24,7 @@ class FlowEvent(enum.StrEnum):
ShowPlot: Indicates that the node/socket should enable its plotted preview.
This should generally be used if the node is rendering to an image, for viewing through the Blender image editor.
LinkChanged: Indicates that a link to a node/socket was added/removed.
In nodes, this is accompanied by a `socket_name` to indicate which socket it is that had its links altered.
Is translated to `DataChanged` on sockets before propagation.
DataChanged: Indicates that data flowing through a node/socket was altered.
In nodes, this event is accompanied by a `socket_name` or `prop_name`, to indicate which socket/property it is that was changed.
**This event is essential**, as it invalidates all input/output socket caches along its path.

View File

@ -195,7 +195,7 @@ class ExtractDataNode(base.MaxwellSimNode):
@events.computes_output_socket(
'Data',
kind=ct.FlowKind.Value,
kind=ct.FlowKind.Array,
props={'extract_filter'},
input_sockets={'Monitor Data'},
)
@ -210,7 +210,7 @@ class ExtractDataNode(base.MaxwellSimNode):
'Data',
kind=ct.FlowKind.LazyValueFunc,
output_sockets={'Data'},
output_socket_kinds={'Data': ct.FlowKind.Value},
output_socket_kinds={'Data': ct.FlowKind.Array},
)
def compute_extracted_data_lazy(self, output_sockets: dict) -> ct.LazyValueFuncFlow:
return ct.LazyValueFuncFlow(

View File

@ -1,3 +1,4 @@
import enum
import typing as typ
import bpy
@ -14,6 +15,13 @@ log = logger.get(__name__)
class FilterMathNode(base.MaxwellSimNode):
"""Reduces the dimensionality of data.
Attributes:
operation: Operation to apply to the input.
dim: Dims to use when filtering data
"""
node_type = ct.NodeType.FilterMath
bl_label = 'Filter Math'
@ -31,32 +39,17 @@ class FilterMathNode(base.MaxwellSimNode):
####################
# - Properties
####################
operation: bpy.props.EnumProperty(
name='Op',
description='Operation to filter with',
items=lambda self, _: self.search_operations(),
update=lambda self, context: self.on_prop_changed('operation', context),
operation: enum.Enum = bl_cache.BLField(
prop_ui=True, enum_cb=lambda self, _: self.search_operations()
)
dim: bpy.props.StringProperty(
name='Dim',
description='Dims to use when filtering data',
default='',
search=lambda self, _, edit_text: self.search_dims(edit_text),
update=lambda self, context: self.on_prop_changed('dim', context),
dim: str = bl_cache.BLField(
'', prop_ui=True, str_cb=lambda self, _, edit_text: self.search_dims(edit_text)
)
dim_names: list[str] = bl_cache.BLField([])
dim_lens: dict[str, int] = bl_cache.BLField({})
@property
def has_dim(self) -> bool:
return (
self.active_socket_set in ['By Dim', 'By Dim Value']
and self.inputs['Data'].is_linked
and self.dim_names
)
####################
# - Operation Search
####################
@ -77,7 +70,7 @@ class FilterMathNode(base.MaxwellSimNode):
# - Dim Search
####################
def search_dims(self, edit_text: str) -> list[tuple[str, str, str]]:
if self.has_dim:
if self.dim_names:
dims = [
(dim_name, dim_name)
for dim_name in self.dim_names
@ -94,17 +87,23 @@ class FilterMathNode(base.MaxwellSimNode):
# - UI
####################
def draw_props(self, _: bpy.types.Context, layout: bpy.types.UILayout) -> None:
layout.prop(self, 'operation', text='')
if self.has_dim:
layout.prop(self, 'dim', text='')
layout.prop(self, self.blfields['operation'], text='')
if self.dim_names:
layout.prop(self, self.blfields['dim'], text='')
####################
# - Events
####################
@events.on_value_changed(
prop_name='active_socket_set',
)
def on_socket_set_changed(self):
self.operation = bl_cache.Signal.ResetEnumItems
@events.on_value_changed(
socket_name={'Data'},
prop_name={'active_socket_set', 'dim'},
props={'active_socket_set', 'dim'},
prop_name={'active_socket_set'},
props={'active_socket_set'},
input_sockets={'Data'},
input_socket_kinds={'Data': ct.FlowKind.Info},
input_sockets_optional={'Data': True},
@ -121,21 +120,32 @@ class FilterMathNode(base.MaxwellSimNode):
self.dim_names = []
self.dim_lens = {}
# Add Input Value w/Unit from InfoFlow
## Socket Type is determined from the Unit
# Reset String Searcher
self.dim = bl_cache.Signal.ResetStrSearch
@events.on_value_changed(
prop_name='dim',
props={'active_socket_set', 'dim'},
input_sockets={'Data'},
input_socket_kinds={'Data': ct.FlowKind.Info},
input_sockets_optional={'Data': True},
)
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'] != ''
and props['dim'] in input_sockets['Data'].dim_names
):
socket_def = sockets.SOCKET_DEFS[
# Get Current and Wanted Socket Defs
current_socket_def = self.loose_input_sockets.get('Value')
wanted_socket_def = sockets.SOCKET_DEFS[
ct.unit_to_socket_type(input_sockets['Data'].dim_idx[props['dim']].unit)
]
if (
_val_socket_def := self.loose_input_sockets.get('Value')
) is None or _val_socket_def != socket_def:
# Determine Whether to Declare New Loose Input SOcket
if current_socket_def is None or current_socket_def != wanted_socket_def:
self.loose_input_sockets = {
'Value': socket_def(),
'Value': wanted_socket_def(),
}
elif self.loose_input_sockets:
self.loose_input_sockets = {}
@ -151,18 +161,18 @@ class FilterMathNode(base.MaxwellSimNode):
input_socket_kinds={'Data': {ct.FlowKind.LazyValueFunc, ct.FlowKind.Info}},
)
def compute_data(self, props: dict, input_sockets: dict):
# Retrieve Inputs
lazy_value_func = input_sockets['Data'][ct.FlowKind.LazyValueFunc]
info = input_sockets['Data'][ct.FlowKind.Info]
# Determine Bound/Free Parameters
if props['dim'] in info.dim_names:
# Compute Bound/Free Parameters
func_args = [int] if props['active_socket_set'] == 'By Dim Value' else []
if props['dim']:
axis = info.dim_names.index(props['dim'])
else:
msg = 'Dimension invalid'
msg = 'Dimension cannot be empty'
raise ValueError(msg)
func_args = [int] if props['active_socket_set'] == 'By Dim Value' else []
# Select Function
filter_func: typ.Callable[[jax.Array], jax.Array] = {
'By Dim': {'SQUEEZE': lambda data: jnp.squeeze(data, axis)},
@ -189,8 +199,11 @@ class FilterMathNode(base.MaxwellSimNode):
},
)
def compute_array(self, output_sockets: dict) -> ct.ArrayFlow:
# Retrieve Inputs
lazy_value_func = output_sockets['Data'][ct.FlowKind.LazyValueFunc]
params = output_sockets['Data'][ct.FlowKind.Params]
# Compute Array
return ct.ArrayFlow(
values=lazy_value_func.func_jax(*params.func_args, **params.func_kwargs),
unit=None, ## TODO: Unit Propagation
@ -207,14 +220,18 @@ class FilterMathNode(base.MaxwellSimNode):
input_socket_kinds={'Data': ct.FlowKind.Info},
)
def compute_data_info(self, props: dict, input_sockets: dict) -> ct.InfoFlow:
# Retrieve Inputs
info = input_sockets['Data']
if props['dim'] in info.dim_names:
# Compute Bound/Free Parameters
## Empty Dimension -> Empty InfoFlow
if props['dim']:
axis = info.dim_names.index(props['dim'])
else:
return ct.InfoFlow()
# Compute Axis
# Compute Information
## Compute Info w/By-Operation Change to Dimensions
if (props['active_socket_set'], props['operation']) in [
('By Dim', 'SQUEEZE'),
('By Dim Value', 'FIX'),
@ -228,6 +245,7 @@ class FilterMathNode(base.MaxwellSimNode):
},
)
# Fallback to Empty InfoFlow
return ct.InfoFlow()
@events.computes_output_socket(
@ -238,20 +256,30 @@ class FilterMathNode(base.MaxwellSimNode):
input_socket_kinds={'Data': {ct.FlowKind.Info, ct.FlowKind.Params}},
input_sockets_optional={'Value': True},
)
def compute_data_params(self, props: dict, input_sockets: dict) -> ct.ParamsFlow:
def compute_composed_params(
self, props: dict, input_sockets: dict
) -> ct.ParamsFlow:
# Retrieve Inputs
info = input_sockets['Data'][ct.FlowKind.Info]
params = input_sockets['Data'][ct.FlowKind.Params]
# Compute Composed Parameters
## -> Only operations that add parameters.
## -> A dimension must be selected.
## -> There must be an input value.
if (
(props['active_socket_set'], props['operation'])
in [
('By Dim Value', 'FIX'),
]
and props['dim'] in info.dim_names
and props['dim']
and input_sockets['Value'] is not None
):
# Compute IDX Corresponding to Value
## Aka. "indexing by a float"
# Compute IDX Corresponding to Coordinate Value
## -> Each dimension declares a unit-aware real number at each index.
## -> "Value" is a unit-aware real number from loose input socket.
## -> This finds the dimensional index closest to "Value".
## Total Effect: Indexing by a unit-aware real number.
nearest_idx_to_value = info.dim_idx[props['dim']].nearest_idx_of(
input_sockets['Value'], require_sorted=True
)
@ -269,6 +297,3 @@ BL_REGISTER = [
FilterMathNode,
]
BL_NODES = {ct.NodeType.FilterMath: (ct.NodeCategory.MAXWELLSIM_ANALYSIS_MATH)}
## TODO TODO Okay so just like, Value needs to be a Loose socket, events needs to be able to handle sets of kinds, the invalidator needs to be able to handle sets of kinds too. Given all that, we only need to propagate the output array unit; given all all that, we are 100% goddamn ready to fix that goddamn coordinate.

View File

@ -753,8 +753,8 @@ class MaxwellSimNode(bpy.types.Node):
Notes:
This can be an unpredictably heavy function, depending on the node graph topology.
Doesn't currently accept `LinkChanged` (->Output) events; rather, these propagate as `DataChanged` events.
**This may change** if it becomes important for the node to differentiate between "change in data" and "change in link".
Doesn't accept `LinkChanged` events; they are translated to `DataChanged` on the socket.
This is on purpose: It seems to be a bad idea to try and differentiate between "changes in data" and "changes in linkage".
Parameters:
event: The event to report forwards/backwards along the node tree.
@ -762,10 +762,12 @@ class MaxwellSimNode(bpy.types.Node):
pop_name: The property that was altered, if any, in order to trigger this event.
"""
# Outflow Socket Kinds
## Something has happened, that much is for sure.
## Output methods might require invalidation of (outsck, FlowKind)s.
## Whichever FlowKinds we do happen to invalidate, we should mark.
## This way, each FlowKind gets its own invalidation chain.
## -> Something has happened!
## -> The effect is yet to be determined...
## -> We will watch for which kinds actually invalidate.
## -> ...Then ONLY propagate kinds that have an invalidated outsck.
## -> This way, kinds get "their own" invalidation chains.
## -> ...While still respecting "crossovers".
altered_socket_kinds = set()
# Invalidate Caches on DataChanged
@ -869,7 +871,6 @@ class MaxwellSimNode(bpy.types.Node):
"""
if hasattr(self, prop_name):
# Invalidate UI BLField Caches
log.critical((prop_name, self.ui_blfields))
if prop_name in self.ui_blfields:
setattr(self, prop_name, bl_cache.Signal.InvalidateCache)

View File

@ -137,7 +137,7 @@ class ViewerNode(base.MaxwellSimNode):
props={'auto_plot'},
)
def on_changed_plot_preview(self, props):
if self.inputs['Any'].is_linked and props['auto_plot']:
if props['auto_plot']:
self.trigger_event(ct.FlowEvent.ShowPlot)
@events.on_value_changed(
@ -150,7 +150,7 @@ class ViewerNode(base.MaxwellSimNode):
# Remove Non-Repreviewed Previews on Close
with node_tree.repreview_all():
if self.inputs['Any'].is_linked and props['auto_3d_preview']:
if props['auto_3d_preview']:
self.trigger_event(ct.FlowEvent.ShowPreview)

View File

@ -712,9 +712,11 @@ class BLField:
kwargs_prop['options'].add('SKIP_SAVE')
if self._str_cb is not None:
kwargs_prop |= lambda _self, context, edit_text: self._safe_str_cb(
kwargs_prop |= {
'search': lambda _self, context, edit_text: self._safe_str_cb(
_self, context, edit_text
)
}
## Path
elif AttrType is Path:
@ -845,6 +847,16 @@ class BLField:
self._cached_bl_property.__set__(bl_instance, Signal.InvalidateCache)
elif value == Signal.ResetStrSearch:
# Set String to ''
## Prevents the presence of an invalid value not in the new search.
## -> Infinite recursion if we don't check current value for ''.
## -> May cause a hiccup (chains will trigger twice)
current_value = self._cached_bl_property.__get__(
bl_instance, bl_instance.__class__
)
if current_value != '':
self._cached_bl_property.__set__(bl_instance, '')
# Pop the Cached String Search Items
## The next time Blender does a str search, it'll update.
self._str_cb_cache.pop(bl_instance.instance_id, None)