diff --git a/src/blender_maxwell/node_trees/maxwell_sim_nodes/contracts/node_types.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/contracts/node_types.py
index 8887f87..c8c4d67 100644
--- a/src/blender_maxwell/node_trees/maxwell_sim_nodes/contracts/node_types.py
+++ b/src/blender_maxwell/node_trees/maxwell_sim_nodes/contracts/node_types.py
@@ -38,8 +38,6 @@ class NodeType(blender_type_enum.BlenderTypeEnum):
Scene = enum.auto()
## Inputs / Constants
ExprConstant = enum.auto()
- NumberConstant = enum.auto()
- PhysicalConstant = enum.auto()
ScientificConstant = enum.auto()
UnitSystemConstant = enum.auto()
BlenderConstant = enum.auto()
diff --git a/src/blender_maxwell/node_trees/maxwell_sim_nodes/contracts/sim_types.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/contracts/sim_types.py
index b30ea44..5169f1e 100644
--- a/src/blender_maxwell/node_trees/maxwell_sim_nodes/contracts/sim_types.py
+++ b/src/blender_maxwell/node_trees/maxwell_sim_nodes/contracts/sim_types.py
@@ -21,9 +21,11 @@ import enum
import typing as typ
import jax.numpy as jnp
+import sympy as sp
import tidy3d as td
from blender_maxwell.services import tdcloud
+from blender_maxwell.utils import extra_sympy_units as spux
####################
diff --git a/src/blender_maxwell/node_trees/maxwell_sim_nodes/managed_objs/managed_bl_image.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/managed_objs/managed_bl_image.py
index 218cd2b..2789a4e 100644
--- a/src/blender_maxwell/node_trees/maxwell_sim_nodes/managed_objs/managed_bl_image.py
+++ b/src/blender_maxwell/node_trees/maxwell_sim_nodes/managed_objs/managed_bl_image.py
@@ -47,8 +47,13 @@ class ManagedBLImage(base.ManagedObj):
managed_obj_type = ct.ManagedObjType.ManagedBLImage
_bl_image_name: str
- def __init__(self, name: str):
- self._bl_image_name = name
+ def __init__(self, name: str, prev_name: str | None = None):
+ if prev_name is not None:
+ self._bl_image_name = prev_name
+ else:
+ self._bl_image_name = name
+
+ self.name = name
@property
def name(self):
@@ -57,26 +62,29 @@ class ManagedBLImage(base.ManagedObj):
@name.setter
def name(self, value: str):
log.info(
- 'Setting ManagedBLImage from "%s" to "%s"',
+ 'Changing ManagedBLImage from "%s" to "%s"',
self.name,
value,
)
- current_bl_image = bpy.data.images.get(self._bl_image_name)
- wanted_bl_image = bpy.data.images.get(value)
+ existing_bl_image = bpy.data.images.get(self.name)
- # Yoink Image Name
- if current_bl_image is None and wanted_bl_image is None:
+ # No Existing Image: Set Value to Name
+ if existing_bl_image is None:
self._bl_image_name = value
- # Alter Image Name
- elif current_bl_image is not None and wanted_bl_image is None:
+ # Existing Image: Rename to New Name
+ else:
+ existing_bl_image.name = value
self._bl_image_name = value
- current_bl_image.name = value
- # Overlapping Image Name
- elif wanted_bl_image is not None:
- msg = f'ManagedBLImage "{self._bl_image_name}" could not change its name to "{value}", since it already exists.'
- raise ValueError(msg)
+ # Check: Blender Rename -> Synchronization Error
+ ## -> We can't do much else than report to the user & free().
+ if existing_bl_image.name != self._bl_image_name:
+ log.critical(
+ 'BLImage: Failed to set name of %s to %s, as %s already exists.'
+ )
+ self._bl_image_name = existing_bl_image.name
+ self.free()
def free(self):
bl_image = bpy.data.images.get(self.name)
diff --git a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/inputs/constants/expr_constant.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/inputs/constants/expr_constant.py
index 3b19980..ac248cd 100644
--- a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/inputs/constants/expr_constant.py
+++ b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/inputs/constants/expr_constant.py
@@ -16,6 +16,8 @@
import typing as typ
+import sympy as sp
+
from .... import contracts as ct
from .... import sockets
from ... import base, events
@@ -26,27 +28,69 @@ class ExprConstantNode(base.MaxwellSimNode):
bl_label = 'Expr Constant'
input_sockets: typ.ClassVar = {
- 'Expr': sockets.ExprSocketDef(),
+ 'Expr': sockets.ExprSocketDef(
+ active_kind=ct.FlowKind.LazyValueFunc,
+ ),
}
output_sockets: typ.ClassVar = {
- 'Expr': sockets.ExprSocketDef(),
+ 'Expr': sockets.ExprSocketDef(
+ active_kind=ct.FlowKind.LazyValueFunc,
+ show_info_columns=True,
+ ),
}
- ## TODO: Symbols (defined w/props?)
- ## - Currently expr constant isn't excessively useful, since there are no variables.
- ## - We'll define the #, type, name with props.
- ## - We'll add loose-socket inputs as int/real/complex/physical socket (based on type) for Param.
- ## - We the output expr would support `Value` (just the expression), `LazyValueFunc` (evaluate w/symbol support), `Param` (example values for symbols).
+ ## TODO: Allow immediately realizing any symbol, or just passing it along.
+ ## TODO: Alter output physical_type when the input PhysicalType changes.
####################
- # - Callbacks
+ # - FlowKinds
####################
@events.computes_output_socket(
- 'Expr', kind=ct.FlowKind.Value, input_sockets={'Expr'}
+ # Trigger
+ 'Expr',
+ kind=ct.FlowKind.Value,
+ # Loaded
+ input_sockets={'Expr'},
)
def compute_value(self, input_sockets: dict) -> typ.Any:
return input_sockets['Expr']
+ @events.computes_output_socket(
+ # Trigger
+ 'Expr',
+ kind=ct.FlowKind.LazyValueFunc,
+ # Loaded
+ input_sockets={'Expr'},
+ input_socket_kinds={'Expr': ct.FlowKind.LazyValueFunc},
+ )
+ def compute_lazy_value_func(self, input_sockets: dict) -> typ.Any:
+ return input_sockets['Expr']
+
+ ####################
+ # - FlowKinds: Auxiliary
+ ####################
+ @events.computes_output_socket(
+ # Trigger
+ 'Expr',
+ kind=ct.FlowKind.Info,
+ # Loaded
+ input_sockets={'Expr'},
+ input_socket_kinds={'Expr': ct.FlowKind.Info},
+ )
+ def compute_info(self, input_sockets: dict) -> typ.Any:
+ return input_sockets['Expr']
+
+ @events.computes_output_socket(
+ # Trigger
+ 'Expr',
+ kind=ct.FlowKind.Params,
+ # Loaded
+ input_sockets={'Expr'},
+ input_socket_kinds={'Expr': ct.FlowKind.Params},
+ )
+ def compute_params(self, input_sockets: dict) -> typ.Any:
+ return input_sockets['Expr']
+
####################
# - Blender Registration
diff --git a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/inputs/constants/number_constant.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/inputs/constants/number_constant.py
deleted file mode 100644
index 522c038..0000000
--- a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/inputs/constants/number_constant.py
+++ /dev/null
@@ -1,90 +0,0 @@
-# blender_maxwell
-# Copyright (C) 2024 blender_maxwell Project Contributors
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Affero General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program. If not, see .
-
-import typing as typ
-
-import bpy
-
-from blender_maxwell.utils import bl_cache
-from blender_maxwell.utils import extra_sympy_units as spux
-
-from .... import contracts as ct
-from .... import sockets
-from ... import base, events
-
-
-class NumberConstantNode(base.MaxwellSimNode):
- """A unitless number of configurable math type ex. integer, real, etc. .
-
- Attributes:
- mathtype: The math type to specify the number as.
- """
-
- node_type = ct.NodeType.NumberConstant
- bl_label = 'Numerical Constant'
-
- input_sockets: typ.ClassVar = {
- 'Value': sockets.ExprSocketDef(),
- }
- output_sockets: typ.ClassVar = {
- 'Value': sockets.ExprSocketDef(),
- }
-
- ####################
- # - Properties
- ####################
- mathtype: spux.MathType = bl_cache.BLField(
- spux.MathType.Integer,
- prop_ui=True,
- )
-
- size: spux.NumberSize1D = bl_cache.BLField(
- spux.NumberSize1D.Scalar,
- prop_ui=True,
- )
-
- ####################
- # - UI
- ####################
- def draw_props(self, _, col: bpy.types.UILayout) -> None:
- row = col.row(align=True)
- row.prop(self, self.blfields['mathtype'], text='')
- row.prop(self, self.blfields['size'], text='')
-
- ####################
- # - Events
- ####################
- @events.on_value_changed(prop_name={'mathtype', 'size'}, props={'mathtype', 'size'})
- def on_mathtype_size_changed(self, props) -> None:
- """Change the input/output expression sockets to match the mathtype declared in the node."""
- self.inputs['Value'].mathtype = props['mathtype']
- self.inputs['Value'].shape = props['size'].shape
-
- ####################
- # - FlowKind
- ####################
- @events.computes_output_socket('Value', input_sockets={'Value'})
- def compute_value(self, input_sockets) -> typ.Any:
- return input_sockets['Value']
-
-
-####################
-# - Blender Registration
-####################
-BL_REGISTER = [
- NumberConstantNode,
-]
-BL_NODES = {ct.NodeType.NumberConstant: (ct.NodeCategory.MAXWELLSIM_INPUTS_CONSTANTS)}
diff --git a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/inputs/constants/physical_constant.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/inputs/constants/physical_constant.py
deleted file mode 100644
index 8897416..0000000
--- a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/inputs/constants/physical_constant.py
+++ /dev/null
@@ -1,143 +0,0 @@
-# blender_maxwell
-# Copyright (C) 2024 blender_maxwell Project Contributors
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Affero General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program. If not, see .
-
-import typing as typ
-
-import bpy
-import sympy as sp
-
-from blender_maxwell.utils import bl_cache
-from blender_maxwell.utils import extra_sympy_units as spux
-
-from .... import contracts, sockets
-from ... import base, events
-
-
-class PhysicalConstantNode(base.MaxwellSimNode):
- """A number of configurable unit dimension, ex. time, length, etc. .
-
- Attributes:
- physical_type: The physical type to specify.
- size: The size of the physical type, if it can be a vector.
- """
-
- node_type = contracts.NodeType.PhysicalConstant
- bl_label = 'Physical Constant'
-
- input_sockets: typ.ClassVar = {
- 'Value': sockets.ExprSocketDef(),
- }
- output_sockets: typ.ClassVar = {
- 'Value': sockets.ExprSocketDef(),
- }
-
- ####################
- # - Properties
- ####################
- physical_type: spux.PhysicalType = bl_cache.BLField(
- spux.PhysicalType.Time,
- prop_ui=True,
- )
-
- mathtype: spux.MathType = bl_cache.BLField(
- enum_cb=lambda self, _: self.search_mathtypes(),
- prop_ui=True,
- )
-
- size: spux.NumberSize1D = bl_cache.BLField(
- enum_cb=lambda self, _: self.search_sizes(),
- )
-
- ####################
- # - Searchers
- ####################
- def search_mathtypes(self):
- return [
- mathtype.bl_enum_element(i)
- for i, mathtype in enumerate(self.physical_type.valid_mathtypes)
- ]
-
- def search_sizes(self):
- return [
- spux.NumberSize1D.from_shape(shape).bl_enum_element(i)
- for i, shape in enumerate(self.physical_type.valid_shapes)
- if spux.NumberSize1D.has_shape(shape)
- ]
-
- ####################
- # - UI
- ####################
- def draw_props(self, _, col: bpy.types.UILayout) -> None:
- col.prop(self, self.blfields['physical_type'], text='')
-
- row = col.row(align=True)
- row.prop(self, self.blfields['mathtype'], text='')
- row.prop(self, self.blfields['size'], text='')
-
- ####################
- # - Events
- ####################
- @events.on_value_changed(
- # Trigger
- prop_name={'physical_type'},
- run_on_init=True,
- # Loaded
- props={'physical_type'},
- )
- def on_physical_type_changed(self, props) -> None:
- """Change the input/output expression sockets to match the mathtype and size declared in the node."""
- # Set Input Socket Physical Type
- if self.inputs['Value'].physical_type != props['physical_type']:
- self.inputs['Value'].physical_type = props['physical_type']
- self.mathtype = bl_cache.Signal.ResetEnumItems
- self.size = bl_cache.Signal.ResetEnumItems
-
- @events.on_value_changed(
- # Trigger
- prop_name={'mathtype', 'size'},
- run_on_init=True,
- # Loaded
- props={'physical_type', 'mathtype', 'size'},
- )
- def on_mathtype_or_size_changed(self, props) -> None:
- # Set Input Socket Math Type
- if self.inputs['Value'].mathtype != props['mathtype']:
- self.inputs['Value'].mathtype = props['mathtype']
-
- # Set Input Socket Shape
- shape = props['size'].shape
- if self.inputs['Value'].shape != shape:
- self.inputs['Value'].shape = shape
-
- ####################
- # - Callbacks
- ####################
- @events.computes_output_socket('Value', input_sockets={'Value'})
- def compute_value(self, input_sockets) -> sp.Expr:
- return input_sockets['Value']
-
-
-####################
-# - Blender Registration
-####################
-BL_REGISTER = [
- PhysicalConstantNode,
-]
-BL_NODES = {
- contracts.NodeType.PhysicalConstant: (
- contracts.NodeCategory.MAXWELLSIM_INPUTS_CONSTANTS
- )
-}
diff --git a/src/blender_maxwell/node_trees/maxwell_sim_nodes/sockets/expr.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/sockets/expr.py
index 33ba169..0d5f673 100644
--- a/src/blender_maxwell/node_trees/maxwell_sim_nodes/sockets/expr.py
+++ b/src/blender_maxwell/node_trees/maxwell_sim_nodes/sockets/expr.py
@@ -116,13 +116,22 @@ class ExprBLSocket(base.MaxwellSimSocket):
size: spux.NumberSize1D = bl_cache.BLField(spux.NumberSize1D.Scalar)
mathtype: spux.MathType = bl_cache.BLField(spux.MathType.Real)
physical_type: spux.PhysicalType = bl_cache.BLField(spux.PhysicalType.NonPhysical)
+
+ # Symbols
+ # active_symbols: list[sim_symbols.SimSymbol] = bl_cache.BLField([])
symbols: frozenset[sp.Symbol] = bl_cache.BLField(frozenset())
+ # @property
+ # def symbols(self) -> set[sp.Symbol]:
+ # """Current symbols as an unordered set."""
+ # return {sim_symbol.sp_symbol for sim_symbol in self.active_symbols}
+
@bl_cache.cached_bl_property(depends_on={'symbols'})
def sorted_symbols(self) -> list[sp.Symbol]:
- """Name-sorted symbols."""
+ """Current symbols as a sorted list."""
return sorted(self.symbols, key=lambda sym: sym.name)
+ # Unit
active_unit: enum.StrEnum = bl_cache.BLField(
enum_cb=lambda self, _: self.search_valid_units(),
cb_depends_on={'physical_type'},
@@ -672,16 +681,16 @@ class ExprBLSocket(base.MaxwellSimSocket):
Whether information about the expression passing through a linked socket is shown is governed by `self.show_info_columns`.
"""
info = self.compute_data(kind=ct.FlowKind.Info)
- has_dims = not ct.FlowSignal.check(info) and info.dim_names
+ has_info = not ct.FlowSignal.check(info)
- if has_dims:
+ if has_info:
split = row.split(factor=0.85, align=True)
_row = split.row(align=False)
else:
_row = row
_row.label(text=text)
- if has_dims:
+ if has_info:
if self.show_info_columns:
_row.prop(self, self.blfields['info_columns'])
@@ -735,9 +744,17 @@ class ExprBLSocket(base.MaxwellSimSocket):
# - UI: Active FlowKind
####################
def draw_value(self, col: bpy.types.UILayout) -> None:
- """Draw the socket body for a single values/expression.
+ """Draw the socket body for a single value/expression.
- Drawn when `self.active_kind == FlowKind.Value`.
+ This implements the base UI for `ExprSocket`, for when `self.size`, `self.mathtype`, `self.physical_type`, and `self.symbols` are set.
+
+ Notes:
+ Drawn when `self.active_kind == FlowKind.Value`.
+
+ Alone, `draw_value` provides no mechanism for altering expression constraints like size.
+ Thus, `FlowKind.Value` is a good choice for when the expression must be of a very particular type.
+
+ However, `draw_value` may also be called by the `draw_*` methods of other `FlowKinds`, who may choose to layer more flexibility around this base UI.
"""
if self.symbols:
col.prop(self, self.blfields['raw_value_spstr'], text='')
@@ -819,23 +836,43 @@ class ExprBLSocket(base.MaxwellSimSocket):
col.prop(self, self.blfields['steps'], text='')
def draw_lazy_value_func(self, col: bpy.types.UILayout) -> None:
- """Draw the socket body for a value/expression meant for use in a lazy function composition chain.
+ """Draw the socket body for a single flexible value/expression, for down-chain lazy evaluation.
- Drawn when `self.active_kind == FlowKind.LazyValueFunc`.
+ This implements the most flexible variant of the `ExprSocket` UI, providing the user with full runtime-configuration of the exact `self.size`, `self.mathtype`, `self.physical_type`, and `self.symbols` of the expression.
+
+ Notes:
+ Drawn when `self.active_kind == FlowKind.LazyValueFunc`.
+
+ This is an ideal choice for ex. math nodes that need to accept arbitrary expressions as inputs, with an eye towards lazy evaluation of ex. symbolic terms.
+
+ Uses `draw_value` to draw the base UI
"""
+ # Physical Type Selector
+ ## -> Determines whether/which unit-dropdown will be shown.
col.prop(self, self.blfields['physical_type'], text='')
+
+ # Non-Symbolic: Size/Mathtype Selector
+ ## -> Symbols imply str expr input.
+ ## -> For arbitrary str exprs, size/mathtype are derived from the expr.
+ ## -> Otherwise, size/mathtype must be pre-specified for a nice UI.
if not self.symbols:
row = col.row(align=True)
row.prop(self, self.blfields['size'], text='')
row.prop(self, self.blfields['mathtype'], text='')
+ # Base UI
+ ## -> Draws the UI appropriate for the above choice of constraints.
self.draw_value(col)
+ # Symbol UI
+ ## -> Draws the UI appropriate for the above choice of constraints.
+ ## -> TODO
+
####################
# - UI: InfoFlow
####################
def draw_info(self, info: ct.InfoFlow, col: bpy.types.UILayout) -> None:
- if self.active_kind == ct.FlowKind.Array and self.show_info_columns:
+ if self.active_kind == ct.FlowKind.LazyValueFunc and self.show_info_columns:
row = col.row()
box = row.box()
grid = box.grid_flow(
@@ -899,7 +936,8 @@ class ExprSocketDef(base.SocketDef):
physical_type: spux.PhysicalType = spux.PhysicalType.NonPhysical
default_unit: spux.Unit | None = None
- symbols: frozenset[spux.Symbol] = frozenset()
+ # symbols: list[sim_symbols.SimSymbol] = frozenset()
+ symbols: frozenset[spux.SympyExpr] = frozenset()
# FlowKind: Value
default_value: spux.SympyExpr = 0
diff --git a/src/blender_maxwell/utils/sim_symbols.py b/src/blender_maxwell/utils/sim_symbols.py
new file mode 100644
index 0000000..a090c6f
--- /dev/null
+++ b/src/blender_maxwell/utils/sim_symbols.py
@@ -0,0 +1,76 @@
+# blender_maxwell
+# Copyright (C) 2024 blender_maxwell Project Contributors
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+
+import dataclasses
+import enum
+import typing as typ
+
+import sympy as sp
+
+from . import extra_sympy_units as spux
+
+
+class SimSymbolNames(enum.StrEnum):
+ LowerA = enum.auto()
+ LowerLambda = enum.auto()
+
+ @staticmethod
+ def to_name(v: typ.Self) -> str:
+ """Convert the enum value to a human-friendly name.
+
+ Notes:
+ Used to print names in `EnumProperty`s based on this enum.
+
+ Returns:
+ A human-friendly name corresponding to the enum value.
+ """
+ SSN = SimSymbolNames
+ return {
+ SSN.LowerA: 'a',
+ SSN.LowerLambda: 'λ',
+ }[v]
+
+ @staticmethod
+ def to_icon(_: typ.Self) -> str:
+ """Convert the enum value to a Blender icon.
+
+ Notes:
+ Used to print icons in `EnumProperty`s based on this enum.
+
+ Returns:
+ A human-friendly name corresponding to the enum value.
+ """
+ return ''
+
+
+@dataclasses.dataclass(kw_only=True, frozen=True)
+class SimSymbol:
+ name: SimSymbolNames = SimSymbolNames.LowerLambda
+ mathtype: spux.MathType = spux.MathType.Real
+
+ ## TODO:
+ ## -> Physical Type: Track unit dimension information on the side.
+ ## -> Domain: Ability to constrain mathtype ex. (-pi,pi]
+ ## -> Shape: For using sp.MatrixSymbol w/predefined rows/cols.
+
+ @property
+ def sp_symbol(self):
+ mathtype_kwarg = {}
+ match self.mathtype:
+ case spux.MathType.Real:
+ mathtype_kwarg = {}
+
+ return sp.Symbol(self.name, **mathtype_kwarg)