diff --git a/src/blender_maxwell/assets/geonodes.py b/src/blender_maxwell/assets/geonodes.py index 4b1cfd3..7a3dc69 100644 --- a/src/blender_maxwell/assets/geonodes.py +++ b/src/blender_maxwell/assets/geonodes.py @@ -75,7 +75,7 @@ class GeoNodes(enum.StrEnum): ## Monitor MonitorEHField = '_monitor_eh_field' MonitorPowerFlux = '_monitor_power_flux' - MonitorEpsTensor = '_monitor_eps_tensor' + MonitorPermittivity = '_monitor_permittivity' MonitorDiffraction = '_monitor_diffraction' MonitorProjCartEHField = '_monitor_proj_eh_field' MonitorProjAngEHField = '_monitor_proj_ang_eh_field' @@ -186,7 +186,7 @@ class GeoNodes(enum.StrEnum): ## Monitor GN.MonitorEHField: 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.MonitorProjCartEHField: GN_INTERNAL_MONITORS_PATH, GN.MonitorProjAngEHField: GN_INTERNAL_MONITORS_PATH, diff --git a/src/blender_maxwell/assets/internal/monitor/_monitor_permittivity.blend b/src/blender_maxwell/assets/internal/monitor/_monitor_permittivity.blend new file mode 100644 index 0000000..5d24864 --- /dev/null +++ b/src/blender_maxwell/assets/internal/monitor/_monitor_permittivity.blend @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:48e668a6384bb2ad748984b0dfcde49f9c107ac21861aee2dd24dc77122d6ecb +size 848107 diff --git a/src/blender_maxwell/node_trees/maxwell_sim_nodes/contracts/__init__.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/contracts/__init__.py index b672d2f..498acf2 100644 --- a/src/blender_maxwell/node_trees/maxwell_sim_nodes/contracts/__init__.py +++ b/src/blender_maxwell/node_trees/maxwell_sim_nodes/contracts/__init__.py @@ -61,6 +61,7 @@ from .sim_types import ( BoundCondType, NewSimCloudTask, SimAxisDir, + SimFieldPols, SimSpaceAxis, manual_amp_time, ) @@ -104,6 +105,7 @@ __all__ = [ 'BoundCondType', 'NewSimCloudTask', 'SimAxisDir', + 'SimFieldPols', 'SimSpaceAxis', 'manual_amp_time', 'NodeCategory', diff --git a/src/blender_maxwell/node_trees/maxwell_sim_nodes/contracts/flow_kinds/flow_kinds.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/contracts/flow_kinds/flow_kinds.py index 9baff92..8bebf54 100644 --- a/src/blender_maxwell/node_trees/maxwell_sim_nodes/contracts/flow_kinds/flow_kinds.py +++ b/src/blender_maxwell/node_trees/maxwell_sim_nodes/contracts/flow_kinds/flow_kinds.py @@ -120,7 +120,7 @@ class FlowKind(enum.StrEnum): FlowKind.Value: 'Value', FlowKind.Array: 'Array', FlowKind.LazyArrayRange: 'Range', - FlowKind.LazyValueFunc: 'Lazy Value', + FlowKind.LazyValueFunc: 'Func', FlowKind.Params: 'Parameters', FlowKind.Info: 'Information', }[v] diff --git a/src/blender_maxwell/node_trees/maxwell_sim_nodes/contracts/sim_types.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/contracts/sim_types.py index 33cdb0e..b30ea44 100644 --- a/src/blender_maxwell/node_trees/maxwell_sim_nodes/contracts/sim_types.py +++ b/src/blender_maxwell/node_trees/maxwell_sim_nodes/contracts/sim_types.py @@ -169,6 +169,52 @@ class SimAxisDir(enum.StrEnum): 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 #################### diff --git a/src/blender_maxwell/node_trees/maxwell_sim_nodes/managed_objs/base.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/managed_objs/base.py index 29ba0f6..f3fd0dd 100644 --- a/src/blender_maxwell/node_trees/maxwell_sim_nodes/managed_objs/base.py +++ b/src/blender_maxwell/node_trees/maxwell_sim_nodes/managed_objs/base.py @@ -25,14 +25,28 @@ log = logger.get(__name__) 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 @abc.abstractmethod - def __init__( - self, - name: ct.ManagedObjName, - ): - """Initializes the managed object with a unique name.""" + def __init__(self, name: ct.ManagedObjName, prev_name: str | None = None): + """Initializes the managed object with a unique name. + + Use `prev_name` to indicate that the managed object will initially be avaiable under `prev_name`, but that it should be renamed to `name`. + """ #################### # - Properties @@ -60,7 +74,7 @@ class ManagedObj(abc.ABC): @abc.abstractmethod 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 diff --git a/src/blender_maxwell/node_trees/maxwell_sim_nodes/managed_objs/managed_bl_mesh.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/managed_objs/managed_bl_mesh.py index 89e6f35..12b8cb3 100644 --- a/src/blender_maxwell/node_trees/maxwell_sim_nodes/managed_objs/managed_bl_mesh.py +++ b/src/blender_maxwell/node_trees/maxwell_sim_nodes/managed_objs/managed_bl_mesh.py @@ -45,82 +45,57 @@ class ManagedBLMesh(base.ManagedObj): @name.setter def name(self, value: str) -> None: - log.info( + log.debug( 'Changing BLMesh w/Name "%s" to Name "%s"', self._bl_object_name, value ) - if self._bl_object_name == value: - ## 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 + existing_bl_object = bpy.data.objects.get(self.name) - if (bl_object := bpy.data.objects.get(value)) is None: - log.info( - '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 + # No Existing Object: Set Value to Name + if existing_bl_object is None: self._bl_object_name = value + + # Existing Object: Rename to New Name else: - log.info( - 'Desired BLMesh Name "%s" is Taken. Using Blender Rename', - value, - ) + existing_bl_object.name = value + self._bl_object_name = value - # Set Name Anyway, but Respect Blender's Renaming - ## When a name already exists, Blender adds .### to prevent overlap. - ## `set_name` is allowed to change the name; nodes account for this. - bl_object.name = value - self._bl_object_name = bl_object.name - - log.info( - 'Changed BLMesh Name to "%s"', - bl_object.name, - ) + # Check: Blender Rename -> Synchronization Error + ## -> We can't do much else than report to the user & free(). + if existing_bl_object.name != self._bl_object_name: + log.critical( + 'BLMesh: Failed to set name of %s to %s, as %s already exists.' + ) + self._bl_object_name = existing_bl_object.name + self.free() #################### # - 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 #################### # - Deallocation #################### 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 - # Delete the Underlying Datablock - ## This automatically deletes the object too - log.info('Removing "%s" BLMesh', bl_object.type) + # Delete the Mesh Datablock + ## -> This automatically deletes the object too + log.info('BLMesh: Freeing "%s"', self.name) bpy.data.meshes.remove(bl_object.data) #################### # - Methods #################### - @property - def exists(self) -> bool: - return bpy.data.objects.get(self.name) is not None - def show_preview(self) -> None: """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) 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() if bl_object.name not in preview_collection().objects: @@ -136,24 +111,19 @@ class ManagedBLMesh(base.ManagedObj): 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. - """ + """Hide any active preview of the managed object, if it exists, and if such an operation makes sense.""" 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.""" - if (bl_object := bpy.data.objects.get(self.name)) is not None: + """Select the managed object in Blender, if it exists, and if such an operation makes sense.""" + bl_object = bpy.data.objects.get(self.name) + if bl_object is not None: bpy.ops.object.select_all(action='DESELECT') bl_object.select_set(True) - msg = 'Managed BLMesh does not exist' - raise ValueError(msg) - #################### # - BLMesh Management #################### diff --git a/src/blender_maxwell/node_trees/maxwell_sim_nodes/managed_objs/managed_bl_modifier.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/managed_objs/managed_bl_modifier.py index 100e3ed..5530c5a 100644 --- a/src/blender_maxwell/node_trees/maxwell_sim_nodes/managed_objs/managed_bl_modifier.py +++ b/src/blender_maxwell/node_trees/maxwell_sim_nodes/managed_objs/managed_bl_modifier.py @@ -26,6 +26,7 @@ from blender_maxwell.utils import logger from .. import bl_socket_map from .. import contracts as ct from . import base +from .managed_bl_mesh import ManagedBLMesh log = logger.get(__name__) @@ -71,7 +72,7 @@ def read_modifier(bl_modifier: bpy.types.Modifier) -> ModifierAttrs: return { 'node_group': bl_modifier.node_group, } - elif bl_modifier.type == 'ARRAY': + if bl_modifier.type == 'ARRAY': raise NotImplementedError raise NotImplementedError @@ -162,6 +163,7 @@ def write_modifier( class ManagedBLModifier(base.ManagedObj): managed_obj_type = ct.ManagedObjType.ManagedBLModifier _modifier_name: str | None = None + twin_bl_mesh: ManagedBLMesh | None = None #################### # - BL Object Name @@ -172,94 +174,117 @@ class ManagedBLModifier(base.ManagedObj): @name.setter def name(self, value: str) -> None: - ## TODO: Handle name conflict within same BLObject - log.info( - 'Changing BLModifier w/Name "%s" to Name "%s"', self._modifier_name, value - ) - self._modifier_name = value + log.debug('Changing BLModifier w/Name "%s" to Name "%s"', self.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 #################### # - 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 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: - pass + self.twin_bl_mesh.hide_preview() #################### # - Deallocation #################### def free(self): - """Not needed - when the object is removed, its modifiers are also removed.""" - - 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) + log.info('BLModifier: Freeing "%s" w/Twin BLObject of same name', self.name) + self.twin_bl_mesh.free() #################### # - Modifiers #################### def bl_modifier( self, - bl_object: bpy.types.Object, modifier_type: ct.BLModifierType, modifier_attrs: ModifierAttrs, + location: tuple[float, float, float] = (0, 0, 0), ): """Creates a new modifier for the current `bl_object`. - Modifier Type Names: """ - # 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 - if ( - bl_modifier := bl_object.modifiers.get(self.name) - ) and bl_modifier.type != modifier_type: + if bl_modifier is not None and bl_modifier.type != modifier_type: log.info( - 'Removing (recreating) BLModifier "%s" on BLObject "%s" (existing modifier_type is "%s", but "%s" is requested)', - bl_modifier.name, - bl_object.name, - bl_modifier.type, - modifier_type, + 'BLModifier: Clearing BLModifier "%s" from BLObject "%s"', + self.name, + twin_bl_object.name, ) - self.free_from_bl_object(bl_object) + twin_bl_object.modifiers.remove(bl_modifier) modifier_was_removed = True - # Create Modifier + # No/Removed Modifier: Create if bl_modifier is None or modifier_was_removed: log.info( - 'Creating BLModifier "%s" on BLObject "%s" with modifier_type "%s"', + 'BLModifier: (Re)Creating BLModifier "%s" on BLObject "%s" (type=%s)', self.name, - bl_object.name, + twin_bl_object.name, modifier_type, ) - bl_modifier = bl_object.modifiers.new( + bl_modifier = twin_bl_object.modifiers.new( name=self.name, 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) if modifier_altered: - bl_object.data.update() + twin_bl_object.data.update() return bl_modifier + + #################### + # - Mesh Data + #################### + @property + def mesh_as_arrays(self) -> dict: + return self.twin_bl_mesh.mesh_as_arrays diff --git a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/base.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/base.py index 60cdf79..071422a 100644 --- a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/base.py +++ b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/base.py @@ -131,27 +131,10 @@ class MaxwellSimNode(bpy.types.Node, bl_instance.BLInstance): for i, (preset_name, preset_def) in enumerate(cls.presets.items()) ] - #################### - # - Managed Objects - #################### - @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 {} + # Managed Objects + managed_objs: dict[str, _managed_objs.ManagedObj] = bl_cache.BLField( + {}, use_prop_update=False + ) #################### # - Class Methods @@ -221,25 +204,30 @@ class MaxwellSimNode(bpy.types.Node, bl_instance.BLInstance): #################### @events.on_value_changed( prop_name='sim_node_name', - props={'sim_node_name', 'managed_objs'}, + props={'sim_node_name', 'managed_objs', 'managed_obj_types'}, stop_propagation=True, ) def _on_sim_node_name_changed(self, props): - log.info( + log.debug( 'Changed Sim Node Name of a "%s" to "%s" (self=%s)', self.bl_idname, props['sim_node_name'], str(self), ) - # Set Name of Managed Objects - for mobj in props['managed_objs'].values(): - mobj.name = props['sim_node_name'] - - ## Invalidate Cache - ## -> Persistance doesn't happen if we simply mutate. - ## -> This ensures that the name change is picked up. - self.managed_objs = bl_cache.Signal.InvalidateCache + # (Re)Construct Managed Objects + ## -> Due to 'prev_name', the new MObjs will be renamed on construction + self.managed_objs = { + mobj_name: mobj_type( + self.sim_node_name, + prev_name=( + props['managed_objs'][mobj_name].name + 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') def _on_socket_set_changed(self): @@ -1027,12 +1015,14 @@ class MaxwellSimNode(bpy.types.Node, bl_instance.BLInstance): bl_socket.reset_instance_id() # 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 # Event Methods - ## Run any 'DataChanged' methods with 'run_on_init' set. - ## -> Copying a node _arguably_ re-initializes the new node. + ## -> Re-run any 'DataChanged' methods with 'run_on_init' set. + ## -> Copying a node ~ re-initializing the new node. for event_method in [ event_method for event_method in self.event_methods_by_event[ct.FlowEvent.DataChanged] diff --git a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/bounds/bound_cond_nodes/bloch_bound_cond.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/bounds/bound_cond_nodes/bloch_bound_cond.py index a96730b..e1087cf 100644 --- a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/bounds/bound_cond_nodes/bloch_bound_cond.py +++ b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/bounds/bound_cond_nodes/bloch_bound_cond.py @@ -132,7 +132,7 @@ class BlochBoundCondNode(base.MaxwellSimNode): #################### # - 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 diff --git a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/mediums/library_medium.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/mediums/library_medium.py index 121b053..ac6ec82 100644 --- a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/mediums/library_medium.py +++ b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/mediums/library_medium.py @@ -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 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 ... import contracts as ct from ... import managed_objs, sockets from .. import base, events +log = logger.get(__name__) + _mat_lib_iter = iter(td.material_library) _mat_key = '' @@ -131,7 +133,8 @@ class LibraryMediumNode(base.MaxwellSimNode): #################### vendored_medium: VendoredMedium = bl_cache.BLField(VendoredMedium.Au) 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]: @@ -141,7 +144,7 @@ class LibraryMediumNode(base.MaxwellSimNode): #################### # - 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: """Deduce the actual medium variant from `self.vendored_medium` and `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: 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 #################### @@ -264,13 +252,6 @@ class LibraryMediumNode(base.MaxwellSimNode): def compute_medium(self, props) -> sp.Expr: 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( 'Valid Freqs', kind=ct.FlowKind.LazyArrayRange, @@ -285,13 +266,6 @@ class LibraryMediumNode(base.MaxwellSimNode): 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( 'Valid WLs', kind=ct.FlowKind.LazyArrayRange, @@ -320,7 +294,7 @@ class LibraryMediumNode(base.MaxwellSimNode): props, ): 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, ) ## TODO: Plot based on Wl, not freq. diff --git a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/monitors/__init__.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/monitors/__init__.py index 4b8bd34..e2b690c 100644 --- a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/monitors/__init__.py +++ b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/monitors/__init__.py @@ -14,20 +14,19 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -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 BL_REGISTER = [ *eh_field_monitor.BL_REGISTER, *field_power_flux_monitor.BL_REGISTER, - # *epsilon_tensor_monitor.BL_REGISTER, + *permittivity_monitor.BL_REGISTER, # *diffraction_monitor.BL_REGISTER, ] BL_NODES = { **eh_field_monitor.BL_NODES, **field_power_flux_monitor.BL_NODES, - # **epsilon_tensor_monitor.BL_NODES, + **permittivity_monitor.BL_NODES, # **diffraction_monitor.BL_NODES, } diff --git a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/monitors/eh_field_monitor.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/monitors/eh_field_monitor.py index af9469d..b3d27cc 100644 --- a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/monitors/eh_field_monitor.py +++ b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/monitors/eh_field_monitor.py @@ -16,13 +16,14 @@ import typing as typ +import bpy import sympy as sp import sympy.physics.units as spu import tidy3d as td 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 logger from ... import contracts as ct from ... import managed_objs, sockets @@ -50,11 +51,13 @@ class EHFieldMonitorNode(base.MaxwellSimNode): size=spux.NumberSize1D.Vec3, physical_type=spux.PhysicalType.Length, default_value=sp.Matrix([1, 1, 1]), + abs_min=0, ), - 'Spatial Subdivs': sockets.ExprSocketDef( + 'Stride': sockets.ExprSocketDef( size=spux.NumberSize1D.Vec3, mathtype=spux.MathType.Integer, default_value=sp.Matrix([10, 10, 10]), + abs_min=0, ), } input_socket_sets: typ.ClassVar = { @@ -69,15 +72,15 @@ class EHFieldMonitorNode(base.MaxwellSimNode): ), }, 'Time Domain': { - 'Time Range': sockets.ExprSocketDef( + 't Range': sockets.ExprSocketDef( active_kind=ct.FlowKind.LazyArrayRange, physical_type=spux.PhysicalType.Time, default_unit=spu.picosecond, default_min=0, default_max=10, - default_steps=2, + default_steps=0, ), - 'Temporal Subdivs': sockets.ExprSocketDef( + 't Stride': sockets.ExprSocketDef( mathtype=spux.MathType.Integer, default_value=100, ), @@ -89,20 +92,30 @@ class EHFieldMonitorNode(base.MaxwellSimNode): } managed_obj_types: typ.ClassVar = { - 'mesh': managed_objs.ManagedBLMesh, '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 #################### @events.computes_output_socket( 'Freq Monitor', - props={'sim_node_name'}, + props={'sim_node_name', 'fields'}, input_sockets={ 'Center', 'Size', - 'Spatial Subdivs', + 'Stride', 'Freqs', }, input_socket_kinds={ @@ -131,28 +144,29 @@ class EHFieldMonitorNode(base.MaxwellSimNode): center=input_sockets['Center'], size=input_sockets['Size'], name=props['sim_node_name'], - interval_space=tuple(input_sockets['Spatial Subdivs']), + interval_space=tuple(input_sockets['Stride']), freqs=input_sockets['Freqs'].realize().values, + fields=props['fields'], ) @events.computes_output_socket( 'Time Monitor', - props={'sim_node_name'}, + props={'sim_node_name', 'fields'}, input_sockets={ 'Center', 'Size', - 'Spatial Subdivs', - 'Time Range', - 'Temporal Subdivs', + 'Stride', + 't Range', + 't Stride', }, input_socket_kinds={ - 'Time Range': ct.FlowKind.LazyArrayRange, + 't Range': ct.FlowKind.LazyArrayRange, }, unit_systems={'Tidy3DUnits': ct.UNITS_TIDY3D}, scale_input_sockets={ 'Center': 'Tidy3DUnits', 'Size': 'Tidy3DUnits', - 'Time Range': 'Tidy3DUnits', + 't Range': 'Tidy3DUnits', }, ) def compute_time_monitor( @@ -171,10 +185,11 @@ class EHFieldMonitorNode(base.MaxwellSimNode): center=input_sockets['Center'], size=input_sockets['Size'], name=props['sim_node_name'], - interval_space=tuple(input_sockets['Spatial Subdivs']), - start=input_sockets['Time Range'].realize_start(), - stop=input_sockets['Time Range'].realize_stop(), - interval=input_sockets['Temporal Subdivs'], + interval_space=tuple(input_sockets['Stride']), + start=input_sockets['t Range'].realize_start(), + stop=input_sockets['t Range'].realize_stop(), + interval=input_sockets['t Stride'], + fields=props['fields'], ) #################### @@ -184,26 +199,21 @@ class EHFieldMonitorNode(base.MaxwellSimNode): # Trigger prop_name='preview_active', # Loaded - managed_objs={'mesh'}, + managed_objs={'modifier'}, 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 + def on_preview_changed(self, managed_objs, props): if props['preview_active']: - mesh.show_preview() + managed_objs['modifier'].show_preview() else: - mesh.hide_preview() + managed_objs['modifier'].hide_preview() @events.on_value_changed( # Trigger socket_name={'Center', 'Size'}, run_on_init=True, # Loaded - managed_objs={'mesh', 'modifier'}, + managed_objs={'modifier'}, input_sockets={'Center', 'Size'}, unit_systems={'BlenderUnits': ct.UNITS_BLENDER}, scale_input_sockets={ @@ -218,7 +228,6 @@ class EHFieldMonitorNode(base.MaxwellSimNode): ): # Push Input Values to GeoNodes Modifier managed_objs['modifier'].bl_modifier( - managed_objs['mesh'].bl_object(location=input_sockets['Center']), 'NODES', { 'node_group': import_geonodes(GeoNodes.MonitorEHField), @@ -227,6 +236,7 @@ class EHFieldMonitorNode(base.MaxwellSimNode): 'Size': input_sockets['Size'], }, }, + location=input_sockets['Center'], ) diff --git a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/monitors/epsilon_tensor_monitor.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/monitors/epsilon_tensor_monitor.py deleted file mode 100644 index 5b7d824..0000000 --- a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/monitors/epsilon_tensor_monitor.py +++ /dev/null @@ -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 . - -#################### -# - Blender Registration -#################### -BL_REGISTER = [] -BL_NODES = {} diff --git a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/monitors/field_power_flux_monitor.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/monitors/field_power_flux_monitor.py index d0519ed..de1371e 100644 --- a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/monitors/field_power_flux_monitor.py +++ b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/monitors/field_power_flux_monitor.py @@ -16,13 +16,14 @@ import typing as typ +import bpy import sympy as sp import sympy.physics.units as spu import tidy3d as td 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 logger from ... import contracts as ct from ... import managed_objs, sockets @@ -32,6 +33,8 @@ log = logger.get(__name__) 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 bl_label = 'Power Flux Monitor' use_sim_node_name = True @@ -48,13 +51,14 @@ class PowerFluxMonitorNode(base.MaxwellSimNode): size=spux.NumberSize1D.Vec3, physical_type=spux.PhysicalType.Length, default_value=sp.Matrix([1, 1, 1]), + abs_min=0, ), - 'Samples/Space': sockets.ExprSocketDef( + 'Stride': sockets.ExprSocketDef( size=spux.NumberSize1D.Vec3, mathtype=spux.MathType.Integer, default_value=sp.Matrix([10, 10, 10]), + abs_min=0, ), - 'Direction': sockets.BoolSocketDef(), } input_socket_sets: typ.ClassVar = { 'Freq Domain': { @@ -68,7 +72,7 @@ class PowerFluxMonitorNode(base.MaxwellSimNode): ), }, 'Time Domain': { - 'Time Range': sockets.ExprSocketDef( + 't Range': sockets.ExprSocketDef( active_kind=ct.FlowKind.LazyArrayRange, physical_type=spux.PhysicalType.Time, default_unit=spu.picosecond, @@ -76,7 +80,7 @@ class PowerFluxMonitorNode(base.MaxwellSimNode): default_max=10, default_steps=2, ), - 'Samples/Time': sockets.ExprSocketDef( + 't Stride': sockets.ExprSocketDef( mathtype=spux.MathType.Integer, default_value=100, ), @@ -92,18 +96,45 @@ class PowerFluxMonitorNode(base.MaxwellSimNode): '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 #################### @events.computes_output_socket( 'Freq Monitor', - props={'sim_node_name'}, + props={'sim_node_name', 'direction_2d'}, input_sockets={ 'Center', 'Size', - 'Samples/Space', + 'Stride', 'Freqs', - 'Direction', }, input_socket_kinds={ 'Freqs': ct.FlowKind.LazyArrayRange, @@ -133,7 +164,7 @@ class PowerFluxMonitorNode(base.MaxwellSimNode): name=props['sim_node_name'], interval_space=(1, 1, 1), 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 prop_name='preview_active', # Loaded - managed_objs={'mesh'}, + managed_objs={'modifier'}, props={'preview_active'}, ) 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() + managed_objs['modifier'].show_preview() else: - mesh.hide_preview() + managed_objs['modifier'].hide_preview() @events.on_value_changed( # Trigger - socket_name={'Center', 'Size', 'Direction'}, + socket_name={'Center', 'Size'}, + prop_name={'direction_2d'}, run_on_init=True, # Loaded managed_objs={'mesh', 'modifier'}, - input_sockets={'Center', 'Size', 'Direction'}, + props={'direction_2d'}, + 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, - ): + def on_inputs_changed(self, managed_objs, props, input_sockets, unit_systems): # Push Input Values to GeoNodes Modifier managed_objs['modifier'].bl_modifier( - managed_objs['mesh'].bl_object(location=input_sockets['Center']), 'NODES', { 'node_group': import_geonodes(GeoNodes.MonitorPowerFlux), 'unit_system': unit_systems['BlenderUnits'], 'inputs': { 'Size': input_sockets['Size'], - 'Direction': input_sockets['Direction'], + 'Direction': props['direction_2d'].true_or_false, }, }, + location=input_sockets['Center'], ) diff --git a/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/monitors/permittivity_monitor.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/monitors/permittivity_monitor.py new file mode 100644 index 0000000..ae3e568 --- /dev/null +++ b/src/blender_maxwell/node_trees/maxwell_sim_nodes/nodes/monitors/permittivity_monitor.py @@ -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 . + +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)} diff --git a/src/blender_maxwell/node_trees/maxwell_sim_nodes/sockets/basic/bool.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/sockets/basic/bool.py index d45a19a..b4b9913 100644 --- a/src/blender_maxwell/node_trees/maxwell_sim_nodes/sockets/basic/bool.py +++ b/src/blender_maxwell/node_trees/maxwell_sim_nodes/sockets/basic/bool.py @@ -16,6 +16,8 @@ import bpy +from blender_maxwell.utils import bl_cache, logger + from ... import contracts as ct from .. import base @@ -30,19 +32,13 @@ class BoolBLSocket(base.MaxwellSimSocket): #################### # - Properties #################### - raw_value: bpy.props.BoolProperty( - name='Boolean', - description='Represents a boolean value', - default=False, - update=(lambda self, context: self.on_prop_changed('raw_value', context)), - ) + raw_value: bool = bl_cache.BLField(False) #################### # - Socket UI #################### 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, 'raw_value', text='') + label_col_row.prop(self, self.blfields['raw_value'], text=text, toggle=True) #################### # - Computation of Default Value diff --git a/src/blender_maxwell/node_trees/maxwell_sim_nodes/sockets/expr.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/sockets/expr.py index 7a57d0c..33ba169 100644 --- a/src/blender_maxwell/node_trees/maxwell_sim_nodes/sockets/expr.py +++ b/src/blender_maxwell/node_trees/maxwell_sim_nodes/sockets/expr.py @@ -22,7 +22,6 @@ import typing as typ import bpy import pydantic as pyd import sympy as sp -import sympy.physics.units as spu from blender_maxwell.utils import bl_cache, logger 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( enum_cb=lambda self, _: self.search_valid_units(), - use_prop_update=False, cb_depends_on={'physical_type'}, ) diff --git a/src/blender_maxwell/node_trees/maxwell_sim_nodes/sockets/maxwell/medium.py b/src/blender_maxwell/node_trees/maxwell_sim_nodes/sockets/maxwell/medium.py index cb6dfa3..55f707e 100644 --- a/src/blender_maxwell/node_trees/maxwell_sim_nodes/sockets/maxwell/medium.py +++ b/src/blender_maxwell/node_trees/maxwell_sim_nodes/sockets/maxwell/medium.py @@ -19,11 +19,14 @@ import scipy as sc import sympy.physics.units as spu import tidy3d as td +from blender_maxwell.utils import bl_cache, logger from blender_maxwell.utils import extra_sympy_units as spux from ... import contracts as ct from .. import base +log = logger.get(__name__) + VAC_SPEED_OF_LIGHT = sc.constants.speed_of_light * spu.meter / spu.second FIXED_WL = 500 * spu.nm @@ -36,16 +39,7 @@ class MaxwellMediumBLSocket(base.MaxwellSimSocket): #################### # - Properties #################### - rel_permittivity: bpy.props.FloatVectorProperty( - 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) - ), - ) + rel_permittivity: tuple[float, float] = bl_cache.BLField((1.0, 0.0), float_prec=2) #################### # - FlowKinds @@ -83,7 +77,7 @@ class MaxwellMediumBLSocket(base.MaxwellSimSocket): col.label(text='ϵ_r (ℂ)') col = split.column(align=True) - col.prop(self, 'rel_permittivity', text='') + col.prop(self, self.blfields['rel_permittivity'], text='') #################### diff --git a/src/blender_maxwell/utils/bl_cache/bl_prop_type.py b/src/blender_maxwell/utils/bl_cache/bl_prop_type.py index dd23ee1..9f85a68 100644 --- a/src/blender_maxwell/utils/bl_cache/bl_prop_type.py +++ b/src/blender_maxwell/utils/bl_cache/bl_prop_type.py @@ -649,7 +649,8 @@ class BLPropType(enum.StrEnum): case BPT.SingleEnum if isinstance(raw_value, str): return obj_type(raw_value) 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. ## -> The critical distinction is that dynamic enums can't be coerced beyond str. diff --git a/src/blender_maxwell/utils/bl_cache/cached_bl_property.py b/src/blender_maxwell/utils/bl_cache/cached_bl_property.py index 48f9309..cbeac2e 100644 --- a/src/blender_maxwell/utils/bl_cache/cached_bl_property.py +++ b/src/blender_maxwell/utils/bl_cache/cached_bl_property.py @@ -154,7 +154,6 @@ class CachedBLProperty: if self.persist and not self.suppress_write.get( bl_instance.instance_id ): - self.suppress_next_write(bl_instance) self.bl_prop.write(bl_instance, self.getter_method(bl_instance)) else: