refactor: Big breakthrough on Expr socket (non working)

main
Sofus Albert Høgsbro Rose 2024-04-28 18:02:54 +02:00
parent d8170a67e4
commit 80d7b21c34
Signed by: so-rose
GPG Key ID: AD901CB0F3701434
34 changed files with 743 additions and 2154 deletions

View File

@ -96,10 +96,6 @@ class CapabilitiesFlow:
for name in self.must_match for name in self.must_match
) )
) )
return (
self.socket_type == other.socket_type
and self.active_kind == other.active_kind
) or other.is_universal
#################### ####################

View File

@ -1,12 +1,13 @@
"""Implements `WaveConstantNode`."""
import typing as typ import typing as typ
import bpy import bpy
import sympy as sp import sympy as sp
import sympy.physics.units as spu import sympy.physics.units as spu
from blender_maxwell.utils import bl_cache, logger, sci_constants
from blender_maxwell.utils import extra_sympy_units as spux from blender_maxwell.utils import extra_sympy_units as spux
from blender_maxwell.utils import logger
from blender_maxwell.utils import sci_constants as constants
from ... import contracts as ct from ... import contracts as ct
from ... import sockets from ... import sockets
@ -16,84 +17,158 @@ log = logger.get(__name__)
class WaveConstantNode(base.MaxwellSimNode): class WaveConstantNode(base.MaxwellSimNode):
"""Translates vaccum wavelength/frequency into both, either as a scalar, or as a memory-efficient uniform range of values.
Socket Sets:
Wavelength: Input a wavelength (range) to produce both wavelength/frequency (ranges).
Frequency: Input a frequency (range) to produce both wavelength/frequency (ranges).
Attributes:
use_range: Whether to specify a range of wavelengths/frequencies, or just one.
"""
node_type = ct.NodeType.WaveConstant node_type = ct.NodeType.WaveConstant
bl_label = 'Wave Constant' bl_label = 'Wave Constant'
input_socket_sets: typ.ClassVar = { input_socket_sets: typ.ClassVar = {
'Wavelength': {}, 'Wavelength': {
'Frequency': {}, 'WL': sockets.ExprSocketDef(
active_kind=ct.FlowKind.Value,
unit_dimension=spux.unit_dims.length,
# Defaults
default_unit=spu.nm,
default_value=500,
default_min=200,
default_max=700,
default_steps=2,
)
},
'Frequency': {
'Freq': sockets.ExprSocketDef(
active_kind=ct.FlowKind.Value,
unit_dimension=spux.unit_dims.frequency,
# Defaults
default_unit=spux.THz,
default_value=1,
default_min=0.3,
default_max=3,
default_steps=2,
),
},
}
output_sockets: typ.ClassVar = {
'WL': sockets.ExprSocketDef(
active_kind=ct.FlowKind.Value,
unit_dimension=spux.unit_dims.length,
),
'Freq': sockets.ExprSocketDef(
active_kind=ct.FlowKind.Value,
unit_dimension=spux.unit_dims.frequency,
),
} }
use_range: bpy.props.BoolProperty( ####################
name='Range', # - Properties
description='Whether to use a wavelength/frequency range', ####################
default=False, use_range: bool = bl_cache.BLField(False)
update=lambda self, context: self.on_prop_changed('use_range', context),
)
def draw_props(self, _: bpy.types.Context, col: bpy.types.UILayout):
col.prop(self, 'use_range', toggle=True)
#################### ####################
# - Event Methods: Wavelength Output # - UI
####################
def draw_props(self, _: bpy.types.Context, col: bpy.types.UILayout) -> None:
"""Draws the button that allows toggling between single and range output.
Parameters:
col: Target for defining UI elements.
"""
col.prop(self, self.blfields['use_range'], toggle=True)
####################
# - Events
####################
@events.on_value_changed(
prop_name={'active_socket_set', 'use_range'},
props='use_range',
run_on_init=True,
)
def on_use_range_changed(self, props: dict) -> None:
"""Synchronize the `active_kind` of input/output sockets, to either produce a `ct.FlowKind.Value` or a `ct.FlowKind.LazyArrayRange`."""
if self.inputs.get('WL') is not None:
active_input = self.inputs['WL']
else:
active_input = self.inputs['Freq']
# Modify Active Kind(s)
## Input active_kind -> Value/LazyArrayRange
active_input_uses_range = active_input.active_kind == ct.FlowKind.LazyArrayRange
if active_input_uses_range != props['use_range']:
active_input.active_kind = (
ct.FlowKind.LazyArrayRange if props['use_range'] else ct.FlowKind.Value
)
## Output active_kind -> Value/LazyArrayRange
for active_output in self.outputs.values():
active_output_uses_range = (
active_output.active_kind == ct.FlowKind.LazyArrayRange
)
if active_output_uses_range != props['use_range']:
active_output.active_kind = (
ct.FlowKind.LazyArrayRange
if props['use_range']
else ct.FlowKind.Value
)
####################
# - FlowKinds
#################### ####################
@events.computes_output_socket( @events.computes_output_socket(
'WL', 'WL',
kind=ct.FlowKind.Value, kind=ct.FlowKind.Value,
# Data
input_sockets={'WL', 'Freq'}, input_sockets={'WL', 'Freq'},
input_sockets_optional={'WL': True, 'Freq': True}, input_sockets_optional={'WL': True, 'Freq': True},
) )
def compute_wl_value(self, input_sockets: dict) -> sp.Expr: def compute_wl_value(self, input_sockets: dict) -> sp.Expr:
"""Compute a single wavelength value from either wavelength/frequency."""
if input_sockets['WL'] is not None: if input_sockets['WL'] is not None:
return input_sockets['WL'] return input_sockets['WL']
if input_sockets['WL'] is None and input_sockets['Freq'] is None: return sci_constants.vac_speed_of_light / input_sockets['Freq']
msg = 'Both WL and Freq are None.'
raise RuntimeError(msg)
return constants.vac_speed_of_light / input_sockets['Freq']
@events.computes_output_socket( @events.computes_output_socket(
'Freq', 'Freq',
kind=ct.FlowKind.Value, kind=ct.FlowKind.Value,
# Data
input_sockets={'WL', 'Freq'}, input_sockets={'WL', 'Freq'},
input_sockets_optional={'WL': True, 'Freq': True}, input_sockets_optional={'WL': True, 'Freq': True},
) )
def compute_freq_value(self, input_sockets: dict) -> sp.Expr: def compute_freq_value(self, input_sockets: dict) -> sp.Expr:
"""Compute a single frequency value from either wavelength/frequency."""
if input_sockets['Freq'] is not None: if input_sockets['Freq'] is not None:
return input_sockets['Freq'] return input_sockets['Freq']
if input_sockets['WL'] is None and input_sockets['Freq'] is None: return sci_constants.vac_speed_of_light / input_sockets['WL']
msg = 'Both WL and Freq are None.'
raise RuntimeError(msg)
return constants.vac_speed_of_light / input_sockets['WL']
@events.computes_output_socket( @events.computes_output_socket(
'WL', 'WL',
kind=ct.FlowKind.LazyArrayRange, kind=ct.FlowKind.LazyArrayRange,
# Data
input_sockets={'WL', 'Freq'}, input_sockets={'WL', 'Freq'},
input_socket_kinds={
'WL': ct.FlowKind.LazyArrayRange,
'Freq': ct.FlowKind.LazyArrayRange,
},
input_sockets_optional={'WL': True, 'Freq': True}, input_sockets_optional={'WL': True, 'Freq': True},
) )
def compute_wl_range(self, input_sockets: dict) -> sp.Expr: def compute_wl_range(self, input_sockets: dict) -> sp.Expr:
"""Compute wavelength range from either wavelength/frequency ranges."""
if input_sockets['WL'] is not None: if input_sockets['WL'] is not None:
return input_sockets['WL'] return input_sockets['WL']
if input_sockets['WL'] is None and input_sockets['Freq'] is None:
msg = 'Both WL and Freq are None.'
raise RuntimeError(msg)
return input_sockets['Freq'].rescale_bounds( return input_sockets['Freq'].rescale_bounds(
lambda bound: constants.vac_speed_of_light / bound, reverse=True lambda bound: sci_constants.vac_speed_of_light / bound, reverse=True
) )
@events.computes_output_socket( @events.computes_output_socket(
'Freq', 'Freq',
kind=ct.FlowKind.LazyArrayRange, kind=ct.FlowKind.LazyArrayRange,
# Data
input_sockets={'WL', 'Freq'}, input_sockets={'WL', 'Freq'},
input_socket_kinds={ input_socket_kinds={
'WL': ct.FlowKind.LazyArrayRange, 'WL': ct.FlowKind.LazyArrayRange,
@ -102,48 +177,14 @@ class WaveConstantNode(base.MaxwellSimNode):
input_sockets_optional={'WL': True, 'Freq': True}, input_sockets_optional={'WL': True, 'Freq': True},
) )
def compute_freq_range(self, input_sockets: dict) -> sp.Expr: def compute_freq_range(self, input_sockets: dict) -> sp.Expr:
"""Compute frequency range from either wavelength/frequency ranges."""
if input_sockets['Freq'] is not None: if input_sockets['Freq'] is not None:
return input_sockets['Freq'] return input_sockets['Freq']
if input_sockets['WL'] is None and input_sockets['Freq'] is None:
msg = 'Both WL and Freq are None.'
raise RuntimeError(msg)
return input_sockets['WL'].rescale_bounds( return input_sockets['WL'].rescale_bounds(
lambda bound: constants.vac_speed_of_light / bound, reverse=True lambda bound: sci_constants.vac_speed_of_light / bound, reverse=True
) )
####################
# - Event Methods
####################
@events.on_value_changed(
prop_name={'active_socket_set', 'use_range'},
props={'active_socket_set', 'use_range'},
run_on_init=True,
)
def on_input_spec_change(self, props: dict):
if props['active_socket_set'] == 'Wavelength':
self.loose_input_sockets = {
'WL': sockets.PhysicalLengthSocketDef(
is_array=props['use_range'],
default_value=500 * spu.nm,
default_unit=spu.nm,
)
}
else:
self.loose_input_sockets = {
'Freq': sockets.PhysicalFreqSocketDef(
is_array=props['use_range'],
default_value=600 * spux.THz,
default_unit=spux.THz,
)
}
self.loose_output_sockets = {
'WL': sockets.PhysicalLengthSocketDef(is_array=props['use_range']),
'Freq': sockets.PhysicalFreqSocketDef(is_array=props['use_range']),
}
#################### ####################
# - Blender Registration # - Blender Registration

View File

@ -1,26 +0,0 @@
#import typing as typ
#import bpy
#
#from .. import contracts as ct
#
#
#
#class MaxwellSimProp(bpy.types.PropertyGroup):
# """A Blender property usable in nodes and sockets."""
# name: str = ""
# data_flow_kind: ct.FlowKind
#
# value: dict[str, tuple[bpy.types.Property, dict]] | None = None
#
# def __init_subclass__(cls, **kwargs: typ.Any):
# log.debug('Initializing Prop: %s', cls.node_type)
# super().__init_subclass__(**kwargs)
#
# # Setup Value
# if cls.value:
# cls.__annotations__['raw_value'] = value
#
#
# @property
# def value(self):
# if self.data_flow_kind

View File

@ -1,11 +1,11 @@
from blender_maxwell.utils import logger from blender_maxwell.utils import logger
from .. import contracts as ct from .. import contracts as ct
from . import basic, blender, maxwell, number, physical, tidy3d, vector from . import basic, blender, maxwell, physical, tidy3d
from .scan_socket_defs import scan_for_socket_defs from .scan_socket_defs import scan_for_socket_defs
log = logger.get(__name__) log = logger.get(__name__)
sockets_modules = [basic, number, vector, physical, blender, maxwell, tidy3d] sockets_modules = [basic, physical, blender, maxwell, tidy3d]
#################### ####################
# - Scan for SocketDefs # - Scan for SocketDefs
@ -33,8 +33,6 @@ for socket_type in ct.SocketType:
#################### ####################
BL_REGISTER = [ BL_REGISTER = [
*basic.BL_REGISTER, *basic.BL_REGISTER,
*number.BL_REGISTER,
*vector.BL_REGISTER,
*physical.BL_REGISTER, *physical.BL_REGISTER,
*blender.BL_REGISTER, *blender.BL_REGISTER,
*maxwell.BL_REGISTER, *maxwell.BL_REGISTER,
@ -43,8 +41,6 @@ BL_REGISTER = [
__all__ = [ __all__ = [
'basic', 'basic',
'number',
'vector',
'physical', 'physical',
'blender', 'blender',
'maxwell', 'maxwell',

View File

@ -1,5 +1,4 @@
import abc import abc
import functools
import typing as typ import typing as typ
import uuid import uuid
from types import MappingProxyType from types import MappingProxyType
@ -193,6 +192,7 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
log.debug('Initializing Socket: %s', cls.socket_type) log.debug('Initializing Socket: %s', cls.socket_type)
super().__init_subclass__(**kwargs) super().__init_subclass__(**kwargs)
# cls._assert_attrs_valid() # cls._assert_attrs_valid()
## TODO: Implement this :)
# Socket Properties # Socket Properties
## Identifiers ## Identifiers
@ -235,46 +235,10 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
#################### ####################
# - Units # - Units
#################### ####################
# TODO: Refactor
@functools.cached_property
def possible_units(self) -> dict[str, sp.Expr]:
if not self.use_units:
msg = "Tried to get possible units for socket {self}, but socket doesn't `use_units`"
raise ValueError(msg)
return ct.SOCKET_UNITS[self.socket_type]['values']
@property
def unit(self) -> sp.Expr:
return self.possible_units[self.active_unit]
@property @property
def prev_unit(self) -> sp.Expr: def prev_unit(self) -> sp.Expr:
return self.possible_units[self.prev_active_unit] return self.possible_units[self.prev_active_unit]
@unit.setter
def unit(self, value: str | sp.Expr) -> None:
# Retrieve Unit by String
if isinstance(value, str) and value in self.possible_units:
self.active_unit = self.possible_units[value]
return
# Retrieve =1 Matching Unit Name
matching_unit_names = [
unit_name
for unit_name, unit_sympy in self.possible_units.items()
if value == unit_sympy
]
if len(matching_unit_names) == 0:
msg = f"Tried to set unit for socket {self} with value {value}, but it is not one of possible units {''.join(self.possible_units.values())} for this socket (as defined in `contracts.SOCKET_UNITS`)"
raise ValueError(msg)
if len(matching_unit_names) > 1:
msg = f"Tried to set unit for socket {self} with value {value}, but multiple possible matching units {''.join(self.possible_units.values())} for this socket (as defined in `contracts.SOCKET_UNITS`); there may only be one"
raise RuntimeError(msg)
self.active_unit = matching_unit_names[0]
#################### ####################
# - Property Event: On Update # - Property Event: On Update
#################### ####################
@ -285,37 +249,9 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
Called by `self.on_prop_changed()` when `self.active_kind` was changed. Called by `self.on_prop_changed()` when `self.active_kind` was changed.
""" """
self.display_shape = ( self.display_shape = (
'SQUARE' 'SQUARE' if self.active_kind == ct.FlowKind.LazyValueRange else 'CIRCLE'
if self.active_kind in {ct.FlowKind.Array, ct.FlowKind.LazyValueRange}
else 'CIRCLE'
) + ('_DOT' if self.use_units else '') ) + ('_DOT' if self.use_units else '')
## TODO: Valid Active Kinds should be a subset/subenum(?) of FlowKind
def _on_unit_changed(self) -> None:
"""Synchronizes the `FlowKind` data to the newly set unit.
When a new unit is set, the internal ex. floating point properties become out of sync.
This function applies a rescaling operation based on the factor between the previous unit (`self.prev_unit`) and the new unit `(self.unit)`.
- **Value**: Retrieve the value (with incorrect new unit), exchange the new unit for the old unit, and assign it back.
- **Array**: Replace the internal unit with the old (correct) unit, and rescale all values in the array to the new unit.
Notes:
Called by `self.on_prop_changed()` when `self.active_unit` is changed.
This allows for a unit-scaling operation **without needing to know anything about the data representation** (at the cost of performance).
"""
if self.active_kind == ct.FlowKind.Value:
self.value = self.value / self.unit * self.prev_unit
elif self.active_kind in [ct.FlowKind.Array, ct.FlowKind.LazyArrayRange]:
self.lazy_value_range = self.lazy_value_range.correct_unit(
self.prev_unit
).rescale_to_unit(self.unit)
else:
msg = f'Socket {self.bl_label} ({self.socket_type}): Active kind {self.active_kind} declares no method of scaling units from {self.prev_active_unit} to {self.active_unit})'
raise RuntimeError(msg)
self.prev_active_unit = self.active_unit
def on_prop_changed(self, prop_name: str, _: bpy.types.Context) -> None: def on_prop_changed(self, prop_name: str, _: bpy.types.Context) -> None:
"""Called when a property has been updated. """Called when a property has been updated.
@ -880,7 +816,6 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
col = row.column(align=True) col = row.column(align=True)
{ {
ct.FlowKind.Value: self.draw_value, ct.FlowKind.Value: self.draw_value,
ct.FlowKind.Array: self.draw_array,
ct.FlowKind.LazyArrayRange: self.draw_lazy_array_range, ct.FlowKind.LazyArrayRange: self.draw_lazy_array_range,
}[self.active_kind](col) }[self.active_kind](col)
@ -922,7 +857,7 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
self.draw_info(info, col) self.draw_info(info, col)
#################### ####################
# - UI Methods: Active FlowKind # - UI Methods: Label Rows
#################### ####################
def draw_label_row( def draw_label_row(
self, self,
@ -978,6 +913,9 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
""" """
row.label(text=text) row.label(text=text)
####################
# - UI Methods: Active FlowKind
####################
def draw_value(self, col: bpy.types.UILayout) -> None: def draw_value(self, col: bpy.types.UILayout) -> None:
"""Draws the socket value on its own line. """Draws the socket value on its own line.
@ -988,16 +926,6 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
col: Target for defining UI elements. col: Target for defining UI elements.
""" """
def draw_array(self, col: bpy.types.UILayout) -> None:
"""Draws the socket array on its own line.
Notes:
Should be overriden by individual socket classes, if they have an editable `FlowKind.Array`.
Parameters:
col: Target for defining UI elements.
"""
def draw_lazy_array_range(self, col: bpy.types.UILayout) -> None: def draw_lazy_array_range(self, col: bpy.types.UILayout) -> None:
"""Draws the socket lazy array range on its own line. """Draws the socket lazy array range on its own line.

View File

@ -1,205 +0,0 @@
import enum
import typing as typ
import bpy
from blender_maxwell.utils import bl_cache, logger
from blender_maxwell.utils import extra_sympy_units as spux
from ... import contracts as ct
from .. import base
log = logger.get(__name__)
def unicode_superscript(n):
return ''.join(['⁰¹²³⁴⁵⁶⁷⁸⁹'[ord(c) - ord('0')] for c in str(n)])
class DataInfoColumn(enum.StrEnum):
Length = enum.auto()
MathType = enum.auto()
Unit = enum.auto()
@staticmethod
def to_name(value: typ.Self) -> str:
return {
DataInfoColumn.Length: 'L',
DataInfoColumn.MathType: '',
DataInfoColumn.Unit: 'U',
}[value]
@staticmethod
def to_icon(value: typ.Self) -> str:
return {
DataInfoColumn.Length: '',
DataInfoColumn.MathType: '',
DataInfoColumn.Unit: '',
}[value]
####################
# - Blender Socket
####################
class DataBLSocket(base.MaxwellSimSocket):
socket_type = ct.SocketType.Data
bl_label = 'Data'
use_info_draw = True
####################
# - Properties: Format
####################
format: str = bl_cache.BLField('')
## TODO: typ.Literal['xarray', 'jax']
show_info_columns: bool = bl_cache.BLField(
True,
prop_ui=True,
# use_prop_update=False,
)
info_columns: DataInfoColumn = bl_cache.BLField(
{DataInfoColumn.MathType, DataInfoColumn.Unit},
prop_ui=True,
enum_many=True,
# use_prop_update=False,
)
####################
# - FlowKind
####################
@property
def capabilities(self) -> ct.CapabilitiesFlow:
return ct.CapabilitiesFlow(
socket_type=self.socket_type,
active_kind=self.active_kind,
must_match={'format': self.format},
)
####################
# - UI
####################
def draw_input_label_row(self, row: bpy.types.UILayout, text) -> None:
info = self.compute_data(kind=ct.FlowKind.Info)
has_dims = (
not ct.FlowSignal.check(info) and self.format == 'jax' and info.dim_names
)
if has_dims:
split = row.split(factor=0.85, align=True)
_row = split.row(align=False)
else:
_row = row
_row.label(text=text)
if has_dims:
if self.show_info_columns:
_row.prop(self, self.blfields['info_columns'])
_row = split.row(align=True)
_row.alignment = 'RIGHT'
_row.prop(
self,
self.blfields['show_info_columns'],
toggle=True,
text='',
icon=ct.Icon.ToggleSocketInfo,
)
def draw_output_label_row(self, row: bpy.types.UILayout, text) -> None:
info = self.compute_data(kind=ct.FlowKind.Info)
has_dims = (
not ct.FlowSignal.check(info) and self.format == 'jax' and info.dim_names
)
if has_dims:
split = row.split(factor=0.15, align=True)
_row = split.row(align=True)
_row.prop(
self,
self.blfields['show_info_columns'],
toggle=True,
text='',
icon=ct.Icon.ToggleSocketInfo,
)
_row = split.row(align=False)
_row.alignment = 'RIGHT'
if self.show_info_columns:
_row.prop(self, self.blfields['info_columns'])
else:
_col = _row.column()
_col.alignment = 'EXPAND'
_col.label(text='')
else:
_row = row
_row.label(text=text)
def draw_info(self, info: ct.InfoFlow, col: bpy.types.UILayout) -> None:
if self.format == 'jax' and info.dim_names and self.show_info_columns:
row = col.row()
box = row.box()
grid = box.grid_flow(
columns=len(self.info_columns) + 1,
row_major=True,
even_columns=True,
# even_rows=True,
align=True,
)
# Dimensions
for dim_name in info.dim_names:
dim_idx = info.dim_idx[dim_name]
grid.label(text=dim_name)
if DataInfoColumn.Length in self.info_columns:
grid.label(text=str(len(dim_idx)))
if DataInfoColumn.MathType in self.info_columns:
grid.label(text=spux.MathType.to_str(dim_idx.mathtype))
if DataInfoColumn.Unit in self.info_columns:
grid.label(text=spux.sp_to_str(dim_idx.unit))
# Outputs
grid.label(text=info.output_name)
if DataInfoColumn.Length in self.info_columns:
grid.label(text='', icon=ct.Icon.DataSocketOutput)
if DataInfoColumn.MathType in self.info_columns:
grid.label(
text=(
spux.MathType.to_str(info.output_mathtype)
+ (
'ˣ'.join(
[
unicode_superscript(out_axis)
for out_axis in info.output_shape
]
)
if info.output_shape
else ''
)
)
)
if DataInfoColumn.Unit in self.info_columns:
grid.label(text=f'{spux.sp_to_str(info.output_unit)}')
####################
# - Socket Configuration
####################
class DataSocketDef(base.SocketDef):
socket_type: ct.SocketType = ct.SocketType.Data
format: typ.Literal['xarray', 'jax', 'monitor_data']
default_show_info_columns: bool = True
def init(self, bl_socket: DataBLSocket) -> None:
bl_socket.format = self.format
bl_socket.default_show_info_columns = self.default_show_info_columns
####################
# - Blender Registration
####################
BL_REGISTER = [
DataBLSocket,
]

View File

@ -1,147 +0,0 @@
import typing as typ
import bpy
import pydantic as pyd
import sympy as sp
from blender_maxwell.utils import bl_cache, logger
from blender_maxwell.utils import extra_sympy_units as spux
from ... import contracts as ct
from .. import base
log = logger.get(__name__)
class ExprBLSocket(base.MaxwellSimSocket):
socket_type = ct.SocketType.Expr
bl_label = 'Expr'
####################
# - Properties
####################
raw_value: bpy.props.StringProperty(
name='Expr',
description='Represents a symbolic expression',
default='',
update=(lambda self, context: self.on_prop_changed('raw_value', context)),
)
int_symbols: frozenset[spux.IntSymbol] = bl_cache.BLField(frozenset())
real_symbols: frozenset[spux.RealSymbol] = bl_cache.BLField(frozenset())
complex_symbols: frozenset[spux.ComplexSymbol] = bl_cache.BLField(frozenset())
@bl_cache.cached_bl_property(persist=False)
def symbols(self) -> list[spux.Symbol]:
"""Retrieves all symbols by concatenating int, real, and complex symbols, and sorting them by name.
The order is guaranteed to be **deterministic**.
Returns:
All symbols valid for use in the expression.
"""
return sorted(
self.int_symbols | self.real_symbols | self.complex_symbols,
key=lambda sym: sym.name,
)
####################
# - Socket UI
####################
def draw_value(self, col: bpy.types.UILayout) -> None:
col.prop(self, 'raw_value', text='')
if len(self.symbols) > 0:
box = col.box()
split = box.split(factor=0.3)
# Left Col
col = split.column()
col.label(text='Let:')
# Right Col
col = split.column()
col.alignment = 'RIGHT'
for sym in self.symbols:
col.label(text=spux.pretty_symbol(sym))
####################
# - Computation of Default Value
####################
@property
def value(self) -> sp.Expr:
return sp.sympify(
self.raw_value,
locals={sym.name: sym for sym in self.symbols},
strict=False,
convert_xor=True,
).subs(spux.ALL_UNIT_SYMBOLS)
@value.setter
def value(self, value: str) -> None:
self.raw_value = sp.sstr(value)
@property
def lazy_value_func(self) -> ct.LazyValueFuncFlow:
return ct.LazyValueFuncFlow(
func=sp.lambdify(self.symbols, self.value, 'jax'),
func_args=[spux.sympy_to_python_type(sym) for sym in self.symbols],
supports_jax=True,
)
####################
# - Socket Configuration
####################
class ExprSocketDef(base.SocketDef):
socket_type: ct.SocketType = ct.SocketType.Expr
int_symbols: frozenset[spux.IntSymbol] = frozenset()
real_symbols: frozenset[spux.RealSymbol] = frozenset()
complex_symbols: frozenset[spux.ComplexSymbol] = frozenset()
@property
def symbols(self) -> list[spux.Symbol]:
"""Retrieves all symbols by concatenating int, real, and complex symbols, and sorting them by name.
The order is guaranteed to be **deterministic**.
Returns:
All symbols valid for use in the expression.
"""
return sorted(
self.int_symbols | self.real_symbols | self.complex_symbols,
key=lambda sym: sym.name,
)
# Expression
default_expr: spux.SympyExpr = sp.S(1)
allow_units: bool = True
@pyd.model_validator(mode='after')
def check_default_expr_follows_unit_allowance(self) -> typ.Self:
"""Checks that `self.default_expr` only uses units if `self.allow_units` is defined.
Raises:
ValueError: If the expression uses symbols not defined in `self.symbols`.
"""
if spux.uses_units(self.default_expr) and not self.allow_units:
msg = f'Expression {self.default_expr} uses units, but "self.allow_units" is False'
raise ValueError(msg)
return self
## TODO: Validator for Symbol Usage
def init(self, bl_socket: ExprBLSocket) -> None:
bl_socket.value = self.default_expr
bl_socket.int_symbols = self.int_symbols
bl_socket.real_symbols = self.real_symbols
bl_socket.complex_symbols = self.complex_symbols
####################
# - Blender Registration
####################
BL_REGISTER = [
ExprBLSocket,
]

View File

@ -0,0 +1,617 @@
import enum
import typing as typ
import bpy
import sympy as sp
from blender_maxwell.utils import bl_cache, logger
from blender_maxwell.utils import extra_sympy_units as spux
from ... import contracts as ct
from .. import base
## TODO: This is a big node, and there's a lot to get right.
## - Dynamically adjust the value when the user changes the unit in the UI.
## - Dynamically adjust socket color in response to, especially, the unit dimension.
## - Iron out the meaning of display shapes.
## - Generally pay attention to validity checking; it's make or break.
## - For array generation, it may pay to have both a symbolic expression (producing output according to `size` as usual) denoting how to actually make values, and how many. Enables ex. easy symbolic
## - For array generation, it may pay to have both a symbolic expression (producing output according to `size` as usual)
log = logger.get(__name__)
Int2: typ.TypeAlias = tuple[int, int]
Int3: typ.TypeAlias = tuple[int, int, int]
Int22: typ.TypeAlias = tuple[tuple[int, int], tuple[int, int]]
Int32: typ.TypeAlias = tuple[tuple[int, int], tuple[int, int], tuple[int, int]]
Float2: typ.TypeAlias = tuple[float, float]
Float3: typ.TypeAlias = tuple[float, float, float]
Float22: typ.TypeAlias = tuple[tuple[float, float], tuple[float, float]]
Float32: typ.TypeAlias = tuple[
tuple[float, float], tuple[float, float], tuple[float, float]
]
def unicode_superscript(n):
return ''.join(['⁰¹²³⁴⁵⁶⁷⁸⁹'[ord(c) - ord('0')] for c in str(n)])
class InfoDisplayCol(enum.StrEnum):
"""Valid columns for specifying displayed information from an `ct.InfoFlow`."""
Length = enum.auto()
MathType = enum.auto()
Unit = enum.auto()
@staticmethod
def to_name(value: typ.Self) -> str:
IDC = InfoDisplayCol
return {
IDC.Length: 'L',
IDC.MathType: '',
IDC.Unit: 'U',
}[value]
@staticmethod
def to_icon(value: typ.Self) -> str:
IDC = InfoDisplayCol
return {
IDC.Length: '',
IDC.MathType: '',
IDC.Unit: '',
}[value]
class ExprBLSocket(base.MaxwellSimSocket):
socket_type = ct.SocketType.Expr
bl_label = 'Expr'
####################
# - Properties
####################
size: typ.Literal[None, 2, 3] = bl_cache.BLField(None, prop_ui=True)
mathtype: spux.MathType = bl_cache.BLField(spux.MathType.Real, prop_ui=True)
symbols: frozenset[spux.Symbol] = bl_cache.BLField(frozenset())
## Units
unit_dim: spux.UnitDimension | None = bl_cache.BLField(None)
active_unit: enum.Enum = bl_cache.BLField(
None, enum_cb=lambda self, _: self.search_units(), prop_ui=True
)
## Info Display
show_info_columns: bool = bl_cache.BLField(False, prop_ui=True)
info_columns: InfoDisplayCol = bl_cache.BLField(
{InfoDisplayCol.MathType, InfoDisplayCol.Unit},
prop_ui=True,
enum_many=True,
)
# UI: Value
## Expression
raw_value_spstr: str = bl_cache.BLField('', prop_ui=True)
## 1D
raw_value_int: int = bl_cache.BLField(0, prop_ui=True)
raw_value_rat: Int2 = bl_cache.BLField((0, 1), prop_ui=True)
raw_value_float: float = bl_cache.BLField(0.0, float_prec=4, prop_ui=True)
raw_value_complex: Float2 = bl_cache.BLField((0, 1), float_prec=4, prop_ui=True)
## 2D
raw_value_int2: Int2 = bl_cache.BLField((0, 0), prop_ui=True)
raw_value_rat2: Int22 = bl_cache.BLField(((0, 1), (0, 1)), prop_ui=True)
raw_value_float2: Float2 = bl_cache.BLField((0.0, 0.0), float_prec=4, prop_ui=True)
raw_value_complex2: Float22 = bl_cache.BLField(
((0.0, 0.0), (0.0, 0.0)), float_prec=4, prop_ui=True
)
## 3D
raw_value_int3: Int3 = bl_cache.BLField((0, 0, 0), prop_ui=True)
raw_value_rat3: Int32 = bl_cache.BLField(((0, 1), (0, 1), (0, 1)), prop_ui=True)
raw_value_float3: Float3 = bl_cache.BLField((0.0, 0.0), float_prec=4, prop_ui=True)
raw_value_complex3: Float32 = bl_cache.BLField(
((0.0, 0.0), (0.0, 0.0), (0.0, 0.0)), float_prec=4, prop_ui=True
)
# UI: LazyArrayRange
steps: int = bl_cache.BLField(2, abs_min=2)
## Expression
raw_min_spstr: str = bl_cache.BLField('', prop_ui=True)
raw_max_spstr: str = bl_cache.BLField('', prop_ui=True)
## By MathType
raw_range_int: Int2 = bl_cache.BLField((0, 1), prop_ui=True)
raw_range_rat: Int22 = bl_cache.BLField(((0, 1), (1, 1)), prop_ui=True)
raw_range_float: Float2 = bl_cache.BLField((0.0, 1.0), prop_ui=True)
raw_range_complex: Float22 = bl_cache.BLField(
((0.0, 0.0), (1.0, 1.0)), float_prec=4, prop_ui=True
)
####################
# - Computed: Raw Expressions
####################
@property
def raw_value_sp(self) -> spux.SympyExpr:
return self._parse_expr_str(self.raw_value_spstr)
@property
def raw_min_sp(self) -> spux.SympyExpr:
return self._parse_expr_str(self.raw_min_spstr)
@property
def raw_max_sp(self) -> spux.SympyExpr:
return self._parse_expr_str(self.raw_max_spstr)
####################
# - Computed: Units
####################
def search_units(self, _: bpy.types.Context) -> list[ct.BLEnumElement]:
if self.unit_dim is not None:
return [
(sp.sstr(unit), spux.sp_to_str(unit), sp.sstr(unit), '', i)
for i, unit in enumerate(spux.unit_dim_units(self.unit_dim))
]
return []
@property
def unit(self) -> spux.Unit | None:
if self.active_unit != 'NONE':
return spux.unit_str_to_unit(self.active_unit)
return None
@unit.setter
def unit(self, unit: spux.Unit) -> None:
valid_units = spux.unit_dim_units(self.unit_dim)
if unit in valid_units:
self.active_unit = sp.sstr(unit)
msg = f'Tried to set invalid unit {unit} (unit dim "{self.unit_dim}" only supports "{valid_units}")'
raise ValueError(msg)
####################
# - Methods
####################
def _parse_expr_info(
self, expr: spux.SympyExpr
) -> tuple[spux.MathType, typ.Literal[None, 2, 3], spux.UnitDimension]:
# Parse MathType
mathtype = spux.MathType.from_expr(expr)
if self.mathtype != mathtype:
msg = f'MathType is {self.mathtype}, but tried to set expr {expr} with mathtype {mathtype}'
raise ValueError(msg)
# Parse Symbols
if expr.free_symbols:
if self.mathtype is not None:
msg = f'MathType is {self.mathtype}, but tried to set expr {expr} with free symbols {expr.free_symbols}'
raise ValueError(msg)
if not expr.free_symbols.issubset(self.symbols):
msg = f'Tried to set expr {expr} with free symbols {expr.free_symbols}, which is incompatible with socket symbols {self.symbols}'
raise ValueError(msg)
# Parse Dimensions
size = spux.parse_size(expr)
if size != self.size:
msg = f'Expr {expr} has {size} dimensions, which is incompatible with the expr socket ({self.size} dimensions)'
raise ValueError(msg)
# Parse Unit Dimension
unit_dim = spux.parse_unit_dim(expr)
if unit_dim != self.unit_dim:
msg = f'Expr {expr} has unit dimension {unit_dim}, which is incompatible with socket unit dimension {self.unit_dim}'
raise ValueError(msg)
return mathtype, size, unit_dim
def _to_raw_value(self, expr: spux.SympyExpr):
if self.unit is not None:
return spux.sympy_to_python(spux.scale_to_unit(expr, self.unit))
return spux.sympy_to_python(expr)
def _parse_expr_str(self, expr_spstr: str) -> None:
expr = sp.sympify(
expr_spstr,
locals={sym.name: sym for sym in self.symbols},
strict=False,
convert_xor=True,
).subs(spux.ALL_UNIT_SYMBOLS) * (self.unit if self.unit is not None else 1)
# Try Parsing and Returning the Expression
try:
self._parse_expr_info(expr)
except ValueError(expr) as ex:
log.exception(
'Couldn\'t parse expression "%s" in Expr socket.',
expr_spstr,
)
else:
return expr
return None
####################
# - FlowKind: Value
####################
@property
def value(self) -> spux.SympyExpr:
"""Return the expression defined by the socket.
- **Num Dims**: Determine which property dimensionality to pull data from.
- **MathType**: Determine which property type to pull data from.
When `self.mathtype` is `None`, the expression is parsed from the string `self.raw_value_spstr`.
Notes:
Called to compute the internal `FlowKind.Value` of this socket.
Return:
The expression defined by the socket, in the socket's unit.
"""
if self.symbols:
expr = self.raw_value_sp
if expr is None:
return ct.FlowSignal.FlowPending
MT_Z = spux.MathType.Integer
MT_Q = spux.MathType.Rational
MT_R = spux.MathType.Real
MT_C = spux.MathType.Complex
Z = sp.Integer
Q = sp.Rational
R = sp.RealNumber
return {
None: {
MT_Z: lambda: Z(self.raw_value_int),
MT_Q: lambda: Q(self.raw_value_rat[0], self.raw_value_rat[1]),
MT_R: lambda: R(self.raw_value_float),
MT_C: lambda: (
self.raw_value_complex[0] + sp.I * self.raw_value_complex[1]
),
},
2: {
MT_Z: lambda: sp.Matrix([Z(i) for i in self.raw_value_int2]),
MT_Q: lambda: sp.Matrix([Q(q[0], q[1]) for q in self.raw_value_rat2]),
MT_R: lambda: sp.Matrix([R(r) for r in self.raw_value_float2]),
MT_C: lambda: sp.Matrix(
[c[0] + sp.I * c[1] for c in self.raw_value_complex2]
),
},
3: {
MT_Z: lambda: sp.Matrix([Z(i) for i in self.raw_value_int3]),
MT_Q: lambda: sp.Matrix([Q(q[0], q[1]) for q in self.raw_value_rat3]),
MT_R: lambda: sp.Matrix([R(r) for r in self.raw_value_float3]),
MT_C: lambda: sp.Matrix(
[c[0] + sp.I * c[1] for c in self.raw_value_complex3]
),
},
}[self.size][self.mathtype]() * (self.unit if self.unit is not None else 1)
@value.setter
def value(self, expr: spux.SympyExpr) -> None:
"""Set the expression defined by the socket.
Notes:
Called to set the internal `FlowKind.Value` of this socket.
"""
mathtype, size, unit_dim = self._parse_expr_info(expr)
if self.symbols:
self.raw_value_spstr = sp.sstr(expr)
else:
MT_Z = spux.MathType.Integer
MT_Q = spux.MathType.Rational
MT_R = spux.MathType.Real
MT_C = spux.MathType.Complex
if size is None:
if mathtype == MT_Z:
self.raw_value_int = self._to_raw_value(expr)
elif mathtype == MT_Q:
self.raw_value_rat = self._to_raw_value(expr)
elif mathtype == MT_R:
self.raw_value_float = self._to_raw_value(expr)
elif mathtype == MT_C:
self.raw_value_complex = self._to_raw_value(expr)
elif size == 2:
if mathtype == MT_Z:
self.raw_value_int2 = self._to_raw_value(expr)
elif mathtype == MT_Q:
self.raw_value_rat2 = self._to_raw_value(expr)
elif mathtype == MT_R:
self.raw_value_float2 = self._to_raw_value(expr)
elif mathtype == MT_C:
self.raw_value_complex2 = self._to_raw_value(expr)
elif size == 3:
if mathtype == MT_Z:
self.raw_value_int3 = self._to_raw_value(expr)
elif mathtype == MT_Q:
self.raw_value_rat3 = self._to_raw_value(expr)
elif mathtype == MT_R:
self.raw_value_float3 = self._to_raw_value(expr)
elif mathtype == MT_C:
self.raw_value_complex3 = self._to_raw_value(expr)
####################
# - FlowKind: LazyArrayRange
####################
@property
def lazy_array_range(self) -> ct.LazyArrayRangeFlow:
"""Return the not-yet-computed uniform array defined by the socket.
Notes:
Called to compute the internal `FlowKind.LazyArrayRange` of this socket.
Return:
The range of lengths, which uses no symbols.
"""
return ct.LazyArrayRangeFlow(
start=sp.S(self.min_value) * self.unit,
stop=sp.S(self.max_value) * self.unit,
steps=self.steps,
scaling='lin',
unit=self.unit,
)
@lazy_array_range.setter
def lazy_array_range(self, value: ct.LazyArrayRangeFlow) -> None:
"""Set the not-yet-computed uniform array defined by the socket.
Notes:
Called to compute the internal `FlowKind.LazyArrayRange` of this socket.
"""
self.min_value = spux.sympy_to_python(
spux.scale_to_unit(value.start * value.unit, self.unit)
)
self.max_value = spux.sympy_to_python(
spux.scale_to_unit(value.stop * value.unit, self.unit)
)
self.steps = value.steps
####################
# - FlowKind: LazyValueFunc
####################
@property
def lazy_value_func(self) -> ct.LazyValueFuncFlow:
return ct.LazyValueFuncFlow(
func=sp.lambdify(self.symbols, self.value, 'jax'),
func_args=[spux.sympy_to_python_type(sym) for sym in self.symbols],
supports_jax=True,
)
####################
# - FlowKind: Array
####################
@property
def array(self) -> ct.ArrayFlow:
if not self.symbols:
return ct.ArrayFlow(
values=self.lazy_value_func.func_jax(),
unit=self.unit,
)
msg = "Expr socket can't produce array from expression with free symbols"
raise ValueError(msg)
####################
# - FlowKind: Info
####################
@property
def info(self) -> ct.ArrayFlow:
return ct.InfoFlow(
output_name='_', ## TODO: Something else
output_shape=(self.size,) if self.size is not None else None,
output_mathtype=self.mathtype,
output_unit=self.unit,
)
####################
# - FlowKind: Capabilities
####################
@property
def capabilities(self) -> None:
return ct.CapabilitiesFlow(
socket_type=self.socket_type,
active_kind=self.active_kind,
)
## TODO: Prevent all invalid linkage between sockets used as expressions, but don't be too brutal :)
## - This really is a killer feature. But we want to get it right. So we leave it as todo until we know exactly how to tailor CapabilitiesFlow to these needs.
####################
# - UI
####################
def draw_value(self, col: bpy.types.UILayout) -> None:
# Property Interface
if self.symbols:
col.prop(self, self.blfields['raw_value_spstr'], text='')
else:
MT_Z = spux.MathType.Integer
MT_Q = spux.MathType.Rational
MT_R = spux.MathType.Real
MT_C = spux.MathType.Complex
if self.size is None:
if self.mathtype == MT_Z:
col.prop(self, self.blfields['raw_value_int'], text='')
elif self.mathtype == MT_Q:
col.prop(self, self.blfields['raw_value_rat'], text='')
elif self.mathtype == MT_R:
col.prop(self, self.blfields['raw_value_float'], text='')
elif self.mathtype == MT_C:
col.prop(self, self.blfields['raw_value_complex'], text='')
elif self.size == 2:
if self.mathtype == MT_Z:
col.prop(self, self.blfields['raw_value_int2'], text='')
elif self.mathtype == MT_Q:
col.prop(self, self.blfields['raw_value_rat2'], text='')
elif self.mathtype == MT_R:
col.prop(self, self.blfields['raw_value_float2'], text='')
elif self.mathtype == MT_C:
col.prop(self, self.blfields['raw_value_complex2'], text='')
elif self.size == 3:
if self.mathtype == MT_Z:
col.prop(self, self.blfields['raw_value_int3'], text='')
elif self.mathtype == MT_Q:
col.prop(self, self.blfields['raw_value_rat3'], text='')
elif self.mathtype == MT_R:
col.prop(self, self.blfields['raw_value_float3'], text='')
elif self.mathtype == MT_C:
col.prop(self, self.blfields['raw_value_complex3'], text='')
# Symbol Information
if self.symbols:
box = col.box()
split = box.split(factor=0.3)
# Left Col
col = split.column()
col.label(text='Let:')
# Right Col
col = split.column()
col.alignment = 'RIGHT'
for sym in self.symbols:
col.label(text=spux.pretty_symbol(sym))
def draw_input_label_row(self, row: bpy.types.UILayout, text) -> None:
info = self.compute_data(kind=ct.FlowKind.Info)
has_dims = not ct.FlowSignal.check(info) and info.dim_names
if has_dims:
split = row.split(factor=0.85, align=True)
_row = split.row(align=False)
else:
_row = row
_row.label(text=text)
if has_dims:
if self.show_info_columns:
_row.prop(self, self.blfields['info_columns'])
_row = split.row(align=True)
_row.alignment = 'RIGHT'
_row.prop(
self,
self.blfields['show_info_columns'],
toggle=True,
text='',
icon=ct.Icon.ToggleSocketInfo,
)
def draw_output_label_row(self, row: bpy.types.UILayout, text) -> None:
info = self.compute_data(kind=ct.FlowKind.Info)
has_info = not ct.FlowSignal.check(info)
if has_info:
split = row.split(factor=0.15, align=True)
_row = split.row(align=True)
_row.prop(
self,
self.blfields['show_info_columns'],
toggle=True,
text='',
icon=ct.Icon.ToggleSocketInfo,
)
_row = split.row(align=False)
_row.alignment = 'RIGHT'
if self.show_info_columns:
_row.prop(self, self.blfields['info_columns'])
else:
_col = _row.column()
_col.alignment = 'EXPAND'
_col.label(text='')
else:
_row = row
_row.label(text=text)
def draw_info(self, info: ct.InfoFlow, col: bpy.types.UILayout) -> None:
if info.dim_names and self.show_info_columns:
row = col.row()
box = row.box()
grid = box.grid_flow(
columns=len(self.info_columns) + 1,
row_major=True,
even_columns=True,
# even_rows=True,
align=True,
)
# Dimensions
for dim_name in info.dim_names:
dim_idx = info.dim_idx[dim_name]
grid.label(text=dim_name)
if InfoDisplayCol.Length in self.info_columns:
grid.label(text=str(len(dim_idx)))
if InfoDisplayCol.MathType in self.info_columns:
grid.label(text=spux.MathType.to_str(dim_idx.mathtype))
if InfoDisplayCol.Unit in self.info_columns:
grid.label(text=spux.sp_to_str(dim_idx.unit))
# Outputs
grid.label(text=info.output_name)
if InfoDisplayCol.Length in self.info_columns:
grid.label(text='', icon=ct.Icon.DataSocketOutput)
if InfoDisplayCol.MathType in self.info_columns:
grid.label(
text=(
spux.MathType.to_str(info.output_mathtype)
+ (
'ˣ'.join(
[
unicode_superscript(out_axis)
for out_axis in info.output_shape
]
)
if info.output_shape
else ''
)
)
)
if InfoDisplayCol.Unit in self.info_columns:
grid.label(text=f'{spux.sp_to_str(info.output_unit)}')
####################
# - Socket Configuration
####################
class ExprSocketDef(base.SocketDef):
socket_type: ct.SocketType = ct.SocketType.Expr
active_kind: typ.Literal[ct.FlowKind.Value, ct.FlowKind.LazyArrayRange] = (
ct.FlowKind.Value
)
# Properties
size: typ.Literal[None, 2, 3] = None
mathtype: spux.MathType = spux.MathType.Real
symbols: frozenset[spux.Symbol] = frozenset()
## Units
unit_dim: spux.UnitDimension | None = None
## Info Display
show_info_columns: bool = False
## TODO: Buncha validation :)
# Defaults
default_unit: spux.Unit | None = None
default_value: spux.SympyExpr = sp.S(1)
default_min: spux.SympyExpr = sp.S(0)
default_max: spux.SympyExpr = sp.S(1)
default_steps: spux.SympyExpr = sp.S(2)
def init(self, bl_socket: ExprBLSocket) -> None:
bl_socket.active_kind = self.active_kind
bl_socket.size = self.size
bl_socket.mathtype = self.size
bl_socket.symbols = self.symbols
bl_socket.unit_dim = self.size
bl_socket.unit = self.symbols
bl_socket.show_info_columns = self.show_info_columns
bl_socket.value = self.default
####################
# - Blender Registration
####################
BL_REGISTER = [
ExprBLSocket,
]

View File

@ -1,23 +0,0 @@
from . import integer_number
IntegerNumberSocketDef = integer_number.IntegerNumberSocketDef
from . import rational_number
RationalNumberSocketDef = rational_number.RationalNumberSocketDef
from . import real_number
RealNumberSocketDef = real_number.RealNumberSocketDef
from . import complex_number
ComplexNumberSocketDef = complex_number.ComplexNumberSocketDef
BL_REGISTER = [
*integer_number.BL_REGISTER,
*rational_number.BL_REGISTER,
*real_number.BL_REGISTER,
*complex_number.BL_REGISTER,
]

View File

@ -1,146 +0,0 @@
import typing as typ
import bpy
import sympy as sp
from blender_maxwell.utils import extra_sympy_units as spux
from ... import contracts as ct
from .. import base
####################
# - Blender Socket
####################
class ComplexNumberBLSocket(base.MaxwellSimSocket):
socket_type = ct.SocketType.ComplexNumber
bl_label = 'Complex Number'
####################
# - Properties
####################
raw_value: bpy.props.FloatVectorProperty(
name='Complex Number',
description='Represents a complex number (real, imaginary)',
size=2,
default=(0.0, 0.0),
update=(lambda self, context: self.on_prop_changed('raw_value', context)),
)
coord_sys: bpy.props.EnumProperty(
name='Coordinate System',
description='Choose between cartesian and polar form',
items=[
(
'CARTESIAN',
'Cartesian',
'Use Cartesian Coordinates',
'EMPTY_AXIS',
0,
),
(
'POLAR',
'Polar',
'Use Polar Coordinates',
'DRIVER_ROTATIONAL_DIFFERENCE',
1,
),
],
default='CARTESIAN',
update=lambda self, context: self.on_coord_sys_changed(context),
)
####################
# - Event Methods
####################
def on_coord_sys_changed(self, context: bpy.types.Context):
r"""Transforms values when the coordinate system changes.
Notes:
Cartesian coordinates with $y=0$ has no corresponding $\theta$
Therefore, we manually set $\theta=0$.
"""
if self.coord_sys == 'CARTESIAN':
r, theta_rad = self.raw_value
self.raw_value = (
r * sp.cos(theta_rad),
r * sp.sin(theta_rad),
)
elif self.coord_sys == 'POLAR':
x, y = self.raw_value
cart_value = x + sp.I * y
self.raw_value = (
float(sp.Abs(cart_value)),
float(sp.arg(cart_value)) if y != 0 else float(0),
)
self.on_prop_changed('coord_sys', context)
####################
# - Socket UI
####################
def draw_value(self, col: bpy.types.UILayout) -> None:
"""Draw the value of the complex number, including a toggle for specifying the active coordinate system."""
# Value Row
row = col.row()
row.prop(self, 'raw_value', text='')
# Coordinate System Dropdown
col.prop(self, 'coord_sys', text='')
####################
# - Computation of Default Value
####################
@property
def value(self) -> spux.ComplexNumber:
"""Return the complex number as a sympy expression, of a form determined by the coordinate system.
- **Cartesian**: $(a,b) -> a + ib$
- **Polar**: $(r,t) -> re^(it)$
Returns:
The complex number as a `sympy` type.
"""
v1, v2 = self.raw_value
return {
'CARTESIAN': v1 + sp.I * v2,
'POLAR': v1 * sp.exp(sp.I * v2),
}[self.coord_sys]
@value.setter
def value(self, value: spux.ComplexNumber) -> None:
"""Set the complex number from a sympy expression, by numerically simplifying it into coordinate-system determined components.
- **Cartesian**: $(a,b) -> a + ib$
- **Polar**: $(r,t) -> re^(it)$
Parameters:
value: The complex number as a `sympy` type.
"""
self.raw_value = {
'CARTESIAN': (float(sp.re(value)), float(sp.im(value))),
'POLAR': (float(sp.Abs(value)), float(sp.arg(value))),
}[self.coord_sys]
####################
# - Socket Configuration
####################
class ComplexNumberSocketDef(base.SocketDef):
socket_type: ct.SocketType = ct.SocketType.ComplexNumber
default_value: spux.ComplexNumber = sp.S(0)
coord_sys: typ.Literal['CARTESIAN', 'POLAR'] = 'CARTESIAN'
def init(self, bl_socket: ComplexNumberBLSocket) -> None:
bl_socket.value = self.default_value
bl_socket.coord_sys = self.coord_sys
####################
# - Blender Registration
####################
BL_REGISTER = [
ComplexNumberBLSocket,
]

View File

@ -1,58 +0,0 @@
import bpy
from ... import contracts as ct
from .. import base
####################
# - Blender Socket
####################
class IntegerNumberBLSocket(base.MaxwellSimSocket):
socket_type = ct.SocketType.IntegerNumber
bl_label = 'Integer Number'
####################
# - Properties
####################
raw_value: bpy.props.IntProperty(
name='Integer',
description='Represents an integer',
default=0,
update=(lambda self, context: self.on_prop_changed('raw_value', context)),
)
####################
# - Socket UI
####################
def draw_value(self, col: bpy.types.UILayout) -> None:
col_row = col.row()
col_row.prop(self, 'raw_value', text='')
####################
# - Default Value
####################
@property
def value(self) -> int:
return self.raw_value
@value.setter
def value(self, value: int) -> None:
self.raw_value = value
####################
# - Socket Configuration
####################
class IntegerNumberSocketDef(base.SocketDef):
socket_type: ct.SocketType = ct.SocketType.IntegerNumber
default_value: int = 0
def init(self, bl_socket: IntegerNumberBLSocket) -> None:
bl_socket.value = self.default_value
####################
# - Blender Registration
####################
BL_REGISTER = [IntegerNumberBLSocket]

View File

@ -1,72 +0,0 @@
import bpy
import sympy as sp
from blender_maxwell.utils.pydantic_sympy import SympyExpr
from ... import contracts as ct
from .. import base
####################
# - Blender Socket
####################
class RationalNumberBLSocket(base.MaxwellSimSocket):
socket_type = ct.SocketType.RationalNumber
bl_label = 'Rational Number'
####################
# - Properties
####################
raw_value: bpy.props.IntVectorProperty(
name='Rational Number',
description='Represents a rational number (int / int)',
size=2,
default=(1, 1),
subtype='NONE',
update=(lambda self, context: self.on_prop_changed('raw_value', context)),
)
####################
# - UI
####################
def draw_value(self, col: bpy.types.UILayout) -> None:
col_row = col.row(align=True)
col_row.prop(self, 'raw_value', text='')
####################
# - Default Value
####################
@property
def value(self) -> sp.Rational:
p, q = self.raw_value
return sp.Rational(p, q)
@value.setter
def value(self, value: float | tuple[int, int] | SympyExpr) -> None:
if isinstance(value, float):
approx_rational = sp.nsimplify(value)
self.raw_value = (approx_rational.p, approx_rational.q)
elif isinstance(value, tuple):
self.raw_value = value
else:
self.raw_value = (value.p, value.q)
####################
# - Socket Configuration
####################
class RationalNumberSocketDef(base.SocketDef):
socket_type: ct.SocketType = ct.SocketType.RationalNumber
default_value: SympyExpr = sp.Rational(0, 1)
def init(self, bl_socket: RationalNumberBLSocket) -> None:
bl_socket.value = self.default_value
####################
# - Blender Registration
####################
BL_REGISTER = [
RationalNumberBLSocket,
]

View File

@ -1,66 +0,0 @@
import bpy
from blender_maxwell.utils.pydantic_sympy import SympyExpr
from ... import contracts as ct
from .. import base
####################
# - Blender Socket
####################
class RealNumberBLSocket(base.MaxwellSimSocket):
socket_type = ct.SocketType.RealNumber
bl_label = 'Real Number'
####################
# - Properties
####################
raw_value: bpy.props.FloatProperty(
name='Real Number',
description='Represents a real number',
default=0.0,
precision=6,
update=(lambda self, context: self.on_prop_changed('raw_value', context)),
)
####################
# - Socket UI
####################
def draw_value(self, col: bpy.types.UILayout) -> None:
col_row = col.row()
col_row.prop(self, 'raw_value', text='')
####################
# - Computation of Default Value
####################
@property
def value(self) -> float:
return self.raw_value
@value.setter
def value(self, value: float | SympyExpr) -> None:
if isinstance(value, float):
self.raw_value = value
else:
float(value.n())
####################
# - Socket Configuration
####################
class RealNumberSocketDef(base.SocketDef):
socket_type: ct.SocketType = ct.SocketType.RealNumber
default_value: float = 0.0
def init(self, bl_socket: RealNumberBLSocket) -> None:
bl_socket.value = self.default_value
####################
# - Blender Registration
####################
BL_REGISTER = [
RealNumberBLSocket,
]

View File

@ -1,61 +1,9 @@
from . import unit_system from . import pol, unit_system
PhysicalUnitSystemSocketDef = unit_system.PhysicalUnitSystemSocketDef
from . import time
PhysicalTimeSocketDef = time.PhysicalTimeSocketDef
from . import angle
PhysicalAngleSocketDef = angle.PhysicalAngleSocketDef
from . import area, length, volume
PhysicalLengthSocketDef = length.PhysicalLengthSocketDef
PhysicalAreaSocketDef = area.PhysicalAreaSocketDef
PhysicalVolumeSocketDef = volume.PhysicalVolumeSocketDef
from . import point_3d
PhysicalPoint3DSocketDef = point_3d.PhysicalPoint3DSocketDef
from . import size_3d
PhysicalSize3DSocketDef = size_3d.PhysicalSize3DSocketDef
from . import mass
PhysicalMassSocketDef = mass.PhysicalMassSocketDef
from . import accel_scalar, force_scalar, speed
PhysicalSpeedSocketDef = speed.PhysicalSpeedSocketDef
PhysicalAccelScalarSocketDef = accel_scalar.PhysicalAccelScalarSocketDef
PhysicalForceScalarSocketDef = force_scalar.PhysicalForceScalarSocketDef
from . import pol
PhysicalPolSocketDef = pol.PhysicalPolSocketDef PhysicalPolSocketDef = pol.PhysicalPolSocketDef
from . import freq
PhysicalFreqSocketDef = freq.PhysicalFreqSocketDef
BL_REGISTER = [ BL_REGISTER = [
*unit_system.BL_REGISTER, *unit_system.BL_REGISTER,
*time.BL_REGISTER,
*angle.BL_REGISTER,
*length.BL_REGISTER,
*area.BL_REGISTER,
*volume.BL_REGISTER,
*point_3d.BL_REGISTER,
*size_3d.BL_REGISTER,
*mass.BL_REGISTER,
*speed.BL_REGISTER,
*accel_scalar.BL_REGISTER,
*force_scalar.BL_REGISTER,
*pol.BL_REGISTER, *pol.BL_REGISTER,
*freq.BL_REGISTER,
] ]

View File

@ -1,65 +0,0 @@
import bpy
import sympy.physics.units as spu
from blender_maxwell.utils.pydantic_sympy import SympyExpr
from ... import contracts as ct
from .. import base
####################
# - Blender Socket
####################
class PhysicalAccelScalarBLSocket(base.MaxwellSimSocket):
socket_type = ct.SocketType.PhysicalAccelScalar
bl_label = 'Accel Scalar'
use_units = True
####################
# - Properties
####################
raw_value: bpy.props.FloatProperty(
name='Unitless Acceleration',
description='Represents the unitless part of the acceleration',
default=0.0,
precision=6,
update=(lambda self, context: self.on_prop_changed('raw_value', context)),
)
####################
# - Socket UI
####################
def draw_value(self, col: bpy.types.UILayout) -> None:
col.prop(self, 'raw_value', text='')
####################
# - Default Value
####################
@property
def value(self) -> SympyExpr:
return self.raw_value * self.unit
@value.setter
def value(self, value: SympyExpr) -> None:
self.raw_value = spu.convert_to(value, self.unit) / self.unit
####################
# - Socket Configuration
####################
class PhysicalAccelScalarSocketDef(base.SocketDef):
socket_type: ct.SocketType = ct.SocketType.PhysicalAccelScalar
default_unit: SympyExpr | None = None
def init(self, bl_socket: PhysicalAccelScalarBLSocket) -> None:
if self.default_unit:
bl_socket.unit = self.default_unit
####################
# - Blender Registration
####################
BL_REGISTER = [
PhysicalAccelScalarBLSocket,
]

View File

@ -1,65 +0,0 @@
import bpy
import sympy.physics.units as spu
from blender_maxwell.utils.pydantic_sympy import SympyExpr
from ... import contracts as ct
from .. import base
####################
# - Blender Socket
####################
class PhysicalAngleBLSocket(base.MaxwellSimSocket):
socket_type = ct.SocketType.PhysicalAngle
bl_label = 'Physical Angle'
use_units = True
####################
# - Properties
####################
raw_value: bpy.props.FloatProperty(
name='Unitless Acceleration',
description='Represents the unitless part of the acceleration',
default=0.0,
precision=4,
update=(lambda self, context: self.on_prop_changed('raw_value', context)),
)
####################
# - Socket UI
####################
def draw_value(self, col: bpy.types.UILayout) -> None:
col.prop(self, 'raw_value', text='')
####################
# - Default Value
####################
@property
def value(self) -> SympyExpr:
return self.raw_value * self.unit
@value.setter
def value(self, value: SympyExpr) -> None:
self.raw_value = spu.convert_to(value, self.unit) / self.unit
####################
# - Socket Configuration
####################
class PhysicalAngleSocketDef(base.SocketDef):
socket_type: ct.SocketType = ct.SocketType.PhysicalAngle
default_unit: SympyExpr | None = None
def init(self, bl_socket: PhysicalAngleBLSocket) -> None:
if self.default_unit:
bl_socket.unit = self.default_unit
####################
# - Blender Registration
####################
BL_REGISTER = [
PhysicalAngleBLSocket,
]

View File

@ -1,72 +0,0 @@
import typing as typ
import bpy
import sympy as sp
from ... import contracts as ct
from .. import base
class PhysicalAreaBLSocket(base.MaxwellSimSocket):
socket_type = ct.SocketType.PhysicalArea
bl_label = 'Physical Area'
use_units = True
####################
# - Properties
####################
raw_value: bpy.props.FloatProperty(
name='Unitless Area',
description='Represents the unitless part of the area',
default=0.0,
precision=6,
update=(lambda self, context: self.on_prop_changed('raw_value', context)),
)
####################
# - Socket UI
####################
def draw_value(self, col: bpy.types.UILayout) -> None:
col.prop(self, 'raw_value', text='')
####################
# - Computation of Default Value
####################
@property
def default_value(self) -> sp.Expr:
"""Return the area as a sympy expression, which is a pure real
number perfectly expressed as the active unit.
Returns:
The area as a sympy expression (with units).
"""
return self.raw_value * self.unit
@default_value.setter
def default_value(self, value: typ.Any) -> None:
"""Set the area from a sympy expression, including any required
unit conversions to normalize the input value to the selected
units.
"""
self.raw_value = self.value_as_unit(value)
####################
# - Socket Configuration
####################
class PhysicalAreaSocketDef(base.SocketDef):
socket_type: ct.SocketType = ct.SocketType.PhysicalArea
default_unit: typ.Any | None = None
def init(self, bl_socket: PhysicalAreaBLSocket) -> None:
if self.default_unit:
bl_socket.unit = self.default_unit
####################
# - Blender Registration
####################
BL_REGISTER = [
PhysicalAreaBLSocket,
]

View File

@ -1,65 +0,0 @@
import bpy
import sympy.physics.units as spu
from blender_maxwell.utils.pydantic_sympy import SympyExpr
from ... import contracts as ct
from .. import base
####################
# - Blender Socket
####################
class PhysicalForceScalarBLSocket(base.MaxwellSimSocket):
socket_type = ct.SocketType.PhysicalForceScalar
bl_label = 'Force Scalar'
use_units = True
####################
# - Properties
####################
raw_value: bpy.props.FloatProperty(
name='Unitless Force',
description='Represents the unitless part of the force',
default=0.0,
precision=6,
update=(lambda self, context: self.on_prop_changed('raw_value', context)),
)
####################
# - Socket UI
####################
def draw_value(self, col: bpy.types.UILayout) -> None:
col.prop(self, 'raw_value', text='')
####################
# - Default Value
####################
@property
def value(self) -> SympyExpr:
return self.raw_value * self.unit
@value.setter
def value(self, value: SympyExpr) -> None:
self.raw_value = spu.convert_to(value, self.unit) / self.unit
####################
# - Socket Configuration
####################
class PhysicalForceScalarSocketDef(base.SocketDef):
socket_type: ct.SocketType = ct.SocketType.PhysicalForceScalar
default_unit: SympyExpr | None = None
def init(self, bl_socket: PhysicalForceScalarBLSocket) -> None:
if self.default_unit:
bl_socket.unit = self.default_unit
####################
# - Blender Registration
####################
BL_REGISTER = [
PhysicalForceScalarBLSocket,
]

View File

@ -1,126 +0,0 @@
import bpy
import sympy as sp
from blender_maxwell.utils import extra_sympy_units as spux
from blender_maxwell.utils import logger
from blender_maxwell.utils.pydantic_sympy import SympyExpr
from ... import contracts as ct
from .. import base
log = logger.get(__name__)
####################
# - Blender Socket
####################
class PhysicalFreqBLSocket(base.MaxwellSimSocket):
socket_type = ct.SocketType.PhysicalFreq
bl_label = 'Frequency'
use_units = True
####################
# - Properties
####################
raw_value: bpy.props.FloatProperty(
name='Unitless Frequency',
description='Represents the unitless part of the frequency',
default=0.0,
precision=6,
update=(lambda self, context: self.on_prop_changed('raw_value', context)),
)
min_freq: bpy.props.FloatProperty(
name='Min Frequency',
description='Lowest frequency',
default=0.0,
precision=4,
update=(lambda self, context: self.on_prop_changed('min_freq', context)),
)
max_freq: bpy.props.FloatProperty(
name='Max Frequency',
description='Highest frequency',
default=0.0,
precision=4,
update=(lambda self, context: self.on_prop_changed('max_freq', context)),
)
steps: bpy.props.IntProperty(
name='Frequency Steps',
description='# of steps between min and max',
default=2,
update=(lambda self, context: self.on_prop_changed('steps', context)),
)
####################
# - Socket UI
####################
def draw_value(self, col: bpy.types.UILayout) -> None:
col.prop(self, 'raw_value', text='')
def draw_lazy_value_range(self, col: bpy.types.UILayout) -> None:
col.prop(self, 'min_freq', text='Min')
col.prop(self, 'max_freq', text='Max')
col.prop(self, 'steps', text='Steps')
####################
# - Default Value
####################
@property
def value(self) -> SympyExpr:
return self.raw_value * self.unit
@value.setter
def value(self, value: SympyExpr) -> None:
self.raw_value = spux.sympy_to_python(spux.scale_to_unit(value, self.unit))
@property
def lazy_array_range(self) -> ct.LazyArrayRangeFlow:
return ct.LazyArrayRangeFlow(
symbols=set(),
unit=self.unit,
start=sp.S(self.min_freq) * self.unit,
stop=sp.S(self.max_freq) * self.unit,
steps=self.steps,
scaling='lin',
)
@lazy_array_range.setter
def lazy_array_range(self, value: ct.LazyArrayRangeFlow) -> None:
self.min_freq = spux.sympy_to_python(
spux.scale_to_unit(value.start * value.unit, self.unit)
)
self.max_freq = spux.sympy_to_python(
spux.scale_to_unit(value.stop * value.unit, self.unit)
)
self.steps = value.steps
####################
# - Socket Configuration
####################
class PhysicalFreqSocketDef(base.SocketDef):
socket_type: ct.SocketType = ct.SocketType.PhysicalFreq
is_array: bool = False
default_value: SympyExpr = 500 * spux.terahertz
default_unit: SympyExpr = spux.terahertz
min_freq: SympyExpr = 400.0 * spux.terahertz
max_freq: SympyExpr = 600.0 * spux.terahertz
steps: int = 50
def init(self, bl_socket: PhysicalFreqBLSocket) -> None:
bl_socket.unit = self.default_unit
bl_socket.value = self.default_value
if self.is_array:
bl_socket.active_kind = ct.FlowKind.LazyArrayRange
bl_socket.lazy_value_range = (self.min_freq, self.max_freq, self.steps)
####################
# - Blender Registration
####################
BL_REGISTER = [
PhysicalFreqBLSocket,
]

View File

@ -1,128 +0,0 @@
import bpy
import sympy as sp
import sympy.physics.units as spu
from blender_maxwell.utils import extra_sympy_units as spux
from blender_maxwell.utils import logger
from blender_maxwell.utils.pydantic_sympy import SympyExpr
from ... import contracts as ct
from .. import base
log = logger.get(__name__)
####################
# - Blender Socket
####################
class PhysicalLengthBLSocket(base.MaxwellSimSocket):
socket_type = ct.SocketType.PhysicalLength
bl_label = 'PhysicalLength'
use_units = True
####################
# - Properties
####################
raw_value: bpy.props.FloatProperty(
name='Unitless Length',
description='Represents the unitless part of the length',
default=0.0,
precision=6,
update=(lambda self, context: self.on_prop_changed('raw_value', context)),
)
min_len: bpy.props.FloatProperty(
name='Min Length',
description='Lowest length',
default=0.0,
precision=4,
update=(lambda self, context: self.on_prop_changed('min_len', context)),
)
max_len: bpy.props.FloatProperty(
name='Max Length',
description='Highest length',
default=0.0,
precision=4,
update=(lambda self, context: self.on_prop_changed('max_len', context)),
)
steps: bpy.props.IntProperty(
name='Length Steps',
description='# of steps between min and max',
default=2,
update=(lambda self, context: self.on_prop_changed('steps', context)),
)
####################
# - Socket UI
####################
def draw_value(self, col: bpy.types.UILayout) -> None:
col.prop(self, 'raw_value', text='')
def draw_lazy_value_range(self, col: bpy.types.UILayout) -> None:
col.prop(self, 'min_len', text='Min')
col.prop(self, 'max_len', text='Max')
col.prop(self, 'steps', text='Steps')
####################
# - Default Value
####################
@property
def value(self) -> SympyExpr:
return self.raw_value * self.unit
@value.setter
def value(self, value: SympyExpr) -> None:
self.raw_value = spux.sympy_to_python(spux.scale_to_unit(value, self.unit))
@property
def lazy_array_range(self) -> ct.LazyArrayRangeFlow:
return ct.LazyArrayRangeFlow(
symbols=set(),
unit=self.unit,
start=sp.S(self.min_len) * self.unit,
stop=sp.S(self.max_len) * self.unit,
steps=self.steps,
scaling='lin',
)
@lazy_array_range.setter
def lazy_array_range(self, value: ct.LazyArrayRangeFlow) -> None:
self.min_len = spux.sympy_to_python(
spux.scale_to_unit(value.start * value.unit, self.unit)
)
self.max_len = spux.sympy_to_python(
spux.scale_to_unit(value.stop * value.unit, self.unit)
)
self.steps = value.steps
####################
# - Socket Configuration
####################
class PhysicalLengthSocketDef(base.SocketDef):
socket_type: ct.SocketType = ct.SocketType.PhysicalLength
is_array: bool = False
default_value: SympyExpr = 1 * spu.um
default_unit: SympyExpr | None = None
min_len: SympyExpr = 400.0 * spu.nm
max_len: SympyExpr = 700.0 * spu.nm
steps: SympyExpr = 50
def init(self, bl_socket: PhysicalLengthBLSocket) -> None:
if self.default_unit:
bl_socket.unit = self.default_unit
bl_socket.value = self.default_value
if self.is_array:
bl_socket.active_kind = ct.FlowKind.LazyArrayRange
bl_socket.lazy_value_range = (self.min_len, self.max_len, self.steps)
####################
# - Blender Registration
####################
BL_REGISTER = [
PhysicalLengthBLSocket,
]

View File

@ -1,64 +0,0 @@
import bpy
import sympy.physics.units as spu
from blender_maxwell.utils.pydantic_sympy import SympyExpr
from ... import contracts as ct
from .. import base
####################
# - Blender Socket
####################
class PhysicalMassBLSocket(base.MaxwellSimSocket):
socket_type = ct.SocketType.PhysicalMass
bl_label = 'Mass'
use_units = True
####################
# - Properties
####################
raw_value: bpy.props.FloatProperty(
name='Unitless Mass',
description='Represents the unitless part of mass',
default=0.0,
precision=6,
update=(lambda self, context: self.on_prop_changed('raw_value', context)),
)
####################
# - Socket UI
####################
def draw_value(self, col: bpy.types.UILayout) -> None:
col.prop(self, 'raw_value', text='')
####################
# - Default Value
####################
@property
def value(self) -> SympyExpr:
return self.raw_value * self.unit
@value.setter
def value(self, value: SympyExpr) -> None:
self.raw_value = spu.convert_to(value, self.unit) / self.unit
####################
# - Socket Configuration
####################
class PhysicalMassSocketDef(base.SocketDef):
socket_type: ct.SocketType = ct.SocketType.PhysicalMass
default_unit: SympyExpr | None = None
def init(self, bl_socket: PhysicalMassBLSocket) -> None:
pass
####################
# - Blender Registration
####################
BL_REGISTER = [
PhysicalMassBLSocket,
]

View File

@ -1,66 +0,0 @@
import typing as typ
import bpy
import sympy as sp
import sympy.physics.units as spu
from blender_maxwell.utils.pydantic_sympy import SympyExpr
from ... import contracts as ct
from .. import base
class PhysicalPoint3DBLSocket(base.MaxwellSimSocket):
socket_type = ct.SocketType.PhysicalPoint3D
bl_label = 'Volume'
use_units = True
####################
# - Properties
####################
raw_value: bpy.props.FloatVectorProperty(
name='Unitless 3D Point (global coordinate system)',
description='Represents the unitless part of the 3D point',
size=3,
default=(0.0, 0.0, 0.0),
precision=4,
update=(lambda self, context: self.on_prop_changed('raw_value', context)),
)
####################
# - Socket UI
####################
def draw_value(self, col: bpy.types.UILayout) -> None:
col.prop(self, 'raw_value', text='')
####################
# - Default Value
####################
@property
def value(self) -> sp.MatrixBase:
return sp.Matrix(tuple(self.raw_value)) * self.unit
@value.setter
def value(self, value: SympyExpr) -> None:
self.raw_value = tuple(spu.convert_to(value, self.unit) / self.unit)
####################
# - Socket Configuration
####################
class PhysicalPoint3DSocketDef(base.SocketDef):
socket_type: ct.SocketType = ct.SocketType.PhysicalPoint3D
default_unit: typ.Any | None = None
def init(self, bl_socket: PhysicalPoint3DBLSocket) -> None:
if self.default_unit:
bl_socket.unit = self.default_unit
####################
# - Blender Registration
####################
BL_REGISTER = [
PhysicalPoint3DBLSocket,
]

View File

@ -1,66 +0,0 @@
import bpy
import sympy as sp
import sympy.physics.units as spu
from blender_maxwell.utils.pydantic_sympy import SympyExpr
from ... import contracts as ct
from .. import base
class PhysicalSize3DBLSocket(base.MaxwellSimSocket):
socket_type = ct.SocketType.PhysicalSize3D
bl_label = '3D Size'
use_units = True
####################
# - Properties
####################
raw_value: bpy.props.FloatVectorProperty(
name='Unitless 3D Size',
description='Represents the unitless part of the 3D size',
size=3,
default=(1.0, 1.0, 1.0),
precision=4,
update=(lambda self, context: self.on_prop_changed('raw_value', context)),
)
####################
# - Socket UI
####################
def draw_value(self, col: bpy.types.UILayout) -> None:
col.prop(self, 'raw_value', text='')
####################
# - Computation of Default Value
####################
@property
def value(self) -> SympyExpr:
return sp.Matrix(tuple(self.raw_value)) * self.unit
@value.setter
def value(self, value: SympyExpr) -> None:
self.raw_value = tuple(spu.convert_to(value, self.unit) / self.unit)
####################
# - Socket Configuration
####################
class PhysicalSize3DSocketDef(base.SocketDef):
socket_type: ct.SocketType = ct.SocketType.PhysicalSize3D
default_value: SympyExpr = sp.Matrix([1, 1, 1]) * spu.um
default_unit: SympyExpr | None = None
def init(self, bl_socket: PhysicalSize3DBLSocket) -> None:
bl_socket.value = self.default_value
if self.default_unit:
bl_socket.unit = self.default_unit
####################
# - Blender Registration
####################
BL_REGISTER = [
PhysicalSize3DBLSocket,
]

View File

@ -1,65 +0,0 @@
import bpy
import sympy.physics.units as spu
from blender_maxwell.utils.pydantic_sympy import SympyExpr
from ... import contracts as ct
from .. import base
####################
# - Blender Socket
####################
class PhysicalSpeedBLSocket(base.MaxwellSimSocket):
socket_type = ct.SocketType.PhysicalSpeed
bl_label = 'Speed'
use_units = True
####################
# - Properties
####################
raw_value: bpy.props.FloatProperty(
name='Unitless Speed',
description='Represents the unitless part of the speed',
default=0.0,
precision=6,
update=(lambda self, context: self.on_prop_changed('raw_value', context)),
)
####################
# - Socket UI
####################
def draw_value(self, col: bpy.types.UILayout) -> None:
col.prop(self, 'raw_value', text='')
####################
# - Default Value
####################
@property
def value(self) -> SympyExpr:
return self.raw_value * self.unit
@value.setter
def value(self, value: SympyExpr) -> None:
self.raw_value = spu.convert_to(value, self.unit) / self.unit
####################
# - Socket Configuration
####################
class PhysicalSpeedSocketDef(base.SocketDef):
socket_type: ct.SocketType = ct.SocketType.PhysicalSpeed
default_unit: SympyExpr | None = None
def init(self, bl_socket: PhysicalSpeedBLSocket) -> None:
if self.default_unit:
bl_socket.unit = self.default_unit
####################
# - Blender Registration
####################
BL_REGISTER = [
PhysicalSpeedBLSocket,
]

View File

@ -1,70 +0,0 @@
import typing as typ
import bpy
import sympy.physics.units as spu
from blender_maxwell.utils.pydantic_sympy import SympyExpr
from ... import contracts as ct
from .. import base
####################
# - Blender Socket
####################
class PhysicalTimeBLSocket(base.MaxwellSimSocket):
socket_type = ct.SocketType.PhysicalTime
bl_label = 'Time'
use_units = True
####################
# - Properties
####################
raw_value: bpy.props.FloatProperty(
name='Unitless Time',
description='Represents the unitless part of time',
default=0.0,
precision=4,
update=(lambda self, context: self.on_prop_changed('raw_value', context)),
)
####################
# - Socket UI
####################
def draw_value(self, col: bpy.types.UILayout) -> None:
col.prop(self, 'raw_value', text='')
####################
# - Default Value
####################
@property
def value(self) -> SympyExpr:
return self.raw_value * self.unit
@value.setter
def value(self, value: SympyExpr) -> None:
self.raw_value = spu.convert_to(value, self.unit) / self.unit
####################
# - Socket Configuration
####################
class PhysicalTimeSocketDef(base.SocketDef):
socket_type: ct.SocketType = ct.SocketType.PhysicalTime
default_value: SympyExpr | None = None
default_unit: typ.Any | None = None
def init(self, bl_socket: PhysicalTimeBLSocket) -> None:
if self.default_value:
bl_socket.value = self.default_value
if self.default_unit:
bl_socket.unit = self.default_unit
####################
# - Blender Registration
####################
BL_REGISTER = [
PhysicalTimeBLSocket,
]

View File

@ -1,62 +0,0 @@
import bpy
import sympy.physics.units as spu
from blender_maxwell.utils.pydantic_sympy import SympyExpr
from ... import contracts as ct
from .. import base
class PhysicalVolumeBLSocket(base.MaxwellSimSocket):
socket_type = ct.SocketType.PhysicalVolume
bl_label = 'Volume'
use_units = True
####################
# - Properties
####################
raw_value: bpy.props.FloatProperty(
name='Unitless Volume',
description='Represents the unitless part of the area',
default=0.0,
precision=6,
update=(lambda self, context: self.on_prop_changed('raw_value', context)),
)
####################
# - Socket UI
####################
def draw_value(self, col: bpy.types.UILayout) -> None:
col.prop(self, 'raw_value', text='')
####################
# - Default Value
####################
@property
def value(self) -> SympyExpr:
return self.raw_value * self.unit
@value.setter
def value(self, value: SympyExpr) -> None:
self.raw_value = spu.convert_to(value, self.unit) / self.unit
####################
# - Socket Configuration
####################
class PhysicalVolumeSocketDef(base.SocketDef):
socket_type: ct.SocketType = ct.SocketType.PhysicalVolume
default_unit: SympyExpr | None = None
def init(self, bl_socket: PhysicalVolumeBLSocket) -> None:
if self.default_unit:
bl_socket.unit = self.default_unit
####################
# - Blender Registration
####################
BL_REGISTER = [
PhysicalVolumeBLSocket,
]

View File

@ -1,19 +0,0 @@
from . import complex_2d_vector, real_2d_vector
Real2DVectorSocketDef = real_2d_vector.Real2DVectorSocketDef
Complex2DVectorSocketDef = complex_2d_vector.Complex2DVectorSocketDef
from . import complex_3d_vector, integer_3d_vector, real_3d_vector
Integer3DVectorSocketDef = integer_3d_vector.Integer3DVectorSocketDef
Real3DVectorSocketDef = real_3d_vector.Real3DVectorSocketDef
Complex3DVectorSocketDef = complex_3d_vector.Complex3DVectorSocketDef
BL_REGISTER = [
*real_2d_vector.BL_REGISTER,
*complex_2d_vector.BL_REGISTER,
*integer_3d_vector.BL_REGISTER,
*real_3d_vector.BL_REGISTER,
*complex_3d_vector.BL_REGISTER,
]

View File

@ -1,29 +0,0 @@
from ... import contracts as ct
from .. import base
####################
# - Blender Socket
####################
class Complex2DVectorBLSocket(base.MaxwellSimSocket):
socket_type = ct.SocketType.Complex2DVector
bl_label = 'Complex 2D Vector'
####################
# - Socket Configuration
####################
class Complex2DVectorSocketDef(base.SocketDef):
socket_type: ct.SocketType = ct.SocketType.Complex2DVector
def init(self, bl_socket: Complex2DVectorBLSocket) -> None:
pass
####################
# - Blender Registration
####################
BL_REGISTER = [
Complex2DVectorBLSocket,
]

View File

@ -1,29 +0,0 @@
from ... import contracts as ct
from .. import base
####################
# - Blender Socket
####################
class Complex3DVectorBLSocket(base.MaxwellSimSocket):
socket_type = ct.SocketType.Complex3DVector
bl_label = 'Complex 3D Vector'
####################
# - Socket Configuration
####################
class Complex3DVectorSocketDef(base.SocketDef):
socket_type: ct.SocketType = ct.SocketType.Complex3DVector
def init(self, bl_socket: Complex3DVectorBLSocket) -> None:
pass
####################
# - Blender Registration
####################
BL_REGISTER = [
Complex3DVectorBLSocket,
]

View File

@ -1,71 +0,0 @@
import bpy
import sympy as sp
from blender_maxwell.utils.pydantic_sympy import ConstrSympyExpr
from ... import contracts as ct
from .. import base
Integer3DVector = ConstrSympyExpr(
allow_variables=False,
allow_units=False,
allowed_sets={'integer'},
allowed_structures={'matrix'},
allowed_matrix_shapes={(3, 1)},
)
####################
# - Blender Socket
####################
class Integer3DVectorBLSocket(base.MaxwellSimSocket):
socket_type = ct.SocketType.Integer3DVector
bl_label = 'Integer 3D Vector'
####################
# - Properties
####################
raw_value: bpy.props.IntVectorProperty(
name='Int 3D Vector',
description='Represents an integer 3D (coordinate) vector',
size=3,
default=(0, 0, 0),
update=(lambda self, context: self.on_prop_changed('raw_value', context)),
)
####################
# - Socket UI
####################
def draw_value(self, col: bpy.types.UILayout) -> None:
col.prop(self, 'raw_value', text='')
####################
# - Computation of Default Value
####################
@property
def value(self) -> Integer3DVector:
return sp.Matrix(tuple(self.raw_value))
@value.setter
def value(self, value: Integer3DVector) -> None:
self.raw_value = tuple(int(el) for el in value)
####################
# - Socket Configuration
####################
class Integer3DVectorSocketDef(base.SocketDef):
socket_type: ct.SocketType = ct.SocketType.Integer3DVector
default_value: Integer3DVector = sp.Matrix([0, 0, 0])
def init(self, bl_socket: Integer3DVectorBLSocket) -> None:
bl_socket.value = self.default_value
####################
# - Blender Registration
####################
BL_REGISTER = [
Integer3DVectorBLSocket,
]

View File

@ -1,72 +0,0 @@
import bpy
import sympy as sp
from blender_maxwell.utils.pydantic_sympy import ConstrSympyExpr
from ... import contracts as ct
from .. import base
Real2DVector = ConstrSympyExpr(
allow_variables=False,
allow_units=False,
allowed_sets={'integer', 'rational', 'real'},
allowed_structures={'matrix'},
allowed_matrix_shapes={(2, 1)},
)
####################
# - Blender Socket
####################
class Real2DVectorBLSocket(base.MaxwellSimSocket):
socket_type = ct.SocketType.Real2DVector
bl_label = 'Real2DVector'
####################
# - Properties
####################
raw_value: bpy.props.FloatVectorProperty(
name='Unitless 2D Vector (global coordinate system)',
description='Represents a real 2D (coordinate) vector',
size=2,
default=(0.0, 0.0),
precision=4,
update=(lambda self, context: self.on_prop_changed('raw_value', context)),
)
####################
# - Socket UI
####################
def draw_value(self, col: bpy.types.UILayout) -> None:
col.prop(self, 'raw_value', text='')
####################
# - Computation of Default Value
####################
@property
def value(self) -> Real2DVector:
return sp.Matrix(tuple(self.raw_value))
@value.setter
def value(self, value: Real2DVector) -> None:
self.raw_value = tuple(value)
####################
# - Socket Configuration
####################
class Real2DVectorSocketDef(base.SocketDef):
socket_type: ct.SocketType = ct.SocketType.Real2DVector
default_value: Real2DVector = sp.Matrix([0.0, 0.0])
def init(self, bl_socket: Real2DVectorBLSocket) -> None:
bl_socket.value = self.default_value
####################
# - Blender Registration
####################
BL_REGISTER = [
Real2DVectorBLSocket,
]

View File

@ -1,64 +0,0 @@
import bpy
import sympy as sp
import blender_maxwell.utils.extra_sympy_units as spux
from ... import contracts as ct
from .. import base
####################
# - Blender Socket
####################
class Real3DVectorBLSocket(base.MaxwellSimSocket):
socket_type = ct.SocketType.Real3DVector
bl_label = 'Real 3D Vector'
####################
# - Properties
####################
raw_value: bpy.props.FloatVectorProperty(
name='Real 3D Vector',
description='Represents a real 3D (coordinate) vector',
size=3,
default=(0.0, 0.0, 0.0),
precision=4,
update=(lambda self, context: self.on_prop_changed('raw_value', context)),
)
####################
# - Socket UI
####################
def draw_value(self, col: bpy.types.UILayout) -> None:
col.prop(self, 'raw_value', text='')
####################
# - Computation of Default Value
####################
@property
def value(self) -> spux.Real3DVector:
return sp.Matrix(tuple(self.raw_value))
@value.setter
def value(self, value: spux.Real3DVector) -> None:
self.raw_value = tuple(value)
####################
# - Socket Configuration
####################
class Real3DVectorSocketDef(base.SocketDef):
socket_type: ct.SocketType = ct.SocketType.Real3DVector
default_value: spux.Real3DVector = sp.Matrix([0.0, 0.0, 0.0])
def init(self, bl_socket: Real3DVectorBLSocket) -> None:
bl_socket.value = self.default_value
####################
# - Blender Registration
####################
BL_REGISTER = [
Real3DVectorBLSocket,
]

View File

@ -14,7 +14,6 @@ import enum
import itertools import itertools
import typing as typ import typing as typ
import jax.numpy as jnp
import pydantic as pyd import pydantic as pyd
import sympy as sp import sympy as sp
import sympy.physics.units as spu import sympy.physics.units as spu
@ -25,6 +24,8 @@ SympyType = sp.Basic | sp.Expr | sp.MatrixBase | sp.MutableDenseMatrix | spu.Qua
class MathType(enum.StrEnum): class MathType(enum.StrEnum):
"""Set identities encompassing common mathematical objects."""
Bool = enum.auto() Bool = enum.auto()
Integer = enum.auto() Integer = enum.auto()
Rational = enum.auto() Rational = enum.auto()
@ -49,7 +50,9 @@ class MathType(enum.StrEnum):
return MathType.Bool return MathType.Bool
if sp_obj.is_integer: if sp_obj.is_integer:
return MathType.Integer return MathType.Integer
if sp_obj.is_rational or sp_obj.is_real: if sp_obj.is_rational:
return MathType.Rational
if sp_obj.is_real:
return MathType.Real return MathType.Real
if sp_obj.is_complex: if sp_obj.is_complex:
return MathType.Complex return MathType.Complex
@ -580,6 +583,7 @@ Symbol: typ.TypeAlias = IntSymbol | RealSymbol | ComplexSymbol
# Unit # Unit
## Technically a "unit expression", which includes compound types. ## Technically a "unit expression", which includes compound types.
## Support for this is the killer feature compared to spu.Quantity. ## Support for this is the killer feature compared to spu.Quantity.
UnitDimension: typ.TypeAlias = spu.Dimension
Unit: typ.TypeAlias = ConstrSympyExpr( Unit: typ.TypeAlias = ConstrSympyExpr(
allow_variables=False, allow_variables=False,
allow_units=True, allow_units=True,