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
|
||||
- [ ] Wave Constant
|
||||
- [x] Wave Constant
|
||||
- Bounds
|
||||
- [ ] Boundary Conds
|
||||
- [ ] PML
|
||||
|
@ -18,8 +18,8 @@
|
|||
- [ ] Data File Import
|
||||
- [ ] DataFit Medium
|
||||
- Monitors
|
||||
- [ ] EH Field
|
||||
- [ ] Power Flux
|
||||
- [x] EH Field
|
||||
- [x] Power Flux
|
||||
- [ ] Permittivity
|
||||
- [ ] Diffraction
|
||||
- Structures
|
||||
|
@ -49,9 +49,9 @@
|
|||
- Integration
|
||||
- [ ] Simulation and Analysis of Maxim's Cavity
|
||||
- Constants
|
||||
- [ ] Number Constant
|
||||
- [ ] Vector Constant
|
||||
- [ ] Physical Constant
|
||||
- [x] Number Constant
|
||||
- [x] Vector Constant
|
||||
- [x] Physical Constant
|
||||
|
||||
- [ ] 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.
|
||||
|
||||
- [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.
|
||||
- [ ] Implement support for additional symbols via `Expr`.
|
||||
- [x] Math / Filter Math
|
||||
|
@ -81,8 +81,6 @@
|
|||
|
||||
## Inputs
|
||||
- [x] Wave Constant
|
||||
- [ ] Fix the LazyValueRange (again!)
|
||||
- [ ] Document
|
||||
- [x] Scene
|
||||
- [ ] 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.
|
||||
|
@ -90,14 +88,14 @@
|
|||
- [x] Constants / Expr Constant
|
||||
- See IDEAS.
|
||||
- [x] Constants / Number Constant
|
||||
- [ ] Fix non-integer sockets
|
||||
- [ ] Constants / Vector Constant
|
||||
- [ ] Constants / Physical Constant
|
||||
- [x] Constants / Vector Constant
|
||||
- [x] Constants / Physical Constant
|
||||
- [x] Constants / Scientific Constant
|
||||
- [ ] 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.
|
||||
- [x] Constants / Blender Constant
|
||||
- [ ] Constants / Blender Constant
|
||||
- [ ] Fix it!
|
||||
|
||||
- [ ] Web / Tidy3D Web Importer
|
||||
|
|
|
@ -168,13 +168,13 @@ class GeoNodes(enum.StrEnum):
|
|||
GN.StructurePrimitiveCapsule: GN_INTERNAL_STRUCTURES_PATH,
|
||||
GN.StructurePrimitiveCone: GN_INTERNAL_STRUCTURES_PATH,
|
||||
## Monitor
|
||||
GN.MonitorEHField: GN_INTERNAL_STRUCTURES_PATH,
|
||||
GN.MonitorPowerFlux: GN_INTERNAL_STRUCTURES_PATH,
|
||||
GN.MonitorEpsTensor: GN_INTERNAL_STRUCTURES_PATH,
|
||||
GN.MonitorDiffraction: GN_INTERNAL_STRUCTURES_PATH,
|
||||
GN.MonitorProjCartEHField: GN_INTERNAL_STRUCTURES_PATH,
|
||||
GN.MonitorProjAngEHField: GN_INTERNAL_STRUCTURES_PATH,
|
||||
GN.MonitorProjKSpaceEHField: GN_INTERNAL_STRUCTURES_PATH,
|
||||
GN.MonitorEHField: GN_INTERNAL_MONITORS_PATH,
|
||||
GN.MonitorPowerFlux: GN_INTERNAL_MONITORS_PATH,
|
||||
GN.MonitorEpsTensor: GN_INTERNAL_MONITORS_PATH,
|
||||
GN.MonitorDiffraction: GN_INTERNAL_MONITORS_PATH,
|
||||
GN.MonitorProjCartEHField: GN_INTERNAL_MONITORS_PATH,
|
||||
GN.MonitorProjAngEHField: GN_INTERNAL_MONITORS_PATH,
|
||||
GN.MonitorProjKSpaceEHField: GN_INTERNAL_MONITORS_PATH,
|
||||
## Simulation
|
||||
GN.SimulationSimDomain: 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:
|
||||
return bpy.data.node_groups[geonodes]
|
||||
|
||||
filename = geonodes
|
||||
filename = str(geonodes)
|
||||
filepath = str(geonodes.parent_path / (geonodes + '.blend') / 'NodeTree' / geonodes)
|
||||
directory = filepath.removesuffix(geonodes)
|
||||
log.info(
|
||||
|
|
|
@ -25,58 +25,60 @@ class BLSocketInfo:
|
|||
bl_isocket_identifier: spux.ScalarUnitlessRealExpr
|
||||
|
||||
|
||||
@blender_type_enum.prefix_values_with('NodeSocket')
|
||||
class BLSocketType(enum.StrEnum):
|
||||
Virtual = 'Virtual'
|
||||
Virtual = 'NodeSocketVirtual'
|
||||
# Blender
|
||||
Image = 'Image'
|
||||
Shader = 'Shader'
|
||||
Material = 'Material'
|
||||
Geometry = 'Material'
|
||||
Object = 'Object'
|
||||
Collection = 'Collection'
|
||||
Image = 'NodeSocketImage'
|
||||
Shader = 'NodeSocketShader'
|
||||
Material = 'NodeSocketMaterial'
|
||||
Geometry = 'NodeSocketGeometry'
|
||||
Object = 'NodeSocketObject'
|
||||
Collection = 'NodeSocketCollection'
|
||||
# Basic
|
||||
Bool = 'Bool'
|
||||
String = 'String'
|
||||
Menu = 'Menu'
|
||||
Bool = 'NodeSocketBool'
|
||||
String = 'NodeSocketString'
|
||||
Menu = 'NodeSocketMenu'
|
||||
# Float
|
||||
Float = 'Float'
|
||||
FloatUnsigned = 'FloatUnsigned'
|
||||
FloatAngle = 'FloatAngle'
|
||||
FloatDistance = 'FloatDistance'
|
||||
FloatFactor = 'FloatFactor'
|
||||
FloatPercentage = 'FloatPercentage'
|
||||
FloatTime = 'FloatTime'
|
||||
FloatTimeAbsolute = 'FloatTimeAbsolute'
|
||||
Float = 'NodeSocketFloat'
|
||||
FloatUnsigned = 'NodeSocketFloatUnsigned'
|
||||
FloatAngle = 'NodeSocketFloatAngle'
|
||||
FloatDistance = 'NodeSocketFloatDistance'
|
||||
FloatFactor = 'NodeSocketFloatFactor'
|
||||
FloatPercentage = 'NodeSocketFloatPercentage'
|
||||
FloatTime = 'NodeSocketFloatTime'
|
||||
FloatTimeAbsolute = 'NodeSocketFloatTimeAbsolute'
|
||||
# Int
|
||||
Int = 'Int'
|
||||
IntFactor = 'IntFactor'
|
||||
IntPercentage = 'IntPercentage'
|
||||
IntUnsigned = 'IntUnsigned'
|
||||
Int = 'NodeSocketInt'
|
||||
IntFactor = 'NodeSocketIntFactor'
|
||||
IntPercentage = 'NodeSocketIntPercentage'
|
||||
IntUnsigned = 'NodeSocketIntUnsigned'
|
||||
# Vector
|
||||
Color = 'Color'
|
||||
Rotation = 'Rotation'
|
||||
Vector = 'Vector'
|
||||
VectorAcceleration = 'Acceleration'
|
||||
VectorDirection = 'Direction'
|
||||
VectorEuler = 'Euler'
|
||||
VectorTranslation = 'Translation'
|
||||
VectorVelocity = 'Velocity'
|
||||
VectorXYZ = 'XYZ'
|
||||
Color = 'NodeSocketColor'
|
||||
Rotation = 'NodeSocketRotation'
|
||||
Vector = 'NodeSocketVector'
|
||||
VectorAcceleration = 'NodeSocketAcceleration'
|
||||
VectorDirection = 'NodeSocketDirection'
|
||||
VectorEuler = 'NodeSocketEuler'
|
||||
VectorTranslation = 'NodeSocketTranslation'
|
||||
VectorVelocity = 'NodeSocketVelocity'
|
||||
VectorXYZ = 'NodeSocketXYZ'
|
||||
|
||||
@staticmethod
|
||||
def from_bl_isocket(
|
||||
bl_isocket: bpy.types.NodeTreeInterfaceSocket,
|
||||
) -> typ.Self:
|
||||
return BLSocketType[bl_isocket.bl_socket_idname]
|
||||
return BLSocketType(bl_isocket.bl_socket_idname)
|
||||
|
||||
@staticmethod
|
||||
def info_from_bl_isocket(
|
||||
bl_isocket: bpy.types.NodeTreeInterfaceSocket,
|
||||
) -> typ.Self:
|
||||
return BLSocketType.from_bl_isocket(bl_isocket).parse(
|
||||
bl_isocket.default_value, bl_isocket.description, bl_isocket.identifier
|
||||
)
|
||||
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
|
||||
)
|
||||
return bl_socket_type.parse(None, bl_isocket.description, bl_isocket.identifier)
|
||||
|
||||
####################
|
||||
# - Direct Properties
|
||||
|
@ -288,7 +290,7 @@ class BLSocketType(enum.StrEnum):
|
|||
)
|
||||
|
||||
# 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:
|
||||
default_value = self.mathtype.pytype(bl_default_value)
|
||||
elif description.startswith('2D'):
|
||||
|
|
|
@ -57,19 +57,22 @@ class FlowKind(enum.StrEnum):
|
|||
Info = enum.auto()
|
||||
|
||||
@classmethod
|
||||
def scale_to_unit_system(cls, kind: typ.Self, value, socket_type, unit_system):
|
||||
def scale_to_unit_system(
|
||||
cls,
|
||||
kind: typ.Self,
|
||||
value,
|
||||
unit_system: spux.UnitSystem,
|
||||
):
|
||||
if kind == cls.Value:
|
||||
return spux.sympy_to_python(
|
||||
spux.scale_to_unit(
|
||||
value,
|
||||
unit_system[socket_type],
|
||||
)
|
||||
return spux.scale_to_unit_system(
|
||||
value,
|
||||
unit_system,
|
||||
)
|
||||
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:
|
||||
return value.rescale_to_unit(unit_system[socket_type])
|
||||
return value.rescale_to_unit_system(unit_system)
|
||||
|
||||
msg = 'Tried to scale unknown kind'
|
||||
raise ValueError(msg)
|
||||
|
@ -187,6 +190,9 @@ class ArrayFlow:
|
|||
msg = f'Tried to rescale unitless LazyDataValueRange to unit {unit}'
|
||||
raise ValueError(msg)
|
||||
|
||||
def rescale_to_unit_system(self, unit: spu.Quantity) -> typ.Self:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
####################
|
||||
# - Lazy Value Func
|
||||
|
@ -469,14 +475,13 @@ class LazyArrayRangeFlow:
|
|||
|
||||
# Get Stop Mathtype
|
||||
if isinstance(self.stop, spux.SympyType):
|
||||
stop_mathtype = spux.MathType.from_expr(type(self.stop))
|
||||
stop_mathtype = spux.MathType.from_expr(self.stop)
|
||||
else:
|
||||
stop_mathtype = spux.MathType.from_pytype(type(self.stop))
|
||||
stop_mathtype = spux.MathType.from_pytype(self.stop)
|
||||
|
||||
# Check Equal
|
||||
if start_mathtype != stop_mathtype:
|
||||
msg = "Mathtypes of start and stop don't agree. Please fix!"
|
||||
raise ValueError(msg)
|
||||
return spux.MathType.combine(start_mathtype, stop_mathtype)
|
||||
|
||||
return start_mathtype
|
||||
|
||||
|
@ -525,8 +530,8 @@ class LazyArrayRangeFlow:
|
|||
"""
|
||||
if self.unit is not None:
|
||||
return LazyArrayRangeFlow(
|
||||
start=spu.scale_to_unit(self.start * self.unit, unit),
|
||||
stop=spu.scale_to_unit(self.stop * self.unit, unit),
|
||||
start=spux.scale_to_unit(self.start * self.unit, unit),
|
||||
stop=spux.scale_to_unit(self.stop * self.unit, unit),
|
||||
steps=self.steps,
|
||||
scaling=self.scaling,
|
||||
unit=unit,
|
||||
|
@ -536,6 +541,39 @@ class LazyArrayRangeFlow:
|
|||
msg = f'Tried to rescale unitless LazyDataValueRange to unit {unit}'
|
||||
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
|
||||
####################
|
||||
|
|
|
@ -16,6 +16,7 @@ class FlowSignal(enum.StrEnum):
|
|||
|
||||
"""
|
||||
|
||||
FlowInitializing = enum.auto()
|
||||
FlowPending = enum.auto()
|
||||
NoFlow = enum.auto()
|
||||
|
||||
|
|
|
@ -110,26 +110,23 @@ class ManagedBLMesh(base.ManagedObj):
|
|||
|
||||
If it's already included, do nothing.
|
||||
"""
|
||||
if (bl_object := bpy.data.objects.get(self.name)) is not None:
|
||||
if bl_object.name not in preview_collection().objects:
|
||||
log.info('Moving "%s" to Preview Collection', bl_object.name)
|
||||
preview_collection().objects.link(bl_object)
|
||||
else:
|
||||
msg = 'Managed BLMesh does not exist'
|
||||
raise ValueError(msg)
|
||||
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:
|
||||
log.info('Moving "%s" to Preview Collection', bl_object.name)
|
||||
preview_collection().objects.link(bl_object)
|
||||
|
||||
def hide_preview(self) -> None:
|
||||
"""Removes the managed Blender object from the preview collection.
|
||||
|
||||
If it's already removed, do nothing.
|
||||
"""
|
||||
if (bl_object := bpy.data.objects.get(self.name)) is not None:
|
||||
if bl_object.name in preview_collection().objects:
|
||||
log.info('Removing "%s" from Preview Collection', bl_object.name)
|
||||
preview_collection().objects.unlink(bl_object)
|
||||
else:
|
||||
msg = 'Managed BLMesh does not exist'
|
||||
raise ValueError(msg)
|
||||
bl_object = bpy.data.objects.get(self.name)
|
||||
if bl_object is not None and bl_object.name in preview_collection().objects:
|
||||
log.info('Removing "%s" from Preview Collection', bl_object.name)
|
||||
preview_collection().objects.unlink(bl_object)
|
||||
|
||||
def bl_select(self) -> None:
|
||||
"""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."""
|
||||
log.info('Unlocking All Nodes in NodeTree "%s"', self.bl_label)
|
||||
for node in self.nodes:
|
||||
if node.type in ['REROUTE', 'FRAME']:
|
||||
continue
|
||||
node.locked = False
|
||||
for bl_socket in [*node.inputs, *node.outputs]:
|
||||
bl_socket.locked = False
|
||||
|
@ -229,7 +231,9 @@ class MaxwellSimTree(bpy.types.NodeTree):
|
|||
@contextlib.contextmanager
|
||||
def repreview_all(self) -> None:
|
||||
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.newly_previewed_nodes = {}
|
||||
|
|
|
@ -1,40 +1,37 @@
|
|||
# from . import kitchen_sink
|
||||
# from . import bounds
|
||||
from . import (
|
||||
analysis,
|
||||
# bounds,
|
||||
inputs,
|
||||
mediums,
|
||||
# mediums,
|
||||
monitors,
|
||||
outputs,
|
||||
simulations,
|
||||
sources,
|
||||
structures,
|
||||
utilities,
|
||||
# simulations,
|
||||
# sources,
|
||||
# structures,
|
||||
# utilities,
|
||||
)
|
||||
|
||||
BL_REGISTER = [
|
||||
# *kitchen_sink.BL_REGISTER,
|
||||
*analysis.BL_REGISTER,
|
||||
*inputs.BL_REGISTER,
|
||||
*outputs.BL_REGISTER,
|
||||
*sources.BL_REGISTER,
|
||||
*mediums.BL_REGISTER,
|
||||
*structures.BL_REGISTER,
|
||||
# *sources.BL_REGISTER,
|
||||
# *mediums.BL_REGISTER,
|
||||
# *structures.BL_REGISTER,
|
||||
# *bounds.BL_REGISTER,
|
||||
*monitors.BL_REGISTER,
|
||||
*simulations.BL_REGISTER,
|
||||
*utilities.BL_REGISTER,
|
||||
# *simulations.BL_REGISTER,
|
||||
# *utilities.BL_REGISTER,
|
||||
]
|
||||
BL_NODES = {
|
||||
# **kitchen_sink.BL_NODES,
|
||||
**analysis.BL_NODES,
|
||||
**inputs.BL_NODES,
|
||||
**outputs.BL_NODES,
|
||||
**sources.BL_NODES,
|
||||
**mediums.BL_NODES,
|
||||
**structures.BL_NODES,
|
||||
# **sources.BL_NODES,
|
||||
# **mediums.BL_NODES,
|
||||
# **structures.BL_NODES,
|
||||
# **bounds.BL_NODES,
|
||||
**monitors.BL_NODES,
|
||||
**simulations.BL_NODES,
|
||||
**utilities.BL_NODES,
|
||||
# **simulations.BL_NODES,
|
||||
# **utilities.BL_NODES,
|
||||
}
|
||||
|
|
|
@ -401,8 +401,8 @@ class MapMathNode(base.MaxwellSimNode):
|
|||
run_on_init=True,
|
||||
)
|
||||
def on_input_changed(self):
|
||||
if self.operation not in MapOperation.by_element_shape(self.expr_output_shape):
|
||||
self.operation = bl_cache.Signal.ResetEnumItems
|
||||
# if self.operation not in MapOperation.by_element_shape(self.expr_output_shape):
|
||||
self.operation = bl_cache.Signal.ResetEnumItems
|
||||
|
||||
@events.on_value_changed(
|
||||
# Trigger
|
||||
|
|
|
@ -20,6 +20,10 @@ FUNCS = {
|
|||
'MUL': lambda exprs: exprs[0] * exprs[1],
|
||||
'DIV': 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
|
||||
|
@ -52,8 +56,8 @@ class OperateMathNode(base.MaxwellSimNode):
|
|||
bl_label = 'Operate Math'
|
||||
|
||||
input_sockets: typ.ClassVar = {
|
||||
'Expr L': sockets.ExprSocketDef(show_info_columns=False),
|
||||
'Expr R': sockets.ExprSocketDef(show_info_columns=False),
|
||||
'Expr L': sockets.ExprSocketDef(),
|
||||
'Expr R': sockets.ExprSocketDef(),
|
||||
}
|
||||
output_sockets: typ.ClassVar = {
|
||||
'Expr': sockets.ExprSocketDef(),
|
||||
|
@ -73,10 +77,12 @@ class OperateMathNode(base.MaxwellSimNode):
|
|||
def search_categories(self) -> list[ct.BLEnumElement]:
|
||||
"""Deduce and return a list of valid categories for the current socket set and input data."""
|
||||
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', kind=ct.FlowKind.Info, optional=True
|
||||
'Expr R',
|
||||
kind=ct.FlowKind.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:
|
||||
categories = [NUMBER_NUMBER]
|
||||
|
||||
## * | Number
|
||||
elif expr_r_info.output_shape is None:
|
||||
categories = []
|
||||
|
||||
## Number | Vector
|
||||
elif (
|
||||
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'),
|
||||
('ATAN2', 'atan2(L,R)', 'atan2(L,R)'),
|
||||
]
|
||||
if self.category in 'Vector | Vector':
|
||||
if self.category == 'Vector | Vector':
|
||||
if items:
|
||||
items += [None]
|
||||
items += [
|
||||
('VEC_VEC_DOT', 'L · R', 'Vector-Vector Product'),
|
||||
('CROSS', 'L x R', 'Cross Product'),
|
||||
('PROJ', 'proj(L, R)', 'Projection'),
|
||||
]
|
||||
if self.category == 'Matrix | Vector':
|
||||
if items:
|
||||
|
@ -364,9 +373,7 @@ class OperateMathNode(base.MaxwellSimNode):
|
|||
'Expr R': ct.FlowKind.Params,
|
||||
},
|
||||
)
|
||||
def compute_params(
|
||||
self, props, input_sockets
|
||||
) -> ct.ParamsFlow | ct.FlowSignal:
|
||||
def compute_params(self, props, input_sockets) -> ct.ParamsFlow | ct.FlowSignal:
|
||||
operation = props['operation']
|
||||
params_l = input_sockets['Expr L']
|
||||
params_r = input_sockets['Expr R']
|
||||
|
|
|
@ -2,8 +2,11 @@ import enum
|
|||
import typing as typ
|
||||
|
||||
import bpy
|
||||
import jax
|
||||
import jax.numpy as jnp
|
||||
import jaxtyping as jtyp
|
||||
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 extra_sympy_units as spux
|
||||
|
@ -192,7 +195,10 @@ class VizNode(base.MaxwellSimNode):
|
|||
# - Sockets
|
||||
####################
|
||||
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 = {
|
||||
'Preview': sockets.AnySocketDef(),
|
||||
|
@ -221,8 +227,12 @@ class VizNode(base.MaxwellSimNode):
|
|||
## - Mode Searcher
|
||||
#####################
|
||||
@property
|
||||
def data_info(self) -> ct.InfoFlow:
|
||||
return self._compute_input('Expr', kind=ct.FlowKind.Info)
|
||||
def data_info(self) -> ct.InfoFlow | None:
|
||||
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]:
|
||||
if not ct.FlowSignal.check(self.data_info):
|
||||
|
@ -298,7 +308,9 @@ class VizNode(base.MaxwellSimNode):
|
|||
managed_objs={'plot'},
|
||||
props={'viz_mode', 'viz_target', 'colormap'},
|
||||
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,
|
||||
)
|
||||
def on_show_plot(
|
||||
|
|
|
@ -599,22 +599,28 @@ class MaxwellSimNode(bpy.types.Node):
|
|||
It must be currently active.
|
||||
kind: The data flow kind to compute.
|
||||
"""
|
||||
if (bl_socket := self.inputs.get(input_socket_name)) is not None:
|
||||
return (
|
||||
ct.FlowKind.scale_to_unit_system(
|
||||
kind,
|
||||
bl_socket.compute_data(kind=kind),
|
||||
bl_socket.socket_type,
|
||||
unit_system,
|
||||
bl_socket = self.inputs.get(input_socket_name)
|
||||
if bl_socket is not None:
|
||||
if bl_socket.instance_id:
|
||||
return (
|
||||
ct.FlowKind.scale_to_unit_system(
|
||||
kind,
|
||||
bl_socket.compute_data(kind=kind),
|
||||
unit_system,
|
||||
)
|
||||
if unit_system is not None
|
||||
else bl_socket.compute_data(kind=kind)
|
||||
)
|
||||
if unit_system is not None
|
||||
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:
|
||||
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)
|
||||
|
||||
####################
|
||||
|
|
|
@ -3,6 +3,7 @@ import inspect
|
|||
import typing as typ
|
||||
from types import MappingProxyType
|
||||
|
||||
from blender_maxwell.utils import extra_sympy_units as spux
|
||||
from blender_maxwell.utils import logger
|
||||
|
||||
from .. import contracts as ct
|
||||
|
@ -10,7 +11,6 @@ from .. import contracts as ct
|
|||
log = logger.get(__name__)
|
||||
|
||||
UnitSystemID = str
|
||||
UnitSystem = dict[ct.SocketType, typ.Any]
|
||||
|
||||
|
||||
####################
|
||||
|
@ -70,7 +70,7 @@ def event_decorator(
|
|||
all_loose_input_sockets: bool = False,
|
||||
all_loose_output_sockets: bool = False,
|
||||
# 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_output_sockets: dict[ct.SocketName, UnitSystemID] = MappingProxyType({}),
|
||||
):
|
||||
|
@ -213,7 +213,6 @@ def event_decorator(
|
|||
kind=kind,
|
||||
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)),
|
||||
)
|
||||
|
||||
|
@ -269,9 +268,21 @@ def event_decorator(
|
|||
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
|
||||
## If there is a FlowPending, then the method would fail.
|
||||
## Therefore, propagate FlowPending if found.
|
||||
return method(
|
||||
node,
|
||||
**method_kw_args,
|
||||
|
|
|
@ -1,18 +1,22 @@
|
|||
# from . import scientific_constant
|
||||
# from . import physical_constant
|
||||
from . import blender_constant, expr_constant, number_constant, scientific_constant
|
||||
from . import (
|
||||
blender_constant,
|
||||
expr_constant,
|
||||
number_constant,
|
||||
physical_constant,
|
||||
scientific_constant,
|
||||
)
|
||||
|
||||
BL_REGISTER = [
|
||||
*expr_constant.BL_REGISTER,
|
||||
*scientific_constant.BL_REGISTER,
|
||||
*number_constant.BL_REGISTER,
|
||||
# *physical_constant.BL_REGISTER,
|
||||
*physical_constant.BL_REGISTER,
|
||||
*blender_constant.BL_REGISTER,
|
||||
]
|
||||
BL_NODES = {
|
||||
**expr_constant.BL_NODES,
|
||||
**scientific_constant.BL_NODES,
|
||||
**number_constant.BL_NODES,
|
||||
# **physical_constant.BL_NODES,
|
||||
**physical_constant.BL_NODES,
|
||||
**blender_constant.BL_NODES,
|
||||
}
|
||||
|
|
|
@ -44,7 +44,7 @@ class NumberConstantNode(base.MaxwellSimNode):
|
|||
####################
|
||||
# - 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.prop(self, self.blfields['mathtype'], text='')
|
||||
row.prop(self, self.blfields['size'], text='')
|
||||
|
@ -56,7 +56,7 @@ class NumberConstantNode(base.MaxwellSimNode):
|
|||
def on_mathtype_size_changed(self, props) -> None:
|
||||
"""Change the input/output expression sockets to match the mathtype declared in the node."""
|
||||
self.inputs['Value'].mathtype = props['mathtype']
|
||||
self.inputs['Value'].shape = props['mathtype'].shape
|
||||
self.inputs['Value'].shape = props['size'].shape
|
||||
|
||||
####################
|
||||
# - FlowKind
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import enum
|
||||
import typing as typ
|
||||
|
||||
import bpy
|
||||
import sympy as sp
|
||||
|
||||
from blender_maxwell.utils import bl_cache
|
||||
|
@ -10,7 +10,7 @@ from .... import contracts, sockets
|
|||
from ... import base, events
|
||||
|
||||
|
||||
class PhysicalConstantNode(base.MaxwellSimTreeNode):
|
||||
class PhysicalConstantNode(base.MaxwellSimNode):
|
||||
"""A number of configurable unit dimension, ex. time, length, etc. .
|
||||
|
||||
Attributes:
|
||||
|
@ -36,12 +36,12 @@ class PhysicalConstantNode(base.MaxwellSimTreeNode):
|
|||
prop_ui=True,
|
||||
)
|
||||
|
||||
mathtype: enum.Enum = bl_cache.BLField(
|
||||
mathtype: spux.MathType = bl_cache.BLField(
|
||||
enum_cb=lambda self, _: self.search_mathtypes(),
|
||||
prop_ui=True,
|
||||
)
|
||||
|
||||
size: enum.Enum = bl_cache.BLField(
|
||||
size: spux.NumberSize1D = bl_cache.BLField(
|
||||
enum_cb=lambda self, _: self.search_sizes(),
|
||||
prop_ui=True,
|
||||
)
|
||||
|
@ -62,16 +62,25 @@ class PhysicalConstantNode(base.MaxwellSimTreeNode):
|
|||
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.on_value_changed(
|
||||
prop_name={'physical_type', 'mathtype', 'size'},
|
||||
run_on_init=True,
|
||||
props={'physical_type', 'mathtype', 'size'},
|
||||
)
|
||||
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."""
|
||||
shape = spux.NumberSize1D(props['size']).shape
|
||||
shape = props['size'].shape
|
||||
|
||||
# Set Input Socket Physical Type
|
||||
if self.inputs['Value'].physical_type != props['physical_type']:
|
||||
|
@ -90,9 +99,9 @@ class PhysicalConstantNode(base.MaxwellSimTreeNode):
|
|||
####################
|
||||
# - Callbacks
|
||||
####################
|
||||
@events.computes_output_socket('value')
|
||||
def compute_value(self: contracts.NodeTypeProtocol) -> sp.Expr:
|
||||
return self.compute_input('value')
|
||||
@events.computes_output_socket('Value', input_sockets={'Value'})
|
||||
def compute_value(self, input_sockets) -> sp.Expr:
|
||||
return input_sockets['Value']
|
||||
|
||||
|
||||
####################
|
||||
|
|
|
@ -2,7 +2,7 @@ import typing as typ
|
|||
|
||||
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 sockets
|
||||
|
@ -20,63 +20,43 @@ class ScientificConstantNode(base.MaxwellSimNode):
|
|||
####################
|
||||
# - Properties
|
||||
####################
|
||||
sci_constant: bpy.props.StringProperty(
|
||||
name='Sci Constant',
|
||||
description='The name of a scientific constant',
|
||||
default='',
|
||||
search=lambda self, _, edit_text: self.search_sci_constants(edit_text),
|
||||
update=lambda self, context: self.on_update_sci_constant(context),
|
||||
sci_constant: str = bl_cache.BLField(
|
||||
'',
|
||||
prop_ui=True,
|
||||
str_cb=lambda self, _, edit_text: self.search_sci_constants(edit_text),
|
||||
)
|
||||
|
||||
cache__units: bpy.props.StringProperty(default='')
|
||||
cache__uncertainty: bpy.props.StringProperty(default='')
|
||||
|
||||
def search_sci_constants(
|
||||
self,
|
||||
edit_text: str,
|
||||
):
|
||||
return [
|
||||
name
|
||||
for name in constants.SCI_CONSTANTS
|
||||
for name in sci_constants.SCI_CONSTANTS
|
||||
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
|
||||
####################
|
||||
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:
|
||||
if self.sci_constant:
|
||||
col.label(text=f'Units: {self.cache__units}')
|
||||
col.label(text=f'Uncertainty: {self.cache__uncertainty}')
|
||||
|
||||
col.label(text=f'Ref: {constants.SCI_CONSTANTS_REF[0]}')
|
||||
col.label(
|
||||
text=f'Units: {sci_constants.SCI_CONSTANTS_INFO[self.sci_constant]["units"]}'
|
||||
)
|
||||
col.label(
|
||||
text=f'Uncertainty: {sci_constants.SCI_CONSTANTS_INFO[self.sci_constant]["uncertainty"]}'
|
||||
)
|
||||
|
||||
####################
|
||||
# - Callbacks
|
||||
# - Output
|
||||
####################
|
||||
@events.computes_output_socket('Value', props={'sci_constant'})
|
||||
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 = {
|
||||
'Wavelength': {
|
||||
'WL': sockets.ExprSocketDef(
|
||||
active_kind=ct.FlowKind.Value,
|
||||
physical_type=spux.PhysicalType.Length,
|
||||
# Defaults
|
||||
default_unit=spu.nm,
|
||||
|
@ -58,18 +59,18 @@ class WaveConstantNode(base.MaxwellSimNode):
|
|||
output_sockets: typ.ClassVar = {
|
||||
'WL': sockets.ExprSocketDef(
|
||||
active_kind=ct.FlowKind.Value,
|
||||
unit_dimension=spux.Dims.length,
|
||||
physical_type=spux.PhysicalType.Length,
|
||||
),
|
||||
'Freq': sockets.ExprSocketDef(
|
||||
active_kind=ct.FlowKind.Value,
|
||||
unit_dimension=spux.Dims.frequency,
|
||||
physical_type=spux.PhysicalType.Freq,
|
||||
),
|
||||
}
|
||||
|
||||
####################
|
||||
# - Properties
|
||||
####################
|
||||
use_range: bool = bl_cache.BLField(False)
|
||||
use_range: bool = bl_cache.BLField(False, prop_ui=True)
|
||||
|
||||
####################
|
||||
# - UI
|
||||
|
@ -80,14 +81,14 @@ class WaveConstantNode(base.MaxwellSimNode):
|
|||
Parameters:
|
||||
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.on_value_changed(
|
||||
prop_name={'active_socket_set', 'use_range'},
|
||||
props='use_range',
|
||||
props={'use_range'},
|
||||
run_on_init=True,
|
||||
)
|
||||
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:
|
||||
"""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 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:
|
||||
"""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 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:
|
||||
"""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['Freq'].rescale_bounds(
|
||||
lambda bound: sci_constants.vac_speed_of_light / bound, reverse=True
|
||||
freq = input_sockets['Freq']
|
||||
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(
|
||||
|
@ -177,11 +189,20 @@ class WaveConstantNode(base.MaxwellSimNode):
|
|||
)
|
||||
def compute_freq_range(self, input_sockets: dict) -> sp.Expr:
|
||||
"""Compute frequency range from either wavelength/frequency ranges."""
|
||||
if input_sockets['Freq'] is not None:
|
||||
has_freq = not ct.FlowSignal.check(input_sockets['Freq'])
|
||||
if has_freq:
|
||||
return input_sockets['Freq']
|
||||
|
||||
return input_sockets['WL'].rescale_bounds(
|
||||
lambda bound: sci_constants.vac_speed_of_light / bound, reverse=True
|
||||
wl = input_sockets['WL']
|
||||
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
|
||||
|
||||
# Try Loading Simulation Data
|
||||
node.sim_data = bl_cache.Signal.InvalidateCache
|
||||
#node.sim_data = bl_cache.Signal.InvalidateCache
|
||||
sim_data = node.sim_data
|
||||
if sim_data is None:
|
||||
self.report(
|
||||
|
@ -70,18 +70,26 @@ class Tidy3DWebImporterNode(base.MaxwellSimNode):
|
|||
should_exist=True,
|
||||
),
|
||||
}
|
||||
output_sockets: typ.ClassVar = {
|
||||
'Sim Data': sockets.MaxwellFDTDSimDataSocketDef(),
|
||||
}
|
||||
|
||||
####################
|
||||
# - Properties
|
||||
####################
|
||||
sim_data_loaded: bool = bl_cache.BLField(False)
|
||||
|
||||
@bl_cache.cached_bl_property()
|
||||
####################
|
||||
# - Computed
|
||||
####################
|
||||
@property
|
||||
def sim_data(self) -> td.SimulationData | None:
|
||||
cloud_task = self._compute_input(
|
||||
'Cloud Task', kind=ct.FlowKind.Value, optional=True
|
||||
)
|
||||
has_cloud_task = not ct.FlowSignal.check(cloud_task)
|
||||
if (
|
||||
# Check Flow
|
||||
not ct.FlowSignal.check(cloud_task)
|
||||
# Check Task
|
||||
has_cloud_task
|
||||
and cloud_task is not None
|
||||
and isinstance(cloud_task, tdcloud.CloudTask)
|
||||
and cloud_task.status == 'success'
|
||||
|
@ -97,7 +105,7 @@ class Tidy3DWebImporterNode(base.MaxwellSimNode):
|
|||
####################
|
||||
# - UI
|
||||
####################
|
||||
def draw_operators(self, context, layout):
|
||||
def draw_operators(self, _: bpy.types.Context, layout: bpy.types.UILayout):
|
||||
if self.sim_data_loaded:
|
||||
layout.operator(ct.OperatorType.NodeLoadCloudSim, text='Reload Sim')
|
||||
else:
|
||||
|
@ -106,11 +114,6 @@ class Tidy3DWebImporterNode(base.MaxwellSimNode):
|
|||
####################
|
||||
# - 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(
|
||||
prop_name='sim_data_loaded', run_on_init=True, props={'sim_data_loaded'}
|
||||
)
|
||||
|
|
|
@ -33,6 +33,7 @@ class EHFieldMonitorNode(base.MaxwellSimNode):
|
|||
'Size': sockets.ExprSocketDef(
|
||||
shape=(3,),
|
||||
physical_type=spux.PhysicalType.Length,
|
||||
default_value=sp.Matrix([1, 1, 1]),
|
||||
),
|
||||
'Spatial Subdivs': sockets.ExprSocketDef(
|
||||
shape=(3,),
|
||||
|
@ -124,11 +125,30 @@ class EHFieldMonitorNode(base.MaxwellSimNode):
|
|||
# - Preview
|
||||
####################
|
||||
@events.on_value_changed(
|
||||
socket_name={'Center', 'Size'},
|
||||
# Trigger
|
||||
prop_name='preview_active',
|
||||
# Loaded
|
||||
managed_objs={'mesh'},
|
||||
props={'preview_active'},
|
||||
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'},
|
||||
input_sockets={'Center', 'Size'},
|
||||
unit_systems={'BlenderUnits': ct.UNITS_BLENDER},
|
||||
scale_input_sockets={
|
||||
'Center': 'BlenderUnits',
|
||||
|
@ -136,7 +156,6 @@ class EHFieldMonitorNode(base.MaxwellSimNode):
|
|||
)
|
||||
def on_inputs_changed(
|
||||
self,
|
||||
props: dict,
|
||||
managed_objs: dict,
|
||||
input_sockets: 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(
|
||||
shape=(3,),
|
||||
physical_type=spux.PhysicalType.Length,
|
||||
default_value=sp.Matrix([1, 1, 1]),
|
||||
),
|
||||
'Samples/Space': sockets.ExprSocketDef(
|
||||
shape=(3,),
|
||||
|
@ -123,11 +124,29 @@ class PowerFluxMonitorNode(base.MaxwellSimNode):
|
|||
# - Preview - Changes to Input Sockets
|
||||
####################
|
||||
@events.on_value_changed(
|
||||
socket_name={'Center', 'Size'},
|
||||
# Trigger
|
||||
prop_name='preview_active',
|
||||
# Loaded
|
||||
managed_objs={'mesh'},
|
||||
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'},
|
||||
input_sockets={'Center', 'Size'},
|
||||
unit_systems={'BlenderUnits': ct.UNITS_BLENDER},
|
||||
scale_input_sockets={
|
||||
'Center': 'BlenderUnits',
|
||||
|
@ -135,7 +154,6 @@ class PowerFluxMonitorNode(base.MaxwellSimNode):
|
|||
)
|
||||
def on_inputs_changed(
|
||||
self,
|
||||
props: dict,
|
||||
managed_objs: dict,
|
||||
input_sockets: 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 = [
|
||||
*viewer.BL_REGISTER,
|
||||
*file_exporters.BL_REGISTER,
|
||||
*web_exporters.BL_REGISTER,
|
||||
#*file_exporters.BL_REGISTER,
|
||||
#*web_exporters.BL_REGISTER,
|
||||
]
|
||||
BL_NODES = {
|
||||
**viewer.BL_NODES,
|
||||
**file_exporters.BL_NODES,
|
||||
**web_exporters.BL_NODES,
|
||||
#**file_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.
|
||||
"""
|
||||
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 '')
|
||||
## TODO: Valid Active Kinds should be a subset/subenum(?) of FlowKind
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ import enum
|
|||
import typing as typ
|
||||
|
||||
import bpy
|
||||
import pydantic as pyd
|
||||
import sympy as sp
|
||||
|
||||
from blender_maxwell.utils import bl_cache, logger
|
||||
|
@ -63,6 +64,7 @@ class InfoDisplayCol(enum.StrEnum):
|
|||
class ExprBLSocket(base.MaxwellSimSocket):
|
||||
socket_type = ct.SocketType.Expr
|
||||
bl_label = 'Expr'
|
||||
use_info_draw = True
|
||||
|
||||
####################
|
||||
# - Properties
|
||||
|
@ -70,7 +72,7 @@ class ExprBLSocket(base.MaxwellSimSocket):
|
|||
shape: tuple[int, ...] | None = bl_cache.BLField(None)
|
||||
mathtype: spux.MathType = bl_cache.BLField(spux.MathType.Real, prop_ui=True)
|
||||
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(
|
||||
None, enum_cb=lambda self, _: self.search_units(), prop_ui=True
|
||||
|
@ -102,7 +104,7 @@ class ExprBLSocket(base.MaxwellSimSocket):
|
|||
)
|
||||
|
||||
# UI: LazyArrayRange
|
||||
steps: int = bl_cache.BLField(2, abs_min=2)
|
||||
steps: int = bl_cache.BLField(2, abs_min=2, prop_ui=True)
|
||||
## Expression
|
||||
raw_min_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
|
||||
####################
|
||||
@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
|
||||
def raw_value_sp(self) -> spux.SympyExpr:
|
||||
return self._parse_expr_str(self.raw_value_spstr)
|
||||
|
@ -140,7 +151,7 @@ class ExprBLSocket(base.MaxwellSimSocket):
|
|||
####################
|
||||
# - 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:
|
||||
return [
|
||||
(sp.sstr(unit), spux.sp_to_str(unit), sp.sstr(unit), '', i)
|
||||
|
@ -163,33 +174,38 @@ class ExprBLSocket(base.MaxwellSimSocket):
|
|||
return None
|
||||
|
||||
@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.
|
||||
|
||||
Notes:
|
||||
To set a new unit, **and** convert the `raw_*` UI properties to the new unit, use `self.convert_unit()` instead.
|
||||
"""
|
||||
if unit in self.physical_type.valid_units:
|
||||
self.active_unit = sp.sstr(unit)
|
||||
|
||||
msg = f'Tried to set invalid unit {unit} (physical type "{self.physical_type}" only supports "{self.physical_type.valid_units}")'
|
||||
raise ValueError(msg)
|
||||
if self.physical_type is not None:
|
||||
if unit in self.physical_type.valid_units:
|
||||
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}")'
|
||||
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:
|
||||
if self.active_kind == ct.FlowKind.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
|
||||
self.unit = unit_to
|
||||
self.lazy_array_range = current_lazy_array_range
|
||||
current_value = self.value
|
||||
current_lazy_array_range = self.lazy_array_range
|
||||
|
||||
self.unit = bl_cache.Signal.InvalidateCache
|
||||
|
||||
self.value = current_value
|
||||
self.lazy_array_range = current_lazy_array_range
|
||||
|
||||
####################
|
||||
# - Property Callback
|
||||
####################
|
||||
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))
|
||||
|
||||
####################
|
||||
|
@ -200,23 +216,23 @@ class ExprBLSocket(base.MaxwellSimSocket):
|
|||
) -> tuple[spux.MathType, tuple[int, ...] | None, spux.UnitDimension]:
|
||||
# Parse MathType
|
||||
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}'
|
||||
raise ValueError(msg)
|
||||
|
||||
# Parse Symbols
|
||||
if expr.free_symbols:
|
||||
if self.mathtype is not None:
|
||||
msg = f'MathType is {self.mathtype}, but tried to set expr {expr} with free symbols {expr.free_symbols}'
|
||||
raise ValueError(msg)
|
||||
|
||||
if not expr.free_symbols.issubset(self.symbols):
|
||||
msg = f'Tried to set expr {expr} with free symbols {expr.free_symbols}, which is incompatible with socket symbols {self.symbols}'
|
||||
raise ValueError(msg)
|
||||
if expr.free_symbols and not expr.free_symbols.issubset(self.symbols):
|
||||
msg = f'Tried to set expr {expr} with free symbols {expr.free_symbols}, which is incompatible with socket symbols {self.symbols}'
|
||||
raise ValueError(msg)
|
||||
|
||||
# Parse Dimensions
|
||||
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})'
|
||||
raise ValueError(msg)
|
||||
|
||||
|
@ -238,7 +254,7 @@ class ExprBLSocket(base.MaxwellSimSocket):
|
|||
# Try Parsing and Returning the Expression
|
||||
try:
|
||||
self._parse_expr_info(expr)
|
||||
except ValueError(expr) as ex:
|
||||
except ValueError:
|
||||
log.exception(
|
||||
'Couldn\'t parse expression "%s" in Expr socket.',
|
||||
expr_spstr,
|
||||
|
@ -270,6 +286,7 @@ class ExprBLSocket(base.MaxwellSimSocket):
|
|||
expr = self.raw_value_sp
|
||||
if expr is None:
|
||||
return ct.FlowSignal.FlowPending
|
||||
return expr
|
||||
|
||||
MT_Z = spux.MathType.Integer
|
||||
MT_Q = spux.MathType.Rational
|
||||
|
@ -312,7 +329,7 @@ class ExprBLSocket(base.MaxwellSimSocket):
|
|||
Notes:
|
||||
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,)]:
|
||||
self.raw_value_spstr = sp.sstr(expr)
|
||||
|
||||
|
@ -321,32 +338,33 @@ class ExprBLSocket(base.MaxwellSimSocket):
|
|||
MT_Q = spux.MathType.Rational
|
||||
MT_R = spux.MathType.Real
|
||||
MT_C = spux.MathType.Complex
|
||||
if shape is None:
|
||||
if mathtype == MT_Z:
|
||||
if self.shape is None:
|
||||
if self.mathtype == MT_Z:
|
||||
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)
|
||||
elif mathtype == MT_R:
|
||||
elif self.mathtype == MT_R:
|
||||
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)
|
||||
elif shape == (2,):
|
||||
if mathtype == MT_Z:
|
||||
elif self.shape == (2,):
|
||||
if self.mathtype == MT_Z:
|
||||
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)
|
||||
elif mathtype == MT_R:
|
||||
elif self.mathtype == MT_R:
|
||||
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)
|
||||
elif shape == (3,):
|
||||
if mathtype == MT_Z:
|
||||
elif self.shape == (3,):
|
||||
log.critical(expr)
|
||||
if self.mathtype == MT_Z:
|
||||
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)
|
||||
elif mathtype == MT_R:
|
||||
elif self.mathtype == MT_R:
|
||||
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)
|
||||
|
||||
####################
|
||||
|
@ -404,7 +422,6 @@ class ExprBLSocket(base.MaxwellSimSocket):
|
|||
Called to compute the internal `FlowKind.LazyArrayRange` of this socket.
|
||||
"""
|
||||
self.steps = value.steps
|
||||
self.unit = value.unit
|
||||
|
||||
if self.symbols:
|
||||
self.raw_min_spstr = sp.sstr(value.start)
|
||||
|
@ -416,21 +433,26 @@ class ExprBLSocket(base.MaxwellSimSocket):
|
|||
MT_R = spux.MathType.Real
|
||||
MT_C = spux.MathType.Complex
|
||||
|
||||
unit = value.unit if value.unit is not None else 1
|
||||
if value.mathtype == MT_Z:
|
||||
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:
|
||||
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:
|
||||
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:
|
||||
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
|
||||
if self.symbols or self.shape not in [None, (2,), (3,)]:
|
||||
return ct.LazyValueFuncFlow(
|
||||
func=sp.lambdify(self.symbols, self.value, 'jax'),
|
||||
func_args=[spux.MathType.from_expr(sym) for sym in self.symbols],
|
||||
func=sp.lambdify(self.sorted_symbols, self.value, 'jax'),
|
||||
func_args=[spux.MathType.from_expr(sym) for sym in self.sorted_symbols],
|
||||
supports_jax=True,
|
||||
)
|
||||
|
||||
|
@ -482,8 +504,7 @@ class ExprBLSocket(base.MaxwellSimSocket):
|
|||
unit=self.unit,
|
||||
)
|
||||
|
||||
msg = "Expr socket can't produce array from expression with free symbols"
|
||||
raise ValueError(msg)
|
||||
return ct.FlowSignal.NoFlow
|
||||
|
||||
####################
|
||||
# - FlowKind: Info
|
||||
|
@ -496,6 +517,7 @@ class ExprBLSocket(base.MaxwellSimSocket):
|
|||
output_mathtype=self.mathtype,
|
||||
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
|
||||
|
@ -520,10 +542,11 @@ class ExprBLSocket(base.MaxwellSimSocket):
|
|||
_row.label(text=text)
|
||||
|
||||
_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:
|
||||
# Property Interface
|
||||
if self.symbols:
|
||||
col.prop(self, self.blfields['raw_value_spstr'], text='')
|
||||
|
||||
|
@ -575,6 +598,27 @@ class ExprBLSocket(base.MaxwellSimSocket):
|
|||
for sym in self.symbols:
|
||||
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:
|
||||
info = self.compute_data(kind=ct.FlowKind.Info)
|
||||
has_dims = not ct.FlowSignal.check(info) and info.dim_names
|
||||
|
@ -630,7 +674,7 @@ class ExprBLSocket(base.MaxwellSimSocket):
|
|||
_row.label(text=text)
|
||||
|
||||
def draw_info(self, info: ct.InfoFlow, col: bpy.types.UILayout) -> None:
|
||||
if info.dim_names and self.show_info_columns:
|
||||
if self.show_info_columns:
|
||||
row = col.row()
|
||||
box = row.box()
|
||||
grid = box.grid_flow(
|
||||
|
@ -696,19 +740,87 @@ class ExprSocketDef(base.SocketDef):
|
|||
default_unit: spux.Unit | None = None
|
||||
|
||||
# FlowKind: Value
|
||||
default_value: spux.SympyExpr = sp.S(0)
|
||||
default_value: spux.SympyExpr = sp.RealNumber(0)
|
||||
|
||||
# FlowKind: LazyArrayRange
|
||||
default_min: spux.SympyExpr = sp.S(0)
|
||||
default_max: spux.SympyExpr = sp.S(1)
|
||||
default_min: spux.SympyExpr = sp.RealNumber(0)
|
||||
default_max: spux.SympyExpr = sp.RealNumber(1)
|
||||
default_steps: int = 2
|
||||
## TODO: Configure lin/log/... scaling (w/enumprop in UI)
|
||||
|
||||
## TODO: Buncha validation :)
|
||||
|
||||
# UI
|
||||
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:
|
||||
bl_socket.active_kind = self.active_kind
|
||||
|
||||
|
@ -718,12 +830,13 @@ class ExprSocketDef(base.SocketDef):
|
|||
bl_socket.physical_type = self.physical_type
|
||||
bl_socket.symbols = self.symbols
|
||||
|
||||
# Socket Units
|
||||
if self.default_unit is not None:
|
||||
# Socket Units & FlowKind.Value
|
||||
log.critical(self)
|
||||
if self.physical_type is not None:
|
||||
bl_socket.unit = self.default_unit
|
||||
|
||||
# FlowKind: Value
|
||||
bl_socket.value = self.default_value
|
||||
bl_socket.value = self.default_value * self.default_unit
|
||||
else:
|
||||
bl_socket.value = self.default_value
|
||||
|
||||
# FlowKind: LazyArrayRange
|
||||
bl_socket.lazy_array_range = ct.LazyArrayRangeFlow(
|
||||
|
|
|
@ -96,6 +96,23 @@ class Tidy3DCloudTaskBLSocket(base.MaxwellSimSocket):
|
|||
|
||||
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
|
||||
def capabilities(self) -> ct.CapabilitiesFlow:
|
||||
return ct.CapabilitiesFlow(
|
||||
|
@ -122,7 +139,7 @@ class Tidy3DCloudTaskBLSocket(base.MaxwellSimSocket):
|
|||
return (self.new_task_name, cloud_folder)
|
||||
|
||||
# No Task Selected: Return None
|
||||
if self.existing_task_id == 'NONE':
|
||||
if self.existing_task_id is None:
|
||||
return None
|
||||
|
||||
# Retrieve Cloud Task
|
||||
|
@ -135,7 +152,7 @@ class Tidy3DCloudTaskBLSocket(base.MaxwellSimSocket):
|
|||
|
||||
return cloud_task
|
||||
|
||||
return None
|
||||
return ct.FlowSignal.FlowPending
|
||||
|
||||
####################
|
||||
# - Searchers
|
||||
|
@ -158,7 +175,7 @@ class Tidy3DCloudTaskBLSocket(base.MaxwellSimSocket):
|
|||
return []
|
||||
|
||||
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 []
|
||||
|
||||
# Get Cloud Folder
|
||||
|
@ -221,10 +238,6 @@ class Tidy3DCloudTaskBLSocket(base.MaxwellSimSocket):
|
|||
def on_prepare_new_task(self):
|
||||
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
|
||||
####################
|
||||
|
|
|
@ -13,6 +13,7 @@ def prefix_values_with(prefix: str) -> type[enum.Enum]:
|
|||
Returns:
|
||||
A new StrEnum class with altered member values.
|
||||
"""
|
||||
## TODO: DO NOT USE FOR ENUMS WITH METHODS
|
||||
|
||||
def _decorator(cls: enum.StrEnum):
|
||||
new_members = {
|
||||
|
|
|
@ -547,6 +547,9 @@ class BLField:
|
|||
self._str_cb = str_cb
|
||||
self._enum_cb = enum_cb
|
||||
|
||||
## Type Coercion
|
||||
self._coerce_output_to = None
|
||||
|
||||
## Vector/Matrix Identity
|
||||
## -> Matrix Shape assists in the workaround for Matrix Display Bug
|
||||
self._is_vector = False
|
||||
|
@ -797,7 +800,11 @@ class BLField:
|
|||
}
|
||||
|
||||
## 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
|
||||
BLProp = bpy.props.EnumProperty
|
||||
kwargs_prop |= {
|
||||
|
@ -814,9 +821,14 @@ class BLField:
|
|||
}
|
||||
if self._enum_many:
|
||||
kwargs_prop['options'].add('ENUM_FLAG')
|
||||
self._coerce_output_to = AttrType
|
||||
|
||||
## 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:
|
||||
msg = 'When using dynamic enum, default value must be None'
|
||||
raise ValueError(msg)
|
||||
|
@ -828,6 +840,8 @@ class BLField:
|
|||
}
|
||||
if self._enum_many:
|
||||
kwargs_prop['options'].add('ENUM_FLAG')
|
||||
if AttrType is not enum.Enum:
|
||||
self._coerce_output_to = AttrType
|
||||
|
||||
## BL Reference
|
||||
elif AttrType in typ.get_args(ct.BLIDStruct):
|
||||
|
@ -888,6 +902,9 @@ class BLField:
|
|||
def __get__(
|
||||
self, bl_instance: BLInstance | None, owner: type[BLInstance]
|
||||
) -> typ.Any:
|
||||
if bl_instance is None:
|
||||
return None
|
||||
|
||||
value = self._cached_bl_property.__get__(bl_instance, owner)
|
||||
|
||||
# enum.Enum: Cast Auto-Injected Dynamic Enum 'NONE' -> None
|
||||
|
@ -913,7 +930,7 @@ class BLField:
|
|||
## -> Reject modernity. Return to tuple[].
|
||||
if self._is_vector:
|
||||
## -> tuple()ify the np.array to respect tuple[] type annotation.
|
||||
return tuple(np.array(value))
|
||||
return tuple(value)
|
||||
|
||||
if self._is_matrix:
|
||||
# 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))
|
||||
)
|
||||
|
||||
# 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
|
||||
|
||||
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 . import logger
|
||||
|
||||
log = logger.get(__name__)
|
||||
|
||||
SympyType = (
|
||||
sp.Basic
|
||||
| sp.Expr
|
||||
|
@ -47,21 +51,42 @@ class MathType(enum.StrEnum):
|
|||
Real = enum.auto()
|
||||
Complex = enum.auto()
|
||||
|
||||
@staticmethod
|
||||
def combine(*mathtypes: list[typ.Self]) -> typ.Self:
|
||||
if MathType.Complex in mathtypes:
|
||||
return MathType.Complex
|
||||
elif MathType.Real in mathtypes:
|
||||
if MathType.Real in mathtypes:
|
||||
return MathType.Real
|
||||
elif MathType.Rational in mathtypes:
|
||||
if MathType.Rational in mathtypes:
|
||||
return MathType.Rational
|
||||
elif MathType.Integer in mathtypes:
|
||||
if MathType.Integer in mathtypes:
|
||||
return MathType.Integer
|
||||
elif MathType.Bool in mathtypes:
|
||||
if MathType.Bool in mathtypes:
|
||||
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
|
||||
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):
|
||||
return MathType.Bool
|
||||
if sp_obj.is_integer:
|
||||
|
@ -172,7 +197,7 @@ class NumberSize1D(enum.StrEnum):
|
|||
None: NS.Scalar,
|
||||
(2,): NS.Vec2,
|
||||
(3,): NS.Vec3,
|
||||
(4,): NS.Vec3,
|
||||
(4,): NS.Vec4,
|
||||
}[shape]
|
||||
|
||||
@property
|
||||
|
@ -182,7 +207,7 @@ class NumberSize1D(enum.StrEnum):
|
|||
NS.Scalar: None,
|
||||
NS.Vec2: (2,),
|
||||
NS.Vec3: (3,),
|
||||
NS.Vec3: (4,),
|
||||
NS.Vec4: (4,),
|
||||
}[self]
|
||||
|
||||
|
||||
|
@ -702,7 +727,6 @@ def scale_to_unit(sp_obj: SympyType, unit: spu.Quantity) -> Number:
|
|||
Raises:
|
||||
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
|
||||
if not uses_units(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:
|
||||
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)
|
||||
|
||||
|
||||
|
@ -802,7 +826,7 @@ class PhysicalType(enum.StrEnum):
|
|||
# Global
|
||||
PT.Time: Dims.time,
|
||||
PT.Angle: Dims.angle,
|
||||
PT.SolidAngle: Dims.steradian, ## MISSING
|
||||
PT.SolidAngle: spu.steradian.dimension, ## MISSING
|
||||
PT.Freq: Dims.frequency,
|
||||
PT.AngFreq: Dims.angle * Dims.frequency,
|
||||
# Cartesian
|
||||
|
@ -836,7 +860,7 @@ class PhysicalType(enum.StrEnum):
|
|||
PT.HField: Dims.current / Dims.length,
|
||||
# Luminal
|
||||
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,
|
||||
# Optics
|
||||
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))
|
||||
|
||||
|
||||
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(
|
||||
sp_obj: SympyExpr, unit_system: UnitSystem, use_jax_array: bool = False
|
||||
) -> 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.
|
||||
|
||||
Unit stripping is "dumb": Substitute any `sympy` object in `unit_system.values()` with `1`.
|
||||
Afterwards, it is converted to an appropriate Python type.
|
||||
|
||||
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`.
|
||||
"""
|
||||
return sympy_to_python(
|
||||
convert_to_unit_system(sp_obj, unit_system).subs(
|
||||
{unit: 1 for unit in unit_system.values()}
|
||||
),
|
||||
strip_unit_system(convert_to_unit_system(sp_obj, unit_system), unit_system),
|
||||
use_jax_array=use_jax_array,
|
||||
)
|
||||
|
|
|
@ -81,6 +81,7 @@ _NaivelyEncodableTypeSet = frozenset(typ.get_args(NaivelyEncodableType))
|
|||
class TypeID(enum.StrEnum):
|
||||
Complex: str = '!type=complex'
|
||||
SympyType: str = '!type=sympytype'
|
||||
SympyExpr: str = '!type=sympyexpr'
|
||||
SocketDef: str = '!type=socketdef'
|
||||
ManagedObj: str = '!type=managedobj'
|
||||
|
||||
|
|
Loading…
Reference in New Issue