fix: Inching closer.
I'm of the belief that the correct abstractions are now actually available, and that most-to-all of the required functionality actually already exists within the code base. The art is bringing it together!main
parent
e330b9a451
commit
7263d585e5
26
TODO.md
26
TODO.md
|
@ -1,5 +1,5 @@
|
||||||
# Working TODO
|
# Working TODO
|
||||||
- [ ] Wave Constant
|
- [x] Wave Constant
|
||||||
- Bounds
|
- Bounds
|
||||||
- [ ] Boundary Conds
|
- [ ] Boundary Conds
|
||||||
- [ ] PML
|
- [ ] PML
|
||||||
|
@ -18,8 +18,8 @@
|
||||||
- [ ] Data File Import
|
- [ ] Data File Import
|
||||||
- [ ] DataFit Medium
|
- [ ] DataFit Medium
|
||||||
- Monitors
|
- Monitors
|
||||||
- [ ] EH Field
|
- [x] EH Field
|
||||||
- [ ] Power Flux
|
- [x] Power Flux
|
||||||
- [ ] Permittivity
|
- [ ] Permittivity
|
||||||
- [ ] Diffraction
|
- [ ] Diffraction
|
||||||
- Structures
|
- Structures
|
||||||
|
@ -49,9 +49,9 @@
|
||||||
- Integration
|
- Integration
|
||||||
- [ ] Simulation and Analysis of Maxim's Cavity
|
- [ ] Simulation and Analysis of Maxim's Cavity
|
||||||
- Constants
|
- Constants
|
||||||
- [ ] Number Constant
|
- [x] Number Constant
|
||||||
- [ ] Vector Constant
|
- [x] Vector Constant
|
||||||
- [ ] Physical Constant
|
- [x] Physical Constant
|
||||||
|
|
||||||
- [ ] Fix many problems by persisting `_enum_cb_cache` and `_str_cb_cache`.
|
- [ ] Fix many problems by persisting `_enum_cb_cache` and `_str_cb_cache`.
|
||||||
|
|
||||||
|
@ -70,7 +70,7 @@
|
||||||
- [ ] Pol SocketType: 3D Poincare sphere visualization of Stokes vectors.
|
- [ ] Pol SocketType: 3D Poincare sphere visualization of Stokes vectors.
|
||||||
|
|
||||||
- [x] Math / Map Math
|
- [x] Math / Map Math
|
||||||
- [ ] Remove "By x" socket set let socket sets only be "Function"/"Expr"; then add a dynamic enum underneath to select "By x" based on data support.
|
- [x] Remove "By x" socket set let socket sets only be "Function"/"Expr"; then add a dynamic enum underneath to select "By x" based on data support.
|
||||||
- [ ] Filter the operations based on data support, ex. use positive-definiteness to guide cholesky.
|
- [ ] Filter the operations based on data support, ex. use positive-definiteness to guide cholesky.
|
||||||
- [ ] Implement support for additional symbols via `Expr`.
|
- [ ] Implement support for additional symbols via `Expr`.
|
||||||
- [x] Math / Filter Math
|
- [x] Math / Filter Math
|
||||||
|
@ -81,8 +81,6 @@
|
||||||
|
|
||||||
## Inputs
|
## Inputs
|
||||||
- [x] Wave Constant
|
- [x] Wave Constant
|
||||||
- [ ] Fix the LazyValueRange (again!)
|
|
||||||
- [ ] Document
|
|
||||||
- [x] Scene
|
- [x] Scene
|
||||||
- [ ] Implement export of scene time via. Blender unit system.
|
- [ ] Implement export of scene time via. Blender unit system.
|
||||||
- [ ] Implement optional scene-synced time exporting, so that the simulation definition and scene definition match for analysis needs.
|
- [ ] Implement optional scene-synced time exporting, so that the simulation definition and scene definition match for analysis needs.
|
||||||
|
@ -90,14 +88,14 @@
|
||||||
- [x] Constants / Expr Constant
|
- [x] Constants / Expr Constant
|
||||||
- See IDEAS.
|
- See IDEAS.
|
||||||
- [x] Constants / Number Constant
|
- [x] Constants / Number Constant
|
||||||
- [ ] Fix non-integer sockets
|
- [x] Constants / Vector Constant
|
||||||
- [ ] Constants / Vector Constant
|
- [x] Constants / Physical Constant
|
||||||
- [ ] Constants / Physical Constant
|
|
||||||
- [x] Constants / Scientific Constant
|
- [x] Constants / Scientific Constant
|
||||||
- [ ] Nicer (boxed?) node information, maybe centered headers, in a box, etc. .
|
- [ ] Nicer (boxed?) node information, maybe centered headers, in a box, etc. .
|
||||||
- [x] Constants / Unit System Constant
|
- [ ] Constants / Unit System Constant
|
||||||
|
- [ ] Re-implement with `PhysicalType`.
|
||||||
- [ ] Implement presets, including "Tidy3D" and "Blender", shown in the label row.
|
- [ ] Implement presets, including "Tidy3D" and "Blender", shown in the label row.
|
||||||
- [x] Constants / Blender Constant
|
- [ ] Constants / Blender Constant
|
||||||
- [ ] Fix it!
|
- [ ] Fix it!
|
||||||
|
|
||||||
- [ ] Web / Tidy3D Web Importer
|
- [ ] Web / Tidy3D Web Importer
|
||||||
|
|
|
@ -168,13 +168,13 @@ class GeoNodes(enum.StrEnum):
|
||||||
GN.StructurePrimitiveCapsule: GN_INTERNAL_STRUCTURES_PATH,
|
GN.StructurePrimitiveCapsule: GN_INTERNAL_STRUCTURES_PATH,
|
||||||
GN.StructurePrimitiveCone: GN_INTERNAL_STRUCTURES_PATH,
|
GN.StructurePrimitiveCone: GN_INTERNAL_STRUCTURES_PATH,
|
||||||
## Monitor
|
## Monitor
|
||||||
GN.MonitorEHField: GN_INTERNAL_STRUCTURES_PATH,
|
GN.MonitorEHField: GN_INTERNAL_MONITORS_PATH,
|
||||||
GN.MonitorPowerFlux: GN_INTERNAL_STRUCTURES_PATH,
|
GN.MonitorPowerFlux: GN_INTERNAL_MONITORS_PATH,
|
||||||
GN.MonitorEpsTensor: GN_INTERNAL_STRUCTURES_PATH,
|
GN.MonitorEpsTensor: GN_INTERNAL_MONITORS_PATH,
|
||||||
GN.MonitorDiffraction: GN_INTERNAL_STRUCTURES_PATH,
|
GN.MonitorDiffraction: GN_INTERNAL_MONITORS_PATH,
|
||||||
GN.MonitorProjCartEHField: GN_INTERNAL_STRUCTURES_PATH,
|
GN.MonitorProjCartEHField: GN_INTERNAL_MONITORS_PATH,
|
||||||
GN.MonitorProjAngEHField: GN_INTERNAL_STRUCTURES_PATH,
|
GN.MonitorProjAngEHField: GN_INTERNAL_MONITORS_PATH,
|
||||||
GN.MonitorProjKSpaceEHField: GN_INTERNAL_STRUCTURES_PATH,
|
GN.MonitorProjKSpaceEHField: GN_INTERNAL_MONITORS_PATH,
|
||||||
## Simulation
|
## Simulation
|
||||||
GN.SimulationSimDomain: GN_INTERNAL_SIMULATIONS_PATH,
|
GN.SimulationSimDomain: GN_INTERNAL_SIMULATIONS_PATH,
|
||||||
GN.SimulationBoundConds: GN_INTERNAL_SIMULATIONS_PATH,
|
GN.SimulationBoundConds: GN_INTERNAL_SIMULATIONS_PATH,
|
||||||
|
@ -225,7 +225,7 @@ def import_geonodes(
|
||||||
if import_method == 'link' and geonodes in bpy.data.node_groups:
|
if import_method == 'link' and geonodes in bpy.data.node_groups:
|
||||||
return bpy.data.node_groups[geonodes]
|
return bpy.data.node_groups[geonodes]
|
||||||
|
|
||||||
filename = geonodes
|
filename = str(geonodes)
|
||||||
filepath = str(geonodes.parent_path / (geonodes + '.blend') / 'NodeTree' / geonodes)
|
filepath = str(geonodes.parent_path / (geonodes + '.blend') / 'NodeTree' / geonodes)
|
||||||
directory = filepath.removesuffix(geonodes)
|
directory = filepath.removesuffix(geonodes)
|
||||||
log.info(
|
log.info(
|
||||||
|
|
|
@ -25,58 +25,60 @@ class BLSocketInfo:
|
||||||
bl_isocket_identifier: spux.ScalarUnitlessRealExpr
|
bl_isocket_identifier: spux.ScalarUnitlessRealExpr
|
||||||
|
|
||||||
|
|
||||||
@blender_type_enum.prefix_values_with('NodeSocket')
|
|
||||||
class BLSocketType(enum.StrEnum):
|
class BLSocketType(enum.StrEnum):
|
||||||
Virtual = 'Virtual'
|
Virtual = 'NodeSocketVirtual'
|
||||||
# Blender
|
# Blender
|
||||||
Image = 'Image'
|
Image = 'NodeSocketImage'
|
||||||
Shader = 'Shader'
|
Shader = 'NodeSocketShader'
|
||||||
Material = 'Material'
|
Material = 'NodeSocketMaterial'
|
||||||
Geometry = 'Material'
|
Geometry = 'NodeSocketGeometry'
|
||||||
Object = 'Object'
|
Object = 'NodeSocketObject'
|
||||||
Collection = 'Collection'
|
Collection = 'NodeSocketCollection'
|
||||||
# Basic
|
# Basic
|
||||||
Bool = 'Bool'
|
Bool = 'NodeSocketBool'
|
||||||
String = 'String'
|
String = 'NodeSocketString'
|
||||||
Menu = 'Menu'
|
Menu = 'NodeSocketMenu'
|
||||||
# Float
|
# Float
|
||||||
Float = 'Float'
|
Float = 'NodeSocketFloat'
|
||||||
FloatUnsigned = 'FloatUnsigned'
|
FloatUnsigned = 'NodeSocketFloatUnsigned'
|
||||||
FloatAngle = 'FloatAngle'
|
FloatAngle = 'NodeSocketFloatAngle'
|
||||||
FloatDistance = 'FloatDistance'
|
FloatDistance = 'NodeSocketFloatDistance'
|
||||||
FloatFactor = 'FloatFactor'
|
FloatFactor = 'NodeSocketFloatFactor'
|
||||||
FloatPercentage = 'FloatPercentage'
|
FloatPercentage = 'NodeSocketFloatPercentage'
|
||||||
FloatTime = 'FloatTime'
|
FloatTime = 'NodeSocketFloatTime'
|
||||||
FloatTimeAbsolute = 'FloatTimeAbsolute'
|
FloatTimeAbsolute = 'NodeSocketFloatTimeAbsolute'
|
||||||
# Int
|
# Int
|
||||||
Int = 'Int'
|
Int = 'NodeSocketInt'
|
||||||
IntFactor = 'IntFactor'
|
IntFactor = 'NodeSocketIntFactor'
|
||||||
IntPercentage = 'IntPercentage'
|
IntPercentage = 'NodeSocketIntPercentage'
|
||||||
IntUnsigned = 'IntUnsigned'
|
IntUnsigned = 'NodeSocketIntUnsigned'
|
||||||
# Vector
|
# Vector
|
||||||
Color = 'Color'
|
Color = 'NodeSocketColor'
|
||||||
Rotation = 'Rotation'
|
Rotation = 'NodeSocketRotation'
|
||||||
Vector = 'Vector'
|
Vector = 'NodeSocketVector'
|
||||||
VectorAcceleration = 'Acceleration'
|
VectorAcceleration = 'NodeSocketAcceleration'
|
||||||
VectorDirection = 'Direction'
|
VectorDirection = 'NodeSocketDirection'
|
||||||
VectorEuler = 'Euler'
|
VectorEuler = 'NodeSocketEuler'
|
||||||
VectorTranslation = 'Translation'
|
VectorTranslation = 'NodeSocketTranslation'
|
||||||
VectorVelocity = 'Velocity'
|
VectorVelocity = 'NodeSocketVelocity'
|
||||||
VectorXYZ = 'XYZ'
|
VectorXYZ = 'NodeSocketXYZ'
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_bl_isocket(
|
def from_bl_isocket(
|
||||||
bl_isocket: bpy.types.NodeTreeInterfaceSocket,
|
bl_isocket: bpy.types.NodeTreeInterfaceSocket,
|
||||||
) -> typ.Self:
|
) -> typ.Self:
|
||||||
return BLSocketType[bl_isocket.bl_socket_idname]
|
return BLSocketType(bl_isocket.bl_socket_idname)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def info_from_bl_isocket(
|
def info_from_bl_isocket(
|
||||||
bl_isocket: bpy.types.NodeTreeInterfaceSocket,
|
bl_isocket: bpy.types.NodeTreeInterfaceSocket,
|
||||||
) -> typ.Self:
|
) -> typ.Self:
|
||||||
return BLSocketType.from_bl_isocket(bl_isocket).parse(
|
bl_socket_type = BLSocketType.from_bl_isocket(bl_isocket)
|
||||||
|
if bl_socket_type.has_support:
|
||||||
|
return bl_socket_type.parse(
|
||||||
bl_isocket.default_value, bl_isocket.description, bl_isocket.identifier
|
bl_isocket.default_value, bl_isocket.description, bl_isocket.identifier
|
||||||
)
|
)
|
||||||
|
return bl_socket_type.parse(None, bl_isocket.description, bl_isocket.identifier)
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Direct Properties
|
# - Direct Properties
|
||||||
|
@ -288,7 +290,7 @@ class BLSocketType(enum.StrEnum):
|
||||||
)
|
)
|
||||||
|
|
||||||
# Parse the Default Value
|
# Parse the Default Value
|
||||||
if self.mathtype is not None:
|
if self.mathtype is not None and bl_default_value is not None:
|
||||||
if self.size == spux.NumberSize1D.Scalar:
|
if self.size == spux.NumberSize1D.Scalar:
|
||||||
default_value = self.mathtype.pytype(bl_default_value)
|
default_value = self.mathtype.pytype(bl_default_value)
|
||||||
elif description.startswith('2D'):
|
elif description.startswith('2D'):
|
||||||
|
|
|
@ -57,19 +57,22 @@ class FlowKind(enum.StrEnum):
|
||||||
Info = enum.auto()
|
Info = enum.auto()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def scale_to_unit_system(cls, kind: typ.Self, value, socket_type, unit_system):
|
def scale_to_unit_system(
|
||||||
if kind == cls.Value:
|
cls,
|
||||||
return spux.sympy_to_python(
|
kind: typ.Self,
|
||||||
spux.scale_to_unit(
|
|
||||||
value,
|
value,
|
||||||
unit_system[socket_type],
|
unit_system: spux.UnitSystem,
|
||||||
)
|
):
|
||||||
|
if kind == cls.Value:
|
||||||
|
return spux.scale_to_unit_system(
|
||||||
|
value,
|
||||||
|
unit_system,
|
||||||
)
|
)
|
||||||
if kind == cls.LazyArrayRange:
|
if kind == cls.LazyArrayRange:
|
||||||
return value.rescale_to_unit(unit_system[socket_type])
|
return value.rescale_to_unit_system(unit_system)
|
||||||
|
|
||||||
if kind == cls.Params:
|
if kind == cls.Params:
|
||||||
return value.rescale_to_unit(unit_system[socket_type])
|
return value.rescale_to_unit_system(unit_system)
|
||||||
|
|
||||||
msg = 'Tried to scale unknown kind'
|
msg = 'Tried to scale unknown kind'
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
|
@ -187,6 +190,9 @@ class ArrayFlow:
|
||||||
msg = f'Tried to rescale unitless LazyDataValueRange to unit {unit}'
|
msg = f'Tried to rescale unitless LazyDataValueRange to unit {unit}'
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
|
|
||||||
|
def rescale_to_unit_system(self, unit: spu.Quantity) -> typ.Self:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Lazy Value Func
|
# - Lazy Value Func
|
||||||
|
@ -469,14 +475,13 @@ class LazyArrayRangeFlow:
|
||||||
|
|
||||||
# Get Stop Mathtype
|
# Get Stop Mathtype
|
||||||
if isinstance(self.stop, spux.SympyType):
|
if isinstance(self.stop, spux.SympyType):
|
||||||
stop_mathtype = spux.MathType.from_expr(type(self.stop))
|
stop_mathtype = spux.MathType.from_expr(self.stop)
|
||||||
else:
|
else:
|
||||||
stop_mathtype = spux.MathType.from_pytype(type(self.stop))
|
stop_mathtype = spux.MathType.from_pytype(self.stop)
|
||||||
|
|
||||||
# Check Equal
|
# Check Equal
|
||||||
if start_mathtype != stop_mathtype:
|
if start_mathtype != stop_mathtype:
|
||||||
msg = "Mathtypes of start and stop don't agree. Please fix!"
|
return spux.MathType.combine(start_mathtype, stop_mathtype)
|
||||||
raise ValueError(msg)
|
|
||||||
|
|
||||||
return start_mathtype
|
return start_mathtype
|
||||||
|
|
||||||
|
@ -525,8 +530,8 @@ class LazyArrayRangeFlow:
|
||||||
"""
|
"""
|
||||||
if self.unit is not None:
|
if self.unit is not None:
|
||||||
return LazyArrayRangeFlow(
|
return LazyArrayRangeFlow(
|
||||||
start=spu.scale_to_unit(self.start * self.unit, unit),
|
start=spux.scale_to_unit(self.start * self.unit, unit),
|
||||||
stop=spu.scale_to_unit(self.stop * self.unit, unit),
|
stop=spux.scale_to_unit(self.stop * self.unit, unit),
|
||||||
steps=self.steps,
|
steps=self.steps,
|
||||||
scaling=self.scaling,
|
scaling=self.scaling,
|
||||||
unit=unit,
|
unit=unit,
|
||||||
|
@ -536,6 +541,39 @@ class LazyArrayRangeFlow:
|
||||||
msg = f'Tried to rescale unitless LazyDataValueRange to unit {unit}'
|
msg = f'Tried to rescale unitless LazyDataValueRange to unit {unit}'
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
|
|
||||||
|
def rescale_to_unit_system(self, unit_system: spux.Unit) -> typ.Self:
|
||||||
|
"""Replaces the units, **with** rescaling of the bounds.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
unit: The unit to convert the bounds to.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A new `LazyArrayRangeFlow` with replaced unit.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: If the existing unit is `None`, indicating that there is no unit to correct.
|
||||||
|
"""
|
||||||
|
if self.unit is not None:
|
||||||
|
return LazyArrayRangeFlow(
|
||||||
|
start=spux.strip_unit_system(
|
||||||
|
spux.convert_to_unit_system(self.start * self.unit, unit_system),
|
||||||
|
unit_system,
|
||||||
|
),
|
||||||
|
stop=spux.strip_unit_system(
|
||||||
|
spux.convert_to_unit_system(self.start * self.unit, unit_system),
|
||||||
|
unit_system,
|
||||||
|
),
|
||||||
|
steps=self.steps,
|
||||||
|
scaling=self.scaling,
|
||||||
|
unit=unit_system[spux.PhysicalType.from_unit(self.unit)],
|
||||||
|
symbols=self.symbols,
|
||||||
|
)
|
||||||
|
|
||||||
|
msg = (
|
||||||
|
f'Tried to rescale unitless LazyDataValueRange to unit system {unit_system}'
|
||||||
|
)
|
||||||
|
raise ValueError(msg)
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Bound Operations
|
# - Bound Operations
|
||||||
####################
|
####################
|
||||||
|
|
|
@ -16,6 +16,7 @@ class FlowSignal(enum.StrEnum):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
FlowInitializing = enum.auto()
|
||||||
FlowPending = enum.auto()
|
FlowPending = enum.auto()
|
||||||
NoFlow = enum.auto()
|
NoFlow = enum.auto()
|
||||||
|
|
||||||
|
|
|
@ -110,26 +110,23 @@ class ManagedBLMesh(base.ManagedObj):
|
||||||
|
|
||||||
If it's already included, do nothing.
|
If it's already included, do nothing.
|
||||||
"""
|
"""
|
||||||
if (bl_object := bpy.data.objects.get(self.name)) is not None:
|
bl_object = bpy.data.objects.get(self.name)
|
||||||
|
if bl_object is None:
|
||||||
|
bl_object = self.bl_object()
|
||||||
|
|
||||||
if bl_object.name not in preview_collection().objects:
|
if bl_object.name not in preview_collection().objects:
|
||||||
log.info('Moving "%s" to Preview Collection', bl_object.name)
|
log.info('Moving "%s" to Preview Collection', bl_object.name)
|
||||||
preview_collection().objects.link(bl_object)
|
preview_collection().objects.link(bl_object)
|
||||||
else:
|
|
||||||
msg = 'Managed BLMesh does not exist'
|
|
||||||
raise ValueError(msg)
|
|
||||||
|
|
||||||
def hide_preview(self) -> None:
|
def hide_preview(self) -> None:
|
||||||
"""Removes the managed Blender object from the preview collection.
|
"""Removes the managed Blender object from the preview collection.
|
||||||
|
|
||||||
If it's already removed, do nothing.
|
If it's already removed, do nothing.
|
||||||
"""
|
"""
|
||||||
if (bl_object := bpy.data.objects.get(self.name)) is not None:
|
bl_object = bpy.data.objects.get(self.name)
|
||||||
if bl_object.name in preview_collection().objects:
|
if bl_object is not None and bl_object.name in preview_collection().objects:
|
||||||
log.info('Removing "%s" from Preview Collection', bl_object.name)
|
log.info('Removing "%s" from Preview Collection', bl_object.name)
|
||||||
preview_collection().objects.unlink(bl_object)
|
preview_collection().objects.unlink(bl_object)
|
||||||
else:
|
|
||||||
msg = 'Managed BLMesh does not exist'
|
|
||||||
raise ValueError(msg)
|
|
||||||
|
|
||||||
def bl_select(self) -> None:
|
def bl_select(self) -> None:
|
||||||
"""Selects the managed Blender object, causing it to be ex. outlined in the 3D viewport."""
|
"""Selects the managed Blender object, causing it to be ex. outlined in the 3D viewport."""
|
||||||
|
|
|
@ -206,6 +206,8 @@ class MaxwellSimTree(bpy.types.NodeTree):
|
||||||
"""Unlock all nodes in the node tree, making them editable."""
|
"""Unlock all nodes in the node tree, making them editable."""
|
||||||
log.info('Unlocking All Nodes in NodeTree "%s"', self.bl_label)
|
log.info('Unlocking All Nodes in NodeTree "%s"', self.bl_label)
|
||||||
for node in self.nodes:
|
for node in self.nodes:
|
||||||
|
if node.type in ['REROUTE', 'FRAME']:
|
||||||
|
continue
|
||||||
node.locked = False
|
node.locked = False
|
||||||
for bl_socket in [*node.inputs, *node.outputs]:
|
for bl_socket in [*node.inputs, *node.outputs]:
|
||||||
bl_socket.locked = False
|
bl_socket.locked = False
|
||||||
|
@ -229,7 +231,9 @@ class MaxwellSimTree(bpy.types.NodeTree):
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
def repreview_all(self) -> None:
|
def repreview_all(self) -> None:
|
||||||
all_nodes_with_preview_active = {
|
all_nodes_with_preview_active = {
|
||||||
node.instance_id: node for node in self.nodes if node.preview_active
|
node.instance_id: node
|
||||||
|
for node in self.nodes
|
||||||
|
if node.type not in ['REROUTE', 'FRAME'] and node.preview_active
|
||||||
}
|
}
|
||||||
self.is_currently_repreviewing = True
|
self.is_currently_repreviewing = True
|
||||||
self.newly_previewed_nodes = {}
|
self.newly_previewed_nodes = {}
|
||||||
|
|
|
@ -1,40 +1,37 @@
|
||||||
# from . import kitchen_sink
|
|
||||||
# from . import bounds
|
|
||||||
from . import (
|
from . import (
|
||||||
analysis,
|
analysis,
|
||||||
|
# bounds,
|
||||||
inputs,
|
inputs,
|
||||||
mediums,
|
# mediums,
|
||||||
monitors,
|
monitors,
|
||||||
outputs,
|
outputs,
|
||||||
simulations,
|
# simulations,
|
||||||
sources,
|
# sources,
|
||||||
structures,
|
# structures,
|
||||||
utilities,
|
# utilities,
|
||||||
)
|
)
|
||||||
|
|
||||||
BL_REGISTER = [
|
BL_REGISTER = [
|
||||||
# *kitchen_sink.BL_REGISTER,
|
|
||||||
*analysis.BL_REGISTER,
|
*analysis.BL_REGISTER,
|
||||||
*inputs.BL_REGISTER,
|
*inputs.BL_REGISTER,
|
||||||
*outputs.BL_REGISTER,
|
*outputs.BL_REGISTER,
|
||||||
*sources.BL_REGISTER,
|
# *sources.BL_REGISTER,
|
||||||
*mediums.BL_REGISTER,
|
# *mediums.BL_REGISTER,
|
||||||
*structures.BL_REGISTER,
|
# *structures.BL_REGISTER,
|
||||||
# *bounds.BL_REGISTER,
|
# *bounds.BL_REGISTER,
|
||||||
*monitors.BL_REGISTER,
|
*monitors.BL_REGISTER,
|
||||||
*simulations.BL_REGISTER,
|
# *simulations.BL_REGISTER,
|
||||||
*utilities.BL_REGISTER,
|
# *utilities.BL_REGISTER,
|
||||||
]
|
]
|
||||||
BL_NODES = {
|
BL_NODES = {
|
||||||
# **kitchen_sink.BL_NODES,
|
|
||||||
**analysis.BL_NODES,
|
**analysis.BL_NODES,
|
||||||
**inputs.BL_NODES,
|
**inputs.BL_NODES,
|
||||||
**outputs.BL_NODES,
|
**outputs.BL_NODES,
|
||||||
**sources.BL_NODES,
|
# **sources.BL_NODES,
|
||||||
**mediums.BL_NODES,
|
# **mediums.BL_NODES,
|
||||||
**structures.BL_NODES,
|
# **structures.BL_NODES,
|
||||||
# **bounds.BL_NODES,
|
# **bounds.BL_NODES,
|
||||||
**monitors.BL_NODES,
|
**monitors.BL_NODES,
|
||||||
**simulations.BL_NODES,
|
# **simulations.BL_NODES,
|
||||||
**utilities.BL_NODES,
|
# **utilities.BL_NODES,
|
||||||
}
|
}
|
||||||
|
|
|
@ -401,7 +401,7 @@ class MapMathNode(base.MaxwellSimNode):
|
||||||
run_on_init=True,
|
run_on_init=True,
|
||||||
)
|
)
|
||||||
def on_input_changed(self):
|
def on_input_changed(self):
|
||||||
if self.operation not in MapOperation.by_element_shape(self.expr_output_shape):
|
# if self.operation not in MapOperation.by_element_shape(self.expr_output_shape):
|
||||||
self.operation = bl_cache.Signal.ResetEnumItems
|
self.operation = bl_cache.Signal.ResetEnumItems
|
||||||
|
|
||||||
@events.on_value_changed(
|
@events.on_value_changed(
|
||||||
|
|
|
@ -20,6 +20,10 @@ FUNCS = {
|
||||||
'MUL': lambda exprs: exprs[0] * exprs[1],
|
'MUL': lambda exprs: exprs[0] * exprs[1],
|
||||||
'DIV': lambda exprs: exprs[0] / exprs[1],
|
'DIV': lambda exprs: exprs[0] / exprs[1],
|
||||||
'POW': lambda exprs: exprs[0] ** exprs[1],
|
'POW': lambda exprs: exprs[0] ** exprs[1],
|
||||||
|
'ATAN2': lambda exprs: sp.atan2(exprs[1], exprs[0]),
|
||||||
|
# Vector | Vector
|
||||||
|
'VEC_VEC_DOT': lambda exprs: exprs[0].dot(exprs[1]),
|
||||||
|
'CROSS': lambda exprs: exprs[0].cross(exprs[1]),
|
||||||
}
|
}
|
||||||
|
|
||||||
SP_FUNCS = FUNCS
|
SP_FUNCS = FUNCS
|
||||||
|
@ -52,8 +56,8 @@ class OperateMathNode(base.MaxwellSimNode):
|
||||||
bl_label = 'Operate Math'
|
bl_label = 'Operate Math'
|
||||||
|
|
||||||
input_sockets: typ.ClassVar = {
|
input_sockets: typ.ClassVar = {
|
||||||
'Expr L': sockets.ExprSocketDef(show_info_columns=False),
|
'Expr L': sockets.ExprSocketDef(),
|
||||||
'Expr R': sockets.ExprSocketDef(show_info_columns=False),
|
'Expr R': sockets.ExprSocketDef(),
|
||||||
}
|
}
|
||||||
output_sockets: typ.ClassVar = {
|
output_sockets: typ.ClassVar = {
|
||||||
'Expr': sockets.ExprSocketDef(),
|
'Expr': sockets.ExprSocketDef(),
|
||||||
|
@ -73,10 +77,12 @@ class OperateMathNode(base.MaxwellSimNode):
|
||||||
def search_categories(self) -> list[ct.BLEnumElement]:
|
def search_categories(self) -> list[ct.BLEnumElement]:
|
||||||
"""Deduce and return a list of valid categories for the current socket set and input data."""
|
"""Deduce and return a list of valid categories for the current socket set and input data."""
|
||||||
expr_l_info = self._compute_input(
|
expr_l_info = self._compute_input(
|
||||||
'Expr L', kind=ct.FlowKind.Info, optional=True
|
'Expr L',
|
||||||
|
kind=ct.FlowKind.Info,
|
||||||
)
|
)
|
||||||
expr_r_info = self._compute_input(
|
expr_r_info = self._compute_input(
|
||||||
'Expr R', kind=ct.FlowKind.Info, optional=True
|
'Expr R',
|
||||||
|
kind=ct.FlowKind.Info,
|
||||||
)
|
)
|
||||||
|
|
||||||
has_expr_l_info = not ct.FlowSignal.check(expr_l_info)
|
has_expr_l_info = not ct.FlowSignal.check(expr_l_info)
|
||||||
|
@ -121,6 +127,10 @@ class OperateMathNode(base.MaxwellSimNode):
|
||||||
if expr_l_info.output_shape is None and expr_r_info.output_shape is None:
|
if expr_l_info.output_shape is None and expr_r_info.output_shape is None:
|
||||||
categories = [NUMBER_NUMBER]
|
categories = [NUMBER_NUMBER]
|
||||||
|
|
||||||
|
## * | Number
|
||||||
|
elif expr_r_info.output_shape is None:
|
||||||
|
categories = []
|
||||||
|
|
||||||
## Number | Vector
|
## Number | Vector
|
||||||
elif (
|
elif (
|
||||||
expr_l_info.output_shape is None and len(expr_r_info.output_shape) == 1
|
expr_l_info.output_shape is None and len(expr_r_info.output_shape) == 1
|
||||||
|
@ -170,13 +180,12 @@ class OperateMathNode(base.MaxwellSimNode):
|
||||||
('POW', 'L^R', 'Power'),
|
('POW', 'L^R', 'Power'),
|
||||||
('ATAN2', 'atan2(L,R)', 'atan2(L,R)'),
|
('ATAN2', 'atan2(L,R)', 'atan2(L,R)'),
|
||||||
]
|
]
|
||||||
if self.category in 'Vector | Vector':
|
if self.category == 'Vector | Vector':
|
||||||
if items:
|
if items:
|
||||||
items += [None]
|
items += [None]
|
||||||
items += [
|
items += [
|
||||||
('VEC_VEC_DOT', 'L · R', 'Vector-Vector Product'),
|
('VEC_VEC_DOT', 'L · R', 'Vector-Vector Product'),
|
||||||
('CROSS', 'L x R', 'Cross Product'),
|
('CROSS', 'L x R', 'Cross Product'),
|
||||||
('PROJ', 'proj(L, R)', 'Projection'),
|
|
||||||
]
|
]
|
||||||
if self.category == 'Matrix | Vector':
|
if self.category == 'Matrix | Vector':
|
||||||
if items:
|
if items:
|
||||||
|
@ -364,9 +373,7 @@ class OperateMathNode(base.MaxwellSimNode):
|
||||||
'Expr R': ct.FlowKind.Params,
|
'Expr R': ct.FlowKind.Params,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
def compute_params(
|
def compute_params(self, props, input_sockets) -> ct.ParamsFlow | ct.FlowSignal:
|
||||||
self, props, input_sockets
|
|
||||||
) -> ct.ParamsFlow | ct.FlowSignal:
|
|
||||||
operation = props['operation']
|
operation = props['operation']
|
||||||
params_l = input_sockets['Expr L']
|
params_l = input_sockets['Expr L']
|
||||||
params_r = input_sockets['Expr R']
|
params_r = input_sockets['Expr R']
|
||||||
|
|
|
@ -2,8 +2,11 @@ import enum
|
||||||
import typing as typ
|
import typing as typ
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
|
import jax
|
||||||
|
import jax.numpy as jnp
|
||||||
import jaxtyping as jtyp
|
import jaxtyping as jtyp
|
||||||
import matplotlib.axis as mpl_ax
|
import matplotlib.axis as mpl_ax
|
||||||
|
import sympy as sp
|
||||||
|
|
||||||
from blender_maxwell.utils import bl_cache, image_ops, logger
|
from blender_maxwell.utils import bl_cache, image_ops, logger
|
||||||
from blender_maxwell.utils import extra_sympy_units as spux
|
from blender_maxwell.utils import extra_sympy_units as spux
|
||||||
|
@ -192,7 +195,10 @@ class VizNode(base.MaxwellSimNode):
|
||||||
# - Sockets
|
# - Sockets
|
||||||
####################
|
####################
|
||||||
input_sockets: typ.ClassVar = {
|
input_sockets: typ.ClassVar = {
|
||||||
'Expr': sockets.ExprSocketDef(),
|
'Expr': sockets.ExprSocketDef(
|
||||||
|
symbols={_x := sp.Symbol('x', real=True)},
|
||||||
|
default_value=2 * _x,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
output_sockets: typ.ClassVar = {
|
output_sockets: typ.ClassVar = {
|
||||||
'Preview': sockets.AnySocketDef(),
|
'Preview': sockets.AnySocketDef(),
|
||||||
|
@ -221,8 +227,12 @@ class VizNode(base.MaxwellSimNode):
|
||||||
## - Mode Searcher
|
## - Mode Searcher
|
||||||
#####################
|
#####################
|
||||||
@property
|
@property
|
||||||
def data_info(self) -> ct.InfoFlow:
|
def data_info(self) -> ct.InfoFlow | None:
|
||||||
return self._compute_input('Expr', kind=ct.FlowKind.Info)
|
info = self._compute_input('Expr', kind=ct.FlowKind.Info)
|
||||||
|
if not ct.FlowSignal.check(info):
|
||||||
|
return info
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
def search_modes(self) -> list[ct.BLEnumElement]:
|
def search_modes(self) -> list[ct.BLEnumElement]:
|
||||||
if not ct.FlowSignal.check(self.data_info):
|
if not ct.FlowSignal.check(self.data_info):
|
||||||
|
@ -298,7 +308,9 @@ class VizNode(base.MaxwellSimNode):
|
||||||
managed_objs={'plot'},
|
managed_objs={'plot'},
|
||||||
props={'viz_mode', 'viz_target', 'colormap'},
|
props={'viz_mode', 'viz_target', 'colormap'},
|
||||||
input_sockets={'Expr'},
|
input_sockets={'Expr'},
|
||||||
input_socket_kinds={'Expr': {ct.FlowKind.Array, ct.FlowKind.Info}},
|
input_socket_kinds={
|
||||||
|
'Expr': {ct.FlowKind.Array, ct.FlowKind.LazyValueFunc, ct.FlowKind.Info}
|
||||||
|
},
|
||||||
stop_propagation=True,
|
stop_propagation=True,
|
||||||
)
|
)
|
||||||
def on_show_plot(
|
def on_show_plot(
|
||||||
|
|
|
@ -599,22 +599,28 @@ class MaxwellSimNode(bpy.types.Node):
|
||||||
It must be currently active.
|
It must be currently active.
|
||||||
kind: The data flow kind to compute.
|
kind: The data flow kind to compute.
|
||||||
"""
|
"""
|
||||||
if (bl_socket := self.inputs.get(input_socket_name)) is not None:
|
bl_socket = self.inputs.get(input_socket_name)
|
||||||
|
if bl_socket is not None:
|
||||||
|
if bl_socket.instance_id:
|
||||||
return (
|
return (
|
||||||
ct.FlowKind.scale_to_unit_system(
|
ct.FlowKind.scale_to_unit_system(
|
||||||
kind,
|
kind,
|
||||||
bl_socket.compute_data(kind=kind),
|
bl_socket.compute_data(kind=kind),
|
||||||
bl_socket.socket_type,
|
|
||||||
unit_system,
|
unit_system,
|
||||||
)
|
)
|
||||||
if unit_system is not None
|
if unit_system is not None
|
||||||
else bl_socket.compute_data(kind=kind)
|
else bl_socket.compute_data(kind=kind)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# No Socket Instance ID
|
||||||
|
## -> Indicates that socket_def.preinit() has not yet run.
|
||||||
|
## -> Anyone needing results will need to wait on preinit().
|
||||||
|
return ct.FlowSignal.FlowInitializing
|
||||||
|
|
||||||
if optional:
|
if optional:
|
||||||
return ct.FlowSignal.NoFlow
|
return ct.FlowSignal.NoFlow
|
||||||
|
|
||||||
msg = f'Input socket "{input_socket_name}" on "{self.bl_idname}" is not an active input socket'
|
msg = f'{self.sim_node_name}: Input socket "{input_socket_name}" cannot be computed, as it is not an active input socket'
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
|
|
@ -3,6 +3,7 @@ import inspect
|
||||||
import typing as typ
|
import typing as typ
|
||||||
from types import MappingProxyType
|
from types import MappingProxyType
|
||||||
|
|
||||||
|
from blender_maxwell.utils import extra_sympy_units as spux
|
||||||
from blender_maxwell.utils import logger
|
from blender_maxwell.utils import logger
|
||||||
|
|
||||||
from .. import contracts as ct
|
from .. import contracts as ct
|
||||||
|
@ -10,7 +11,6 @@ from .. import contracts as ct
|
||||||
log = logger.get(__name__)
|
log = logger.get(__name__)
|
||||||
|
|
||||||
UnitSystemID = str
|
UnitSystemID = str
|
||||||
UnitSystem = dict[ct.SocketType, typ.Any]
|
|
||||||
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
@ -70,7 +70,7 @@ def event_decorator(
|
||||||
all_loose_input_sockets: bool = False,
|
all_loose_input_sockets: bool = False,
|
||||||
all_loose_output_sockets: bool = False,
|
all_loose_output_sockets: bool = False,
|
||||||
# Request Unit System Scaling
|
# Request Unit System Scaling
|
||||||
unit_systems: dict[UnitSystemID, UnitSystem] = MappingProxyType({}),
|
unit_systems: dict[UnitSystemID, spux.UnitSystem] = MappingProxyType({}),
|
||||||
scale_input_sockets: dict[ct.SocketName, UnitSystemID] = MappingProxyType({}),
|
scale_input_sockets: dict[ct.SocketName, UnitSystemID] = MappingProxyType({}),
|
||||||
scale_output_sockets: dict[ct.SocketName, UnitSystemID] = MappingProxyType({}),
|
scale_output_sockets: dict[ct.SocketName, UnitSystemID] = MappingProxyType({}),
|
||||||
):
|
):
|
||||||
|
@ -213,7 +213,6 @@ def event_decorator(
|
||||||
kind=kind,
|
kind=kind,
|
||||||
optional=output_sockets_optional.get(output_socket_name, False),
|
optional=output_sockets_optional.get(output_socket_name, False),
|
||||||
),
|
),
|
||||||
node.outputs[output_socket_name].socket_type,
|
|
||||||
unit_systems.get(scale_output_sockets.get(output_socket_name)),
|
unit_systems.get(scale_output_sockets.get(output_socket_name)),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -269,9 +268,21 @@ def event_decorator(
|
||||||
else {}
|
else {}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Propagate Initialization
|
||||||
|
## If there is a FlowInitializing, then the method would fail.
|
||||||
|
## Therefore, propagate FlowInitializing if found.
|
||||||
|
if any(
|
||||||
|
ct.FlowSignal.FlowInitializing in sockets.values()
|
||||||
|
for sockets in [
|
||||||
|
method_kw_args.get('input_sockets', {}),
|
||||||
|
method_kw_args.get('loose_input_sockets', {}),
|
||||||
|
method_kw_args.get('output_sockets', {}),
|
||||||
|
method_kw_args.get('loose_output_sockets', {}),
|
||||||
|
]
|
||||||
|
):
|
||||||
|
return ct.FlowSignal.FlowInitializing
|
||||||
|
|
||||||
# Call Method
|
# Call Method
|
||||||
## If there is a FlowPending, then the method would fail.
|
|
||||||
## Therefore, propagate FlowPending if found.
|
|
||||||
return method(
|
return method(
|
||||||
node,
|
node,
|
||||||
**method_kw_args,
|
**method_kw_args,
|
||||||
|
|
|
@ -1,18 +1,22 @@
|
||||||
# from . import scientific_constant
|
from . import (
|
||||||
# from . import physical_constant
|
blender_constant,
|
||||||
from . import blender_constant, expr_constant, number_constant, scientific_constant
|
expr_constant,
|
||||||
|
number_constant,
|
||||||
|
physical_constant,
|
||||||
|
scientific_constant,
|
||||||
|
)
|
||||||
|
|
||||||
BL_REGISTER = [
|
BL_REGISTER = [
|
||||||
*expr_constant.BL_REGISTER,
|
*expr_constant.BL_REGISTER,
|
||||||
*scientific_constant.BL_REGISTER,
|
*scientific_constant.BL_REGISTER,
|
||||||
*number_constant.BL_REGISTER,
|
*number_constant.BL_REGISTER,
|
||||||
# *physical_constant.BL_REGISTER,
|
*physical_constant.BL_REGISTER,
|
||||||
*blender_constant.BL_REGISTER,
|
*blender_constant.BL_REGISTER,
|
||||||
]
|
]
|
||||||
BL_NODES = {
|
BL_NODES = {
|
||||||
**expr_constant.BL_NODES,
|
**expr_constant.BL_NODES,
|
||||||
**scientific_constant.BL_NODES,
|
**scientific_constant.BL_NODES,
|
||||||
**number_constant.BL_NODES,
|
**number_constant.BL_NODES,
|
||||||
# **physical_constant.BL_NODES,
|
**physical_constant.BL_NODES,
|
||||||
**blender_constant.BL_NODES,
|
**blender_constant.BL_NODES,
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,7 +44,7 @@ class NumberConstantNode(base.MaxwellSimNode):
|
||||||
####################
|
####################
|
||||||
# - UI
|
# - UI
|
||||||
####################
|
####################
|
||||||
def draw_value(self, col: bpy.types.UILayout) -> None:
|
def draw_props(self, _, col: bpy.types.UILayout) -> None:
|
||||||
row = col.row(align=True)
|
row = col.row(align=True)
|
||||||
row.prop(self, self.blfields['mathtype'], text='')
|
row.prop(self, self.blfields['mathtype'], text='')
|
||||||
row.prop(self, self.blfields['size'], text='')
|
row.prop(self, self.blfields['size'], text='')
|
||||||
|
@ -56,7 +56,7 @@ class NumberConstantNode(base.MaxwellSimNode):
|
||||||
def on_mathtype_size_changed(self, props) -> None:
|
def on_mathtype_size_changed(self, props) -> None:
|
||||||
"""Change the input/output expression sockets to match the mathtype declared in the node."""
|
"""Change the input/output expression sockets to match the mathtype declared in the node."""
|
||||||
self.inputs['Value'].mathtype = props['mathtype']
|
self.inputs['Value'].mathtype = props['mathtype']
|
||||||
self.inputs['Value'].shape = props['mathtype'].shape
|
self.inputs['Value'].shape = props['size'].shape
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - FlowKind
|
# - FlowKind
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import enum
|
|
||||||
import typing as typ
|
import typing as typ
|
||||||
|
|
||||||
|
import bpy
|
||||||
import sympy as sp
|
import sympy as sp
|
||||||
|
|
||||||
from blender_maxwell.utils import bl_cache
|
from blender_maxwell.utils import bl_cache
|
||||||
|
@ -10,7 +10,7 @@ from .... import contracts, sockets
|
||||||
from ... import base, events
|
from ... import base, events
|
||||||
|
|
||||||
|
|
||||||
class PhysicalConstantNode(base.MaxwellSimTreeNode):
|
class PhysicalConstantNode(base.MaxwellSimNode):
|
||||||
"""A number of configurable unit dimension, ex. time, length, etc. .
|
"""A number of configurable unit dimension, ex. time, length, etc. .
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
|
@ -36,12 +36,12 @@ class PhysicalConstantNode(base.MaxwellSimTreeNode):
|
||||||
prop_ui=True,
|
prop_ui=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
mathtype: enum.Enum = bl_cache.BLField(
|
mathtype: spux.MathType = bl_cache.BLField(
|
||||||
enum_cb=lambda self, _: self.search_mathtypes(),
|
enum_cb=lambda self, _: self.search_mathtypes(),
|
||||||
prop_ui=True,
|
prop_ui=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
size: enum.Enum = bl_cache.BLField(
|
size: spux.NumberSize1D = bl_cache.BLField(
|
||||||
enum_cb=lambda self, _: self.search_sizes(),
|
enum_cb=lambda self, _: self.search_sizes(),
|
||||||
prop_ui=True,
|
prop_ui=True,
|
||||||
)
|
)
|
||||||
|
@ -62,16 +62,25 @@ class PhysicalConstantNode(base.MaxwellSimTreeNode):
|
||||||
if spux.NumberSize1D.supports_shape(shape)
|
if spux.NumberSize1D.supports_shape(shape)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - UI
|
||||||
|
####################
|
||||||
|
def draw_props(self, _, col: bpy.types.UILayout) -> None:
|
||||||
|
row = col.row(align=True)
|
||||||
|
row.prop(self, self.blfields['mathtype'], text='')
|
||||||
|
row.prop(self, self.blfields['size'], text='')
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Events
|
# - Events
|
||||||
####################
|
####################
|
||||||
@events.on_value_changed(
|
@events.on_value_changed(
|
||||||
prop_name={'physical_type', 'mathtype', 'size'},
|
prop_name={'physical_type', 'mathtype', 'size'},
|
||||||
|
run_on_init=True,
|
||||||
props={'physical_type', 'mathtype', 'size'},
|
props={'physical_type', 'mathtype', 'size'},
|
||||||
)
|
)
|
||||||
def on_mathtype_or_size_changed(self, props) -> None:
|
def on_mathtype_or_size_changed(self, props) -> None:
|
||||||
"""Change the input/output expression sockets to match the mathtype and size declared in the node."""
|
"""Change the input/output expression sockets to match the mathtype and size declared in the node."""
|
||||||
shape = spux.NumberSize1D(props['size']).shape
|
shape = props['size'].shape
|
||||||
|
|
||||||
# Set Input Socket Physical Type
|
# Set Input Socket Physical Type
|
||||||
if self.inputs['Value'].physical_type != props['physical_type']:
|
if self.inputs['Value'].physical_type != props['physical_type']:
|
||||||
|
@ -90,9 +99,9 @@ class PhysicalConstantNode(base.MaxwellSimTreeNode):
|
||||||
####################
|
####################
|
||||||
# - Callbacks
|
# - Callbacks
|
||||||
####################
|
####################
|
||||||
@events.computes_output_socket('value')
|
@events.computes_output_socket('Value', input_sockets={'Value'})
|
||||||
def compute_value(self: contracts.NodeTypeProtocol) -> sp.Expr:
|
def compute_value(self, input_sockets) -> sp.Expr:
|
||||||
return self.compute_input('value')
|
return input_sockets['Value']
|
||||||
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
|
|
@ -2,7 +2,7 @@ import typing as typ
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
|
|
||||||
from blender_maxwell.utils import sci_constants as constants
|
from blender_maxwell.utils import bl_cache, sci_constants
|
||||||
|
|
||||||
from .... import contracts as ct
|
from .... import contracts as ct
|
||||||
from .... import sockets
|
from .... import sockets
|
||||||
|
@ -20,63 +20,43 @@ class ScientificConstantNode(base.MaxwellSimNode):
|
||||||
####################
|
####################
|
||||||
# - Properties
|
# - Properties
|
||||||
####################
|
####################
|
||||||
sci_constant: bpy.props.StringProperty(
|
sci_constant: str = bl_cache.BLField(
|
||||||
name='Sci Constant',
|
'',
|
||||||
description='The name of a scientific constant',
|
prop_ui=True,
|
||||||
default='',
|
str_cb=lambda self, _, edit_text: self.search_sci_constants(edit_text),
|
||||||
search=lambda self, _, edit_text: self.search_sci_constants(edit_text),
|
|
||||||
update=lambda self, context: self.on_update_sci_constant(context),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
cache__units: bpy.props.StringProperty(default='')
|
|
||||||
cache__uncertainty: bpy.props.StringProperty(default='')
|
|
||||||
|
|
||||||
def search_sci_constants(
|
def search_sci_constants(
|
||||||
self,
|
self,
|
||||||
edit_text: str,
|
edit_text: str,
|
||||||
):
|
):
|
||||||
return [
|
return [
|
||||||
name
|
name
|
||||||
for name in constants.SCI_CONSTANTS
|
for name in sci_constants.SCI_CONSTANTS
|
||||||
if edit_text.lower() in name.lower()
|
if edit_text.lower() in name.lower()
|
||||||
]
|
]
|
||||||
|
|
||||||
def on_update_sci_constant(
|
|
||||||
self,
|
|
||||||
context: bpy.types.Context,
|
|
||||||
):
|
|
||||||
if self.sci_constant:
|
|
||||||
self.cache__units = str(
|
|
||||||
constants.SCI_CONSTANTS_INFO[self.sci_constant]['units']
|
|
||||||
)
|
|
||||||
self.cache__uncertainty = str(
|
|
||||||
constants.SCI_CONSTANTS_INFO[self.sci_constant]['uncertainty']
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
self.cache__units = ''
|
|
||||||
self.cache__uncertainty = ''
|
|
||||||
|
|
||||||
self.on_prop_changed('sci_constant', context)
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - UI
|
# - UI
|
||||||
####################
|
####################
|
||||||
def draw_props(self, _: bpy.types.Context, col: bpy.types.UILayout) -> None:
|
def draw_props(self, _: bpy.types.Context, col: bpy.types.UILayout) -> None:
|
||||||
col.prop(self, 'sci_constant', text='')
|
col.prop(self, self.blfields['sci_constant'], text='')
|
||||||
|
|
||||||
def draw_info(self, _: bpy.types.Context, col: bpy.types.UILayout) -> None:
|
def draw_info(self, _: bpy.types.Context, col: bpy.types.UILayout) -> None:
|
||||||
if self.sci_constant:
|
if self.sci_constant:
|
||||||
col.label(text=f'Units: {self.cache__units}')
|
col.label(
|
||||||
col.label(text=f'Uncertainty: {self.cache__uncertainty}')
|
text=f'Units: {sci_constants.SCI_CONSTANTS_INFO[self.sci_constant]["units"]}'
|
||||||
|
)
|
||||||
col.label(text=f'Ref: {constants.SCI_CONSTANTS_REF[0]}')
|
col.label(
|
||||||
|
text=f'Uncertainty: {sci_constants.SCI_CONSTANTS_INFO[self.sci_constant]["uncertainty"]}'
|
||||||
|
)
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Callbacks
|
# - Output
|
||||||
####################
|
####################
|
||||||
@events.computes_output_socket('Value', props={'sci_constant'})
|
@events.computes_output_socket('Value', props={'sci_constant'})
|
||||||
def compute_value(self, props: dict) -> typ.Any:
|
def compute_value(self, props: dict) -> typ.Any:
|
||||||
return constants.SCI_CONSTANTS[props['sci_constant']]
|
return sci_constants.SCI_CONSTANTS[props['sci_constant']]
|
||||||
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
|
|
@ -33,6 +33,7 @@ class WaveConstantNode(base.MaxwellSimNode):
|
||||||
input_socket_sets: typ.ClassVar = {
|
input_socket_sets: typ.ClassVar = {
|
||||||
'Wavelength': {
|
'Wavelength': {
|
||||||
'WL': sockets.ExprSocketDef(
|
'WL': sockets.ExprSocketDef(
|
||||||
|
active_kind=ct.FlowKind.Value,
|
||||||
physical_type=spux.PhysicalType.Length,
|
physical_type=spux.PhysicalType.Length,
|
||||||
# Defaults
|
# Defaults
|
||||||
default_unit=spu.nm,
|
default_unit=spu.nm,
|
||||||
|
@ -58,18 +59,18 @@ class WaveConstantNode(base.MaxwellSimNode):
|
||||||
output_sockets: typ.ClassVar = {
|
output_sockets: typ.ClassVar = {
|
||||||
'WL': sockets.ExprSocketDef(
|
'WL': sockets.ExprSocketDef(
|
||||||
active_kind=ct.FlowKind.Value,
|
active_kind=ct.FlowKind.Value,
|
||||||
unit_dimension=spux.Dims.length,
|
physical_type=spux.PhysicalType.Length,
|
||||||
),
|
),
|
||||||
'Freq': sockets.ExprSocketDef(
|
'Freq': sockets.ExprSocketDef(
|
||||||
active_kind=ct.FlowKind.Value,
|
active_kind=ct.FlowKind.Value,
|
||||||
unit_dimension=spux.Dims.frequency,
|
physical_type=spux.PhysicalType.Freq,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Properties
|
# - Properties
|
||||||
####################
|
####################
|
||||||
use_range: bool = bl_cache.BLField(False)
|
use_range: bool = bl_cache.BLField(False, prop_ui=True)
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - UI
|
# - UI
|
||||||
|
@ -80,14 +81,14 @@ class WaveConstantNode(base.MaxwellSimNode):
|
||||||
Parameters:
|
Parameters:
|
||||||
col: Target for defining UI elements.
|
col: Target for defining UI elements.
|
||||||
"""
|
"""
|
||||||
col.prop(self, self.blfields['use_range'], toggle=True)
|
col.prop(self, self.blfields['use_range'], toggle=True, text='Range')
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Events
|
# - Events
|
||||||
####################
|
####################
|
||||||
@events.on_value_changed(
|
@events.on_value_changed(
|
||||||
prop_name={'active_socket_set', 'use_range'},
|
prop_name={'active_socket_set', 'use_range'},
|
||||||
props='use_range',
|
props={'use_range'},
|
||||||
run_on_init=True,
|
run_on_init=True,
|
||||||
)
|
)
|
||||||
def on_use_range_changed(self, props: dict) -> None:
|
def on_use_range_changed(self, props: dict) -> None:
|
||||||
|
@ -128,7 +129,8 @@ class WaveConstantNode(base.MaxwellSimNode):
|
||||||
)
|
)
|
||||||
def compute_wl_value(self, input_sockets: dict) -> sp.Expr:
|
def compute_wl_value(self, input_sockets: dict) -> sp.Expr:
|
||||||
"""Compute a single wavelength value from either wavelength/frequency."""
|
"""Compute a single wavelength value from either wavelength/frequency."""
|
||||||
if input_sockets['WL'] is not None:
|
has_wl = not ct.FlowSignal.check(input_sockets['WL'])
|
||||||
|
if has_wl:
|
||||||
return input_sockets['WL']
|
return input_sockets['WL']
|
||||||
|
|
||||||
return sci_constants.vac_speed_of_light / input_sockets['Freq']
|
return sci_constants.vac_speed_of_light / input_sockets['Freq']
|
||||||
|
@ -141,7 +143,8 @@ class WaveConstantNode(base.MaxwellSimNode):
|
||||||
)
|
)
|
||||||
def compute_freq_value(self, input_sockets: dict) -> sp.Expr:
|
def compute_freq_value(self, input_sockets: dict) -> sp.Expr:
|
||||||
"""Compute a single frequency value from either wavelength/frequency."""
|
"""Compute a single frequency value from either wavelength/frequency."""
|
||||||
if input_sockets['Freq'] is not None:
|
has_freq = not ct.FlowSignal.check(input_sockets['Freq'])
|
||||||
|
if has_freq:
|
||||||
return input_sockets['Freq']
|
return input_sockets['Freq']
|
||||||
|
|
||||||
return sci_constants.vac_speed_of_light / input_sockets['WL']
|
return sci_constants.vac_speed_of_light / input_sockets['WL']
|
||||||
|
@ -158,11 +161,20 @@ class WaveConstantNode(base.MaxwellSimNode):
|
||||||
)
|
)
|
||||||
def compute_wl_range(self, input_sockets: dict) -> sp.Expr:
|
def compute_wl_range(self, input_sockets: dict) -> sp.Expr:
|
||||||
"""Compute wavelength range from either wavelength/frequency ranges."""
|
"""Compute wavelength range from either wavelength/frequency ranges."""
|
||||||
if input_sockets['WL'] is not None:
|
has_wl = not ct.FlowSignal.check(input_sockets['WL'])
|
||||||
|
if has_wl:
|
||||||
return input_sockets['WL']
|
return input_sockets['WL']
|
||||||
|
|
||||||
return input_sockets['Freq'].rescale_bounds(
|
freq = input_sockets['Freq']
|
||||||
lambda bound: sci_constants.vac_speed_of_light / bound, reverse=True
|
return ct.LazyArrayRangeFlow(
|
||||||
|
start=spux.scale_to_unit(
|
||||||
|
sci_constants.vac_speed_of_light / (freq.stop * freq.unit), spu.um
|
||||||
|
),
|
||||||
|
stop=spux.scale_to_unit(
|
||||||
|
sci_constants.vac_speed_of_light / (freq.start * freq.unit), spu.um
|
||||||
|
),
|
||||||
|
steps=freq.steps,
|
||||||
|
unit=spu.um,
|
||||||
)
|
)
|
||||||
|
|
||||||
@events.computes_output_socket(
|
@events.computes_output_socket(
|
||||||
|
@ -177,11 +189,20 @@ class WaveConstantNode(base.MaxwellSimNode):
|
||||||
)
|
)
|
||||||
def compute_freq_range(self, input_sockets: dict) -> sp.Expr:
|
def compute_freq_range(self, input_sockets: dict) -> sp.Expr:
|
||||||
"""Compute frequency range from either wavelength/frequency ranges."""
|
"""Compute frequency range from either wavelength/frequency ranges."""
|
||||||
if input_sockets['Freq'] is not None:
|
has_freq = not ct.FlowSignal.check(input_sockets['Freq'])
|
||||||
|
if has_freq:
|
||||||
return input_sockets['Freq']
|
return input_sockets['Freq']
|
||||||
|
|
||||||
return input_sockets['WL'].rescale_bounds(
|
wl = input_sockets['WL']
|
||||||
lambda bound: sci_constants.vac_speed_of_light / bound, reverse=True
|
return ct.LazyArrayRangeFlow(
|
||||||
|
start=spux.scale_to_unit(
|
||||||
|
sci_constants.vac_speed_of_light / (wl.stop * wl.unit), spux.THz
|
||||||
|
),
|
||||||
|
stop=spux.scale_to_unit(
|
||||||
|
sci_constants.vac_speed_of_light / (wl.start * wl.unit), spux.THz
|
||||||
|
),
|
||||||
|
steps=wl.steps,
|
||||||
|
unit=spux.THz,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -35,7 +35,7 @@ class LoadCloudSim(bpy.types.Operator):
|
||||||
node = context.node
|
node = context.node
|
||||||
|
|
||||||
# Try Loading Simulation Data
|
# Try Loading Simulation Data
|
||||||
node.sim_data = bl_cache.Signal.InvalidateCache
|
#node.sim_data = bl_cache.Signal.InvalidateCache
|
||||||
sim_data = node.sim_data
|
sim_data = node.sim_data
|
||||||
if sim_data is None:
|
if sim_data is None:
|
||||||
self.report(
|
self.report(
|
||||||
|
@ -70,18 +70,26 @@ class Tidy3DWebImporterNode(base.MaxwellSimNode):
|
||||||
should_exist=True,
|
should_exist=True,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
output_sockets: typ.ClassVar = {
|
||||||
|
'Sim Data': sockets.MaxwellFDTDSimDataSocketDef(),
|
||||||
|
}
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Properties
|
||||||
|
####################
|
||||||
sim_data_loaded: bool = bl_cache.BLField(False)
|
sim_data_loaded: bool = bl_cache.BLField(False)
|
||||||
|
|
||||||
@bl_cache.cached_bl_property()
|
####################
|
||||||
|
# - Computed
|
||||||
|
####################
|
||||||
|
@property
|
||||||
def sim_data(self) -> td.SimulationData | None:
|
def sim_data(self) -> td.SimulationData | None:
|
||||||
cloud_task = self._compute_input(
|
cloud_task = self._compute_input(
|
||||||
'Cloud Task', kind=ct.FlowKind.Value, optional=True
|
'Cloud Task', kind=ct.FlowKind.Value, optional=True
|
||||||
)
|
)
|
||||||
|
has_cloud_task = not ct.FlowSignal.check(cloud_task)
|
||||||
if (
|
if (
|
||||||
# Check Flow
|
has_cloud_task
|
||||||
not ct.FlowSignal.check(cloud_task)
|
|
||||||
# Check Task
|
|
||||||
and cloud_task is not None
|
and cloud_task is not None
|
||||||
and isinstance(cloud_task, tdcloud.CloudTask)
|
and isinstance(cloud_task, tdcloud.CloudTask)
|
||||||
and cloud_task.status == 'success'
|
and cloud_task.status == 'success'
|
||||||
|
@ -97,7 +105,7 @@ class Tidy3DWebImporterNode(base.MaxwellSimNode):
|
||||||
####################
|
####################
|
||||||
# - UI
|
# - UI
|
||||||
####################
|
####################
|
||||||
def draw_operators(self, context, layout):
|
def draw_operators(self, _: bpy.types.Context, layout: bpy.types.UILayout):
|
||||||
if self.sim_data_loaded:
|
if self.sim_data_loaded:
|
||||||
layout.operator(ct.OperatorType.NodeLoadCloudSim, text='Reload Sim')
|
layout.operator(ct.OperatorType.NodeLoadCloudSim, text='Reload Sim')
|
||||||
else:
|
else:
|
||||||
|
@ -106,11 +114,6 @@ class Tidy3DWebImporterNode(base.MaxwellSimNode):
|
||||||
####################
|
####################
|
||||||
# - Events
|
# - Events
|
||||||
####################
|
####################
|
||||||
@events.on_value_changed(socket_name='Cloud Task')
|
|
||||||
def on_cloud_task_changed(self):
|
|
||||||
self.inputs['Cloud Task'].on_cloud_updated()
|
|
||||||
## TODO: Must we babysit sockets like this?
|
|
||||||
|
|
||||||
@events.on_value_changed(
|
@events.on_value_changed(
|
||||||
prop_name='sim_data_loaded', run_on_init=True, props={'sim_data_loaded'}
|
prop_name='sim_data_loaded', run_on_init=True, props={'sim_data_loaded'}
|
||||||
)
|
)
|
||||||
|
|
|
@ -33,6 +33,7 @@ class EHFieldMonitorNode(base.MaxwellSimNode):
|
||||||
'Size': sockets.ExprSocketDef(
|
'Size': sockets.ExprSocketDef(
|
||||||
shape=(3,),
|
shape=(3,),
|
||||||
physical_type=spux.PhysicalType.Length,
|
physical_type=spux.PhysicalType.Length,
|
||||||
|
default_value=sp.Matrix([1, 1, 1]),
|
||||||
),
|
),
|
||||||
'Spatial Subdivs': sockets.ExprSocketDef(
|
'Spatial Subdivs': sockets.ExprSocketDef(
|
||||||
shape=(3,),
|
shape=(3,),
|
||||||
|
@ -124,11 +125,30 @@ class EHFieldMonitorNode(base.MaxwellSimNode):
|
||||||
# - Preview
|
# - Preview
|
||||||
####################
|
####################
|
||||||
@events.on_value_changed(
|
@events.on_value_changed(
|
||||||
socket_name={'Center', 'Size'},
|
# Trigger
|
||||||
prop_name='preview_active',
|
prop_name='preview_active',
|
||||||
|
# Loaded
|
||||||
|
managed_objs={'mesh'},
|
||||||
props={'preview_active'},
|
props={'preview_active'},
|
||||||
input_sockets={'Center', 'Size'},
|
input_sockets={'Center', 'Size'},
|
||||||
|
)
|
||||||
|
def on_preview_changed(self, managed_objs, props, input_sockets):
|
||||||
|
"""Enables/disables previewing of the GeoNodes-driven mesh, regardless of whether a particular GeoNodes tree is chosen."""
|
||||||
|
mesh = managed_objs['mesh']
|
||||||
|
|
||||||
|
# Push Preview State to Managed Mesh
|
||||||
|
if props['preview_active']:
|
||||||
|
mesh.show_preview()
|
||||||
|
else:
|
||||||
|
mesh.hide_preview()
|
||||||
|
|
||||||
|
@events.on_value_changed(
|
||||||
|
# Trigger
|
||||||
|
socket_name={'Center', 'Size'},
|
||||||
|
run_on_init=True,
|
||||||
|
# Loaded
|
||||||
managed_objs={'mesh', 'modifier'},
|
managed_objs={'mesh', 'modifier'},
|
||||||
|
input_sockets={'Center', 'Size'},
|
||||||
unit_systems={'BlenderUnits': ct.UNITS_BLENDER},
|
unit_systems={'BlenderUnits': ct.UNITS_BLENDER},
|
||||||
scale_input_sockets={
|
scale_input_sockets={
|
||||||
'Center': 'BlenderUnits',
|
'Center': 'BlenderUnits',
|
||||||
|
@ -136,7 +156,6 @@ class EHFieldMonitorNode(base.MaxwellSimNode):
|
||||||
)
|
)
|
||||||
def on_inputs_changed(
|
def on_inputs_changed(
|
||||||
self,
|
self,
|
||||||
props: dict,
|
|
||||||
managed_objs: dict,
|
managed_objs: dict,
|
||||||
input_sockets: dict,
|
input_sockets: dict,
|
||||||
unit_systems: dict,
|
unit_systems: dict,
|
||||||
|
@ -153,9 +172,6 @@ class EHFieldMonitorNode(base.MaxwellSimNode):
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
# Push Preview State
|
|
||||||
if props['preview_active']:
|
|
||||||
managed_objs['mesh'].show_preview()
|
|
||||||
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
|
|
@ -31,6 +31,7 @@ class PowerFluxMonitorNode(base.MaxwellSimNode):
|
||||||
'Size': sockets.ExprSocketDef(
|
'Size': sockets.ExprSocketDef(
|
||||||
shape=(3,),
|
shape=(3,),
|
||||||
physical_type=spux.PhysicalType.Length,
|
physical_type=spux.PhysicalType.Length,
|
||||||
|
default_value=sp.Matrix([1, 1, 1]),
|
||||||
),
|
),
|
||||||
'Samples/Space': sockets.ExprSocketDef(
|
'Samples/Space': sockets.ExprSocketDef(
|
||||||
shape=(3,),
|
shape=(3,),
|
||||||
|
@ -123,11 +124,29 @@ class PowerFluxMonitorNode(base.MaxwellSimNode):
|
||||||
# - Preview - Changes to Input Sockets
|
# - Preview - Changes to Input Sockets
|
||||||
####################
|
####################
|
||||||
@events.on_value_changed(
|
@events.on_value_changed(
|
||||||
socket_name={'Center', 'Size'},
|
# Trigger
|
||||||
prop_name='preview_active',
|
prop_name='preview_active',
|
||||||
|
# Loaded
|
||||||
|
managed_objs={'mesh'},
|
||||||
props={'preview_active'},
|
props={'preview_active'},
|
||||||
input_sockets={'Center', 'Size'},
|
)
|
||||||
|
def on_preview_changed(self, managed_objs, props):
|
||||||
|
"""Enables/disables previewing of the GeoNodes-driven mesh, regardless of whether a particular GeoNodes tree is chosen."""
|
||||||
|
mesh = managed_objs['mesh']
|
||||||
|
|
||||||
|
# Push Preview State to Managed Mesh
|
||||||
|
if props['preview_active']:
|
||||||
|
mesh.show_preview()
|
||||||
|
else:
|
||||||
|
mesh.hide_preview()
|
||||||
|
|
||||||
|
@events.on_value_changed(
|
||||||
|
# Trigger
|
||||||
|
socket_name={'Center', 'Size'},
|
||||||
|
run_on_init=True,
|
||||||
|
# Loaded
|
||||||
managed_objs={'mesh', 'modifier'},
|
managed_objs={'mesh', 'modifier'},
|
||||||
|
input_sockets={'Center', 'Size'},
|
||||||
unit_systems={'BlenderUnits': ct.UNITS_BLENDER},
|
unit_systems={'BlenderUnits': ct.UNITS_BLENDER},
|
||||||
scale_input_sockets={
|
scale_input_sockets={
|
||||||
'Center': 'BlenderUnits',
|
'Center': 'BlenderUnits',
|
||||||
|
@ -135,7 +154,6 @@ class PowerFluxMonitorNode(base.MaxwellSimNode):
|
||||||
)
|
)
|
||||||
def on_inputs_changed(
|
def on_inputs_changed(
|
||||||
self,
|
self,
|
||||||
props: dict,
|
|
||||||
managed_objs: dict,
|
managed_objs: dict,
|
||||||
input_sockets: dict,
|
input_sockets: dict,
|
||||||
unit_systems: dict,
|
unit_systems: dict,
|
||||||
|
@ -152,9 +170,6 @@ class PowerFluxMonitorNode(base.MaxwellSimNode):
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
# Push Preview State
|
|
||||||
if props['preview_active']:
|
|
||||||
managed_objs['mesh'].show_preview()
|
|
||||||
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
from . import file_exporters, viewer, web_exporters
|
#from . import file_exporters, viewer, web_exporters
|
||||||
|
from . import viewer
|
||||||
|
|
||||||
BL_REGISTER = [
|
BL_REGISTER = [
|
||||||
*viewer.BL_REGISTER,
|
*viewer.BL_REGISTER,
|
||||||
*file_exporters.BL_REGISTER,
|
#*file_exporters.BL_REGISTER,
|
||||||
*web_exporters.BL_REGISTER,
|
#*web_exporters.BL_REGISTER,
|
||||||
]
|
]
|
||||||
BL_NODES = {
|
BL_NODES = {
|
||||||
**viewer.BL_NODES,
|
**viewer.BL_NODES,
|
||||||
**file_exporters.BL_NODES,
|
#**file_exporters.BL_NODES,
|
||||||
**web_exporters.BL_NODES,
|
#**web_exporters.BL_NODES,
|
||||||
}
|
}
|
||||||
|
|
|
@ -217,7 +217,7 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
|
||||||
Called by `self.on_prop_changed()` when `self.active_kind` was changed.
|
Called by `self.on_prop_changed()` when `self.active_kind` was changed.
|
||||||
"""
|
"""
|
||||||
self.display_shape = (
|
self.display_shape = (
|
||||||
'SQUARE' if self.active_kind == ct.FlowKind.LazyValueRange else 'CIRCLE'
|
'SQUARE' if self.active_kind == ct.FlowKind.LazyArrayRange else 'CIRCLE'
|
||||||
) # + ('_DOT' if self.use_units else '')
|
) # + ('_DOT' if self.use_units else '')
|
||||||
## TODO: Valid Active Kinds should be a subset/subenum(?) of FlowKind
|
## TODO: Valid Active Kinds should be a subset/subenum(?) of FlowKind
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ import enum
|
||||||
import typing as typ
|
import typing as typ
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
|
import pydantic as pyd
|
||||||
import sympy as sp
|
import sympy as sp
|
||||||
|
|
||||||
from blender_maxwell.utils import bl_cache, logger
|
from blender_maxwell.utils import bl_cache, logger
|
||||||
|
@ -63,6 +64,7 @@ class InfoDisplayCol(enum.StrEnum):
|
||||||
class ExprBLSocket(base.MaxwellSimSocket):
|
class ExprBLSocket(base.MaxwellSimSocket):
|
||||||
socket_type = ct.SocketType.Expr
|
socket_type = ct.SocketType.Expr
|
||||||
bl_label = 'Expr'
|
bl_label = 'Expr'
|
||||||
|
use_info_draw = True
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Properties
|
# - Properties
|
||||||
|
@ -70,7 +72,7 @@ class ExprBLSocket(base.MaxwellSimSocket):
|
||||||
shape: tuple[int, ...] | None = bl_cache.BLField(None)
|
shape: tuple[int, ...] | None = bl_cache.BLField(None)
|
||||||
mathtype: spux.MathType = bl_cache.BLField(spux.MathType.Real, prop_ui=True)
|
mathtype: spux.MathType = bl_cache.BLField(spux.MathType.Real, prop_ui=True)
|
||||||
physical_type: spux.PhysicalType | None = bl_cache.BLField(None)
|
physical_type: spux.PhysicalType | None = bl_cache.BLField(None)
|
||||||
symbols: frozenset[spux.Symbol] = bl_cache.BLField(frozenset())
|
symbols: frozenset[sp.Symbol] = bl_cache.BLField(frozenset())
|
||||||
|
|
||||||
active_unit: enum.Enum = bl_cache.BLField(
|
active_unit: enum.Enum = bl_cache.BLField(
|
||||||
None, enum_cb=lambda self, _: self.search_units(), prop_ui=True
|
None, enum_cb=lambda self, _: self.search_units(), prop_ui=True
|
||||||
|
@ -102,7 +104,7 @@ class ExprBLSocket(base.MaxwellSimSocket):
|
||||||
)
|
)
|
||||||
|
|
||||||
# UI: LazyArrayRange
|
# UI: LazyArrayRange
|
||||||
steps: int = bl_cache.BLField(2, abs_min=2)
|
steps: int = bl_cache.BLField(2, abs_min=2, prop_ui=True)
|
||||||
## Expression
|
## Expression
|
||||||
raw_min_spstr: str = bl_cache.BLField('', prop_ui=True)
|
raw_min_spstr: str = bl_cache.BLField('', prop_ui=True)
|
||||||
raw_max_spstr: str = bl_cache.BLField('', prop_ui=True)
|
raw_max_spstr: str = bl_cache.BLField('', prop_ui=True)
|
||||||
|
@ -125,6 +127,15 @@ class ExprBLSocket(base.MaxwellSimSocket):
|
||||||
####################
|
####################
|
||||||
# - Computed: Raw Expressions
|
# - Computed: Raw Expressions
|
||||||
####################
|
####################
|
||||||
|
@property
|
||||||
|
def sorted_symbols(self) -> list[sp.Symbol]:
|
||||||
|
"""Retrieves all symbols and sorts them by name.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Repeateably ordered list of symbols.
|
||||||
|
"""
|
||||||
|
return sorted(self.symbols, key=lambda sym: sym.name)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def raw_value_sp(self) -> spux.SympyExpr:
|
def raw_value_sp(self) -> spux.SympyExpr:
|
||||||
return self._parse_expr_str(self.raw_value_spstr)
|
return self._parse_expr_str(self.raw_value_spstr)
|
||||||
|
@ -140,7 +151,7 @@ class ExprBLSocket(base.MaxwellSimSocket):
|
||||||
####################
|
####################
|
||||||
# - Computed: Units
|
# - Computed: Units
|
||||||
####################
|
####################
|
||||||
def search_units(self, _: bpy.types.Context) -> list[ct.BLEnumElement]:
|
def search_units(self) -> list[ct.BLEnumElement]:
|
||||||
if self.physical_type is not None:
|
if self.physical_type is not None:
|
||||||
return [
|
return [
|
||||||
(sp.sstr(unit), spux.sp_to_str(unit), sp.sstr(unit), '', i)
|
(sp.sstr(unit), spux.sp_to_str(unit), sp.sstr(unit), '', i)
|
||||||
|
@ -163,33 +174,38 @@ class ExprBLSocket(base.MaxwellSimSocket):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@unit.setter
|
@unit.setter
|
||||||
def unit(self, unit: spux.Unit) -> None:
|
def unit(self, unit: spux.Unit | None) -> None:
|
||||||
"""Set the unit, without touching the `raw_*` UI properties.
|
"""Set the unit, without touching the `raw_*` UI properties.
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
To set a new unit, **and** convert the `raw_*` UI properties to the new unit, use `self.convert_unit()` instead.
|
To set a new unit, **and** convert the `raw_*` UI properties to the new unit, use `self.convert_unit()` instead.
|
||||||
"""
|
"""
|
||||||
|
if self.physical_type is not None:
|
||||||
if unit in self.physical_type.valid_units:
|
if unit in self.physical_type.valid_units:
|
||||||
self.active_unit = sp.sstr(unit)
|
self.active_unit = sp.sstr(unit)
|
||||||
|
else:
|
||||||
msg = f'Tried to set invalid unit {unit} (physical type "{self.physical_type}" only supports "{self.physical_type.valid_units}")'
|
msg = f'Tried to set invalid unit {unit} (physical type "{self.physical_type}" only supports "{self.physical_type.valid_units}")'
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
|
elif unit is not None:
|
||||||
|
msg = f'Tried to set invalid unit {unit} (physical type is {self.physical_type}, and has no unit support!)")'
|
||||||
|
raise ValueError(msg)
|
||||||
|
|
||||||
def convert_unit(self, unit_to: spux.Unit) -> None:
|
def convert_unit(self, unit_to: spux.Unit) -> None:
|
||||||
if self.active_kind == ct.FlowKind.Value:
|
|
||||||
current_value = self.value
|
current_value = self.value
|
||||||
self.unit = unit_to
|
|
||||||
self.value = current_value
|
|
||||||
elif self.active_kind == ct.FlowKind.LazyArrayRange:
|
|
||||||
current_lazy_array_range = self.lazy_array_range
|
current_lazy_array_range = self.lazy_array_range
|
||||||
self.unit = unit_to
|
|
||||||
|
self.unit = bl_cache.Signal.InvalidateCache
|
||||||
|
|
||||||
|
self.value = current_value
|
||||||
self.lazy_array_range = current_lazy_array_range
|
self.lazy_array_range = current_lazy_array_range
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Property Callback
|
# - Property Callback
|
||||||
####################
|
####################
|
||||||
def on_socket_prop_changed(self, prop_name: str) -> None:
|
def on_socket_prop_changed(self, prop_name: str) -> None:
|
||||||
if prop_name == 'unit' and self.active_unit is not None:
|
if prop_name == 'physical_type':
|
||||||
|
self.active_unit = bl_cache.Signal.ResetEnumItems
|
||||||
|
if prop_name == 'active_unit' and self.active_unit is not None:
|
||||||
self.convert_unit(spux.unit_str_to_unit(self.active_unit))
|
self.convert_unit(spux.unit_str_to_unit(self.active_unit))
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
@ -200,23 +216,23 @@ class ExprBLSocket(base.MaxwellSimSocket):
|
||||||
) -> tuple[spux.MathType, tuple[int, ...] | None, spux.UnitDimension]:
|
) -> tuple[spux.MathType, tuple[int, ...] | None, spux.UnitDimension]:
|
||||||
# Parse MathType
|
# Parse MathType
|
||||||
mathtype = spux.MathType.from_expr(expr)
|
mathtype = spux.MathType.from_expr(expr)
|
||||||
if self.mathtype != mathtype:
|
if not self.mathtype.is_compatible(mathtype):
|
||||||
msg = f'MathType is {self.mathtype}, but tried to set expr {expr} with mathtype {mathtype}'
|
msg = f'MathType is {self.mathtype}, but tried to set expr {expr} with mathtype {mathtype}'
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
|
|
||||||
# Parse Symbols
|
# Parse Symbols
|
||||||
if expr.free_symbols:
|
if expr.free_symbols and not expr.free_symbols.issubset(self.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}'
|
msg = f'Tried to set expr {expr} with free symbols {expr.free_symbols}, which is incompatible with socket symbols {self.symbols}'
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
|
|
||||||
# Parse Dimensions
|
# Parse Dimensions
|
||||||
shape = spux.parse_shape(expr)
|
shape = spux.parse_shape(expr)
|
||||||
if shape != self.shape:
|
if shape != self.shape and not (
|
||||||
|
shape is not None
|
||||||
|
and self.shape is not None
|
||||||
|
and len(self.shape) == 1
|
||||||
|
and 1 in shape
|
||||||
|
):
|
||||||
msg = f'Expr {expr} has shape {shape}, which is incompatible with the expr socket (shape {self.shape})'
|
msg = f'Expr {expr} has shape {shape}, which is incompatible with the expr socket (shape {self.shape})'
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
|
|
||||||
|
@ -238,7 +254,7 @@ class ExprBLSocket(base.MaxwellSimSocket):
|
||||||
# Try Parsing and Returning the Expression
|
# Try Parsing and Returning the Expression
|
||||||
try:
|
try:
|
||||||
self._parse_expr_info(expr)
|
self._parse_expr_info(expr)
|
||||||
except ValueError(expr) as ex:
|
except ValueError:
|
||||||
log.exception(
|
log.exception(
|
||||||
'Couldn\'t parse expression "%s" in Expr socket.',
|
'Couldn\'t parse expression "%s" in Expr socket.',
|
||||||
expr_spstr,
|
expr_spstr,
|
||||||
|
@ -270,6 +286,7 @@ class ExprBLSocket(base.MaxwellSimSocket):
|
||||||
expr = self.raw_value_sp
|
expr = self.raw_value_sp
|
||||||
if expr is None:
|
if expr is None:
|
||||||
return ct.FlowSignal.FlowPending
|
return ct.FlowSignal.FlowPending
|
||||||
|
return expr
|
||||||
|
|
||||||
MT_Z = spux.MathType.Integer
|
MT_Z = spux.MathType.Integer
|
||||||
MT_Q = spux.MathType.Rational
|
MT_Q = spux.MathType.Rational
|
||||||
|
@ -312,7 +329,7 @@ class ExprBLSocket(base.MaxwellSimSocket):
|
||||||
Notes:
|
Notes:
|
||||||
Called to set the internal `FlowKind.Value` of this socket.
|
Called to set the internal `FlowKind.Value` of this socket.
|
||||||
"""
|
"""
|
||||||
mathtype, shape = self._parse_expr_info(expr)
|
_mathtype, _shape = self._parse_expr_info(expr)
|
||||||
if self.symbols or self.shape not in [None, (2,), (3,)]:
|
if self.symbols or self.shape not in [None, (2,), (3,)]:
|
||||||
self.raw_value_spstr = sp.sstr(expr)
|
self.raw_value_spstr = sp.sstr(expr)
|
||||||
|
|
||||||
|
@ -321,32 +338,33 @@ class ExprBLSocket(base.MaxwellSimSocket):
|
||||||
MT_Q = spux.MathType.Rational
|
MT_Q = spux.MathType.Rational
|
||||||
MT_R = spux.MathType.Real
|
MT_R = spux.MathType.Real
|
||||||
MT_C = spux.MathType.Complex
|
MT_C = spux.MathType.Complex
|
||||||
if shape is None:
|
if self.shape is None:
|
||||||
if mathtype == MT_Z:
|
if self.mathtype == MT_Z:
|
||||||
self.raw_value_int = self._to_raw_value(expr)
|
self.raw_value_int = self._to_raw_value(expr)
|
||||||
elif mathtype == MT_Q:
|
elif self.mathtype == MT_Q:
|
||||||
self.raw_value_rat = self._to_raw_value(expr)
|
self.raw_value_rat = self._to_raw_value(expr)
|
||||||
elif mathtype == MT_R:
|
elif self.mathtype == MT_R:
|
||||||
self.raw_value_float = self._to_raw_value(expr)
|
self.raw_value_float = self._to_raw_value(expr)
|
||||||
elif mathtype == MT_C:
|
elif self.mathtype == MT_C:
|
||||||
self.raw_value_complex = self._to_raw_value(expr)
|
self.raw_value_complex = self._to_raw_value(expr)
|
||||||
elif shape == (2,):
|
elif self.shape == (2,):
|
||||||
if mathtype == MT_Z:
|
if self.mathtype == MT_Z:
|
||||||
self.raw_value_int2 = self._to_raw_value(expr)
|
self.raw_value_int2 = self._to_raw_value(expr)
|
||||||
elif mathtype == MT_Q:
|
elif self.mathtype == MT_Q:
|
||||||
self.raw_value_rat2 = self._to_raw_value(expr)
|
self.raw_value_rat2 = self._to_raw_value(expr)
|
||||||
elif mathtype == MT_R:
|
elif self.mathtype == MT_R:
|
||||||
self.raw_value_float2 = self._to_raw_value(expr)
|
self.raw_value_float2 = self._to_raw_value(expr)
|
||||||
elif mathtype == MT_C:
|
elif self.mathtype == MT_C:
|
||||||
self.raw_value_complex2 = self._to_raw_value(expr)
|
self.raw_value_complex2 = self._to_raw_value(expr)
|
||||||
elif shape == (3,):
|
elif self.shape == (3,):
|
||||||
if mathtype == MT_Z:
|
log.critical(expr)
|
||||||
|
if self.mathtype == MT_Z:
|
||||||
self.raw_value_int3 = self._to_raw_value(expr)
|
self.raw_value_int3 = self._to_raw_value(expr)
|
||||||
elif mathtype == MT_Q:
|
elif self.mathtype == MT_Q:
|
||||||
self.raw_value_rat3 = self._to_raw_value(expr)
|
self.raw_value_rat3 = self._to_raw_value(expr)
|
||||||
elif mathtype == MT_R:
|
elif self.mathtype == MT_R:
|
||||||
self.raw_value_float3 = self._to_raw_value(expr)
|
self.raw_value_float3 = self._to_raw_value(expr)
|
||||||
elif mathtype == MT_C:
|
elif self.mathtype == MT_C:
|
||||||
self.raw_value_complex3 = self._to_raw_value(expr)
|
self.raw_value_complex3 = self._to_raw_value(expr)
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
@ -404,7 +422,6 @@ class ExprBLSocket(base.MaxwellSimSocket):
|
||||||
Called to compute the internal `FlowKind.LazyArrayRange` of this socket.
|
Called to compute the internal `FlowKind.LazyArrayRange` of this socket.
|
||||||
"""
|
"""
|
||||||
self.steps = value.steps
|
self.steps = value.steps
|
||||||
self.unit = value.unit
|
|
||||||
|
|
||||||
if self.symbols:
|
if self.symbols:
|
||||||
self.raw_min_spstr = sp.sstr(value.start)
|
self.raw_min_spstr = sp.sstr(value.start)
|
||||||
|
@ -416,21 +433,26 @@ class ExprBLSocket(base.MaxwellSimSocket):
|
||||||
MT_R = spux.MathType.Real
|
MT_R = spux.MathType.Real
|
||||||
MT_C = spux.MathType.Complex
|
MT_C = spux.MathType.Complex
|
||||||
|
|
||||||
|
unit = value.unit if value.unit is not None else 1
|
||||||
if value.mathtype == MT_Z:
|
if value.mathtype == MT_Z:
|
||||||
self.raw_range_int = [
|
self.raw_range_int = [
|
||||||
self._to_raw_value(bound) for bound in [value.start, value.stop]
|
self._to_raw_value(bound * unit)
|
||||||
|
for bound in [value.start, value.stop]
|
||||||
]
|
]
|
||||||
elif value.mathtype == MT_Q:
|
elif value.mathtype == MT_Q:
|
||||||
self.raw_range_rat = [
|
self.raw_range_rat = [
|
||||||
self._to_raw_value(bound) for bound in [value.start, value.stop]
|
self._to_raw_value(bound * unit)
|
||||||
|
for bound in [value.start, value.stop]
|
||||||
]
|
]
|
||||||
elif value.mathtype == MT_R:
|
elif value.mathtype == MT_R:
|
||||||
self.raw_range_float = [
|
self.raw_range_float = [
|
||||||
self._to_raw_value(bound) for bound in [value.start, value.stop]
|
self._to_raw_value(bound * unit)
|
||||||
|
for bound in [value.start, value.stop]
|
||||||
]
|
]
|
||||||
elif value.mathtype == MT_C:
|
elif value.mathtype == MT_C:
|
||||||
self.raw_range_complex = [
|
self.raw_range_complex = [
|
||||||
self._to_raw_value(bound) for bound in [value.start, value.stop]
|
self._to_raw_value(bound * unit)
|
||||||
|
for bound in [value.start, value.stop]
|
||||||
]
|
]
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
@ -441,8 +463,8 @@ class ExprBLSocket(base.MaxwellSimSocket):
|
||||||
# Lazy Value: Arbitrary Expression
|
# Lazy Value: Arbitrary Expression
|
||||||
if self.symbols or self.shape not in [None, (2,), (3,)]:
|
if self.symbols or self.shape not in [None, (2,), (3,)]:
|
||||||
return ct.LazyValueFuncFlow(
|
return ct.LazyValueFuncFlow(
|
||||||
func=sp.lambdify(self.symbols, self.value, 'jax'),
|
func=sp.lambdify(self.sorted_symbols, self.value, 'jax'),
|
||||||
func_args=[spux.MathType.from_expr(sym) for sym in self.symbols],
|
func_args=[spux.MathType.from_expr(sym) for sym in self.sorted_symbols],
|
||||||
supports_jax=True,
|
supports_jax=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -482,8 +504,7 @@ class ExprBLSocket(base.MaxwellSimSocket):
|
||||||
unit=self.unit,
|
unit=self.unit,
|
||||||
)
|
)
|
||||||
|
|
||||||
msg = "Expr socket can't produce array from expression with free symbols"
|
return ct.FlowSignal.NoFlow
|
||||||
raise ValueError(msg)
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - FlowKind: Info
|
# - FlowKind: Info
|
||||||
|
@ -496,6 +517,7 @@ class ExprBLSocket(base.MaxwellSimSocket):
|
||||||
output_mathtype=self.mathtype,
|
output_mathtype=self.mathtype,
|
||||||
output_unit=self.unit,
|
output_unit=self.unit,
|
||||||
)
|
)
|
||||||
|
## TODO: When expression can be used w/arrays, then allow directly outputting a LazyArrayRange pumped through the given expression. Or something like that.
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - FlowKind: Capabilities
|
# - FlowKind: Capabilities
|
||||||
|
@ -520,10 +542,11 @@ class ExprBLSocket(base.MaxwellSimSocket):
|
||||||
_row.label(text=text)
|
_row.label(text=text)
|
||||||
|
|
||||||
_col = split.column(align=True)
|
_col = split.column(align=True)
|
||||||
_col.prop(self, 'active_unit', text='')
|
_col.prop(self, self.blfields['active_unit'], text='')
|
||||||
|
else:
|
||||||
|
row.label(text=text)
|
||||||
|
|
||||||
def draw_value(self, col: bpy.types.UILayout) -> None:
|
def draw_value(self, col: bpy.types.UILayout) -> None:
|
||||||
# Property Interface
|
|
||||||
if self.symbols:
|
if self.symbols:
|
||||||
col.prop(self, self.blfields['raw_value_spstr'], text='')
|
col.prop(self, self.blfields['raw_value_spstr'], text='')
|
||||||
|
|
||||||
|
@ -575,6 +598,27 @@ class ExprBLSocket(base.MaxwellSimSocket):
|
||||||
for sym in self.symbols:
|
for sym in self.symbols:
|
||||||
col.label(text=spux.pretty_symbol(sym))
|
col.label(text=spux.pretty_symbol(sym))
|
||||||
|
|
||||||
|
def draw_lazy_array_range(self, col: bpy.types.UILayout) -> None:
|
||||||
|
if self.symbols:
|
||||||
|
col.prop(self, self.blfields['raw_min_spstr'], text='')
|
||||||
|
col.prop(self, self.blfields['raw_max_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.mathtype == MT_Z:
|
||||||
|
col.prop(self, self.blfields['raw_range_int'], text='')
|
||||||
|
elif self.mathtype == MT_Q:
|
||||||
|
col.prop(self, self.blfields['raw_range_rat'], text='')
|
||||||
|
elif self.mathtype == MT_R:
|
||||||
|
col.prop(self, self.blfields['raw_range_float'], text='')
|
||||||
|
elif self.mathtype == MT_C:
|
||||||
|
col.prop(self, self.blfields['raw_range_complex'], text='')
|
||||||
|
|
||||||
|
col.prop(self, self.blfields['steps'], text='')
|
||||||
|
|
||||||
def draw_input_label_row(self, row: bpy.types.UILayout, text) -> None:
|
def draw_input_label_row(self, row: bpy.types.UILayout, text) -> None:
|
||||||
info = self.compute_data(kind=ct.FlowKind.Info)
|
info = self.compute_data(kind=ct.FlowKind.Info)
|
||||||
has_dims = not ct.FlowSignal.check(info) and info.dim_names
|
has_dims = not ct.FlowSignal.check(info) and info.dim_names
|
||||||
|
@ -630,7 +674,7 @@ class ExprBLSocket(base.MaxwellSimSocket):
|
||||||
_row.label(text=text)
|
_row.label(text=text)
|
||||||
|
|
||||||
def draw_info(self, info: ct.InfoFlow, col: bpy.types.UILayout) -> None:
|
def draw_info(self, info: ct.InfoFlow, col: bpy.types.UILayout) -> None:
|
||||||
if info.dim_names and self.show_info_columns:
|
if self.show_info_columns:
|
||||||
row = col.row()
|
row = col.row()
|
||||||
box = row.box()
|
box = row.box()
|
||||||
grid = box.grid_flow(
|
grid = box.grid_flow(
|
||||||
|
@ -696,19 +740,87 @@ class ExprSocketDef(base.SocketDef):
|
||||||
default_unit: spux.Unit | None = None
|
default_unit: spux.Unit | None = None
|
||||||
|
|
||||||
# FlowKind: Value
|
# FlowKind: Value
|
||||||
default_value: spux.SympyExpr = sp.S(0)
|
default_value: spux.SympyExpr = sp.RealNumber(0)
|
||||||
|
|
||||||
# FlowKind: LazyArrayRange
|
# FlowKind: LazyArrayRange
|
||||||
default_min: spux.SympyExpr = sp.S(0)
|
default_min: spux.SympyExpr = sp.RealNumber(0)
|
||||||
default_max: spux.SympyExpr = sp.S(1)
|
default_max: spux.SympyExpr = sp.RealNumber(1)
|
||||||
default_steps: int = 2
|
default_steps: int = 2
|
||||||
## TODO: Configure lin/log/... scaling (w/enumprop in UI)
|
## TODO: Configure lin/log/... scaling (w/enumprop in UI)
|
||||||
|
|
||||||
## TODO: Buncha validation :)
|
|
||||||
|
|
||||||
# UI
|
# UI
|
||||||
show_info_columns: bool = False
|
show_info_columns: bool = False
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Validators - Coersion
|
||||||
|
####################
|
||||||
|
@pyd.model_validator(mode='after')
|
||||||
|
def shape_value_coersion(self) -> str:
|
||||||
|
if self.shape is not None and not isinstance(self.default_value, sp.MatrixBase):
|
||||||
|
if len(self.shape) == 1:
|
||||||
|
self.default_value = self.default_value * sp.Matrix.ones(
|
||||||
|
self.shape[0], 1
|
||||||
|
)
|
||||||
|
if len(self.shape) == 2:
|
||||||
|
self.default_value = self.default_value * sp.Matrix.ones(*self.shape)
|
||||||
|
|
||||||
|
return self
|
||||||
|
|
||||||
|
@pyd.model_validator(mode='after')
|
||||||
|
def unit_coersion(self) -> str:
|
||||||
|
if self.physical_type is not None and self.default_unit is None:
|
||||||
|
self.default_unit = self.physical_type.default_unit
|
||||||
|
|
||||||
|
return self
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Validators - Assertion
|
||||||
|
####################
|
||||||
|
@pyd.model_validator(mode='after')
|
||||||
|
def valid_shapes(self) -> str:
|
||||||
|
if self.active_kind == ct.FlowKind.LazyArrayRange and self.shape is not None:
|
||||||
|
msg = "Can't have a non-None shape when LazyArrayRange is set as the active kind."
|
||||||
|
raise ValueError(msg)
|
||||||
|
|
||||||
|
return self
|
||||||
|
|
||||||
|
@pyd.model_validator(mode='after')
|
||||||
|
def mathtype_value(self) -> str:
|
||||||
|
default_value_mathtype = spux.MathType.from_expr(self.default_value)
|
||||||
|
if not self.mathtype.is_compatible(default_value_mathtype):
|
||||||
|
msg = f'MathType is {self.mathtype}, but tried to set default value {self.default_value} with mathtype {default_value_mathtype}'
|
||||||
|
raise ValueError(msg)
|
||||||
|
|
||||||
|
return self
|
||||||
|
|
||||||
|
@pyd.model_validator(mode='after')
|
||||||
|
def symbols_value(self) -> str:
|
||||||
|
if (
|
||||||
|
self.default_value.free_symbols
|
||||||
|
and not self.default_value.free_symbols.issubset(self.symbols)
|
||||||
|
):
|
||||||
|
msg = f'Tried to set default value {self.default_value} with free symbols {self.default_value.free_symbols}, which is incompatible with socket symbols {self.symbols}'
|
||||||
|
raise ValueError(msg)
|
||||||
|
|
||||||
|
return self
|
||||||
|
|
||||||
|
@pyd.model_validator(mode='after')
|
||||||
|
def shape_value(self) -> str:
|
||||||
|
shape = spux.parse_shape(self.default_value)
|
||||||
|
if shape != self.shape and not (
|
||||||
|
shape is not None
|
||||||
|
and self.shape is not None
|
||||||
|
and len(self.shape) == 1
|
||||||
|
and 1 in shape
|
||||||
|
):
|
||||||
|
msg = f'Default value {self.default_value} has shape {shape}, which is incompatible with the expr socket (shape {self.shape})'
|
||||||
|
raise ValueError(msg)
|
||||||
|
|
||||||
|
return self
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Initialization
|
||||||
|
####################
|
||||||
def init(self, bl_socket: ExprBLSocket) -> None:
|
def init(self, bl_socket: ExprBLSocket) -> None:
|
||||||
bl_socket.active_kind = self.active_kind
|
bl_socket.active_kind = self.active_kind
|
||||||
|
|
||||||
|
@ -718,11 +830,12 @@ class ExprSocketDef(base.SocketDef):
|
||||||
bl_socket.physical_type = self.physical_type
|
bl_socket.physical_type = self.physical_type
|
||||||
bl_socket.symbols = self.symbols
|
bl_socket.symbols = self.symbols
|
||||||
|
|
||||||
# Socket Units
|
# Socket Units & FlowKind.Value
|
||||||
if self.default_unit is not None:
|
log.critical(self)
|
||||||
|
if self.physical_type is not None:
|
||||||
bl_socket.unit = self.default_unit
|
bl_socket.unit = self.default_unit
|
||||||
|
bl_socket.value = self.default_value * self.default_unit
|
||||||
# FlowKind: Value
|
else:
|
||||||
bl_socket.value = self.default_value
|
bl_socket.value = self.default_value
|
||||||
|
|
||||||
# FlowKind: LazyArrayRange
|
# FlowKind: LazyArrayRange
|
||||||
|
|
|
@ -96,6 +96,23 @@ class Tidy3DCloudTaskBLSocket(base.MaxwellSimSocket):
|
||||||
|
|
||||||
new_task_name: str = bl_cache.BLField('', prop_ui=True)
|
new_task_name: str = bl_cache.BLField('', prop_ui=True)
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Property Changes
|
||||||
|
####################
|
||||||
|
def on_socket_prop_changed(self, prop_name: str) -> None:
|
||||||
|
if prop_name in [
|
||||||
|
'api_key',
|
||||||
|
'existing_folder_id',
|
||||||
|
'existing_task_id',
|
||||||
|
'new_task_name',
|
||||||
|
'should_exist',
|
||||||
|
]:
|
||||||
|
self.existing_folder_id = bl_cache.Signal.ResetEnumItems
|
||||||
|
self.existing_task_id = bl_cache.Signal.ResetEnumItems
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - FlowKinds
|
||||||
|
####################
|
||||||
@property
|
@property
|
||||||
def capabilities(self) -> ct.CapabilitiesFlow:
|
def capabilities(self) -> ct.CapabilitiesFlow:
|
||||||
return ct.CapabilitiesFlow(
|
return ct.CapabilitiesFlow(
|
||||||
|
@ -122,7 +139,7 @@ class Tidy3DCloudTaskBLSocket(base.MaxwellSimSocket):
|
||||||
return (self.new_task_name, cloud_folder)
|
return (self.new_task_name, cloud_folder)
|
||||||
|
|
||||||
# No Task Selected: Return None
|
# No Task Selected: Return None
|
||||||
if self.existing_task_id == 'NONE':
|
if self.existing_task_id is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Retrieve Cloud Task
|
# Retrieve Cloud Task
|
||||||
|
@ -135,7 +152,7 @@ class Tidy3DCloudTaskBLSocket(base.MaxwellSimSocket):
|
||||||
|
|
||||||
return cloud_task
|
return cloud_task
|
||||||
|
|
||||||
return None
|
return ct.FlowSignal.FlowPending
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Searchers
|
# - Searchers
|
||||||
|
@ -158,7 +175,7 @@ class Tidy3DCloudTaskBLSocket(base.MaxwellSimSocket):
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def search_cloud_tasks(self) -> list[ct.BLEnumElement]:
|
def search_cloud_tasks(self) -> list[ct.BLEnumElement]:
|
||||||
if self.existing_folder_id == 'NONE' or not tdcloud.IS_AUTHENTICATED:
|
if self.existing_folder_id is None or not tdcloud.IS_AUTHENTICATED:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
# Get Cloud Folder
|
# Get Cloud Folder
|
||||||
|
@ -221,10 +238,6 @@ class Tidy3DCloudTaskBLSocket(base.MaxwellSimSocket):
|
||||||
def on_prepare_new_task(self):
|
def on_prepare_new_task(self):
|
||||||
self.should_exist = False
|
self.should_exist = False
|
||||||
|
|
||||||
def on_cloud_updated(self):
|
|
||||||
self.existing_folder_id = bl_cache.Signal.ResetEnumItems
|
|
||||||
self.existing_task_id = bl_cache.Signal.ResetEnumItems
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - UI
|
# - UI
|
||||||
####################
|
####################
|
||||||
|
|
|
@ -13,6 +13,7 @@ def prefix_values_with(prefix: str) -> type[enum.Enum]:
|
||||||
Returns:
|
Returns:
|
||||||
A new StrEnum class with altered member values.
|
A new StrEnum class with altered member values.
|
||||||
"""
|
"""
|
||||||
|
## TODO: DO NOT USE FOR ENUMS WITH METHODS
|
||||||
|
|
||||||
def _decorator(cls: enum.StrEnum):
|
def _decorator(cls: enum.StrEnum):
|
||||||
new_members = {
|
new_members = {
|
||||||
|
|
|
@ -547,6 +547,9 @@ class BLField:
|
||||||
self._str_cb = str_cb
|
self._str_cb = str_cb
|
||||||
self._enum_cb = enum_cb
|
self._enum_cb = enum_cb
|
||||||
|
|
||||||
|
## Type Coercion
|
||||||
|
self._coerce_output_to = None
|
||||||
|
|
||||||
## Vector/Matrix Identity
|
## Vector/Matrix Identity
|
||||||
## -> Matrix Shape assists in the workaround for Matrix Display Bug
|
## -> Matrix Shape assists in the workaround for Matrix Display Bug
|
||||||
self._is_vector = False
|
self._is_vector = False
|
||||||
|
@ -797,7 +800,11 @@ class BLField:
|
||||||
}
|
}
|
||||||
|
|
||||||
## StrEnum
|
## StrEnum
|
||||||
elif inspect.isclass(AttrType) and issubclass(AttrType, enum.StrEnum):
|
elif (
|
||||||
|
inspect.isclass(AttrType)
|
||||||
|
and issubclass(AttrType, enum.StrEnum)
|
||||||
|
and self._enum_cb is None
|
||||||
|
):
|
||||||
default_value = self._default_value
|
default_value = self._default_value
|
||||||
BLProp = bpy.props.EnumProperty
|
BLProp = bpy.props.EnumProperty
|
||||||
kwargs_prop |= {
|
kwargs_prop |= {
|
||||||
|
@ -814,9 +821,14 @@ class BLField:
|
||||||
}
|
}
|
||||||
if self._enum_many:
|
if self._enum_many:
|
||||||
kwargs_prop['options'].add('ENUM_FLAG')
|
kwargs_prop['options'].add('ENUM_FLAG')
|
||||||
|
self._coerce_output_to = AttrType
|
||||||
|
|
||||||
## Dynamic Enum
|
## Dynamic Enum
|
||||||
elif AttrType is enum.Enum and self._enum_cb is not None:
|
elif (
|
||||||
|
AttrType is enum.Enum
|
||||||
|
or (inspect.isclass(AttrType) and issubclass(AttrType, enum.StrEnum))
|
||||||
|
and self._enum_cb is not None
|
||||||
|
):
|
||||||
if self._default_value is not None:
|
if self._default_value is not None:
|
||||||
msg = 'When using dynamic enum, default value must be None'
|
msg = 'When using dynamic enum, default value must be None'
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
|
@ -828,6 +840,8 @@ class BLField:
|
||||||
}
|
}
|
||||||
if self._enum_many:
|
if self._enum_many:
|
||||||
kwargs_prop['options'].add('ENUM_FLAG')
|
kwargs_prop['options'].add('ENUM_FLAG')
|
||||||
|
if AttrType is not enum.Enum:
|
||||||
|
self._coerce_output_to = AttrType
|
||||||
|
|
||||||
## BL Reference
|
## BL Reference
|
||||||
elif AttrType in typ.get_args(ct.BLIDStruct):
|
elif AttrType in typ.get_args(ct.BLIDStruct):
|
||||||
|
@ -888,6 +902,9 @@ class BLField:
|
||||||
def __get__(
|
def __get__(
|
||||||
self, bl_instance: BLInstance | None, owner: type[BLInstance]
|
self, bl_instance: BLInstance | None, owner: type[BLInstance]
|
||||||
) -> typ.Any:
|
) -> typ.Any:
|
||||||
|
if bl_instance is None:
|
||||||
|
return None
|
||||||
|
|
||||||
value = self._cached_bl_property.__get__(bl_instance, owner)
|
value = self._cached_bl_property.__get__(bl_instance, owner)
|
||||||
|
|
||||||
# enum.Enum: Cast Auto-Injected Dynamic Enum 'NONE' -> None
|
# enum.Enum: Cast Auto-Injected Dynamic Enum 'NONE' -> None
|
||||||
|
@ -913,7 +930,7 @@ class BLField:
|
||||||
## -> Reject modernity. Return to tuple[].
|
## -> Reject modernity. Return to tuple[].
|
||||||
if self._is_vector:
|
if self._is_vector:
|
||||||
## -> tuple()ify the np.array to respect tuple[] type annotation.
|
## -> tuple()ify the np.array to respect tuple[] type annotation.
|
||||||
return tuple(np.array(value))
|
return tuple(value)
|
||||||
|
|
||||||
if self._is_matrix:
|
if self._is_matrix:
|
||||||
# Matrix Display Bug: Correctly Read Row-Major Values w/Reshape
|
# Matrix Display Bug: Correctly Read Row-Major Values w/Reshape
|
||||||
|
@ -921,6 +938,13 @@ class BLField:
|
||||||
map(tuple, np.array(value).flatten().reshape(self._matrix_shape))
|
map(tuple, np.array(value).flatten().reshape(self._matrix_shape))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Coerce Output
|
||||||
|
## -> Mainly useful for getting the "real" StrEnum back.
|
||||||
|
if self._coerce_output_to is not None and value is not None:
|
||||||
|
if self._enum_many:
|
||||||
|
return {self._coerce_output_to(v) for v in value}
|
||||||
|
return self._coerce_output_to(value)
|
||||||
|
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def __set__(self, bl_instance: BLInstance | None, value: typ.Any) -> None:
|
def __set__(self, bl_instance: BLInstance | None, value: typ.Any) -> None:
|
||||||
|
|
|
@ -25,6 +25,10 @@ from pydantic_core import core_schema as pyd_core_schema
|
||||||
|
|
||||||
from blender_maxwell import contracts as ct
|
from blender_maxwell import contracts as ct
|
||||||
|
|
||||||
|
from . import logger
|
||||||
|
|
||||||
|
log = logger.get(__name__)
|
||||||
|
|
||||||
SympyType = (
|
SympyType = (
|
||||||
sp.Basic
|
sp.Basic
|
||||||
| sp.Expr
|
| sp.Expr
|
||||||
|
@ -47,21 +51,42 @@ class MathType(enum.StrEnum):
|
||||||
Real = enum.auto()
|
Real = enum.auto()
|
||||||
Complex = enum.auto()
|
Complex = enum.auto()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
def combine(*mathtypes: list[typ.Self]) -> typ.Self:
|
def combine(*mathtypes: list[typ.Self]) -> typ.Self:
|
||||||
if MathType.Complex in mathtypes:
|
if MathType.Complex in mathtypes:
|
||||||
return MathType.Complex
|
return MathType.Complex
|
||||||
elif MathType.Real in mathtypes:
|
if MathType.Real in mathtypes:
|
||||||
return MathType.Real
|
return MathType.Real
|
||||||
elif MathType.Rational in mathtypes:
|
if MathType.Rational in mathtypes:
|
||||||
return MathType.Rational
|
return MathType.Rational
|
||||||
elif MathType.Integer in mathtypes:
|
if MathType.Integer in mathtypes:
|
||||||
return MathType.Integer
|
return MathType.Integer
|
||||||
elif MathType.Bool in mathtypes:
|
if MathType.Bool in mathtypes:
|
||||||
return MathType.Bool
|
return MathType.Bool
|
||||||
|
|
||||||
|
msg = f"Can't combine mathtypes {mathtypes}"
|
||||||
|
raise ValueError(msg)
|
||||||
|
|
||||||
|
def is_compatible(self, other: typ.Self) -> bool:
|
||||||
|
MT = MathType
|
||||||
|
return (
|
||||||
|
other
|
||||||
|
in {
|
||||||
|
MT.Bool: [MT.Bool],
|
||||||
|
MT.Integer: [MT.Integer],
|
||||||
|
MT.Rational: [MT.Integer, MT.Rational],
|
||||||
|
MT.Real: [MT.Integer, MT.Rational, MT.Real],
|
||||||
|
MT.Complex: [MT.Integer, MT.Rational, MT.Real, MT.Complex],
|
||||||
|
}[self]
|
||||||
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_expr(sp_obj: SympyType) -> type:
|
def from_expr(sp_obj: SympyType) -> type:
|
||||||
## TODO: Support for sp.Matrix
|
if isinstance(sp_obj, sp.MatrixBase):
|
||||||
|
return MathType.combine(
|
||||||
|
*[MathType.from_expr(v) for v in sp.flatten(sp_obj)]
|
||||||
|
)
|
||||||
|
|
||||||
if isinstance(sp_obj, sp.logic.boolalg.Boolean):
|
if isinstance(sp_obj, sp.logic.boolalg.Boolean):
|
||||||
return MathType.Bool
|
return MathType.Bool
|
||||||
if sp_obj.is_integer:
|
if sp_obj.is_integer:
|
||||||
|
@ -172,7 +197,7 @@ class NumberSize1D(enum.StrEnum):
|
||||||
None: NS.Scalar,
|
None: NS.Scalar,
|
||||||
(2,): NS.Vec2,
|
(2,): NS.Vec2,
|
||||||
(3,): NS.Vec3,
|
(3,): NS.Vec3,
|
||||||
(4,): NS.Vec3,
|
(4,): NS.Vec4,
|
||||||
}[shape]
|
}[shape]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -182,7 +207,7 @@ class NumberSize1D(enum.StrEnum):
|
||||||
NS.Scalar: None,
|
NS.Scalar: None,
|
||||||
NS.Vec2: (2,),
|
NS.Vec2: (2,),
|
||||||
NS.Vec3: (3,),
|
NS.Vec3: (3,),
|
||||||
NS.Vec3: (4,),
|
NS.Vec4: (4,),
|
||||||
}[self]
|
}[self]
|
||||||
|
|
||||||
|
|
||||||
|
@ -702,7 +727,6 @@ def scale_to_unit(sp_obj: SympyType, unit: spu.Quantity) -> Number:
|
||||||
Raises:
|
Raises:
|
||||||
ValueError: If the result of unit-conversion and -stripping still has units, as determined by `uses_units()`.
|
ValueError: If the result of unit-conversion and -stripping still has units, as determined by `uses_units()`.
|
||||||
"""
|
"""
|
||||||
## TODO: An LFU cache could do better than an LRU.
|
|
||||||
unitless_expr = spu.convert_to(sp_obj, unit) / unit
|
unitless_expr = spu.convert_to(sp_obj, unit) / unit
|
||||||
if not uses_units(unitless_expr):
|
if not uses_units(unitless_expr):
|
||||||
return unitless_expr
|
return unitless_expr
|
||||||
|
@ -739,7 +763,7 @@ def unit_str_to_unit(unit_str: str) -> Unit | None:
|
||||||
if unit_str in _UNIT_STR_MAP:
|
if unit_str in _UNIT_STR_MAP:
|
||||||
return _UNIT_STR_MAP[unit_str]
|
return _UNIT_STR_MAP[unit_str]
|
||||||
|
|
||||||
msg = 'No valid unit for unit string {unit_str}'
|
msg = f'No valid unit for unit string {unit_str}'
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
|
|
||||||
|
|
||||||
|
@ -802,7 +826,7 @@ class PhysicalType(enum.StrEnum):
|
||||||
# Global
|
# Global
|
||||||
PT.Time: Dims.time,
|
PT.Time: Dims.time,
|
||||||
PT.Angle: Dims.angle,
|
PT.Angle: Dims.angle,
|
||||||
PT.SolidAngle: Dims.steradian, ## MISSING
|
PT.SolidAngle: spu.steradian.dimension, ## MISSING
|
||||||
PT.Freq: Dims.frequency,
|
PT.Freq: Dims.frequency,
|
||||||
PT.AngFreq: Dims.angle * Dims.frequency,
|
PT.AngFreq: Dims.angle * Dims.frequency,
|
||||||
# Cartesian
|
# Cartesian
|
||||||
|
@ -836,7 +860,7 @@ class PhysicalType(enum.StrEnum):
|
||||||
PT.HField: Dims.current / Dims.length,
|
PT.HField: Dims.current / Dims.length,
|
||||||
# Luminal
|
# Luminal
|
||||||
PT.LumIntensity: Dims.luminous_intensity,
|
PT.LumIntensity: Dims.luminous_intensity,
|
||||||
PT.LumFlux: Dims.luminous_intensity * Dims.steradian,
|
PT.LumFlux: Dims.luminous_intensity * spu.steradian.dimension,
|
||||||
PT.Illuminance: Dims.luminous_intensity / Dims.length**2,
|
PT.Illuminance: Dims.luminous_intensity / Dims.length**2,
|
||||||
# Optics
|
# Optics
|
||||||
PT.OrdinaryWaveVector: Dims.frequency,
|
PT.OrdinaryWaveVector: Dims.frequency,
|
||||||
|
@ -1263,12 +1287,23 @@ def convert_to_unit_system(sp_obj: SympyExpr, unit_system: UnitSystem) -> SympyE
|
||||||
return spu.convert_to(sp_obj, _flat_unit_system_units(unit_system))
|
return spu.convert_to(sp_obj, _flat_unit_system_units(unit_system))
|
||||||
|
|
||||||
|
|
||||||
|
def strip_unit_system(sp_obj: SympyExpr, unit_system: UnitSystem) -> SympyExpr:
|
||||||
|
"""Strip units occurring in the given unit system from the expression.
|
||||||
|
|
||||||
|
Unit stripping is a "dumb" operation: "Substitute any `sympy` object in `unit_system.values()` with `1`".
|
||||||
|
Obviously, the semantic correctness of this operation depends entirely on _the units adding no semantic meaning to the expression_.
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
You should probably use `scale_to_unit_system()` or `convert_to_unit_system()`.
|
||||||
|
"""
|
||||||
|
return sp_obj.subs({unit: 1 for unit in unit_system.values()})
|
||||||
|
|
||||||
|
|
||||||
def scale_to_unit_system(
|
def scale_to_unit_system(
|
||||||
sp_obj: SympyExpr, unit_system: UnitSystem, use_jax_array: bool = False
|
sp_obj: SympyExpr, unit_system: UnitSystem, use_jax_array: bool = False
|
||||||
) -> int | float | complex | tuple | jax.Array:
|
) -> int | float | complex | tuple | jax.Array:
|
||||||
"""Convert an expression to the units of a given unit system, then strip all units of the unit system.
|
"""Convert an expression to the units of a given unit system, then strip all units of the unit system.
|
||||||
|
|
||||||
Unit stripping is "dumb": Substitute any `sympy` object in `unit_system.values()` with `1`.
|
|
||||||
Afterwards, it is converted to an appropriate Python type.
|
Afterwards, it is converted to an appropriate Python type.
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
|
@ -1287,8 +1322,6 @@ def scale_to_unit_system(
|
||||||
If the returned type is array-like, and `use_jax_array` is specified, then (and **only** then) will a `jax.Array` be returned instead of a nested `tuple`.
|
If the returned type is array-like, and `use_jax_array` is specified, then (and **only** then) will a `jax.Array` be returned instead of a nested `tuple`.
|
||||||
"""
|
"""
|
||||||
return sympy_to_python(
|
return sympy_to_python(
|
||||||
convert_to_unit_system(sp_obj, unit_system).subs(
|
strip_unit_system(convert_to_unit_system(sp_obj, unit_system), unit_system),
|
||||||
{unit: 1 for unit in unit_system.values()}
|
|
||||||
),
|
|
||||||
use_jax_array=use_jax_array,
|
use_jax_array=use_jax_array,
|
||||||
)
|
)
|
||||||
|
|
|
@ -81,6 +81,7 @@ _NaivelyEncodableTypeSet = frozenset(typ.get_args(NaivelyEncodableType))
|
||||||
class TypeID(enum.StrEnum):
|
class TypeID(enum.StrEnum):
|
||||||
Complex: str = '!type=complex'
|
Complex: str = '!type=complex'
|
||||||
SympyType: str = '!type=sympytype'
|
SympyType: str = '!type=sympytype'
|
||||||
|
SympyExpr: str = '!type=sympyexpr'
|
||||||
SocketDef: str = '!type=socketdef'
|
SocketDef: str = '!type=socketdef'
|
||||||
ManagedObj: str = '!type=managedobj'
|
ManagedObj: str = '!type=managedobj'
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue