refactor: Big breakthrough on Expr socket (non working)
parent
d8170a67e4
commit
80d7b21c34
|
@ -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
|
||||
|
||||
|
||||
####################
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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',
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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,
|
||||
]
|
|
@ -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,
|
||||
]
|
|
@ -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,
|
||||
]
|
|
@ -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,
|
||||
]
|
|
@ -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,
|
||||
]
|
|
@ -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]
|
|
@ -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,
|
||||
]
|
|
@ -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,
|
||||
]
|
|
@ -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,
|
||||
]
|
||||
|
|
|
@ -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,
|
||||
]
|
|
@ -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,
|
||||
]
|
|
@ -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,
|
||||
]
|
|
@ -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,
|
||||
]
|
|
@ -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,
|
||||
]
|
|
@ -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,
|
||||
]
|
|
@ -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,
|
||||
]
|
|
@ -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,
|
||||
]
|
|
@ -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,
|
||||
]
|
|
@ -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,
|
||||
]
|
|
@ -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,
|
||||
]
|
|
@ -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,
|
||||
]
|
|
@ -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,
|
||||
]
|
|
@ -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,
|
||||
]
|
|
@ -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,
|
||||
]
|
|
@ -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,
|
||||
]
|
|
@ -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,
|
||||
]
|
|
@ -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,
|
||||
]
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue