feat: perm monitor, enh. monitors, sim node naming
We greatly enhanced the field, flux monitors, and added the permittivity monitor. mobj naming is still a bit borked; we need a node-tree bound namespace to go further with it. For now, don't duplicate nodes, and don't use multiple node trees.main
parent
af358a4d32
commit
92be84ec8a
|
@ -75,7 +75,7 @@ class GeoNodes(enum.StrEnum):
|
||||||
## Monitor
|
## Monitor
|
||||||
MonitorEHField = '_monitor_eh_field'
|
MonitorEHField = '_monitor_eh_field'
|
||||||
MonitorPowerFlux = '_monitor_power_flux'
|
MonitorPowerFlux = '_monitor_power_flux'
|
||||||
MonitorEpsTensor = '_monitor_eps_tensor'
|
MonitorPermittivity = '_monitor_permittivity'
|
||||||
MonitorDiffraction = '_monitor_diffraction'
|
MonitorDiffraction = '_monitor_diffraction'
|
||||||
MonitorProjCartEHField = '_monitor_proj_eh_field'
|
MonitorProjCartEHField = '_monitor_proj_eh_field'
|
||||||
MonitorProjAngEHField = '_monitor_proj_ang_eh_field'
|
MonitorProjAngEHField = '_monitor_proj_ang_eh_field'
|
||||||
|
@ -186,7 +186,7 @@ class GeoNodes(enum.StrEnum):
|
||||||
## Monitor
|
## Monitor
|
||||||
GN.MonitorEHField: GN_INTERNAL_MONITORS_PATH,
|
GN.MonitorEHField: GN_INTERNAL_MONITORS_PATH,
|
||||||
GN.MonitorPowerFlux: GN_INTERNAL_MONITORS_PATH,
|
GN.MonitorPowerFlux: GN_INTERNAL_MONITORS_PATH,
|
||||||
GN.MonitorEpsTensor: GN_INTERNAL_MONITORS_PATH,
|
GN.MonitorPermittivity: GN_INTERNAL_MONITORS_PATH,
|
||||||
GN.MonitorDiffraction: GN_INTERNAL_MONITORS_PATH,
|
GN.MonitorDiffraction: GN_INTERNAL_MONITORS_PATH,
|
||||||
GN.MonitorProjCartEHField: GN_INTERNAL_MONITORS_PATH,
|
GN.MonitorProjCartEHField: GN_INTERNAL_MONITORS_PATH,
|
||||||
GN.MonitorProjAngEHField: GN_INTERNAL_MONITORS_PATH,
|
GN.MonitorProjAngEHField: GN_INTERNAL_MONITORS_PATH,
|
||||||
|
|
BIN
src/blender_maxwell/assets/internal/monitor/_monitor_permittivity.blend (Stored with Git LFS)
100644
BIN
src/blender_maxwell/assets/internal/monitor/_monitor_permittivity.blend (Stored with Git LFS)
100644
Binary file not shown.
|
@ -61,6 +61,7 @@ from .sim_types import (
|
||||||
BoundCondType,
|
BoundCondType,
|
||||||
NewSimCloudTask,
|
NewSimCloudTask,
|
||||||
SimAxisDir,
|
SimAxisDir,
|
||||||
|
SimFieldPols,
|
||||||
SimSpaceAxis,
|
SimSpaceAxis,
|
||||||
manual_amp_time,
|
manual_amp_time,
|
||||||
)
|
)
|
||||||
|
@ -104,6 +105,7 @@ __all__ = [
|
||||||
'BoundCondType',
|
'BoundCondType',
|
||||||
'NewSimCloudTask',
|
'NewSimCloudTask',
|
||||||
'SimAxisDir',
|
'SimAxisDir',
|
||||||
|
'SimFieldPols',
|
||||||
'SimSpaceAxis',
|
'SimSpaceAxis',
|
||||||
'manual_amp_time',
|
'manual_amp_time',
|
||||||
'NodeCategory',
|
'NodeCategory',
|
||||||
|
|
|
@ -120,7 +120,7 @@ class FlowKind(enum.StrEnum):
|
||||||
FlowKind.Value: 'Value',
|
FlowKind.Value: 'Value',
|
||||||
FlowKind.Array: 'Array',
|
FlowKind.Array: 'Array',
|
||||||
FlowKind.LazyArrayRange: 'Range',
|
FlowKind.LazyArrayRange: 'Range',
|
||||||
FlowKind.LazyValueFunc: 'Lazy Value',
|
FlowKind.LazyValueFunc: 'Func',
|
||||||
FlowKind.Params: 'Parameters',
|
FlowKind.Params: 'Parameters',
|
||||||
FlowKind.Info: 'Information',
|
FlowKind.Info: 'Information',
|
||||||
}[v]
|
}[v]
|
||||||
|
|
|
@ -169,6 +169,52 @@ class SimAxisDir(enum.StrEnum):
|
||||||
return {SAD.Plus: True, SAD.Minus: False}[self]
|
return {SAD.Plus: True, SAD.Minus: False}[self]
|
||||||
|
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Simulation Fields
|
||||||
|
####################
|
||||||
|
class SimFieldPols(enum.StrEnum):
|
||||||
|
"""Positive or negative direction along an injection axis."""
|
||||||
|
|
||||||
|
Ex = 'Ex'
|
||||||
|
Ey = 'Ey'
|
||||||
|
Ez = 'Ez'
|
||||||
|
Hx = 'Hx'
|
||||||
|
Hy = 'Hy'
|
||||||
|
Hz = 'Hz'
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def to_name(v: typ.Self) -> str:
|
||||||
|
"""Convert the enum value to a human-friendly name.
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
Used to print names in `EnumProperty`s based on this enum.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A human-friendly name corresponding to the enum value.
|
||||||
|
"""
|
||||||
|
SFP = SimFieldPols
|
||||||
|
return {
|
||||||
|
SFP.Ex: 'Ex',
|
||||||
|
SFP.Ey: 'Ey',
|
||||||
|
SFP.Ez: 'Ez',
|
||||||
|
SFP.Hx: 'Hx',
|
||||||
|
SFP.Hy: 'Hy',
|
||||||
|
SFP.Hz: 'Hz',
|
||||||
|
}[v]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def to_icon(_: typ.Self) -> str:
|
||||||
|
"""Convert the enum value to a Blender icon.
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
Used to print icons in `EnumProperty`s based on this enum.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A human-friendly name corresponding to the enum value.
|
||||||
|
"""
|
||||||
|
return ''
|
||||||
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Boundary Condition Type
|
# - Boundary Condition Type
|
||||||
####################
|
####################
|
||||||
|
|
|
@ -25,14 +25,28 @@ log = logger.get(__name__)
|
||||||
|
|
||||||
|
|
||||||
class ManagedObj(abc.ABC):
|
class ManagedObj(abc.ABC):
|
||||||
|
"""A weak name-based reference to some kind of object external to this software.
|
||||||
|
|
||||||
|
While the object doesn't have to come from Blender's `bpy.types`, that is admittedly the driving motivation for this class: To encapsulate access to the powerful visual tools granted by Blender's 3D viewport, image editor, and UI.
|
||||||
|
Through extensive testing, the functionality of an implicitly-cached, semi-strictly immediate-mode interface, demanding only a weakly-referenced name as persistance, has emerged (with all of the associated tradeoffs).
|
||||||
|
|
||||||
|
While not suited to all use cases, the `ManagedObj` paradigm is perfect for many situations where a node needs to "loosely own" something external and non-trivial.
|
||||||
|
Intriguingly, the precise definition of "loose" has grown to vary greatly between subclasses, as it ends of demonstrating itself to be a matter of taste more than determinism.
|
||||||
|
|
||||||
|
This abstract base class serves to provide a few of the most basic of commonly-available - especially the `dump_as_msgspec`/`parse_as_msgspec` methods that allow it to be persisted using `blender_maxwell.utils.serialize`.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
managed_obj_type: Enum identifier indicating which of the `ct.ManagedObjType` the instance should declare itself as.
|
||||||
|
"""
|
||||||
|
|
||||||
managed_obj_type: ct.ManagedObjType
|
managed_obj_type: ct.ManagedObjType
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def __init__(
|
def __init__(self, name: ct.ManagedObjName, prev_name: str | None = None):
|
||||||
self,
|
"""Initializes the managed object with a unique name.
|
||||||
name: ct.ManagedObjName,
|
|
||||||
):
|
Use `prev_name` to indicate that the managed object will initially be avaiable under `prev_name`, but that it should be renamed to `name`.
|
||||||
"""Initializes the managed object with a unique name."""
|
"""
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Properties
|
# - Properties
|
||||||
|
@ -60,7 +74,7 @@ class ManagedObj(abc.ABC):
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def hide_preview(self) -> None:
|
def hide_preview(self) -> None:
|
||||||
"""Select the managed object in Blender, if such an operation makes sense."""
|
"""Hide any active preview of the managed object, if it exists, and if such an operation makes sense."""
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Serialization
|
# - Serialization
|
||||||
|
|
|
@ -45,82 +45,57 @@ class ManagedBLMesh(base.ManagedObj):
|
||||||
|
|
||||||
@name.setter
|
@name.setter
|
||||||
def name(self, value: str) -> None:
|
def name(self, value: str) -> None:
|
||||||
log.info(
|
log.debug(
|
||||||
'Changing BLMesh w/Name "%s" to Name "%s"', self._bl_object_name, value
|
'Changing BLMesh w/Name "%s" to Name "%s"', self._bl_object_name, value
|
||||||
)
|
)
|
||||||
|
|
||||||
if self._bl_object_name == value:
|
existing_bl_object = bpy.data.objects.get(self.name)
|
||||||
## TODO: This is a workaround.
|
|
||||||
## Really, we can't tell if a name is valid by searching objects.
|
|
||||||
## Since, after all, other managedobjs may have taken a name..
|
|
||||||
## ...but not yet made an object that has it.
|
|
||||||
return
|
|
||||||
|
|
||||||
if (bl_object := bpy.data.objects.get(value)) is None:
|
# No Existing Object: Set Value to Name
|
||||||
log.info(
|
if existing_bl_object is None:
|
||||||
'Desired BLMesh Name "%s" Not Taken',
|
|
||||||
value,
|
|
||||||
)
|
|
||||||
|
|
||||||
if self._bl_object_name is None:
|
|
||||||
log.info(
|
|
||||||
'Set New BLMesh Name to "%s"',
|
|
||||||
value,
|
|
||||||
)
|
|
||||||
elif (bl_object := bpy.data.objects.get(self._bl_object_name)) is not None:
|
|
||||||
log.info(
|
|
||||||
'Changed BLMesh Name to "%s"',
|
|
||||||
value,
|
|
||||||
)
|
|
||||||
bl_object.name = value
|
|
||||||
else:
|
|
||||||
msg = f'ManagedBLMesh with name "{self._bl_object_name}" was deleted'
|
|
||||||
raise RuntimeError(msg)
|
|
||||||
|
|
||||||
# Set Internal Name
|
|
||||||
self._bl_object_name = value
|
self._bl_object_name = value
|
||||||
|
|
||||||
|
# Existing Object: Rename to New Name
|
||||||
else:
|
else:
|
||||||
log.info(
|
existing_bl_object.name = value
|
||||||
'Desired BLMesh Name "%s" is Taken. Using Blender Rename',
|
self._bl_object_name = value
|
||||||
value,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Set Name Anyway, but Respect Blender's Renaming
|
# Check: Blender Rename -> Synchronization Error
|
||||||
## When a name already exists, Blender adds .### to prevent overlap.
|
## -> We can't do much else than report to the user & free().
|
||||||
## `set_name` is allowed to change the name; nodes account for this.
|
if existing_bl_object.name != self._bl_object_name:
|
||||||
bl_object.name = value
|
log.critical(
|
||||||
self._bl_object_name = bl_object.name
|
'BLMesh: Failed to set name of %s to %s, as %s already exists.'
|
||||||
|
|
||||||
log.info(
|
|
||||||
'Changed BLMesh Name to "%s"',
|
|
||||||
bl_object.name,
|
|
||||||
)
|
)
|
||||||
|
self._bl_object_name = existing_bl_object.name
|
||||||
|
self.free()
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Allocation
|
# - Allocation
|
||||||
####################
|
####################
|
||||||
def __init__(self, name: str):
|
def __init__(self, name: str, prev_name: str | None = None):
|
||||||
|
if prev_name is not None:
|
||||||
|
self._bl_object_name = prev_name
|
||||||
|
else:
|
||||||
|
self._bl_object_name = name
|
||||||
|
|
||||||
self.name = name
|
self.name = name
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Deallocation
|
# - Deallocation
|
||||||
####################
|
####################
|
||||||
def free(self):
|
def free(self):
|
||||||
if (bl_object := bpy.data.objects.get(self.name)) is None:
|
bl_object = bpy.data.objects.get(self.name)
|
||||||
|
if bl_object is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Delete the Underlying Datablock
|
# Delete the Mesh Datablock
|
||||||
## This automatically deletes the object too
|
## -> This automatically deletes the object too
|
||||||
log.info('Removing "%s" BLMesh', bl_object.type)
|
log.info('BLMesh: Freeing "%s"', self.name)
|
||||||
bpy.data.meshes.remove(bl_object.data)
|
bpy.data.meshes.remove(bl_object.data)
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Methods
|
# - Methods
|
||||||
####################
|
####################
|
||||||
@property
|
|
||||||
def exists(self) -> bool:
|
|
||||||
return bpy.data.objects.get(self.name) is not None
|
|
||||||
|
|
||||||
def show_preview(self) -> None:
|
def show_preview(self) -> None:
|
||||||
"""Moves the managed Blender object to the preview collection.
|
"""Moves the managed Blender object to the preview collection.
|
||||||
|
|
||||||
|
@ -128,7 +103,7 @@ class ManagedBLMesh(base.ManagedObj):
|
||||||
"""
|
"""
|
||||||
bl_object = bpy.data.objects.get(self.name)
|
bl_object = bpy.data.objects.get(self.name)
|
||||||
if bl_object is None:
|
if bl_object is None:
|
||||||
log.info('Created previewable ManagedBLMesh "%s"', bl_object.name)
|
log.info('%s (ManagedBLMesh): Created BLObject for Preview', bl_object.name)
|
||||||
bl_object = self.bl_object()
|
bl_object = self.bl_object()
|
||||||
|
|
||||||
if bl_object.name not in preview_collection().objects:
|
if bl_object.name not in preview_collection().objects:
|
||||||
|
@ -136,24 +111,19 @@ class ManagedBLMesh(base.ManagedObj):
|
||||||
preview_collection().objects.link(bl_object)
|
preview_collection().objects.link(bl_object)
|
||||||
|
|
||||||
def hide_preview(self) -> None:
|
def hide_preview(self) -> None:
|
||||||
"""Removes the managed Blender object from the preview collection.
|
"""Hide any active preview of the managed object, if it exists, and if such an operation makes sense."""
|
||||||
|
|
||||||
If it's already removed, do nothing.
|
|
||||||
"""
|
|
||||||
bl_object = bpy.data.objects.get(self.name)
|
bl_object = bpy.data.objects.get(self.name)
|
||||||
if bl_object is not None and 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)
|
||||||
|
|
||||||
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."""
|
"""Select the managed object in Blender, if it exists, and if such an operation makes sense."""
|
||||||
if (bl_object := bpy.data.objects.get(self.name)) is not None:
|
bl_object = bpy.data.objects.get(self.name)
|
||||||
|
if bl_object is not None:
|
||||||
bpy.ops.object.select_all(action='DESELECT')
|
bpy.ops.object.select_all(action='DESELECT')
|
||||||
bl_object.select_set(True)
|
bl_object.select_set(True)
|
||||||
|
|
||||||
msg = 'Managed BLMesh does not exist'
|
|
||||||
raise ValueError(msg)
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - BLMesh Management
|
# - BLMesh Management
|
||||||
####################
|
####################
|
||||||
|
|
|
@ -26,6 +26,7 @@ from blender_maxwell.utils import logger
|
||||||
from .. import bl_socket_map
|
from .. import bl_socket_map
|
||||||
from .. import contracts as ct
|
from .. import contracts as ct
|
||||||
from . import base
|
from . import base
|
||||||
|
from .managed_bl_mesh import ManagedBLMesh
|
||||||
|
|
||||||
log = logger.get(__name__)
|
log = logger.get(__name__)
|
||||||
|
|
||||||
|
@ -71,7 +72,7 @@ def read_modifier(bl_modifier: bpy.types.Modifier) -> ModifierAttrs:
|
||||||
return {
|
return {
|
||||||
'node_group': bl_modifier.node_group,
|
'node_group': bl_modifier.node_group,
|
||||||
}
|
}
|
||||||
elif bl_modifier.type == 'ARRAY':
|
if bl_modifier.type == 'ARRAY':
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
@ -162,6 +163,7 @@ def write_modifier(
|
||||||
class ManagedBLModifier(base.ManagedObj):
|
class ManagedBLModifier(base.ManagedObj):
|
||||||
managed_obj_type = ct.ManagedObjType.ManagedBLModifier
|
managed_obj_type = ct.ManagedObjType.ManagedBLModifier
|
||||||
_modifier_name: str | None = None
|
_modifier_name: str | None = None
|
||||||
|
twin_bl_mesh: ManagedBLMesh | None = None
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - BL Object Name
|
# - BL Object Name
|
||||||
|
@ -172,94 +174,117 @@ class ManagedBLModifier(base.ManagedObj):
|
||||||
|
|
||||||
@name.setter
|
@name.setter
|
||||||
def name(self, value: str) -> None:
|
def name(self, value: str) -> None:
|
||||||
## TODO: Handle name conflict within same BLObject
|
log.debug('Changing BLModifier w/Name "%s" to Name "%s"', self.name, value)
|
||||||
log.info(
|
|
||||||
'Changing BLModifier w/Name "%s" to Name "%s"', self._modifier_name, value
|
twin_bl_object = bpy.data.objects.get(self.twin_bl_mesh.name)
|
||||||
)
|
|
||||||
|
# No Existing Twin BLObject
|
||||||
|
## -> Since no modifier-holding object exists, we're all set.
|
||||||
|
if twin_bl_object is None:
|
||||||
|
self._modifier_name = value
|
||||||
|
|
||||||
|
# Existing Twin BLObject
|
||||||
|
else:
|
||||||
|
# No Existing Modifier: Set Value to Name
|
||||||
|
## -> We'll rename the bl_object; otherwise we're set.
|
||||||
|
bl_modifier = twin_bl_object.modifiers.get(self.name)
|
||||||
|
if bl_modifier is None:
|
||||||
|
self.twin_bl_mesh.name = value
|
||||||
|
self._modifier_name = value
|
||||||
|
|
||||||
|
# Existing Modifier: Rename to New Name
|
||||||
|
## -> We'll rename the bl_modifier, then the bl_object.
|
||||||
|
else:
|
||||||
|
bl_modifier.name = value
|
||||||
|
self.twin_bl_mesh.name = value
|
||||||
self._modifier_name = value
|
self._modifier_name = value
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Allocation
|
# - Allocation
|
||||||
####################
|
####################
|
||||||
def __init__(self, name: str):
|
def __init__(self, name: str, prev_name: str | None = None):
|
||||||
|
self.twin_bl_mesh = ManagedBLMesh(name, prev_name=prev_name)
|
||||||
|
if prev_name is not None:
|
||||||
|
self._modifier_name = prev_name
|
||||||
|
else:
|
||||||
|
self._modifier_name = name
|
||||||
|
|
||||||
self.name = name
|
self.name = name
|
||||||
|
|
||||||
def bl_select(self) -> None:
|
def bl_select(self) -> None:
|
||||||
pass
|
self.twin_bl_mesh.bl_select()
|
||||||
|
|
||||||
|
def show_preview(self) -> None:
|
||||||
|
self.twin_bl_mesh.show_preview()
|
||||||
|
|
||||||
def hide_preview(self) -> None:
|
def hide_preview(self) -> None:
|
||||||
pass
|
self.twin_bl_mesh.hide_preview()
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Deallocation
|
# - Deallocation
|
||||||
####################
|
####################
|
||||||
def free(self):
|
def free(self):
|
||||||
"""Not needed - when the object is removed, its modifiers are also removed."""
|
log.info('BLModifier: Freeing "%s" w/Twin BLObject of same name', self.name)
|
||||||
|
self.twin_bl_mesh.free()
|
||||||
def free_from_bl_object(
|
|
||||||
self,
|
|
||||||
bl_object: bpy.types.Object,
|
|
||||||
) -> None:
|
|
||||||
"""Remove the managed BL modifier from the passed Blender object.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
bl_object: The Blender object to remove the modifier from.
|
|
||||||
"""
|
|
||||||
if (bl_modifier := bl_object.modifiers.get(self.name)) is not None:
|
|
||||||
log.info(
|
|
||||||
'Removing (recreating) BLModifier "%s" on BLObject "%s" (existing modifier_type is "%s")',
|
|
||||||
bl_modifier.name,
|
|
||||||
bl_object.name,
|
|
||||||
bl_modifier.type,
|
|
||||||
)
|
|
||||||
bl_modifier = bl_object.modifiers.remove(bl_modifier)
|
|
||||||
else:
|
|
||||||
msg = f'Tried to free bl_modifier "{self.name}", but bl_object "{bl_object.name}" has no modifier of that name'
|
|
||||||
raise ValueError(msg)
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Modifiers
|
# - Modifiers
|
||||||
####################
|
####################
|
||||||
def bl_modifier(
|
def bl_modifier(
|
||||||
self,
|
self,
|
||||||
bl_object: bpy.types.Object,
|
|
||||||
modifier_type: ct.BLModifierType,
|
modifier_type: ct.BLModifierType,
|
||||||
modifier_attrs: ModifierAttrs,
|
modifier_attrs: ModifierAttrs,
|
||||||
|
location: tuple[float, float, float] = (0, 0, 0),
|
||||||
):
|
):
|
||||||
"""Creates a new modifier for the current `bl_object`.
|
"""Creates a new modifier for the current `bl_object`.
|
||||||
|
|
||||||
- Modifier Type Names: <https://docs.blender.org/api/current/bpy_types_enum_items/object_modifier_type_items.html#rna-enum-object-modifier-type-items>
|
- Modifier Type Names: <https://docs.blender.org/api/current/bpy_types_enum_items/object_modifier_type_items.html#rna-enum-object-modifier-type-items>
|
||||||
"""
|
"""
|
||||||
# Remove Mismatching Modifier
|
# Retrieve Twin BLObject
|
||||||
|
twin_bl_object = self.twin_bl_mesh.bl_object(location=location)
|
||||||
|
if twin_bl_object is None:
|
||||||
|
msg = f'BLModifier: No BLObject twin "{self.name}" exists to attach a modifier to.'
|
||||||
|
raise ValueError(msg)
|
||||||
|
|
||||||
|
bl_modifier = twin_bl_object.modifiers.get(self.name)
|
||||||
|
|
||||||
|
# Existing Modifier: Maybe Remove
|
||||||
modifier_was_removed = False
|
modifier_was_removed = False
|
||||||
if (
|
if bl_modifier is not None and bl_modifier.type != modifier_type:
|
||||||
bl_modifier := bl_object.modifiers.get(self.name)
|
|
||||||
) and bl_modifier.type != modifier_type:
|
|
||||||
log.info(
|
log.info(
|
||||||
'Removing (recreating) BLModifier "%s" on BLObject "%s" (existing modifier_type is "%s", but "%s" is requested)',
|
'BLModifier: Clearing BLModifier "%s" from BLObject "%s"',
|
||||||
bl_modifier.name,
|
self.name,
|
||||||
bl_object.name,
|
twin_bl_object.name,
|
||||||
bl_modifier.type,
|
|
||||||
modifier_type,
|
|
||||||
)
|
)
|
||||||
self.free_from_bl_object(bl_object)
|
twin_bl_object.modifiers.remove(bl_modifier)
|
||||||
modifier_was_removed = True
|
modifier_was_removed = True
|
||||||
|
|
||||||
# Create Modifier
|
# No/Removed Modifier: Create
|
||||||
if bl_modifier is None or modifier_was_removed:
|
if bl_modifier is None or modifier_was_removed:
|
||||||
log.info(
|
log.info(
|
||||||
'Creating BLModifier "%s" on BLObject "%s" with modifier_type "%s"',
|
'BLModifier: (Re)Creating BLModifier "%s" on BLObject "%s" (type=%s)',
|
||||||
self.name,
|
self.name,
|
||||||
bl_object.name,
|
twin_bl_object.name,
|
||||||
modifier_type,
|
modifier_type,
|
||||||
)
|
)
|
||||||
bl_modifier = bl_object.modifiers.new(
|
bl_modifier = twin_bl_object.modifiers.new(
|
||||||
name=self.name,
|
name=self.name,
|
||||||
type=modifier_type,
|
type=modifier_type,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Write Modifier Attrs
|
||||||
|
## -> For GeoNodes modifiers, this is the critical component.
|
||||||
|
## -> From 'write_modifier', we only need to know if something changed.
|
||||||
|
## -> If so, we make sure to update the object data.
|
||||||
modifier_altered = write_modifier(bl_modifier, modifier_attrs)
|
modifier_altered = write_modifier(bl_modifier, modifier_attrs)
|
||||||
if modifier_altered:
|
if modifier_altered:
|
||||||
bl_object.data.update()
|
twin_bl_object.data.update()
|
||||||
|
|
||||||
return bl_modifier
|
return bl_modifier
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Mesh Data
|
||||||
|
####################
|
||||||
|
@property
|
||||||
|
def mesh_as_arrays(self) -> dict:
|
||||||
|
return self.twin_bl_mesh.mesh_as_arrays
|
||||||
|
|
|
@ -131,27 +131,10 @@ class MaxwellSimNode(bpy.types.Node, bl_instance.BLInstance):
|
||||||
for i, (preset_name, preset_def) in enumerate(cls.presets.items())
|
for i, (preset_name, preset_def) in enumerate(cls.presets.items())
|
||||||
]
|
]
|
||||||
|
|
||||||
####################
|
# Managed Objects
|
||||||
# - Managed Objects
|
managed_objs: dict[str, _managed_objs.ManagedObj] = bl_cache.BLField(
|
||||||
####################
|
{}, use_prop_update=False
|
||||||
@bl_cache.cached_bl_property(depends_on={'sim_node_name'})
|
)
|
||||||
def managed_objs(self) -> dict[str, _managed_objs.ManagedObj]:
|
|
||||||
"""Access the constructed managed objects defined in `self.managed_obj_types`.
|
|
||||||
|
|
||||||
Managed objects are special in that they **don't keep any non-reproducible state**.
|
|
||||||
In fact, all managed object state can generally be derived entirely from the managed object's `name` attribute.
|
|
||||||
As a result, **consistency in namespacing is of the utmost importance**, if reproducibility of managed objects is to be guaranteed.
|
|
||||||
|
|
||||||
This name must be in sync with the name of the managed "thing", which is where this computed property comes in.
|
|
||||||
The node's half of the responsibility is to push a new name whenever `self.sim_node_name` changes.
|
|
||||||
"""
|
|
||||||
if self.managed_obj_types:
|
|
||||||
return {
|
|
||||||
mobj_name: mobj_type(self.sim_node_name)
|
|
||||||
for mobj_name, mobj_type in self.managed_obj_types.items()
|
|
||||||
}
|
|
||||||
|
|
||||||
return {}
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Class Methods
|
# - Class Methods
|
||||||
|
@ -221,25 +204,30 @@ class MaxwellSimNode(bpy.types.Node, bl_instance.BLInstance):
|
||||||
####################
|
####################
|
||||||
@events.on_value_changed(
|
@events.on_value_changed(
|
||||||
prop_name='sim_node_name',
|
prop_name='sim_node_name',
|
||||||
props={'sim_node_name', 'managed_objs'},
|
props={'sim_node_name', 'managed_objs', 'managed_obj_types'},
|
||||||
stop_propagation=True,
|
stop_propagation=True,
|
||||||
)
|
)
|
||||||
def _on_sim_node_name_changed(self, props):
|
def _on_sim_node_name_changed(self, props):
|
||||||
log.info(
|
log.debug(
|
||||||
'Changed Sim Node Name of a "%s" to "%s" (self=%s)',
|
'Changed Sim Node Name of a "%s" to "%s" (self=%s)',
|
||||||
self.bl_idname,
|
self.bl_idname,
|
||||||
props['sim_node_name'],
|
props['sim_node_name'],
|
||||||
str(self),
|
str(self),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Set Name of Managed Objects
|
# (Re)Construct Managed Objects
|
||||||
for mobj in props['managed_objs'].values():
|
## -> Due to 'prev_name', the new MObjs will be renamed on construction
|
||||||
mobj.name = props['sim_node_name']
|
self.managed_objs = {
|
||||||
|
mobj_name: mobj_type(
|
||||||
## Invalidate Cache
|
self.sim_node_name,
|
||||||
## -> Persistance doesn't happen if we simply mutate.
|
prev_name=(
|
||||||
## -> This ensures that the name change is picked up.
|
props['managed_objs'][mobj_name].name
|
||||||
self.managed_objs = bl_cache.Signal.InvalidateCache
|
if mobj_name in props['managed_objs']
|
||||||
|
else None
|
||||||
|
),
|
||||||
|
)
|
||||||
|
for mobj_name, mobj_type in props['managed_obj_types'].items()
|
||||||
|
}
|
||||||
|
|
||||||
@events.on_value_changed(prop_name='active_socket_set')
|
@events.on_value_changed(prop_name='active_socket_set')
|
||||||
def _on_socket_set_changed(self):
|
def _on_socket_set_changed(self):
|
||||||
|
@ -1027,12 +1015,14 @@ class MaxwellSimNode(bpy.types.Node, bl_instance.BLInstance):
|
||||||
bl_socket.reset_instance_id()
|
bl_socket.reset_instance_id()
|
||||||
|
|
||||||
# Generate New Sim Node Name
|
# Generate New Sim Node Name
|
||||||
## Blender will automatically add .001 so that `self.name` is unique.
|
## -> Blender will adds .00# so that `self.name` is unique.
|
||||||
|
## -> We can shamelessly piggyback on this for unique managed objs.
|
||||||
|
## -> ...But to avoid stealing the old node's mobjs, we first rename.
|
||||||
self.sim_node_name = self.name
|
self.sim_node_name = self.name
|
||||||
|
|
||||||
# Event Methods
|
# Event Methods
|
||||||
## Run any 'DataChanged' methods with 'run_on_init' set.
|
## -> Re-run any 'DataChanged' methods with 'run_on_init' set.
|
||||||
## -> Copying a node _arguably_ re-initializes the new node.
|
## -> Copying a node ~ re-initializing the new node.
|
||||||
for event_method in [
|
for event_method in [
|
||||||
event_method
|
event_method
|
||||||
for event_method in self.event_methods_by_event[ct.FlowEvent.DataChanged]
|
for event_method in self.event_methods_by_event[ct.FlowEvent.DataChanged]
|
||||||
|
|
|
@ -132,7 +132,7 @@ class BlochBoundCondNode(base.MaxwellSimNode):
|
||||||
####################
|
####################
|
||||||
# - Properties
|
# - Properties
|
||||||
####################
|
####################
|
||||||
valid_sim_axis: ct.SimSpaceAxis = bl_cache.BLField(ct.SimSpaceAxis.X, prop_ui=True)
|
valid_sim_axis: ct.SimSpaceAxis = bl_cache.BLField(ct.SimSpaceAxis.X)
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - UI
|
# - UI
|
||||||
|
|
|
@ -24,13 +24,15 @@ import tidy3d as td
|
||||||
from tidy3d.material_library.material_library import MaterialItem as Tidy3DMediumItem
|
from tidy3d.material_library.material_library import MaterialItem as Tidy3DMediumItem
|
||||||
from tidy3d.material_library.material_library import VariantItem as Tidy3DMediumVariant
|
from tidy3d.material_library.material_library import VariantItem as Tidy3DMediumVariant
|
||||||
|
|
||||||
from blender_maxwell.utils import bl_cache, sci_constants
|
from blender_maxwell.utils import bl_cache, logger, sci_constants
|
||||||
from blender_maxwell.utils import extra_sympy_units as spux
|
from blender_maxwell.utils import extra_sympy_units as spux
|
||||||
|
|
||||||
from ... import contracts as ct
|
from ... import contracts as ct
|
||||||
from ... import managed_objs, sockets
|
from ... import managed_objs, sockets
|
||||||
from .. import base, events
|
from .. import base, events
|
||||||
|
|
||||||
|
log = logger.get(__name__)
|
||||||
|
|
||||||
_mat_lib_iter = iter(td.material_library)
|
_mat_lib_iter = iter(td.material_library)
|
||||||
_mat_key = ''
|
_mat_key = ''
|
||||||
|
|
||||||
|
@ -131,7 +133,8 @@ class LibraryMediumNode(base.MaxwellSimNode):
|
||||||
####################
|
####################
|
||||||
vendored_medium: VendoredMedium = bl_cache.BLField(VendoredMedium.Au)
|
vendored_medium: VendoredMedium = bl_cache.BLField(VendoredMedium.Au)
|
||||||
variant_name: enum.StrEnum = bl_cache.BLField(
|
variant_name: enum.StrEnum = bl_cache.BLField(
|
||||||
enum_cb=lambda self, _: self.search_variants()
|
enum_cb=lambda self, _: self.search_variants(),
|
||||||
|
cb_depends_on={'vendored_medium'},
|
||||||
)
|
)
|
||||||
|
|
||||||
def search_variants(self) -> list[ct.BLEnumElement]:
|
def search_variants(self) -> list[ct.BLEnumElement]:
|
||||||
|
@ -141,7 +144,7 @@ class LibraryMediumNode(base.MaxwellSimNode):
|
||||||
####################
|
####################
|
||||||
# - Computed
|
# - Computed
|
||||||
####################
|
####################
|
||||||
@bl_cache.cached_bl_property(depends_on={'vendored_medium', 'variant_name'})
|
@bl_cache.cached_bl_property(depends_on={'variant_name'})
|
||||||
def variant(self) -> Tidy3DMediumVariant:
|
def variant(self) -> Tidy3DMediumVariant:
|
||||||
"""Deduce the actual medium variant from `self.vendored_medium` and `self.variant_name`."""
|
"""Deduce the actual medium variant from `self.vendored_medium` and `self.variant_name`."""
|
||||||
return self.vendored_medium.medium_variants[self.variant_name]
|
return self.vendored_medium.medium_variants[self.variant_name]
|
||||||
|
@ -239,21 +242,6 @@ class LibraryMediumNode(base.MaxwellSimNode):
|
||||||
if self.data_url is not None:
|
if self.data_url is not None:
|
||||||
box.operator('wm.url_open', text='Link to Data').url = self.data_url
|
box.operator('wm.url_open', text='Link to Data').url = self.data_url
|
||||||
|
|
||||||
####################
|
|
||||||
# - Events
|
|
||||||
####################
|
|
||||||
@events.on_value_changed(
|
|
||||||
prop_name={'vendored_medium', 'variant_name'},
|
|
||||||
run_on_init=True,
|
|
||||||
props={'vendored_medium'},
|
|
||||||
)
|
|
||||||
def on_medium_changed(self, props):
|
|
||||||
if self.variant_name not in props['vendored_medium'].medium_variants:
|
|
||||||
self.variant_name = bl_cache.Signal.ResetEnumItems
|
|
||||||
|
|
||||||
self.ui_freq_range = bl_cache.Signal.InvalidateCache
|
|
||||||
self.ui_wl_range = bl_cache.Signal.InvalidateCache
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Output
|
# - Output
|
||||||
####################
|
####################
|
||||||
|
@ -264,13 +252,6 @@ class LibraryMediumNode(base.MaxwellSimNode):
|
||||||
def compute_medium(self, props) -> sp.Expr:
|
def compute_medium(self, props) -> sp.Expr:
|
||||||
return props['medium']
|
return props['medium']
|
||||||
|
|
||||||
@events.computes_output_socket(
|
|
||||||
'Valid Freqs',
|
|
||||||
props={'freq_range'},
|
|
||||||
)
|
|
||||||
def compute_valid_freqs(self, props) -> sp.Expr:
|
|
||||||
return props['freq_range']
|
|
||||||
|
|
||||||
@events.computes_output_socket(
|
@events.computes_output_socket(
|
||||||
'Valid Freqs',
|
'Valid Freqs',
|
||||||
kind=ct.FlowKind.LazyArrayRange,
|
kind=ct.FlowKind.LazyArrayRange,
|
||||||
|
@ -285,13 +266,6 @@ class LibraryMediumNode(base.MaxwellSimNode):
|
||||||
unit=spux.THz,
|
unit=spux.THz,
|
||||||
)
|
)
|
||||||
|
|
||||||
@events.computes_output_socket(
|
|
||||||
'Valid WLs',
|
|
||||||
props={'wl_range'},
|
|
||||||
)
|
|
||||||
def compute_valid_wls(self, props) -> sp.Expr:
|
|
||||||
return props['wl_range']
|
|
||||||
|
|
||||||
@events.computes_output_socket(
|
@events.computes_output_socket(
|
||||||
'Valid WLs',
|
'Valid WLs',
|
||||||
kind=ct.FlowKind.LazyArrayRange,
|
kind=ct.FlowKind.LazyArrayRange,
|
||||||
|
@ -320,7 +294,7 @@ class LibraryMediumNode(base.MaxwellSimNode):
|
||||||
props,
|
props,
|
||||||
):
|
):
|
||||||
managed_objs['plot'].mpl_plot_to_image(
|
managed_objs['plot'].mpl_plot_to_image(
|
||||||
lambda ax: self.medium.plot(props['medium'].frequency_range, ax=ax),
|
lambda ax: props['medium'].plot(props['medium'].frequency_range, ax=ax),
|
||||||
bl_select=True,
|
bl_select=True,
|
||||||
)
|
)
|
||||||
## TODO: Plot based on Wl, not freq.
|
## TODO: Plot based on Wl, not freq.
|
||||||
|
|
|
@ -14,20 +14,19 @@
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from . import eh_field_monitor, field_power_flux_monitor
|
from . import eh_field_monitor, field_power_flux_monitor, permittivity_monitor
|
||||||
|
|
||||||
# from . import epsilon_tensor_monitor
|
|
||||||
# from . import diffraction_monitor
|
# from . import diffraction_monitor
|
||||||
|
|
||||||
BL_REGISTER = [
|
BL_REGISTER = [
|
||||||
*eh_field_monitor.BL_REGISTER,
|
*eh_field_monitor.BL_REGISTER,
|
||||||
*field_power_flux_monitor.BL_REGISTER,
|
*field_power_flux_monitor.BL_REGISTER,
|
||||||
# *epsilon_tensor_monitor.BL_REGISTER,
|
*permittivity_monitor.BL_REGISTER,
|
||||||
# *diffraction_monitor.BL_REGISTER,
|
# *diffraction_monitor.BL_REGISTER,
|
||||||
]
|
]
|
||||||
BL_NODES = {
|
BL_NODES = {
|
||||||
**eh_field_monitor.BL_NODES,
|
**eh_field_monitor.BL_NODES,
|
||||||
**field_power_flux_monitor.BL_NODES,
|
**field_power_flux_monitor.BL_NODES,
|
||||||
# **epsilon_tensor_monitor.BL_NODES,
|
**permittivity_monitor.BL_NODES,
|
||||||
# **diffraction_monitor.BL_NODES,
|
# **diffraction_monitor.BL_NODES,
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,13 +16,14 @@
|
||||||
|
|
||||||
import typing as typ
|
import typing as typ
|
||||||
|
|
||||||
|
import bpy
|
||||||
import sympy as sp
|
import sympy as sp
|
||||||
import sympy.physics.units as spu
|
import sympy.physics.units as spu
|
||||||
import tidy3d as td
|
import tidy3d as td
|
||||||
|
|
||||||
from blender_maxwell.assets.geonodes import GeoNodes, import_geonodes
|
from blender_maxwell.assets.geonodes import GeoNodes, import_geonodes
|
||||||
|
from blender_maxwell.utils import bl_cache, logger
|
||||||
from blender_maxwell.utils import extra_sympy_units as spux
|
from blender_maxwell.utils import extra_sympy_units as spux
|
||||||
from blender_maxwell.utils import logger
|
|
||||||
|
|
||||||
from ... import contracts as ct
|
from ... import contracts as ct
|
||||||
from ... import managed_objs, sockets
|
from ... import managed_objs, sockets
|
||||||
|
@ -50,11 +51,13 @@ class EHFieldMonitorNode(base.MaxwellSimNode):
|
||||||
size=spux.NumberSize1D.Vec3,
|
size=spux.NumberSize1D.Vec3,
|
||||||
physical_type=spux.PhysicalType.Length,
|
physical_type=spux.PhysicalType.Length,
|
||||||
default_value=sp.Matrix([1, 1, 1]),
|
default_value=sp.Matrix([1, 1, 1]),
|
||||||
|
abs_min=0,
|
||||||
),
|
),
|
||||||
'Spatial Subdivs': sockets.ExprSocketDef(
|
'Stride': sockets.ExprSocketDef(
|
||||||
size=spux.NumberSize1D.Vec3,
|
size=spux.NumberSize1D.Vec3,
|
||||||
mathtype=spux.MathType.Integer,
|
mathtype=spux.MathType.Integer,
|
||||||
default_value=sp.Matrix([10, 10, 10]),
|
default_value=sp.Matrix([10, 10, 10]),
|
||||||
|
abs_min=0,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
input_socket_sets: typ.ClassVar = {
|
input_socket_sets: typ.ClassVar = {
|
||||||
|
@ -69,15 +72,15 @@ class EHFieldMonitorNode(base.MaxwellSimNode):
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
'Time Domain': {
|
'Time Domain': {
|
||||||
'Time Range': sockets.ExprSocketDef(
|
't Range': sockets.ExprSocketDef(
|
||||||
active_kind=ct.FlowKind.LazyArrayRange,
|
active_kind=ct.FlowKind.LazyArrayRange,
|
||||||
physical_type=spux.PhysicalType.Time,
|
physical_type=spux.PhysicalType.Time,
|
||||||
default_unit=spu.picosecond,
|
default_unit=spu.picosecond,
|
||||||
default_min=0,
|
default_min=0,
|
||||||
default_max=10,
|
default_max=10,
|
||||||
default_steps=2,
|
default_steps=0,
|
||||||
),
|
),
|
||||||
'Temporal Subdivs': sockets.ExprSocketDef(
|
't Stride': sockets.ExprSocketDef(
|
||||||
mathtype=spux.MathType.Integer,
|
mathtype=spux.MathType.Integer,
|
||||||
default_value=100,
|
default_value=100,
|
||||||
),
|
),
|
||||||
|
@ -89,20 +92,30 @@ class EHFieldMonitorNode(base.MaxwellSimNode):
|
||||||
}
|
}
|
||||||
|
|
||||||
managed_obj_types: typ.ClassVar = {
|
managed_obj_types: typ.ClassVar = {
|
||||||
'mesh': managed_objs.ManagedBLMesh,
|
|
||||||
'modifier': managed_objs.ManagedBLModifier,
|
'modifier': managed_objs.ManagedBLModifier,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Properties
|
||||||
|
####################
|
||||||
|
fields: set[ct.SimFieldPols] = bl_cache.BLField(set(ct.SimFieldPols))
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - UI
|
||||||
|
####################
|
||||||
|
def draw_props(self, _: bpy.types.Context, layout: bpy.types.UILayout) -> None:
|
||||||
|
layout.prop(self, self.blfields['fields'], expand=True)
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Output
|
# - Output
|
||||||
####################
|
####################
|
||||||
@events.computes_output_socket(
|
@events.computes_output_socket(
|
||||||
'Freq Monitor',
|
'Freq Monitor',
|
||||||
props={'sim_node_name'},
|
props={'sim_node_name', 'fields'},
|
||||||
input_sockets={
|
input_sockets={
|
||||||
'Center',
|
'Center',
|
||||||
'Size',
|
'Size',
|
||||||
'Spatial Subdivs',
|
'Stride',
|
||||||
'Freqs',
|
'Freqs',
|
||||||
},
|
},
|
||||||
input_socket_kinds={
|
input_socket_kinds={
|
||||||
|
@ -131,28 +144,29 @@ class EHFieldMonitorNode(base.MaxwellSimNode):
|
||||||
center=input_sockets['Center'],
|
center=input_sockets['Center'],
|
||||||
size=input_sockets['Size'],
|
size=input_sockets['Size'],
|
||||||
name=props['sim_node_name'],
|
name=props['sim_node_name'],
|
||||||
interval_space=tuple(input_sockets['Spatial Subdivs']),
|
interval_space=tuple(input_sockets['Stride']),
|
||||||
freqs=input_sockets['Freqs'].realize().values,
|
freqs=input_sockets['Freqs'].realize().values,
|
||||||
|
fields=props['fields'],
|
||||||
)
|
)
|
||||||
|
|
||||||
@events.computes_output_socket(
|
@events.computes_output_socket(
|
||||||
'Time Monitor',
|
'Time Monitor',
|
||||||
props={'sim_node_name'},
|
props={'sim_node_name', 'fields'},
|
||||||
input_sockets={
|
input_sockets={
|
||||||
'Center',
|
'Center',
|
||||||
'Size',
|
'Size',
|
||||||
'Spatial Subdivs',
|
'Stride',
|
||||||
'Time Range',
|
't Range',
|
||||||
'Temporal Subdivs',
|
't Stride',
|
||||||
},
|
},
|
||||||
input_socket_kinds={
|
input_socket_kinds={
|
||||||
'Time Range': ct.FlowKind.LazyArrayRange,
|
't Range': ct.FlowKind.LazyArrayRange,
|
||||||
},
|
},
|
||||||
unit_systems={'Tidy3DUnits': ct.UNITS_TIDY3D},
|
unit_systems={'Tidy3DUnits': ct.UNITS_TIDY3D},
|
||||||
scale_input_sockets={
|
scale_input_sockets={
|
||||||
'Center': 'Tidy3DUnits',
|
'Center': 'Tidy3DUnits',
|
||||||
'Size': 'Tidy3DUnits',
|
'Size': 'Tidy3DUnits',
|
||||||
'Time Range': 'Tidy3DUnits',
|
't Range': 'Tidy3DUnits',
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
def compute_time_monitor(
|
def compute_time_monitor(
|
||||||
|
@ -171,10 +185,11 @@ class EHFieldMonitorNode(base.MaxwellSimNode):
|
||||||
center=input_sockets['Center'],
|
center=input_sockets['Center'],
|
||||||
size=input_sockets['Size'],
|
size=input_sockets['Size'],
|
||||||
name=props['sim_node_name'],
|
name=props['sim_node_name'],
|
||||||
interval_space=tuple(input_sockets['Spatial Subdivs']),
|
interval_space=tuple(input_sockets['Stride']),
|
||||||
start=input_sockets['Time Range'].realize_start(),
|
start=input_sockets['t Range'].realize_start(),
|
||||||
stop=input_sockets['Time Range'].realize_stop(),
|
stop=input_sockets['t Range'].realize_stop(),
|
||||||
interval=input_sockets['Temporal Subdivs'],
|
interval=input_sockets['t Stride'],
|
||||||
|
fields=props['fields'],
|
||||||
)
|
)
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
@ -184,26 +199,21 @@ class EHFieldMonitorNode(base.MaxwellSimNode):
|
||||||
# Trigger
|
# Trigger
|
||||||
prop_name='preview_active',
|
prop_name='preview_active',
|
||||||
# Loaded
|
# Loaded
|
||||||
managed_objs={'mesh'},
|
managed_objs={'modifier'},
|
||||||
props={'preview_active'},
|
props={'preview_active'},
|
||||||
input_sockets={'Center', 'Size'},
|
|
||||||
)
|
)
|
||||||
def on_preview_changed(self, managed_objs, props, input_sockets):
|
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']:
|
if props['preview_active']:
|
||||||
mesh.show_preview()
|
managed_objs['modifier'].show_preview()
|
||||||
else:
|
else:
|
||||||
mesh.hide_preview()
|
managed_objs['modifier'].hide_preview()
|
||||||
|
|
||||||
@events.on_value_changed(
|
@events.on_value_changed(
|
||||||
# Trigger
|
# Trigger
|
||||||
socket_name={'Center', 'Size'},
|
socket_name={'Center', 'Size'},
|
||||||
run_on_init=True,
|
run_on_init=True,
|
||||||
# Loaded
|
# Loaded
|
||||||
managed_objs={'mesh', 'modifier'},
|
managed_objs={'modifier'},
|
||||||
input_sockets={'Center', 'Size'},
|
input_sockets={'Center', 'Size'},
|
||||||
unit_systems={'BlenderUnits': ct.UNITS_BLENDER},
|
unit_systems={'BlenderUnits': ct.UNITS_BLENDER},
|
||||||
scale_input_sockets={
|
scale_input_sockets={
|
||||||
|
@ -218,7 +228,6 @@ class EHFieldMonitorNode(base.MaxwellSimNode):
|
||||||
):
|
):
|
||||||
# Push Input Values to GeoNodes Modifier
|
# Push Input Values to GeoNodes Modifier
|
||||||
managed_objs['modifier'].bl_modifier(
|
managed_objs['modifier'].bl_modifier(
|
||||||
managed_objs['mesh'].bl_object(location=input_sockets['Center']),
|
|
||||||
'NODES',
|
'NODES',
|
||||||
{
|
{
|
||||||
'node_group': import_geonodes(GeoNodes.MonitorEHField),
|
'node_group': import_geonodes(GeoNodes.MonitorEHField),
|
||||||
|
@ -227,6 +236,7 @@ class EHFieldMonitorNode(base.MaxwellSimNode):
|
||||||
'Size': input_sockets['Size'],
|
'Size': input_sockets['Size'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
location=input_sockets['Center'],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,21 +0,0 @@
|
||||||
# blender_maxwell
|
|
||||||
# Copyright (C) 2024 blender_maxwell Project Contributors
|
|
||||||
#
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU Affero General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU Affero General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Blender Registration
|
|
||||||
####################
|
|
||||||
BL_REGISTER = []
|
|
||||||
BL_NODES = {}
|
|
|
@ -16,13 +16,14 @@
|
||||||
|
|
||||||
import typing as typ
|
import typing as typ
|
||||||
|
|
||||||
|
import bpy
|
||||||
import sympy as sp
|
import sympy as sp
|
||||||
import sympy.physics.units as spu
|
import sympy.physics.units as spu
|
||||||
import tidy3d as td
|
import tidy3d as td
|
||||||
|
|
||||||
from blender_maxwell.assets.geonodes import GeoNodes, import_geonodes
|
from blender_maxwell.assets.geonodes import GeoNodes, import_geonodes
|
||||||
|
from blender_maxwell.utils import bl_cache, logger
|
||||||
from blender_maxwell.utils import extra_sympy_units as spux
|
from blender_maxwell.utils import extra_sympy_units as spux
|
||||||
from blender_maxwell.utils import logger
|
|
||||||
|
|
||||||
from ... import contracts as ct
|
from ... import contracts as ct
|
||||||
from ... import managed_objs, sockets
|
from ... import managed_objs, sockets
|
||||||
|
@ -32,6 +33,8 @@ log = logger.get(__name__)
|
||||||
|
|
||||||
|
|
||||||
class PowerFluxMonitorNode(base.MaxwellSimNode):
|
class PowerFluxMonitorNode(base.MaxwellSimNode):
|
||||||
|
"""Node providing for the monitoring of electromagnetic field flux a given planar region or volume, in either the frequency or the time domain."""
|
||||||
|
|
||||||
node_type = ct.NodeType.PowerFluxMonitor
|
node_type = ct.NodeType.PowerFluxMonitor
|
||||||
bl_label = 'Power Flux Monitor'
|
bl_label = 'Power Flux Monitor'
|
||||||
use_sim_node_name = True
|
use_sim_node_name = True
|
||||||
|
@ -48,13 +51,14 @@ class PowerFluxMonitorNode(base.MaxwellSimNode):
|
||||||
size=spux.NumberSize1D.Vec3,
|
size=spux.NumberSize1D.Vec3,
|
||||||
physical_type=spux.PhysicalType.Length,
|
physical_type=spux.PhysicalType.Length,
|
||||||
default_value=sp.Matrix([1, 1, 1]),
|
default_value=sp.Matrix([1, 1, 1]),
|
||||||
|
abs_min=0,
|
||||||
),
|
),
|
||||||
'Samples/Space': sockets.ExprSocketDef(
|
'Stride': sockets.ExprSocketDef(
|
||||||
size=spux.NumberSize1D.Vec3,
|
size=spux.NumberSize1D.Vec3,
|
||||||
mathtype=spux.MathType.Integer,
|
mathtype=spux.MathType.Integer,
|
||||||
default_value=sp.Matrix([10, 10, 10]),
|
default_value=sp.Matrix([10, 10, 10]),
|
||||||
|
abs_min=0,
|
||||||
),
|
),
|
||||||
'Direction': sockets.BoolSocketDef(),
|
|
||||||
}
|
}
|
||||||
input_socket_sets: typ.ClassVar = {
|
input_socket_sets: typ.ClassVar = {
|
||||||
'Freq Domain': {
|
'Freq Domain': {
|
||||||
|
@ -68,7 +72,7 @@ class PowerFluxMonitorNode(base.MaxwellSimNode):
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
'Time Domain': {
|
'Time Domain': {
|
||||||
'Time Range': sockets.ExprSocketDef(
|
't Range': sockets.ExprSocketDef(
|
||||||
active_kind=ct.FlowKind.LazyArrayRange,
|
active_kind=ct.FlowKind.LazyArrayRange,
|
||||||
physical_type=spux.PhysicalType.Time,
|
physical_type=spux.PhysicalType.Time,
|
||||||
default_unit=spu.picosecond,
|
default_unit=spu.picosecond,
|
||||||
|
@ -76,7 +80,7 @@ class PowerFluxMonitorNode(base.MaxwellSimNode):
|
||||||
default_max=10,
|
default_max=10,
|
||||||
default_steps=2,
|
default_steps=2,
|
||||||
),
|
),
|
||||||
'Samples/Time': sockets.ExprSocketDef(
|
't Stride': sockets.ExprSocketDef(
|
||||||
mathtype=spux.MathType.Integer,
|
mathtype=spux.MathType.Integer,
|
||||||
default_value=100,
|
default_value=100,
|
||||||
),
|
),
|
||||||
|
@ -92,18 +96,45 @@ class PowerFluxMonitorNode(base.MaxwellSimNode):
|
||||||
'modifier': managed_objs.ManagedBLModifier,
|
'modifier': managed_objs.ManagedBLModifier,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Properties
|
||||||
|
####################
|
||||||
|
direction_2d: ct.SimAxisDir = bl_cache.BLField(ct.SimAxisDir.Plus)
|
||||||
|
include_3d: set[ct.SimSpaceAxis] = bl_cache.BLField(set(ct.SimSpaceAxis))
|
||||||
|
include_3d_x: set[ct.SimAxisDir] = bl_cache.BLField(set(ct.SimAxisDir))
|
||||||
|
include_3d_y: set[ct.SimAxisDir] = bl_cache.BLField(set(ct.SimAxisDir))
|
||||||
|
include_3d_z: set[ct.SimAxisDir] = bl_cache.BLField(set(ct.SimAxisDir))
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - UI
|
||||||
|
####################
|
||||||
|
def draw_props(self, _: bpy.types.Context, layout: bpy.types.UILayout) -> None:
|
||||||
|
# 2D Monitor
|
||||||
|
if 0 in self._compute_input('Size'):
|
||||||
|
layout.prop(self, self.blfields['direction_2d'], expand=True)
|
||||||
|
|
||||||
|
# 3D Monitor
|
||||||
|
else:
|
||||||
|
layout.prop(self, self.blfields['include_3d'], expand=True)
|
||||||
|
row = layout.row(align=False)
|
||||||
|
if ct.SimSpaceAxis.X in self.include_3d:
|
||||||
|
row.prop(self, self.blfields['include_3d_x'], expand=True)
|
||||||
|
if ct.SimSpaceAxis.Y in self.include_3d:
|
||||||
|
row.prop(self, self.blfields['include_3d_y'], expand=True)
|
||||||
|
if ct.SimSpaceAxis.Z in self.include_3d:
|
||||||
|
row.prop(self, self.blfields['include_3d_z'], expand=True)
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Event Methods: Computation
|
# - Event Methods: Computation
|
||||||
####################
|
####################
|
||||||
@events.computes_output_socket(
|
@events.computes_output_socket(
|
||||||
'Freq Monitor',
|
'Freq Monitor',
|
||||||
props={'sim_node_name'},
|
props={'sim_node_name', 'direction_2d'},
|
||||||
input_sockets={
|
input_sockets={
|
||||||
'Center',
|
'Center',
|
||||||
'Size',
|
'Size',
|
||||||
'Samples/Space',
|
'Stride',
|
||||||
'Freqs',
|
'Freqs',
|
||||||
'Direction',
|
|
||||||
},
|
},
|
||||||
input_socket_kinds={
|
input_socket_kinds={
|
||||||
'Freqs': ct.FlowKind.LazyArrayRange,
|
'Freqs': ct.FlowKind.LazyArrayRange,
|
||||||
|
@ -133,7 +164,7 @@ class PowerFluxMonitorNode(base.MaxwellSimNode):
|
||||||
name=props['sim_node_name'],
|
name=props['sim_node_name'],
|
||||||
interval_space=(1, 1, 1),
|
interval_space=(1, 1, 1),
|
||||||
freqs=input_sockets['Freqs'].realize_array.values,
|
freqs=input_sockets['Freqs'].realize_array.values,
|
||||||
normal_dir='+' if input_sockets['Direction'] else '-',
|
normal_dir=props['direction_2d'].plus_or_minus,
|
||||||
)
|
)
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
@ -143,49 +174,42 @@ class PowerFluxMonitorNode(base.MaxwellSimNode):
|
||||||
# Trigger
|
# Trigger
|
||||||
prop_name='preview_active',
|
prop_name='preview_active',
|
||||||
# Loaded
|
# Loaded
|
||||||
managed_objs={'mesh'},
|
managed_objs={'modifier'},
|
||||||
props={'preview_active'},
|
props={'preview_active'},
|
||||||
)
|
)
|
||||||
def on_preview_changed(self, managed_objs, props):
|
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']:
|
if props['preview_active']:
|
||||||
mesh.show_preview()
|
managed_objs['modifier'].show_preview()
|
||||||
else:
|
else:
|
||||||
mesh.hide_preview()
|
managed_objs['modifier'].hide_preview()
|
||||||
|
|
||||||
@events.on_value_changed(
|
@events.on_value_changed(
|
||||||
# Trigger
|
# Trigger
|
||||||
socket_name={'Center', 'Size', 'Direction'},
|
socket_name={'Center', 'Size'},
|
||||||
|
prop_name={'direction_2d'},
|
||||||
run_on_init=True,
|
run_on_init=True,
|
||||||
# Loaded
|
# Loaded
|
||||||
managed_objs={'mesh', 'modifier'},
|
managed_objs={'mesh', 'modifier'},
|
||||||
input_sockets={'Center', 'Size', 'Direction'},
|
props={'direction_2d'},
|
||||||
|
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',
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
def on_inputs_changed(
|
def on_inputs_changed(self, managed_objs, props, input_sockets, unit_systems):
|
||||||
self,
|
|
||||||
managed_objs: dict,
|
|
||||||
input_sockets: dict,
|
|
||||||
unit_systems: dict,
|
|
||||||
):
|
|
||||||
# Push Input Values to GeoNodes Modifier
|
# Push Input Values to GeoNodes Modifier
|
||||||
managed_objs['modifier'].bl_modifier(
|
managed_objs['modifier'].bl_modifier(
|
||||||
managed_objs['mesh'].bl_object(location=input_sockets['Center']),
|
|
||||||
'NODES',
|
'NODES',
|
||||||
{
|
{
|
||||||
'node_group': import_geonodes(GeoNodes.MonitorPowerFlux),
|
'node_group': import_geonodes(GeoNodes.MonitorPowerFlux),
|
||||||
'unit_system': unit_systems['BlenderUnits'],
|
'unit_system': unit_systems['BlenderUnits'],
|
||||||
'inputs': {
|
'inputs': {
|
||||||
'Size': input_sockets['Size'],
|
'Size': input_sockets['Size'],
|
||||||
'Direction': input_sockets['Direction'],
|
'Direction': props['direction_2d'].true_or_false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
location=input_sockets['Center'],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,173 @@
|
||||||
|
# blender_maxwell
|
||||||
|
# Copyright (C) 2024 blender_maxwell Project Contributors
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Affero General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import typing as typ
|
||||||
|
|
||||||
|
import sympy as sp
|
||||||
|
import tidy3d as td
|
||||||
|
|
||||||
|
from blender_maxwell.assets.geonodes import GeoNodes, import_geonodes
|
||||||
|
from blender_maxwell.utils import extra_sympy_units as spux
|
||||||
|
from blender_maxwell.utils import logger
|
||||||
|
|
||||||
|
from ... import contracts as ct
|
||||||
|
from ... import managed_objs, sockets
|
||||||
|
from .. import base, events
|
||||||
|
|
||||||
|
log = logger.get(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class PermittivityMonitorNode(base.MaxwellSimNode):
|
||||||
|
"""Provides a bounded 1D/2D/3D recording region for the diagonal of the complex-valued permittivity tensor."""
|
||||||
|
|
||||||
|
node_type = ct.NodeType.PermittivityMonitor
|
||||||
|
bl_label = 'Permittivity Monitor'
|
||||||
|
use_sim_node_name = True
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Sockets
|
||||||
|
####################
|
||||||
|
input_sockets: typ.ClassVar = {
|
||||||
|
'Center': sockets.ExprSocketDef(
|
||||||
|
size=spux.NumberSize1D.Vec3,
|
||||||
|
physical_type=spux.PhysicalType.Length,
|
||||||
|
),
|
||||||
|
'Size': sockets.ExprSocketDef(
|
||||||
|
size=spux.NumberSize1D.Vec3,
|
||||||
|
physical_type=spux.PhysicalType.Length,
|
||||||
|
default_value=sp.Matrix([1, 1, 1]),
|
||||||
|
abs_min=0,
|
||||||
|
),
|
||||||
|
'Stride': sockets.ExprSocketDef(
|
||||||
|
size=spux.NumberSize1D.Vec3,
|
||||||
|
mathtype=spux.MathType.Integer,
|
||||||
|
default_value=sp.Matrix([10, 10, 10]),
|
||||||
|
abs_min=0,
|
||||||
|
),
|
||||||
|
'Freqs': sockets.ExprSocketDef(
|
||||||
|
active_kind=ct.FlowKind.LazyArrayRange,
|
||||||
|
physical_type=spux.PhysicalType.Freq,
|
||||||
|
default_unit=spux.THz,
|
||||||
|
default_min=374.7406, ## 800nm
|
||||||
|
default_max=1498.962, ## 200nm
|
||||||
|
default_steps=100,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
output_sockets: typ.ClassVar = {
|
||||||
|
'Permittivity Monitor': sockets.MaxwellMonitorSocketDef()
|
||||||
|
}
|
||||||
|
|
||||||
|
managed_obj_types: typ.ClassVar = {
|
||||||
|
'modifier': managed_objs.ManagedBLModifier,
|
||||||
|
}
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Output
|
||||||
|
####################
|
||||||
|
@events.computes_output_socket(
|
||||||
|
'Permittivity Monitor',
|
||||||
|
props={'sim_node_name'},
|
||||||
|
input_sockets={
|
||||||
|
'Center',
|
||||||
|
'Size',
|
||||||
|
'Stride',
|
||||||
|
'Freqs',
|
||||||
|
},
|
||||||
|
input_socket_kinds={
|
||||||
|
'Freqs': ct.FlowKind.LazyArrayRange,
|
||||||
|
},
|
||||||
|
unit_systems={'Tidy3DUnits': ct.UNITS_TIDY3D},
|
||||||
|
scale_input_sockets={
|
||||||
|
'Center': 'Tidy3DUnits',
|
||||||
|
'Size': 'Tidy3DUnits',
|
||||||
|
'Freqs': 'Tidy3DUnits',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
def compute_permittivity_monitor(
|
||||||
|
self,
|
||||||
|
input_sockets: dict,
|
||||||
|
props: dict,
|
||||||
|
unit_systems: dict,
|
||||||
|
) -> td.FieldMonitor:
|
||||||
|
log.info(
|
||||||
|
'Computing PermittivityMonitor (name="%s") with center="%s", size="%s"',
|
||||||
|
props['sim_node_name'],
|
||||||
|
input_sockets['Center'],
|
||||||
|
input_sockets['Size'],
|
||||||
|
)
|
||||||
|
return td.PermittivityMonitor(
|
||||||
|
center=input_sockets['Center'],
|
||||||
|
size=input_sockets['Size'],
|
||||||
|
name=props['sim_node_name'],
|
||||||
|
interval_space=tuple(input_sockets['Stride']),
|
||||||
|
freqs=input_sockets['Freqs'].realize().values,
|
||||||
|
)
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Preview
|
||||||
|
####################
|
||||||
|
@events.on_value_changed(
|
||||||
|
# Trigger
|
||||||
|
prop_name='preview_active',
|
||||||
|
# Loaded
|
||||||
|
managed_objs={'modifier'},
|
||||||
|
props={'preview_active'},
|
||||||
|
)
|
||||||
|
def on_preview_changed(self, managed_objs, props):
|
||||||
|
if props['preview_active']:
|
||||||
|
managed_objs['modifier'].show_preview()
|
||||||
|
else:
|
||||||
|
managed_objs['modifier'].hide_preview()
|
||||||
|
|
||||||
|
@events.on_value_changed(
|
||||||
|
# Trigger
|
||||||
|
socket_name={'Center', 'Size'},
|
||||||
|
run_on_init=True,
|
||||||
|
# Loaded
|
||||||
|
managed_objs={'modifier'},
|
||||||
|
input_sockets={'Center', 'Size'},
|
||||||
|
unit_systems={'BlenderUnits': ct.UNITS_BLENDER},
|
||||||
|
scale_input_sockets={
|
||||||
|
'Center': 'BlenderUnits',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
def on_inputs_changed(
|
||||||
|
self,
|
||||||
|
managed_objs: dict,
|
||||||
|
input_sockets: dict,
|
||||||
|
unit_systems: dict,
|
||||||
|
):
|
||||||
|
# Push Input Values to GeoNodes Modifier
|
||||||
|
managed_objs['modifier'].bl_modifier(
|
||||||
|
'NODES',
|
||||||
|
{
|
||||||
|
'node_group': import_geonodes(GeoNodes.MonitorPermittivity),
|
||||||
|
'unit_system': unit_systems['BlenderUnits'],
|
||||||
|
'inputs': {
|
||||||
|
'Size': input_sockets['Size'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
location=input_sockets['Center'],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Blender Registration
|
||||||
|
####################
|
||||||
|
BL_REGISTER = [
|
||||||
|
PermittivityMonitorNode,
|
||||||
|
]
|
||||||
|
BL_NODES = {ct.NodeType.PermittivityMonitor: (ct.NodeCategory.MAXWELLSIM_MONITORS)}
|
|
@ -16,6 +16,8 @@
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
|
|
||||||
|
from blender_maxwell.utils import bl_cache, logger
|
||||||
|
|
||||||
from ... import contracts as ct
|
from ... import contracts as ct
|
||||||
from .. import base
|
from .. import base
|
||||||
|
|
||||||
|
@ -30,19 +32,13 @@ class BoolBLSocket(base.MaxwellSimSocket):
|
||||||
####################
|
####################
|
||||||
# - Properties
|
# - Properties
|
||||||
####################
|
####################
|
||||||
raw_value: bpy.props.BoolProperty(
|
raw_value: bool = bl_cache.BLField(False)
|
||||||
name='Boolean',
|
|
||||||
description='Represents a boolean value',
|
|
||||||
default=False,
|
|
||||||
update=(lambda self, context: self.on_prop_changed('raw_value', context)),
|
|
||||||
)
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Socket UI
|
# - Socket UI
|
||||||
####################
|
####################
|
||||||
def draw_label_row(self, label_col_row: bpy.types.UILayout, text: str) -> None:
|
def draw_label_row(self, label_col_row: bpy.types.UILayout, text: str) -> None:
|
||||||
label_col_row.label(text=text)
|
label_col_row.prop(self, self.blfields['raw_value'], text=text, toggle=True)
|
||||||
label_col_row.prop(self, 'raw_value', text='')
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Computation of Default Value
|
# - Computation of Default Value
|
||||||
|
|
|
@ -22,7 +22,6 @@ import typing as typ
|
||||||
import bpy
|
import bpy
|
||||||
import pydantic as pyd
|
import pydantic as pyd
|
||||||
import sympy as sp
|
import sympy as sp
|
||||||
import sympy.physics.units as spu
|
|
||||||
|
|
||||||
from blender_maxwell.utils import bl_cache, logger
|
from blender_maxwell.utils import bl_cache, logger
|
||||||
from blender_maxwell.utils import extra_sympy_units as spux
|
from blender_maxwell.utils import extra_sympy_units as spux
|
||||||
|
@ -126,7 +125,6 @@ class ExprBLSocket(base.MaxwellSimSocket):
|
||||||
|
|
||||||
active_unit: enum.StrEnum = bl_cache.BLField(
|
active_unit: enum.StrEnum = bl_cache.BLField(
|
||||||
enum_cb=lambda self, _: self.search_valid_units(),
|
enum_cb=lambda self, _: self.search_valid_units(),
|
||||||
use_prop_update=False,
|
|
||||||
cb_depends_on={'physical_type'},
|
cb_depends_on={'physical_type'},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -19,11 +19,14 @@ import scipy as sc
|
||||||
import sympy.physics.units as spu
|
import sympy.physics.units as spu
|
||||||
import tidy3d as td
|
import tidy3d as td
|
||||||
|
|
||||||
|
from blender_maxwell.utils import bl_cache, logger
|
||||||
from blender_maxwell.utils import extra_sympy_units as spux
|
from blender_maxwell.utils import extra_sympy_units as spux
|
||||||
|
|
||||||
from ... import contracts as ct
|
from ... import contracts as ct
|
||||||
from .. import base
|
from .. import base
|
||||||
|
|
||||||
|
log = logger.get(__name__)
|
||||||
|
|
||||||
VAC_SPEED_OF_LIGHT = sc.constants.speed_of_light * spu.meter / spu.second
|
VAC_SPEED_OF_LIGHT = sc.constants.speed_of_light * spu.meter / spu.second
|
||||||
|
|
||||||
FIXED_WL = 500 * spu.nm
|
FIXED_WL = 500 * spu.nm
|
||||||
|
@ -36,16 +39,7 @@ class MaxwellMediumBLSocket(base.MaxwellSimSocket):
|
||||||
####################
|
####################
|
||||||
# - Properties
|
# - Properties
|
||||||
####################
|
####################
|
||||||
rel_permittivity: bpy.props.FloatVectorProperty(
|
rel_permittivity: tuple[float, float] = bl_cache.BLField((1.0, 0.0), float_prec=2)
|
||||||
name='Relative Permittivity',
|
|
||||||
description='Represents a simple, complex permittivity',
|
|
||||||
size=2,
|
|
||||||
default=(1.0, 0.0),
|
|
||||||
precision=2,
|
|
||||||
update=(
|
|
||||||
lambda self, context: self.on_prop_changed('rel_permittivity', context)
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - FlowKinds
|
# - FlowKinds
|
||||||
|
@ -83,7 +77,7 @@ class MaxwellMediumBLSocket(base.MaxwellSimSocket):
|
||||||
col.label(text='ϵ_r (ℂ)')
|
col.label(text='ϵ_r (ℂ)')
|
||||||
|
|
||||||
col = split.column(align=True)
|
col = split.column(align=True)
|
||||||
col.prop(self, 'rel_permittivity', text='')
|
col.prop(self, self.blfields['rel_permittivity'], text='')
|
||||||
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
|
|
@ -649,7 +649,8 @@ class BLPropType(enum.StrEnum):
|
||||||
case BPT.SingleEnum if isinstance(raw_value, str):
|
case BPT.SingleEnum if isinstance(raw_value, str):
|
||||||
return obj_type(raw_value)
|
return obj_type(raw_value)
|
||||||
case BPT.SetEnum if isinstance(raw_value, set):
|
case BPT.SetEnum if isinstance(raw_value, set):
|
||||||
return {obj_type(v) for v in raw_value}
|
SubStrEnum = typ.get_args(obj_type)[0]
|
||||||
|
return {SubStrEnum(v) for v in raw_value}
|
||||||
|
|
||||||
## Dynamic Enums: Nothing to coerce to.
|
## Dynamic Enums: Nothing to coerce to.
|
||||||
## -> The critical distinction is that dynamic enums can't be coerced beyond str.
|
## -> The critical distinction is that dynamic enums can't be coerced beyond str.
|
||||||
|
|
|
@ -154,7 +154,6 @@ class CachedBLProperty:
|
||||||
if self.persist and not self.suppress_write.get(
|
if self.persist and not self.suppress_write.get(
|
||||||
bl_instance.instance_id
|
bl_instance.instance_id
|
||||||
):
|
):
|
||||||
self.suppress_next_write(bl_instance)
|
|
||||||
self.bl_prop.write(bl_instance, self.getter_method(bl_instance))
|
self.bl_prop.write(bl_instance, self.getter_method(bl_instance))
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
Loading…
Reference in New Issue