refactor: Fixes and movement.
parent
29cee2e7a2
commit
c6e00dcd7b
|
@ -36,7 +36,7 @@
|
||||||
- Output: Write the input socket value.
|
- Output: Write the input socket value.
|
||||||
- Condition: Input socket is unlinked. (If it's linked, then lock the object's position. Use sync_link_added() for that)
|
- Condition: Input socket is unlinked. (If it's linked, then lock the object's position. Use sync_link_added() for that)
|
||||||
- Node to BL:
|
- Node to BL:
|
||||||
- Trigger: "Report" action on an input socket that the managed object declares reliance on.
|
- Trigger: "Report" event on an input socket that the managed object declares reliance on.
|
||||||
- Input: The input socket value (linked or unlinked)
|
- Input: The input socket value (linked or unlinked)
|
||||||
- Output: The object location (origin), using a unit system.
|
- Output: The object location (origin), using a unit system.
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
import typing as typ
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
import pydantic as pyd
|
||||||
|
import typing_extensions as typx
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Blender Strings
|
||||||
|
####################
|
||||||
|
BLEnumID = typx.Annotated[
|
||||||
|
str,
|
||||||
|
pyd.StringConstraints(
|
||||||
|
pattern=r'^[A-Z_]+$',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
SocketName = typx.Annotated[
|
||||||
|
str,
|
||||||
|
pyd.StringConstraints(
|
||||||
|
pattern=r'^[a-zA-Z0-9_]+$',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Blender Enums
|
||||||
|
####################
|
||||||
|
BLModifierType: typ.TypeAlias = typx.Literal['NODES', 'ARRAY']
|
||||||
|
BLNodeTreeInterfaceID: typ.TypeAlias = str
|
||||||
|
|
||||||
|
BLIconSet: frozenset[str] = frozenset(
|
||||||
|
bpy.types.UILayout.bl_rna.functions['prop'].parameters['icon'].enum_items.keys()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Blender Structs
|
||||||
|
####################
|
||||||
|
BLClass: typ.TypeAlias = (
|
||||||
|
bpy.types.Panel
|
||||||
|
| bpy.types.UIList
|
||||||
|
| bpy.types.Menu
|
||||||
|
| bpy.types.Header
|
||||||
|
| bpy.types.Operator
|
||||||
|
| bpy.types.KeyingSetInfo
|
||||||
|
| bpy.types.RenderEngine
|
||||||
|
| bpy.types.AssetShelf
|
||||||
|
| bpy.types.FileHandler
|
||||||
|
)
|
||||||
|
BLKeymapItem: typ.TypeAlias = typ.Any ## TODO: Better Type
|
||||||
|
BLColorRGBA = tuple[float, float, float, float]
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Operators
|
||||||
|
####################
|
||||||
|
BLOperatorStatus: typ.TypeAlias = set[
|
||||||
|
typx.Literal['RUNNING_MODAL', 'CANCELLED', 'FINISHED', 'PASS_THROUGH', 'INTERFACE']
|
||||||
|
]
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Addon Types
|
||||||
|
####################
|
||||||
|
ManagedObjName = typx.Annotated[
|
||||||
|
str,
|
||||||
|
pyd.StringConstraints(
|
||||||
|
pattern=r'^[a-z_]+$',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
KeymapItemDef: typ.TypeAlias = typ.Any ## TODO: Better Type
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Blender Strings
|
||||||
|
####################
|
||||||
|
PresetName = typx.Annotated[
|
||||||
|
str,
|
||||||
|
pyd.StringConstraints(
|
||||||
|
pattern=r'^[a-zA-Z0-9_]+$',
|
||||||
|
),
|
||||||
|
]
|
|
@ -1,79 +1,48 @@
|
||||||
# ruff: noqa: I001
|
from blender_maxwell.contracts import (
|
||||||
|
BLColorRGBA,
|
||||||
|
BLEnumID,
|
||||||
|
BLIconSet,
|
||||||
|
BLModifierType,
|
||||||
|
BLNodeTreeInterfaceID,
|
||||||
|
ManagedObjName,
|
||||||
|
PresetName,
|
||||||
|
SocketName,
|
||||||
|
)
|
||||||
|
|
||||||
####################
|
from .bl_socket_desc_map import BL_SOCKET_DESCR_TYPE_MAP
|
||||||
# - String Types
|
from .bl_socket_types import BL_SOCKET_DESCR_ANNOT_STRING, BL_SOCKET_DIRECT_TYPE_MAP
|
||||||
####################
|
from .category_labels import NODE_CAT_LABELS
|
||||||
from .bl import SocketName
|
from .category_types import NodeCategory
|
||||||
from .bl import PresetName
|
from .flow_events import FlowEvent
|
||||||
from .bl import ManagedObjName
|
from .flow_kinds import (
|
||||||
|
ArrayFlow,
|
||||||
|
CapabilitiesFlow,
|
||||||
from .bl import BLEnumID
|
FlowKind,
|
||||||
from .bl import BLColorRGBA
|
InfoFlow,
|
||||||
|
LazyArrayRangeFlow,
|
||||||
####################
|
LazyValueFlow,
|
||||||
# - Icon Types
|
ParamsFlow,
|
||||||
####################
|
ValueFlow,
|
||||||
|
)
|
||||||
from .icons import Icon
|
from .icons import Icon
|
||||||
|
from .mobj_types import ManagedObjType
|
||||||
####################
|
from .node_types import NodeType
|
||||||
# - Tree Types
|
|
||||||
####################
|
|
||||||
from .trees import TreeType
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Socket Types
|
|
||||||
####################
|
|
||||||
from .socket_types import SocketType
|
|
||||||
|
|
||||||
from .socket_units import SOCKET_UNITS
|
|
||||||
from .socket_colors import SOCKET_COLORS
|
from .socket_colors import SOCKET_COLORS
|
||||||
from .socket_shapes import SOCKET_SHAPES
|
from .socket_shapes import SOCKET_SHAPES
|
||||||
|
from .socket_types import SocketType
|
||||||
|
from .socket_units import SOCKET_UNITS
|
||||||
|
from .tree_types import TreeType
|
||||||
from .unit_systems import UNITS_BLENDER, UNITS_TIDY3D
|
from .unit_systems import UNITS_BLENDER, UNITS_TIDY3D
|
||||||
|
|
||||||
from .socket_from_bl_desc import BL_SOCKET_DESCR_TYPE_MAP
|
|
||||||
from .socket_from_bl_direct import BL_SOCKET_DIRECT_TYPE_MAP
|
|
||||||
|
|
||||||
from .socket_from_bl_desc import BL_SOCKET_DESCR_ANNOT_STRING
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Node Types
|
|
||||||
####################
|
|
||||||
from .node_types import NodeType
|
|
||||||
|
|
||||||
from .node_cats import NodeCategory
|
|
||||||
from .node_cat_labels import NODE_CAT_LABELS
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Managed Obj Type
|
|
||||||
####################
|
|
||||||
from .managed_obj_type import ManagedObjType
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Data Flows
|
|
||||||
####################
|
|
||||||
from .data_flows import (
|
|
||||||
FlowKind,
|
|
||||||
CapabilitiesFlow,
|
|
||||||
ValueFlow,
|
|
||||||
ArrayFlow,
|
|
||||||
LazyValueFlow,
|
|
||||||
LazyArrayRangeFlow,
|
|
||||||
ParamsFlow,
|
|
||||||
InfoFlow,
|
|
||||||
)
|
|
||||||
from .data_flow_actions import DataFlowAction
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Export
|
|
||||||
####################
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'SocketName',
|
|
||||||
'PresetName',
|
|
||||||
'ManagedObjName',
|
|
||||||
'BLEnumID',
|
|
||||||
'BLColorRGBA',
|
'BLColorRGBA',
|
||||||
|
'BLEnumID',
|
||||||
|
'BLIconSet',
|
||||||
|
'BLModifierType',
|
||||||
|
'BLNodeTreeInterfaceID',
|
||||||
|
'ManagedObjName',
|
||||||
|
'PresetName',
|
||||||
|
'SocketName',
|
||||||
'Icon',
|
'Icon',
|
||||||
'TreeType',
|
'TreeType',
|
||||||
'SocketType',
|
'SocketType',
|
||||||
|
@ -97,5 +66,5 @@ __all__ = [
|
||||||
'LazyArrayRangeFlow',
|
'LazyArrayRangeFlow',
|
||||||
'ParamsFlow',
|
'ParamsFlow',
|
||||||
'InfoFlow',
|
'InfoFlow',
|
||||||
'DataFlowAction',
|
'FlowEvent',
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,35 +0,0 @@
|
||||||
import pydantic as pyd
|
|
||||||
import typing_extensions as pytypes_ext
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Pure BL Types
|
|
||||||
####################
|
|
||||||
BLEnumID = pytypes_ext.Annotated[
|
|
||||||
str,
|
|
||||||
pyd.StringConstraints(
|
|
||||||
pattern=r'^[A-Z_]+$',
|
|
||||||
),
|
|
||||||
]
|
|
||||||
SocketName = pytypes_ext.Annotated[
|
|
||||||
str,
|
|
||||||
pyd.StringConstraints(
|
|
||||||
pattern=r'^[a-zA-Z0-9_]+$',
|
|
||||||
),
|
|
||||||
]
|
|
||||||
PresetName = pytypes_ext.Annotated[
|
|
||||||
str,
|
|
||||||
pyd.StringConstraints(
|
|
||||||
pattern=r'^[a-zA-Z0-9_]+$',
|
|
||||||
),
|
|
||||||
]
|
|
||||||
BLColorRGBA = tuple[float, float, float, float]
|
|
||||||
|
|
||||||
####################
|
|
||||||
# - Shared-With-BL Types
|
|
||||||
####################
|
|
||||||
ManagedObjName = pytypes_ext.Annotated[
|
|
||||||
str,
|
|
||||||
pyd.StringConstraints(
|
|
||||||
pattern=r'^[a-z_]+$',
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,48 +0,0 @@
|
||||||
import enum
|
|
||||||
import typing as typ
|
|
||||||
|
|
||||||
import typing_extensions as typx
|
|
||||||
|
|
||||||
|
|
||||||
class DataFlowAction(enum.StrEnum):
|
|
||||||
# Locking
|
|
||||||
EnableLock = 'enable_lock'
|
|
||||||
DisableLock = 'disable_lock'
|
|
||||||
|
|
||||||
# Value
|
|
||||||
OutputRequested = 'output_requested'
|
|
||||||
DataChanged = 'value_changed'
|
|
||||||
|
|
||||||
# Previewing
|
|
||||||
ShowPreview = 'show_preview'
|
|
||||||
ShowPlot = 'show_plot'
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def trigger_direction(action: typ.Self) -> typx.Literal['input', 'output']:
|
|
||||||
"""When a given action is triggered, all sockets/nodes/... in this direction should be recursively triggered.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
action: The action for which to retrieve the trigger direction.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
The trigger direction, which can be used ex. in nodes to select `node.inputs` or `node.outputs`.
|
|
||||||
"""
|
|
||||||
return {
|
|
||||||
DataFlowAction.EnableLock: 'input',
|
|
||||||
DataFlowAction.DisableLock: 'input',
|
|
||||||
DataFlowAction.DataChanged: 'output',
|
|
||||||
DataFlowAction.OutputRequested: 'input',
|
|
||||||
DataFlowAction.ShowPreview: 'input',
|
|
||||||
DataFlowAction.ShowPlot: 'input',
|
|
||||||
}[action]
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def stop_if_no_event_methods(action: typ.Self) -> bool:
|
|
||||||
return {
|
|
||||||
DataFlowAction.EnableLock: False,
|
|
||||||
DataFlowAction.DisableLock: False,
|
|
||||||
DataFlowAction.DataChanged: True,
|
|
||||||
DataFlowAction.OutputRequested: True,
|
|
||||||
DataFlowAction.ShowPreview: False,
|
|
||||||
DataFlowAction.ShowPlot: False,
|
|
||||||
}[action]
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
import enum
|
||||||
|
import typing as typ
|
||||||
|
|
||||||
|
import typing_extensions as typx
|
||||||
|
|
||||||
|
from blender_maxwell.utils.staticproperty import staticproperty
|
||||||
|
|
||||||
|
|
||||||
|
class FlowEvent(enum.StrEnum):
|
||||||
|
"""Defines an event that can propagate through the graph (node-socket-node-...).
|
||||||
|
|
||||||
|
Contrary to `FlowKind`, a `FlowEvent` doesn't propagate any data.
|
||||||
|
Instead, it allows for dead-simple communication across direct graph connections.
|
||||||
|
|
||||||
|
The entire system is built around user-defined event handlers, which are also used internally.
|
||||||
|
See `events`.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
EnableLock: Indicates that the node/socket should enable locking.
|
||||||
|
Locking prevents the use of the UI, including adding/removing links.
|
||||||
|
This event can lock a subset of the node tree graph.
|
||||||
|
DisableLock: Indicates that the node/socket should disable locking.
|
||||||
|
This event can unlock part of a locked subgraph.
|
||||||
|
ShowPreview: Indicates that the node/socket should enable its primary preview.
|
||||||
|
This should be used if a more specific preview-esque event doesn't apply.
|
||||||
|
ShowPlot: Indicates that the node/socket should enable its plotted preview.
|
||||||
|
This should generally be used if the node is rendering to an image, for viewing through the Blender image editor.
|
||||||
|
LinkChanged: Indicates that a link to a node/socket was added/removed.
|
||||||
|
In nodes, this is accompanied by a `socket_name` to indicate which socket it is that had its links altered.
|
||||||
|
DataChanged: Indicates that data flowing through a node/socket was altered.
|
||||||
|
In nodes, this event is accompanied by a `socket_name` or `prop_name`, to indicate which socket/property it is that was changed.
|
||||||
|
**This event is essential**, as it invalidates all input/output socket caches along its path.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Lock Events
|
||||||
|
EnableLock = enum.auto()
|
||||||
|
DisableLock = enum.auto()
|
||||||
|
|
||||||
|
# Preview Events
|
||||||
|
ShowPreview = enum.auto()
|
||||||
|
ShowPlot = enum.auto()
|
||||||
|
|
||||||
|
# Data Events
|
||||||
|
LinkChanged = enum.auto()
|
||||||
|
DataChanged = enum.auto()
|
||||||
|
|
||||||
|
# Non-Triggered Events
|
||||||
|
OutputRequested = enum.auto()
|
||||||
|
|
||||||
|
# Properties
|
||||||
|
@staticproperty
|
||||||
|
def flow_direction() -> typx.Literal['input', 'output']:
|
||||||
|
"""Describes the direction in which the event should flow.
|
||||||
|
|
||||||
|
Doesn't include `FlowEvent`s that aren't meant to be triggered:
|
||||||
|
- `OutputRequested`.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
event: The event for which to retrieve the trigger direction.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The trigger direction, which can be used ex. in nodes to select `node.inputs` or `node.outputs`.
|
||||||
|
"""
|
||||||
|
return {
|
||||||
|
# Lock Events
|
||||||
|
FlowEvent.EnableLock: 'input',
|
||||||
|
FlowEvent.DisableLock: 'input',
|
||||||
|
# Preview Events
|
||||||
|
FlowEvent.ShowPreview: 'input',
|
||||||
|
FlowEvent.ShowPlot: 'input',
|
||||||
|
# Data Events
|
||||||
|
FlowEvent.LinkChanged: 'output',
|
||||||
|
FlowEvent.DataChanged: 'output',
|
||||||
|
}
|
|
@ -12,7 +12,6 @@ import sympy.physics.units as spu
|
||||||
import typing_extensions as typx
|
import typing_extensions as typx
|
||||||
|
|
||||||
from ....utils import extra_sympy_units as spux
|
from ....utils import extra_sympy_units as spux
|
||||||
from ....utils import sci_constants as constants
|
|
||||||
from .socket_types import SocketType
|
from .socket_types import SocketType
|
||||||
|
|
||||||
|
|
||||||
|
@ -37,10 +36,8 @@ class FlowKind(enum.StrEnum):
|
||||||
Can be used to represent computations for which all data is not yet known, or for which just-in-time compilation can drastically increase performance.
|
Can be used to represent computations for which all data is not yet known, or for which just-in-time compilation can drastically increase performance.
|
||||||
LazyArrayRange: An object that generates an `Array` from range information (start/stop/step/spacing).
|
LazyArrayRange: An object that generates an `Array` from range information (start/stop/step/spacing).
|
||||||
This should be used instead of `Array` whenever possible.
|
This should be used instead of `Array` whenever possible.
|
||||||
Param: An object providing data to complete `Lazy` data.
|
Param: A dictionary providing particular parameters for a lazy value.
|
||||||
For example,
|
Info: An dictionary providing extra context about any aspect of flow.
|
||||||
Info: An object providing context about other flows.
|
|
||||||
For example,
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
Capabilities = enum.auto()
|
Capabilities = enum.auto()
|
|
@ -1,6 +1,6 @@
|
||||||
import enum
|
import enum
|
||||||
|
|
||||||
from ....utils.blender_type_enum import BlenderTypeEnum
|
from blender_maxwell.blender_type_enum import BlenderTypeEnum
|
||||||
|
|
||||||
|
|
||||||
class ManagedObjType(BlenderTypeEnum):
|
class ManagedObjType(BlenderTypeEnum):
|
|
@ -1,64 +0,0 @@
|
||||||
from .socket_types import SocketType as ST
|
|
||||||
|
|
||||||
SOCKET_SHAPES = {
|
|
||||||
# Basic
|
|
||||||
ST.Any: 'CIRCLE',
|
|
||||||
ST.Bool: 'CIRCLE',
|
|
||||||
ST.String: 'CIRCLE',
|
|
||||||
ST.FilePath: 'CIRCLE',
|
|
||||||
ST.Expr: 'CIRCLE',
|
|
||||||
# Number
|
|
||||||
ST.IntegerNumber: 'CIRCLE',
|
|
||||||
ST.RationalNumber: 'CIRCLE',
|
|
||||||
ST.RealNumber: 'CIRCLE',
|
|
||||||
ST.ComplexNumber: 'CIRCLE',
|
|
||||||
# Vector
|
|
||||||
ST.Integer2DVector: 'CIRCLE',
|
|
||||||
ST.Real2DVector: 'CIRCLE',
|
|
||||||
ST.Complex2DVector: 'CIRCLE',
|
|
||||||
ST.Integer3DVector: 'CIRCLE',
|
|
||||||
ST.Real3DVector: 'CIRCLE',
|
|
||||||
ST.Complex3DVector: 'CIRCLE',
|
|
||||||
# Physical
|
|
||||||
ST.PhysicalUnitSystem: 'CIRCLE',
|
|
||||||
ST.PhysicalTime: 'CIRCLE',
|
|
||||||
ST.PhysicalAngle: 'CIRCLE',
|
|
||||||
ST.PhysicalLength: 'CIRCLE',
|
|
||||||
ST.PhysicalArea: 'CIRCLE',
|
|
||||||
ST.PhysicalVolume: 'CIRCLE',
|
|
||||||
ST.PhysicalPoint2D: 'CIRCLE',
|
|
||||||
ST.PhysicalPoint3D: 'CIRCLE',
|
|
||||||
ST.PhysicalSize2D: 'CIRCLE',
|
|
||||||
ST.PhysicalSize3D: 'CIRCLE',
|
|
||||||
ST.PhysicalMass: 'CIRCLE',
|
|
||||||
ST.PhysicalSpeed: 'CIRCLE',
|
|
||||||
ST.PhysicalAccelScalar: 'CIRCLE',
|
|
||||||
ST.PhysicalForceScalar: 'CIRCLE',
|
|
||||||
ST.PhysicalAccel3D: 'CIRCLE',
|
|
||||||
ST.PhysicalForce3D: 'CIRCLE',
|
|
||||||
ST.PhysicalPol: 'CIRCLE',
|
|
||||||
ST.PhysicalFreq: 'CIRCLE',
|
|
||||||
# Blender
|
|
||||||
ST.BlenderMaterial: 'DIAMOND',
|
|
||||||
ST.BlenderObject: 'DIAMOND',
|
|
||||||
ST.BlenderCollection: 'DIAMOND',
|
|
||||||
ST.BlenderImage: 'DIAMOND',
|
|
||||||
ST.BlenderGeoNodes: 'DIAMOND',
|
|
||||||
ST.BlenderText: 'DIAMOND',
|
|
||||||
# Maxwell
|
|
||||||
ST.MaxwellSource: 'CIRCLE',
|
|
||||||
ST.MaxwellTemporalShape: 'CIRCLE',
|
|
||||||
ST.MaxwellMedium: 'CIRCLE',
|
|
||||||
ST.MaxwellMediumNonLinearity: 'CIRCLE',
|
|
||||||
ST.MaxwellStructure: 'CIRCLE',
|
|
||||||
ST.MaxwellBoundConds: 'CIRCLE',
|
|
||||||
ST.MaxwellBoundCond: 'CIRCLE',
|
|
||||||
ST.MaxwellMonitor: 'CIRCLE',
|
|
||||||
ST.MaxwellFDTDSim: 'CIRCLE',
|
|
||||||
ST.MaxwellFDTDSimData: 'CIRCLE',
|
|
||||||
ST.MaxwellSimGrid: 'CIRCLE',
|
|
||||||
ST.MaxwellSimGridAxis: 'CIRCLE',
|
|
||||||
ST.MaxwellSimDomain: 'CIRCLE',
|
|
||||||
# Tidy3D
|
|
||||||
ST.Tidy3DCloudTask: 'DIAMOND',
|
|
||||||
}
|
|
|
@ -23,6 +23,9 @@ _MPL_CM = matplotlib.cm.get_cmap('viridis', 512)
|
||||||
VIRIDIS_COLORMAP = jnp.array([_MPL_CM(i)[:3] for i in range(512)])
|
VIRIDIS_COLORMAP = jnp.array([_MPL_CM(i)[:3] for i in range(512)])
|
||||||
|
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Image Functions
|
||||||
|
####################
|
||||||
def apply_colormap(normalized_data, colormap):
|
def apply_colormap(normalized_data, colormap):
|
||||||
# Linear interpolation between colormap points
|
# Linear interpolation between colormap points
|
||||||
n_colors = colormap.shape[0]
|
n_colors = colormap.shape[0]
|
||||||
|
@ -74,6 +77,9 @@ def rgba_image_from_2d_map(map_2d, colormap: str | None = None):
|
||||||
return rgba_image_from_2d_map__grayscale(map_2d)
|
return rgba_image_from_2d_map__grayscale(map_2d)
|
||||||
|
|
||||||
|
|
||||||
|
####################
|
||||||
|
# - Managed BL Image
|
||||||
|
####################
|
||||||
class ManagedBLImage(base.ManagedObj):
|
class ManagedBLImage(base.ManagedObj):
|
||||||
managed_obj_type = ct.ManagedObjType.ManagedBLImage
|
managed_obj_type = ct.ManagedObjType.ManagedBLImage
|
||||||
_bl_image_name: str
|
_bl_image_name: str
|
||||||
|
@ -170,7 +176,7 @@ class ManagedBLImage(base.ManagedObj):
|
||||||
)
|
)
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Actions
|
# - Methods
|
||||||
####################
|
####################
|
||||||
def bl_select(self) -> None:
|
def bl_select(self) -> None:
|
||||||
"""Synchronizes the managed object to the preview, by manipulating
|
"""Synchronizes the managed object to the preview, by manipulating
|
||||||
|
|
|
@ -98,7 +98,7 @@ class ManagedBLMesh(base.ManagedObj):
|
||||||
bpy.data.meshes.remove(bl_object.data)
|
bpy.data.meshes.remove(bl_object.data)
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Actions
|
# - Methods
|
||||||
####################
|
####################
|
||||||
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.
|
||||||
|
|
|
@ -12,8 +12,6 @@ from . import base
|
||||||
|
|
||||||
log = logger.get(__name__)
|
log = logger.get(__name__)
|
||||||
|
|
||||||
ModifierType: typ.TypeAlias = typx.Literal['NODES', 'ARRAY']
|
|
||||||
NodeTreeInterfaceID: typ.TypeAlias = str
|
|
||||||
UnitSystem: typ.TypeAlias = typ.Any
|
UnitSystem: typ.TypeAlias = typ.Any
|
||||||
|
|
||||||
|
|
||||||
|
@ -33,7 +31,7 @@ class ModifierAttrsNODES(typ.TypedDict):
|
||||||
|
|
||||||
node_group: bpy.types.GeometryNodeTree
|
node_group: bpy.types.GeometryNodeTree
|
||||||
unit_system: UnitSystem
|
unit_system: UnitSystem
|
||||||
inputs: dict[NodeTreeInterfaceID, typ.Any]
|
inputs: dict[ct.BLNodeTreeInterfaceID, typ.Any]
|
||||||
|
|
||||||
|
|
||||||
class ModifierAttrsARRAY(typ.TypedDict):
|
class ModifierAttrsARRAY(typ.TypedDict):
|
||||||
|
@ -222,7 +220,7 @@ class ManagedBLModifier(base.ManagedObj):
|
||||||
def bl_modifier(
|
def bl_modifier(
|
||||||
self,
|
self,
|
||||||
bl_object: bpy.types.Object,
|
bl_object: bpy.types.Object,
|
||||||
modifier_type: ModifierType,
|
modifier_type: ct.BLModifierType,
|
||||||
modifier_attrs: ModifierAttrs,
|
modifier_attrs: ModifierAttrs,
|
||||||
):
|
):
|
||||||
"""Creates a new modifier for the current `bl_object`.
|
"""Creates a new modifier for the current `bl_object`.
|
||||||
|
|
|
@ -343,7 +343,7 @@ class MaxwellSimTree(bpy.types.NodeTree):
|
||||||
## The link has already been removed, but we can fix that.
|
## The link has already been removed, but we can fix that.
|
||||||
## If NO: Queue re-adding the link (safe since the sockets exist)
|
## If NO: Queue re-adding the link (safe since the sockets exist)
|
||||||
## TODO: Crash if deleting removing linked loose sockets.
|
## TODO: Crash if deleting removing linked loose sockets.
|
||||||
consent_removal = to_socket.sync_link_removed(from_socket)
|
consent_removal = to_socket.allow_remove_link(from_socket)
|
||||||
if not consent_removal:
|
if not consent_removal:
|
||||||
link_corrections['to_add'].append((from_socket, to_socket))
|
link_corrections['to_add'].append((from_socket, to_socket))
|
||||||
|
|
||||||
|
@ -354,12 +354,14 @@ class MaxwellSimTree(bpy.types.NodeTree):
|
||||||
# Retrieve Link Reference
|
# Retrieve Link Reference
|
||||||
link = self.node_link_cache.link_ptrs_as_links[link_ptr]
|
link = self.node_link_cache.link_ptrs_as_links[link_ptr]
|
||||||
|
|
||||||
# Ask 'to_socket' for Consent to Remove Link
|
# Ask 'to_socket' for Consent to Add Link
|
||||||
## The link has already been added, but we can fix that.
|
## The link has already been added, but we can fix that.
|
||||||
## If NO: Queue re-adding the link (safe since the sockets exist)
|
## If NO: Queue re-adding the link (safe since the sockets exist)
|
||||||
consent_added = link.to_socket.sync_link_added(link)
|
consent_added = link.to_socket.allow_add_link(link)
|
||||||
if not consent_added:
|
if not consent_added:
|
||||||
link_corrections['to_remove'].append(link)
|
link_corrections['to_remove'].append(link)
|
||||||
|
else:
|
||||||
|
link.to_socket.on_link_added(link)
|
||||||
|
|
||||||
# Link Corrections
|
# Link Corrections
|
||||||
## ADD: Links that 'to_socket' don't want removed.
|
## ADD: Links that 'to_socket' don't want removed.
|
||||||
|
|
|
@ -121,28 +121,26 @@ class MaxwellSimNode(bpy.types.Node):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _gather_event_methods(cls) -> dict[str, typ.Callable[[], None]]:
|
def _gather_event_methods(cls) -> dict[str, typ.Callable[[], None]]:
|
||||||
"""Gathers all methods called in response to actions/events observed by the node.
|
"""Gathers all methods called in response to events observed by the node.
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
- 'Event methods' must have an attribute 'action_type' in order to be picked up.
|
- 'Event methods' must have an attribute 'event' in order to be picked up.
|
||||||
- 'Event methods' must have an attribute 'action_type'.
|
- 'Event methods' must have an attribute 'event'.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Event methods, indexed by the action that (maybe) triggers them.
|
Event methods, indexed by the event that (maybe) triggers them.
|
||||||
"""
|
"""
|
||||||
event_methods = [
|
event_methods = [
|
||||||
method
|
method
|
||||||
for attr_name in dir(cls)
|
for attr_name in dir(cls)
|
||||||
if hasattr(method := getattr(cls, attr_name), 'action_type')
|
if hasattr(method := getattr(cls, attr_name), 'event')
|
||||||
and method.action_type in set(ct.DataFlowAction)
|
and method.event in set(ct.FlowEvent)
|
||||||
]
|
]
|
||||||
event_methods_by_action = {
|
event_methods_by_event = {event: [] for event in set(ct.FlowEvent)}
|
||||||
action_type: [] for action_type in set(ct.DataFlowAction)
|
|
||||||
}
|
|
||||||
for method in event_methods:
|
for method in event_methods:
|
||||||
event_methods_by_action[method.action_type].append(method)
|
event_methods_by_event[method.event].append(method)
|
||||||
|
|
||||||
return event_methods_by_action
|
return event_methods_by_event
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def socket_set_names(cls) -> list[str]:
|
def socket_set_names(cls) -> list[str]:
|
||||||
|
@ -185,7 +183,7 @@ class MaxwellSimNode(bpy.types.Node):
|
||||||
cls.set_prop('locked', bpy.props.BoolProperty, no_update=True, default=False)
|
cls.set_prop('locked', bpy.props.BoolProperty, no_update=True, default=False)
|
||||||
|
|
||||||
## Event Method Callbacks
|
## Event Method Callbacks
|
||||||
cls.event_methods_by_action = cls._gather_event_methods()
|
cls.event_methods_by_event = cls._gather_event_methods()
|
||||||
|
|
||||||
## Active Socket Set
|
## Active Socket Set
|
||||||
if len(cls.input_socket_sets) + len(cls.output_socket_sets) > 0:
|
if len(cls.input_socket_sets) + len(cls.output_socket_sets) > 0:
|
||||||
|
@ -483,25 +481,22 @@ class MaxwellSimNode(bpy.types.Node):
|
||||||
# - Event Methods
|
# - Event Methods
|
||||||
####################
|
####################
|
||||||
@property
|
@property
|
||||||
def _event_method_filter_by_action(self) -> dict[ct.DataFlowAction, typ.Callable]:
|
def _event_method_filter_by_event(self) -> dict[ct.FlowEvent, typ.Callable]:
|
||||||
"""Compute a map of DataFlowActions, to a function that filters its event methods.
|
"""Compute a map of FlowEvents, to a function that filters its event methods.
|
||||||
|
|
||||||
The returned filter functions are hard-coded, and must always return a `bool`.
|
The returned filter functions are hard-coded, and must always return a `bool`.
|
||||||
They may use attributes of `self`, always return `True` or `False`, or something different.
|
They may use attributes of `self`, always return `True` or `False`, or something different.
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
This is an internal method; you probably want `self.filtered_event_methods_by_action`.
|
This is an internal method; you probably want `self.filtered_event_methods_by_event`.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
The map of `ct.DataFlowAction` to a function that can determine whether any `event_method` should be run.
|
The map of `ct.FlowEvent` to a function that can determine whether any `event_method` should be run.
|
||||||
"""
|
"""
|
||||||
return {
|
return {
|
||||||
ct.DataFlowAction.EnableLock: lambda *_: True,
|
ct.FlowEvent.EnableLock: lambda *_: True,
|
||||||
ct.DataFlowAction.DisableLock: lambda *_: True,
|
ct.FlowEvent.DisableLock: lambda *_: True,
|
||||||
ct.DataFlowAction.DataChanged: lambda event_method,
|
ct.FlowEvent.DataChanged: lambda event_method, socket_name, prop_name, _: (
|
||||||
socket_name,
|
|
||||||
prop_name,
|
|
||||||
_: (
|
|
||||||
(
|
(
|
||||||
socket_name
|
socket_name
|
||||||
and socket_name in event_method.callback_info.on_changed_sockets
|
and socket_name in event_method.callback_info.on_changed_sockets
|
||||||
|
@ -516,7 +511,7 @@ class MaxwellSimNode(bpy.types.Node):
|
||||||
and socket_name in self.loose_input_sockets
|
and socket_name in self.loose_input_sockets
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
ct.DataFlowAction.OutputRequested: lambda output_socket_method,
|
ct.FlowEvent.OutputRequested: lambda output_socket_method,
|
||||||
output_socket_name,
|
output_socket_name,
|
||||||
_,
|
_,
|
||||||
kind: (
|
kind: (
|
||||||
|
@ -526,26 +521,26 @@ class MaxwellSimNode(bpy.types.Node):
|
||||||
== output_socket_method.callback_info.output_socket_name
|
== output_socket_method.callback_info.output_socket_name
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
ct.DataFlowAction.ShowPreview: lambda *_: True,
|
ct.FlowEvent.ShowPreview: lambda *_: True,
|
||||||
ct.DataFlowAction.ShowPlot: lambda *_: True,
|
ct.FlowEvent.ShowPlot: lambda *_: True,
|
||||||
}
|
}
|
||||||
|
|
||||||
def filtered_event_methods_by_action(
|
def filtered_event_methods_by_event(
|
||||||
self,
|
self,
|
||||||
action: ct.DataFlowAction,
|
event: ct.FlowEvent,
|
||||||
_filter: tuple[ct.SocketName, str],
|
_filter: tuple[ct.SocketName, str],
|
||||||
) -> list[typ.Callable]:
|
) -> list[typ.Callable]:
|
||||||
"""Return all event methods that should run, given the context provided by `_filter`.
|
"""Return all event methods that should run, given the context provided by `_filter`.
|
||||||
|
|
||||||
The inclusion decision is made by the internal property `self._event_method_filter_by_action`.
|
The inclusion decision is made by the internal property `self._event_method_filter_by_event`.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
All `event_method`s that should run, as callable objects (they can be run using `event_method(self)`).
|
All `event_method`s that should run, as callable objects (they can be run using `event_method(self)`).
|
||||||
"""
|
"""
|
||||||
return [
|
return [
|
||||||
event_method
|
event_method
|
||||||
for event_method in self.event_methods_by_action[action]
|
for event_method in self.event_methods_by_event[event]
|
||||||
if self._event_method_filter_by_action[action](event_method, *_filter)
|
if self._event_method_filter_by_event[event](event_method, *_filter)
|
||||||
]
|
]
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
@ -591,7 +586,7 @@ class MaxwellSimNode(bpy.types.Node):
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Compute Action: Output Socket
|
# - Compute Event: Output Socket
|
||||||
####################
|
####################
|
||||||
@bl_cache.keyed_cache(
|
@bl_cache.keyed_cache(
|
||||||
exclude={'self', 'optional'},
|
exclude={'self', 'optional'},
|
||||||
|
@ -619,8 +614,8 @@ class MaxwellSimNode(bpy.types.Node):
|
||||||
msg = f"Can't compute nonexistent output socket name {output_socket_name}, as it's not currently active"
|
msg = f"Can't compute nonexistent output socket name {output_socket_name}, as it's not currently active"
|
||||||
raise RuntimeError(msg)
|
raise RuntimeError(msg)
|
||||||
|
|
||||||
output_socket_methods = self.filtered_event_methods_by_action(
|
output_socket_methods = self.filtered_event_methods_by_event(
|
||||||
ct.DataFlowAction.OutputRequested,
|
ct.FlowEvent.OutputRequested,
|
||||||
(output_socket_name, None, kind),
|
(output_socket_name, None, kind),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -636,7 +631,7 @@ class MaxwellSimNode(bpy.types.Node):
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Action Trigger
|
# - Event Trigger
|
||||||
####################
|
####################
|
||||||
def _should_recompute_output_socket(
|
def _should_recompute_output_socket(
|
||||||
self,
|
self,
|
||||||
|
@ -657,25 +652,25 @@ class MaxwellSimNode(bpy.types.Node):
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
def trigger_action(
|
def trigger_event(
|
||||||
self,
|
self,
|
||||||
action: ct.DataFlowAction,
|
event: ct.FlowEvent,
|
||||||
socket_name: ct.SocketName | None = None,
|
socket_name: ct.SocketName | None = None,
|
||||||
prop_name: ct.SocketName | None = None,
|
prop_name: ct.SocketName | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Recursively triggers actions/events forwards or backwards along the node tree, allowing nodes in the update path to react.
|
"""Recursively triggers events forwards or backwards along the node tree, allowing nodes in the update path to react.
|
||||||
|
|
||||||
Use `events` decorators to define methods that react to particular `ct.DataFlowAction`s.
|
Use `events` decorators to define methods that react to particular `ct.FlowEvent`s.
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
This can be an unpredictably heavy function, depending on the node graph topology.
|
This can be an unpredictably heavy function, depending on the node graph topology.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
action: The action/event to report forwards/backwards along the node tree.
|
event: The event to report forwards/backwards along the node tree.
|
||||||
socket_name: The input socket that was altered, if any, in order to trigger this event.
|
socket_name: The input socket that was altered, if any, in order to trigger this event.
|
||||||
pop_name: The property that was altered, if any, in order to trigger this event.
|
pop_name: The property that was altered, if any, in order to trigger this event.
|
||||||
"""
|
"""
|
||||||
if action == ct.DataFlowAction.DataChanged:
|
if event == ct.FlowEvent.DataChanged:
|
||||||
input_socket_name = socket_name ## Trigger direction is forwards
|
input_socket_name = socket_name ## Trigger direction is forwards
|
||||||
|
|
||||||
# Invalidate Input Socket Cache
|
# Invalidate Input Socket Cache
|
||||||
|
@ -687,8 +682,8 @@ class MaxwellSimNode(bpy.types.Node):
|
||||||
)
|
)
|
||||||
|
|
||||||
# Invalidate Output Socket Cache
|
# Invalidate Output Socket Cache
|
||||||
for output_socket_method in self.event_methods_by_action[
|
for output_socket_method in self.event_methods_by_event[
|
||||||
ct.DataFlowAction.OutputRequested
|
ct.FlowEvent.OutputRequested
|
||||||
]:
|
]:
|
||||||
method_info = output_socket_method.callback_info
|
method_info = output_socket_method.callback_info
|
||||||
if self._should_recompute_output_socket(
|
if self._should_recompute_output_socket(
|
||||||
|
@ -701,24 +696,24 @@ class MaxwellSimNode(bpy.types.Node):
|
||||||
|
|
||||||
# Run Triggered Event Methods
|
# Run Triggered Event Methods
|
||||||
stop_propagation = False
|
stop_propagation = False
|
||||||
triggered_event_methods = self.filtered_event_methods_by_action(
|
triggered_event_methods = self.filtered_event_methods_by_event(
|
||||||
action, (socket_name, prop_name, None)
|
event, (socket_name, prop_name, None)
|
||||||
)
|
)
|
||||||
for event_method in triggered_event_methods:
|
for event_method in triggered_event_methods:
|
||||||
stop_propagation |= event_method.stop_propagation
|
stop_propagation |= event_method.stop_propagation
|
||||||
event_method(self)
|
event_method(self)
|
||||||
|
|
||||||
# Propagate Action to All Sockets in "Trigger Direction"
|
# Propagate Event to All Sockets in "Trigger Direction"
|
||||||
## The trigger chain goes node/socket/node/socket/...
|
## The trigger chain goes node/socket/node/socket/...
|
||||||
if not stop_propagation:
|
if not stop_propagation:
|
||||||
triggered_sockets = self._bl_sockets(
|
triggered_sockets = self._bl_sockets(
|
||||||
direc=ct.DataFlowAction.trigger_direction(action)
|
direc=ct.FlowEvent.flow_direction[event]
|
||||||
)
|
)
|
||||||
for bl_socket in triggered_sockets:
|
for bl_socket in triggered_sockets:
|
||||||
bl_socket.trigger_action(action)
|
bl_socket.trigger_event(event)
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Property Action: On Update
|
# - Property Event: On Update
|
||||||
####################
|
####################
|
||||||
def sync_prop(self, prop_name: str, _: bpy.types.Context) -> None:
|
def sync_prop(self, prop_name: str, _: bpy.types.Context) -> None:
|
||||||
"""Report that a particular property has changed, which may cause certain caches to regenerate.
|
"""Report that a particular property has changed, which may cause certain caches to regenerate.
|
||||||
|
@ -732,7 +727,7 @@ class MaxwellSimNode(bpy.types.Node):
|
||||||
prop_name: The name of the property that changed.
|
prop_name: The name of the property that changed.
|
||||||
"""
|
"""
|
||||||
if hasattr(self, prop_name):
|
if hasattr(self, prop_name):
|
||||||
self.trigger_action(ct.DataFlowAction.DataChanged, prop_name=prop_name)
|
self.trigger_event(ct.FlowEvent.DataChanged, prop_name=prop_name)
|
||||||
else:
|
else:
|
||||||
msg = f'Property {prop_name} not defined on node {self}'
|
msg = f'Property {prop_name} not defined on node {self}'
|
||||||
raise RuntimeError(msg)
|
raise RuntimeError(msg)
|
||||||
|
@ -864,9 +859,7 @@ class MaxwellSimNode(bpy.types.Node):
|
||||||
## -> Compromise: Users explicitly say 'run_on_init' in @on_value_changed
|
## -> Compromise: Users explicitly say 'run_on_init' in @on_value_changed
|
||||||
for event_method in [
|
for event_method in [
|
||||||
event_method
|
event_method
|
||||||
for event_method in self.event_methods_by_action[
|
for event_method in self.event_methods_by_event[ct.FlowEvent.DataChanged]
|
||||||
ct.DataFlowAction.DataChanged
|
|
||||||
]
|
|
||||||
if event_method.callback_info.run_on_init
|
if event_method.callback_info.run_on_init
|
||||||
]:
|
]:
|
||||||
event_method(self)
|
event_method(self)
|
||||||
|
@ -915,7 +908,7 @@ class MaxwellSimNode(bpy.types.Node):
|
||||||
bl_socket.is_linked and bl_socket.locked
|
bl_socket.is_linked and bl_socket.locked
|
||||||
for bl_socket in self.inputs.values()
|
for bl_socket in self.inputs.values()
|
||||||
):
|
):
|
||||||
self.trigger_action(ct.DataFlowAction.DisableLock)
|
self.trigger_event(ct.FlowEvent.DisableLock)
|
||||||
|
|
||||||
# Free Managed Objects
|
# Free Managed Objects
|
||||||
for managed_obj in self.managed_objs.values():
|
for managed_obj in self.managed_objs.values():
|
||||||
|
|
|
@ -50,7 +50,7 @@ PropName: typ.TypeAlias = str
|
||||||
|
|
||||||
|
|
||||||
def event_decorator(
|
def event_decorator(
|
||||||
action_type: ct.DataFlowAction,
|
event: ct.FlowEvent,
|
||||||
callback_info: EventCallbackInfo | None,
|
callback_info: EventCallbackInfo | None,
|
||||||
stop_propagation: bool = False,
|
stop_propagation: bool = False,
|
||||||
# Request Data for Callback
|
# Request Data for Callback
|
||||||
|
@ -72,10 +72,10 @@ def event_decorator(
|
||||||
"""Returns a decorator for a method of `MaxwellSimNode`, declaring it as able respond to events passing through a node.
|
"""Returns a decorator for a method of `MaxwellSimNode`, declaring it as able respond to events passing through a node.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
action_type: A name describing which event the decorator should respond to.
|
event: A name describing which event the decorator should respond to.
|
||||||
Set to `return_method.action_type`
|
Set to `return_method.event`
|
||||||
callback_info: A dictionary that provides the caller with additional per-`action_type` information.
|
callback_info: A dictionary that provides the caller with additional per-`event` information.
|
||||||
This might include parameters to help select the most appropriate method(s) to respond to an event with, or actions to take after running the callback.
|
This might include parameters to help select the most appropriate method(s) to respond to an event with, or events to take after running the callback.
|
||||||
props: Set of `props` to compute, then pass to the decorated method.
|
props: Set of `props` to compute, then pass to the decorated method.
|
||||||
stop_propagation: Whether or stop propagating the event through the graph after encountering this method.
|
stop_propagation: Whether or stop propagating the event through the graph after encountering this method.
|
||||||
Other methods defined on the same node will still run.
|
Other methods defined on the same node will still run.
|
||||||
|
@ -93,7 +93,7 @@ def event_decorator(
|
||||||
A decorator, which can be applied to a method of `MaxwellSimNode`.
|
A decorator, which can be applied to a method of `MaxwellSimNode`.
|
||||||
When a `MaxwellSimNode` subclass initializes, such a decorated method will be picked up on.
|
When a `MaxwellSimNode` subclass initializes, such a decorated method will be picked up on.
|
||||||
|
|
||||||
When the `action_type` action passes through the node, then `callback_info` is used to determine
|
When `event` passes through the node, then `callback_info` is used to determine
|
||||||
"""
|
"""
|
||||||
req_params = (
|
req_params = (
|
||||||
{'self'}
|
{'self'}
|
||||||
|
@ -252,14 +252,14 @@ def event_decorator(
|
||||||
)
|
)
|
||||||
|
|
||||||
# Set Decorated Attributes and Return
|
# Set Decorated Attributes and Return
|
||||||
## Fix Introspection + Documentation
|
## TODO: Fix Introspection + Documentation
|
||||||
# decorated.__name__ = method.__name__
|
# decorated.__name__ = method.__name__
|
||||||
# decorated.__module__ = method.__module__
|
# decorated.__module__ = method.__module__
|
||||||
# decorated.__qualname__ = method.__qualname__
|
# decorated.__qualname__ = method.__qualname__
|
||||||
# decorated.__doc__ = method.__doc__
|
decorated.__doc__ = method.__doc__
|
||||||
|
|
||||||
## Add Spice
|
## Add Spice
|
||||||
decorated.action_type = action_type
|
decorated.event = event
|
||||||
decorated.callback_info = callback_info
|
decorated.callback_info = callback_info
|
||||||
decorated.stop_propagation = stop_propagation
|
decorated.stop_propagation = stop_propagation
|
||||||
|
|
||||||
|
@ -275,7 +275,7 @@ def on_enable_lock(
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
return event_decorator(
|
return event_decorator(
|
||||||
action_type=ct.DataFlowAction.EnableLock,
|
event=ct.FlowEvent.EnableLock,
|
||||||
callback_info=None,
|
callback_info=None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
)
|
)
|
||||||
|
@ -285,7 +285,7 @@ def on_disable_lock(
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
return event_decorator(
|
return event_decorator(
|
||||||
action_type=ct.DataFlowAction.DisableLock,
|
event=ct.FlowEvent.DisableLock,
|
||||||
callback_info=None,
|
callback_info=None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
)
|
)
|
||||||
|
@ -300,7 +300,7 @@ def on_value_changed(
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
return event_decorator(
|
return event_decorator(
|
||||||
action_type=ct.DataFlowAction.DataChanged,
|
event=ct.FlowEvent.DataChanged,
|
||||||
callback_info=InfoDataChanged(
|
callback_info=InfoDataChanged(
|
||||||
run_on_init=run_on_init,
|
run_on_init=run_on_init,
|
||||||
on_changed_sockets=(
|
on_changed_sockets=(
|
||||||
|
@ -320,7 +320,7 @@ def computes_output_socket(
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
return event_decorator(
|
return event_decorator(
|
||||||
action_type=ct.DataFlowAction.OutputRequested,
|
event=ct.FlowEvent.OutputRequested,
|
||||||
callback_info=InfoOutputRequested(
|
callback_info=InfoOutputRequested(
|
||||||
output_socket_name=output_socket_name,
|
output_socket_name=output_socket_name,
|
||||||
kind=kind,
|
kind=kind,
|
||||||
|
@ -342,7 +342,7 @@ def on_show_preview(
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
return event_decorator(
|
return event_decorator(
|
||||||
action_type=ct.DataFlowAction.ShowPreview,
|
event=ct.FlowEvent.ShowPreview,
|
||||||
callback_info={},
|
callback_info={},
|
||||||
**kwargs,
|
**kwargs,
|
||||||
)
|
)
|
||||||
|
@ -353,7 +353,7 @@ def on_show_plot(
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
return event_decorator(
|
return event_decorator(
|
||||||
action_type=ct.DataFlowAction.ShowPlot,
|
event=ct.FlowEvent.ShowPlot,
|
||||||
callback_info={},
|
callback_info={},
|
||||||
stop_propagation=stop_propagation,
|
stop_propagation=stop_propagation,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
|
|
|
@ -124,7 +124,7 @@ class ViewerNode(base.MaxwellSimNode):
|
||||||
)
|
)
|
||||||
def on_changed_plot_preview(self, props):
|
def on_changed_plot_preview(self, props):
|
||||||
if self.inputs['Data'].is_linked and props['auto_plot']:
|
if self.inputs['Data'].is_linked and props['auto_plot']:
|
||||||
self.trigger_action(ct.DataFlowAction.ShowPlot)
|
self.trigger_event(ct.FlowEvent.ShowPlot)
|
||||||
|
|
||||||
@events.on_value_changed(
|
@events.on_value_changed(
|
||||||
socket_name='Data',
|
socket_name='Data',
|
||||||
|
@ -137,7 +137,7 @@ class ViewerNode(base.MaxwellSimNode):
|
||||||
# Remove Non-Repreviewed Previews on Close
|
# Remove Non-Repreviewed Previews on Close
|
||||||
with node_tree.repreview_all():
|
with node_tree.repreview_all():
|
||||||
if self.inputs['Data'].is_linked and props['auto_3d_preview']:
|
if self.inputs['Data'].is_linked and props['auto_3d_preview']:
|
||||||
self.trigger_action(ct.DataFlowAction.ShowPreview)
|
self.trigger_event(ct.FlowEvent.ShowPreview)
|
||||||
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
|
|
@ -180,7 +180,7 @@ class Tidy3DWebExporterNode(base.MaxwellSimNode):
|
||||||
####################
|
####################
|
||||||
def sync_lock_tree(self, context):
|
def sync_lock_tree(self, context):
|
||||||
if self.lock_tree:
|
if self.lock_tree:
|
||||||
self.trigger_action(ct.DataFlowAction.EnableLock)
|
self.trigger_event(ct.FlowEvent.EnableLock)
|
||||||
self.locked = False
|
self.locked = False
|
||||||
for bl_socket in self.inputs:
|
for bl_socket in self.inputs:
|
||||||
if bl_socket.name == 'FDTD Sim':
|
if bl_socket.name == 'FDTD Sim':
|
||||||
|
@ -188,7 +188,7 @@ class Tidy3DWebExporterNode(base.MaxwellSimNode):
|
||||||
bl_socket.locked = False
|
bl_socket.locked = False
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.trigger_action(ct.DataFlowAction.DisableLock)
|
self.trigger_event(ct.FlowEvent.DisableLock)
|
||||||
|
|
||||||
self.sync_prop('lock_tree', context)
|
self.sync_prop('lock_tree', context)
|
||||||
|
|
||||||
|
|
|
@ -17,25 +17,68 @@ log = logger.get(__name__)
|
||||||
# - SocketDef
|
# - SocketDef
|
||||||
####################
|
####################
|
||||||
class SocketDef(pyd.BaseModel, abc.ABC):
|
class SocketDef(pyd.BaseModel, abc.ABC):
|
||||||
|
"""Defines everything needed to initialize a `MaxwellSimSocket`.
|
||||||
|
|
||||||
|
Used by nodes to specify which sockets to use as inputs/outputs.
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
Not instantiated directly - rather, individual sockets each define a SocketDef subclass tailored to its own needs.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
socket_type: The socket type to initialize.
|
||||||
|
"""
|
||||||
|
|
||||||
socket_type: ct.SocketType
|
socket_type: ct.SocketType
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def init(self, bl_socket: bpy.types.NodeSocket) -> None:
|
def init(self, bl_socket: bpy.types.NodeSocket) -> None:
|
||||||
"""Initializes a real Blender node socket from this socket definition."""
|
"""Initializes a real Blender node socket from this socket definition.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
bl_socket: The Blender node socket to alter using data from this SocketDef.
|
||||||
|
"""
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Serialization
|
# - Serialization
|
||||||
####################
|
####################
|
||||||
def dump_as_msgspec(self) -> serialize.NaiveRepresentation:
|
def dump_as_msgspec(self) -> serialize.NaiveRepresentation:
|
||||||
|
"""Transforms this `SocketDef` into an object that can be natively serialized by `msgspec`.
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
Makes use of `pydantic.BaseModel.model_dump()` to cast any special fields into a serializable format.
|
||||||
|
If this method is failing, check that `pydantic` can actually cast all the fields in your model.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A particular `list`, with three elements:
|
||||||
|
|
||||||
|
1. The `serialize`-provided "Type Identifier", to differentiate this list from generic list.
|
||||||
|
2. The name of this subclass, so that the correct `SocketDef` can be reconstructed on deserialization.
|
||||||
|
3. A dictionary containing simple Python types, as cast by `pydantic`.
|
||||||
|
"""
|
||||||
return [serialize.TypeID.SocketDef, self.__class__.__name__, self.model_dump()]
|
return [serialize.TypeID.SocketDef, self.__class__.__name__, self.model_dump()]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_as_msgspec(obj: serialize.NaiveRepresentation) -> typ.Self:
|
def parse_as_msgspec(obj: serialize.NaiveRepresentation) -> typ.Self:
|
||||||
return next(
|
"""Transforms an object made by `self.dump_as_msgspec()` into a subclass of `SocketDef`.
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
The method presumes that the deserialized object produced by `msgspec` perfectly matches the object originally created by `self.dump_as_msgspec()`.
|
||||||
|
|
||||||
|
This is a **mostly robust** presumption, as `pydantic` attempts to be quite consistent in how to interpret types with almost identical semantics.
|
||||||
|
Still, yet-unknown edge cases may challenge these presumptions.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A new subclass of `SocketDef`, initialized using the `model_dump()` dictionary.
|
||||||
|
"""
|
||||||
|
initialized_classes = [
|
||||||
subclass(**obj[2])
|
subclass(**obj[2])
|
||||||
for subclass in SocketDef.__subclasses__()
|
for subclass in SocketDef.__subclasses__()
|
||||||
if subclass.__name__ == obj[1]
|
if subclass.__name__ == obj[1]
|
||||||
)
|
]
|
||||||
|
if not initialized_classes:
|
||||||
|
msg = f'No "SocketDef" subclass found for name {obj[1]}. Please report this error'
|
||||||
|
RuntimeError(msg)
|
||||||
|
return next(initialized_classes)
|
||||||
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
@ -72,7 +115,6 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
|
||||||
socket_color: tuple
|
socket_color: tuple
|
||||||
|
|
||||||
# Options
|
# Options
|
||||||
# link_limit: int = 0
|
|
||||||
use_units: bool = False
|
use_units: bool = False
|
||||||
use_prelock: bool = False
|
use_prelock: bool = False
|
||||||
|
|
||||||
|
@ -132,14 +174,10 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
|
||||||
|
|
||||||
# Setup Style
|
# Setup Style
|
||||||
cls.socket_color = ct.SOCKET_COLORS[cls.socket_type]
|
cls.socket_color = ct.SOCKET_COLORS[cls.socket_type]
|
||||||
cls.socket_shape = ct.SOCKET_SHAPES[cls.socket_type]
|
|
||||||
|
|
||||||
# Setup List
|
# Setup List
|
||||||
cls.__annotations__['active_kind'] = bpy.props.StringProperty(
|
cls.set_prop(
|
||||||
name='Active Kind',
|
'active_kind', bpy.props.StringProperty, default=str(ct.FlowKind.Value)
|
||||||
description='The active Data Flow Kind',
|
|
||||||
default=str(ct.FlowKind.Value),
|
|
||||||
update=lambda self, _: self.sync_active_kind(),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Configure Use of Units
|
# Configure Use of Units
|
||||||
|
@ -169,86 +207,116 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
|
||||||
)
|
)
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Action Chain
|
# - Event Chain
|
||||||
####################
|
####################
|
||||||
def trigger_action(
|
def trigger_event(
|
||||||
self,
|
self,
|
||||||
action: ct.DataFlowAction,
|
event: ct.FlowEvent,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Called whenever the socket's output value has changed.
|
"""Called whenever the socket's output value has changed.
|
||||||
|
|
||||||
This also invalidates any of the socket's caches.
|
This also invalidates any of the socket's caches.
|
||||||
|
|
||||||
When called on an input node, the containing node's
|
When called on an input node, the containing node's
|
||||||
`trigger_action` method will be called with this socket.
|
`trigger_event` method will be called with this socket.
|
||||||
|
|
||||||
When called on a linked output node, the linked socket's
|
When called on a linked output node, the linked socket's
|
||||||
`trigger_action` method will be called.
|
`trigger_event` method will be called.
|
||||||
"""
|
"""
|
||||||
# Forwards Chains
|
# Forwards Chains
|
||||||
if action in {ct.DataFlowAction.DataChanged}:
|
if event in {ct.FlowEvent.DataChanged}:
|
||||||
## Input Socket
|
## Input Socket
|
||||||
if not self.is_output:
|
if not self.is_output:
|
||||||
self.node.trigger_action(action, socket_name=self.name)
|
self.node.trigger_event(event, socket_name=self.name)
|
||||||
|
|
||||||
## Linked Output Socket
|
## Linked Output Socket
|
||||||
elif self.is_output and self.is_linked:
|
elif self.is_output and self.is_linked:
|
||||||
for link in self.links:
|
for link in self.links:
|
||||||
link.to_socket.trigger_action(action)
|
link.to_socket.trigger_event(event)
|
||||||
|
|
||||||
# Backwards Chains
|
# Backwards Chains
|
||||||
elif action in {
|
elif event in {
|
||||||
ct.DataFlowAction.EnableLock,
|
ct.FlowEvent.EnableLock,
|
||||||
ct.DataFlowAction.DisableLock,
|
ct.FlowEvent.DisableLock,
|
||||||
ct.DataFlowAction.OutputRequested,
|
ct.FlowEvent.OutputRequested,
|
||||||
ct.DataFlowAction.DataChanged,
|
ct.FlowEvent.DataChanged,
|
||||||
ct.DataFlowAction.ShowPreview,
|
ct.FlowEvent.ShowPreview,
|
||||||
ct.DataFlowAction.ShowPlot,
|
ct.FlowEvent.ShowPlot,
|
||||||
}:
|
}:
|
||||||
if action == ct.DataFlowAction.EnableLock:
|
if event == ct.FlowEvent.EnableLock:
|
||||||
self.locked = True
|
self.locked = True
|
||||||
|
|
||||||
if action == ct.DataFlowAction.DisableLock:
|
if event == ct.FlowEvent.DisableLock:
|
||||||
self.locked = False
|
self.locked = False
|
||||||
|
|
||||||
## Output Socket
|
## Output Socket
|
||||||
if self.is_output:
|
if self.is_output:
|
||||||
self.node.trigger_action(action, socket_name=self.name)
|
self.node.trigger_event(event, socket_name=self.name)
|
||||||
|
|
||||||
## Linked Input Socket
|
## Linked Input Socket
|
||||||
elif not self.is_output and self.is_linked:
|
elif not self.is_output and self.is_linked:
|
||||||
for link in self.links:
|
for link in self.links:
|
||||||
link.from_socket.trigger_action(action)
|
link.from_socket.trigger_event(event)
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# - Action Chain: Event Handlers
|
# - Event Chain: Event Handlers
|
||||||
####################
|
####################
|
||||||
def sync_active_kind(self):
|
def sync_prop(self, prop_name: str, _: bpy.types.Context) -> None:
|
||||||
"""Called when the active data flow kind of the socket changes.
|
"""Called when a property has been updated.
|
||||||
|
|
||||||
Alters the shape of the socket to match the active FlowKind, then triggers `ct.DataFlowAction.DataChanged` on the current socket.
|
Contrary to `node.on_prop_changed()`, socket-specific callbacks are baked into this function:
|
||||||
|
|
||||||
|
- **Active Kind** (`active_kind`): Sets the socket shape to reflect the active `FlowKind`.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
prop_name: The name of the property that was changed.
|
||||||
"""
|
"""
|
||||||
self.display_shape = {
|
# Property: Active Kind
|
||||||
ct.FlowKind.Value: ct.SOCKET_SHAPES[self.socket_type],
|
if prop_name == 'active_kind':
|
||||||
ct.FlowKind.ValueArray: 'SQUARE',
|
self.display_shape(
|
||||||
ct.FlowKind.ValueSpectrum: 'SQUARE',
|
'SQUARE'
|
||||||
ct.FlowKind.LazyValue: ct.SOCKET_SHAPES[self.socket_type],
|
if self.active_kind
|
||||||
ct.FlowKind.LazyValueRange: 'SQUARE',
|
in {ct.FlowKind.LazyValue, ct.FlowKind.LazyValueRange}
|
||||||
ct.FlowKind.LazyValueSpectrum: 'SQUARE',
|
else 'CIRCLE'
|
||||||
}[self.active_kind] + ('_DOT' if self.use_units else '')
|
) + ('_DOT' if self.use_units else '')
|
||||||
|
|
||||||
self.trigger_action(ct.DataFlowAction.DataChanged)
|
# Valid Properties
|
||||||
|
elif hasattr(self, prop_name):
|
||||||
|
self.trigger_event(ct.FlowEvent.DataChanged)
|
||||||
|
|
||||||
def sync_prop(self, prop_name: str, _: bpy.types.Context):
|
# Undefined Properties
|
||||||
"""Called when a property has been updated."""
|
|
||||||
if hasattr(self, prop_name):
|
|
||||||
self.trigger_action(ct.DataFlowAction.DataChanged)
|
|
||||||
else:
|
else:
|
||||||
msg = f'Property {prop_name} not defined on socket {self}'
|
msg = f'Property {prop_name} not defined on socket {self}'
|
||||||
raise RuntimeError(msg)
|
raise RuntimeError(msg)
|
||||||
|
|
||||||
def sync_link_added(self, link) -> bool:
|
def allow_add_link(self, link: bpy.types.NodeLink) -> bool:
|
||||||
"""Called when a link has been added to this (input) socket."""
|
"""Called to ask whether a link may be added to this (input) socket.
|
||||||
|
|
||||||
|
- **Locked**: Locked sockets may not have links added.
|
||||||
|
- **Capabilities**: Capabilities of both sockets participating in the link must be compatible.
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
In practice, the link in question has already been added.
|
||||||
|
This function determines **whether the new link should be instantly removed** - if so, the removal producing the _practical effect_ of the link "not being added" at all.
|
||||||
|
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
link: The node link that was already added, whose continued existance is in question.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Whether or not consent is given to add the link.
|
||||||
|
In practice, the link will simply remain if consent is given.
|
||||||
|
If consent is not given, the new link will be removed.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
RuntimeError: If this socket is an output socket.
|
||||||
|
"""
|
||||||
|
# Output Socket Check
|
||||||
|
if self.is_output:
|
||||||
|
msg = 'Tried to ask output socket for consent to add link'
|
||||||
|
raise RuntimeError(msg)
|
||||||
|
|
||||||
|
# Lock Check
|
||||||
if self.locked:
|
if self.locked:
|
||||||
log.error(
|
log.error(
|
||||||
'Attempted to link output socket "%s" (%s) to input socket "%s" (%s), but input socket is locked',
|
'Attempted to link output socket "%s" (%s) to input socket "%s" (%s), but input socket is locked',
|
||||||
|
@ -258,36 +326,66 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
|
||||||
self.capabilities,
|
self.capabilities,
|
||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
# Capability Check
|
||||||
if not link.from_socket.capabilities.is_compatible_with(self.capabilities):
|
if not link.from_socket.capabilities.is_compatible_with(self.capabilities):
|
||||||
log.error(
|
log.error(
|
||||||
'Attempted to link output socket "%s" (%s) to input socket "%s" (%s), but capabilities are invalid',
|
'Attempted to link output socket "%s" (%s) to input socket "%s" (%s), but capabilities are incompatible',
|
||||||
link.from_socket.bl_label,
|
link.from_socket.bl_label,
|
||||||
link.from_socket.capabilities,
|
link.from_socket.capabilities,
|
||||||
self.bl_label,
|
self.bl_label,
|
||||||
self.capabilities,
|
self.capabilities,
|
||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
if self.is_output:
|
|
||||||
msg = "Tried to sync 'link add' on output socket"
|
|
||||||
raise RuntimeError(msg)
|
|
||||||
|
|
||||||
self.trigger_action(ct.DataFlowAction.DataChanged)
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def sync_link_removed(self, from_socket) -> bool:
|
def on_link_added(self, link: bpy.types.NodeLink) -> None:
|
||||||
"""Called when a link has been removed from this (input) socket.
|
"""Triggers a `ct.FlowEvent.LinkChanged` event on link add.
|
||||||
|
|
||||||
Returns a bool, whether or not the socket consents to the link change.
|
Attributes:
|
||||||
|
link: The node link that was added.
|
||||||
|
Currently unused.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Whether or not consent is given to add the link.
|
||||||
|
In practice, the link will simply remain if consent is given.
|
||||||
|
If consent is not given, the new link will be removed.
|
||||||
"""
|
"""
|
||||||
if self.locked:
|
self.trigger_event(ct.FlowEvent.DataChanged)
|
||||||
return False
|
|
||||||
|
def allow_remove_link(self, from_socket: bpy.types.NodeSocket) -> bool:
|
||||||
|
"""Called to ask whether a link may be removed from this `to_socket`.
|
||||||
|
|
||||||
|
- **Locked**: Locked sockets may not have links removed.
|
||||||
|
- **Capabilities**: Capabilities of both sockets participating in the link must be compatible.
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
In practice, the link in question has already been removed.
|
||||||
|
Therefore, only the `from_socket` that the link _was_ attached to is provided.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
from_socket: The node socket that was attached to before link removal.
|
||||||
|
Currently unused.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Whether or not consent is given to remove the link.
|
||||||
|
If so, nothing will happen.
|
||||||
|
If consent is not given, a new link will be added that is identical to the old one.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
RuntimeError: If this socket is an output socket.
|
||||||
|
"""
|
||||||
|
# Output Socket Check
|
||||||
if self.is_output:
|
if self.is_output:
|
||||||
msg = "Tried to sync 'link add' on output socket"
|
msg = "Tried to sync 'link add' on output socket"
|
||||||
raise RuntimeError(msg)
|
raise RuntimeError(msg)
|
||||||
|
|
||||||
self.trigger_action(ct.DataFlowAction.DataChanged)
|
# Lock Check
|
||||||
|
if self.locked:
|
||||||
|
return False
|
||||||
|
|
||||||
|
self.trigger_event(ct.FlowEvent.DataChanged)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
|
|
@ -18,7 +18,7 @@ class MaxwellMonitorSocketDef(base.SocketDef):
|
||||||
|
|
||||||
def init(self, bl_socket: MaxwellMonitorBLSocket) -> None:
|
def init(self, bl_socket: MaxwellMonitorBLSocket) -> None:
|
||||||
if self.is_list:
|
if self.is_list:
|
||||||
bl_socket.active_kind = ct.FlowKind.ValueArray
|
bl_socket.active_kind = ct.FlowKind.Array
|
||||||
|
|
||||||
|
|
||||||
####################
|
####################
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
class staticproperty(property): # noqa: N801
|
||||||
|
"""A read-only variant of `@property` that is entirely static, for use in specific situations.
|
||||||
|
|
||||||
|
The decorated method must take no arguments whatsoever, including `self`/`cls`.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
Use as usual:
|
||||||
|
```python
|
||||||
|
class Spam:
|
||||||
|
@staticproperty
|
||||||
|
def eggs():
|
||||||
|
return 10
|
||||||
|
|
||||||
|
assert Spam.eggs == 10
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __get__(self, *_):
|
||||||
|
"""Overridden getter that ignores instance and owner, and just returns the value of the evaluated (static) method.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The evaluated value of the static method that was decorated.
|
||||||
|
"""
|
||||||
|
return self.fget()
|
Loading…
Reference in New Issue