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
)
)
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 bpy
import sympy as sp
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 logger
from blender_maxwell.utils import sci_constants as constants
from ... import contracts as ct
from ... import sockets
@ -16,84 +17,158 @@ log = logger.get(__name__)
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
bl_label = 'Wave Constant'
input_socket_sets: typ.ClassVar = {
'Wavelength': {},
'Frequency': {},
'Wavelength': {
'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',
description='Whether to use a wavelength/frequency range',
default=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)
####################
# - Properties
####################
use_range: bool = bl_cache.BLField(False)
####################
# - 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(
'WL',
kind=ct.FlowKind.Value,
# Data
input_sockets={'WL', 'Freq'},
input_sockets_optional={'WL': True, 'Freq': True},
)
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:
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 constants.vac_speed_of_light / input_sockets['Freq']
return sci_constants.vac_speed_of_light / input_sockets['Freq']
@events.computes_output_socket(
'Freq',
kind=ct.FlowKind.Value,
# Data
input_sockets={'WL', 'Freq'},
input_sockets_optional={'WL': True, 'Freq': True},
)
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:
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 constants.vac_speed_of_light / input_sockets['WL']
return sci_constants.vac_speed_of_light / input_sockets['WL']
@events.computes_output_socket(
'WL',
kind=ct.FlowKind.LazyArrayRange,
# Data
input_sockets={'WL', 'Freq'},
input_socket_kinds={
'WL': ct.FlowKind.LazyArrayRange,
'Freq': ct.FlowKind.LazyArrayRange,
},
input_sockets_optional={'WL': True, 'Freq': True},
)
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:
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(
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(
'Freq',
kind=ct.FlowKind.LazyArrayRange,
# Data
input_sockets={'WL', 'Freq'},
input_socket_kinds={
'WL': ct.FlowKind.LazyArrayRange,
@ -102,48 +177,14 @@ class WaveConstantNode(base.MaxwellSimNode):
input_sockets_optional={'WL': True, 'Freq': True},
)
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:
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(
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

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 .. 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
log = logger.get(__name__)
sockets_modules = [basic, number, vector, physical, blender, maxwell, tidy3d]
sockets_modules = [basic, physical, blender, maxwell, tidy3d]
####################
# - Scan for SocketDefs
@ -33,8 +33,6 @@ for socket_type in ct.SocketType:
####################
BL_REGISTER = [
*basic.BL_REGISTER,
*number.BL_REGISTER,
*vector.BL_REGISTER,
*physical.BL_REGISTER,
*blender.BL_REGISTER,
*maxwell.BL_REGISTER,
@ -43,8 +41,6 @@ BL_REGISTER = [
__all__ = [
'basic',
'number',
'vector',
'physical',
'blender',
'maxwell',

View File

@ -1,5 +1,4 @@
import abc
import functools
import typing as typ
import uuid
from types import MappingProxyType
@ -193,6 +192,7 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
log.debug('Initializing Socket: %s', cls.socket_type)
super().__init_subclass__(**kwargs)
# cls._assert_attrs_valid()
## TODO: Implement this :)
# Socket Properties
## Identifiers
@ -235,46 +235,10 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
####################
# - 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
def prev_unit(self) -> sp.Expr:
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
####################
@ -285,37 +249,9 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
Called by `self.on_prop_changed()` when `self.active_kind` was changed.
"""
self.display_shape = (
'SQUARE'
if self.active_kind in {ct.FlowKind.Array, ct.FlowKind.LazyValueRange}
else 'CIRCLE'
'SQUARE' if self.active_kind == ct.FlowKind.LazyValueRange else 'CIRCLE'
) + ('_DOT' if self.use_units else '')
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
## TODO: Valid Active Kinds should be a subset/subenum(?) of FlowKind
def on_prop_changed(self, prop_name: str, _: bpy.types.Context) -> None:
"""Called when a property has been updated.
@ -880,7 +816,6 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
col = row.column(align=True)
{
ct.FlowKind.Value: self.draw_value,
ct.FlowKind.Array: self.draw_array,
ct.FlowKind.LazyArrayRange: self.draw_lazy_array_range,
}[self.active_kind](col)
@ -922,7 +857,7 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
self.draw_info(info, col)
####################
# - UI Methods: Active FlowKind
# - UI Methods: Label Rows
####################
def draw_label_row(
self,
@ -978,6 +913,9 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
"""
row.label(text=text)
####################
# - UI Methods: Active FlowKind
####################
def draw_value(self, col: bpy.types.UILayout) -> None:
"""Draws the socket value on its own line.
@ -988,16 +926,6 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
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:
"""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
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
from . import pol, unit_system
PhysicalPolSocketDef = pol.PhysicalPolSocketDef
from . import freq
PhysicalFreqSocketDef = freq.PhysicalFreqSocketDef
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,
*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 typing as typ
import jax.numpy as jnp
import pydantic as pyd
import sympy as sp
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):
"""Set identities encompassing common mathematical objects."""
Bool = enum.auto()
Integer = enum.auto()
Rational = enum.auto()
@ -49,7 +50,9 @@ class MathType(enum.StrEnum):
return MathType.Bool
if sp_obj.is_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
if sp_obj.is_complex:
return MathType.Complex
@ -580,6 +583,7 @@ Symbol: typ.TypeAlias = IntSymbol | RealSymbol | ComplexSymbol
# Unit
## Technically a "unit expression", which includes compound types.
## Support for this is the killer feature compared to spu.Quantity.
UnitDimension: typ.TypeAlias = spu.Dimension
Unit: typ.TypeAlias = ConstrSympyExpr(
allow_variables=False,
allow_units=True,