refactor: Fixes and movement.
parent
29cee2e7a2
commit
c6e00dcd7b
|
@ -36,7 +36,7 @@
|
|||
- 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)
|
||||
- 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)
|
||||
- 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,
|
||||
)
|
||||
|
||||
####################
|
||||
# - String Types
|
||||
####################
|
||||
from .bl import SocketName
|
||||
from .bl import PresetName
|
||||
from .bl import ManagedObjName
|
||||
|
||||
|
||||
from .bl import BLEnumID
|
||||
from .bl import BLColorRGBA
|
||||
|
||||
####################
|
||||
# - Icon Types
|
||||
####################
|
||||
from .bl_socket_desc_map import BL_SOCKET_DESCR_TYPE_MAP
|
||||
from .bl_socket_types import BL_SOCKET_DESCR_ANNOT_STRING, BL_SOCKET_DIRECT_TYPE_MAP
|
||||
from .category_labels import NODE_CAT_LABELS
|
||||
from .category_types import NodeCategory
|
||||
from .flow_events import FlowEvent
|
||||
from .flow_kinds import (
|
||||
ArrayFlow,
|
||||
CapabilitiesFlow,
|
||||
FlowKind,
|
||||
InfoFlow,
|
||||
LazyArrayRangeFlow,
|
||||
LazyValueFlow,
|
||||
ParamsFlow,
|
||||
ValueFlow,
|
||||
)
|
||||
from .icons import Icon
|
||||
|
||||
####################
|
||||
# - Tree Types
|
||||
####################
|
||||
from .trees import TreeType
|
||||
|
||||
####################
|
||||
# - Socket Types
|
||||
####################
|
||||
from .socket_types import SocketType
|
||||
|
||||
from .socket_units import SOCKET_UNITS
|
||||
from .mobj_types import ManagedObjType
|
||||
from .node_types import NodeType
|
||||
from .socket_colors import SOCKET_COLORS
|
||||
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 .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__ = [
|
||||
'SocketName',
|
||||
'PresetName',
|
||||
'ManagedObjName',
|
||||
'BLEnumID',
|
||||
'BLColorRGBA',
|
||||
'BLEnumID',
|
||||
'BLIconSet',
|
||||
'BLModifierType',
|
||||
'BLNodeTreeInterfaceID',
|
||||
'ManagedObjName',
|
||||
'PresetName',
|
||||
'SocketName',
|
||||
'Icon',
|
||||
'TreeType',
|
||||
'SocketType',
|
||||
|
@ -97,5 +66,5 @@ __all__ = [
|
|||
'LazyArrayRangeFlow',
|
||||
'ParamsFlow',
|
||||
'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
|
||||
|
||||
from ....utils import extra_sympy_units as spux
|
||||
from ....utils import sci_constants as constants
|
||||
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.
|
||||
LazyArrayRange: An object that generates an `Array` from range information (start/stop/step/spacing).
|
||||
This should be used instead of `Array` whenever possible.
|
||||
Param: An object providing data to complete `Lazy` data.
|
||||
For example,
|
||||
Info: An object providing context about other flows.
|
||||
For example,
|
||||
Param: A dictionary providing particular parameters for a lazy value.
|
||||
Info: An dictionary providing extra context about any aspect of flow.
|
||||
"""
|
||||
|
||||
Capabilities = enum.auto()
|
|
@ -1,6 +1,6 @@
|
|||
import enum
|
||||
|
||||
from ....utils.blender_type_enum import BlenderTypeEnum
|
||||
from blender_maxwell.blender_type_enum import 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)])
|
||||
|
||||
|
||||
####################
|
||||
# - Image Functions
|
||||
####################
|
||||
def apply_colormap(normalized_data, colormap):
|
||||
# Linear interpolation between colormap points
|
||||
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)
|
||||
|
||||
|
||||
####################
|
||||
# - Managed BL Image
|
||||
####################
|
||||
class ManagedBLImage(base.ManagedObj):
|
||||
managed_obj_type = ct.ManagedObjType.ManagedBLImage
|
||||
_bl_image_name: str
|
||||
|
@ -170,7 +176,7 @@ class ManagedBLImage(base.ManagedObj):
|
|||
)
|
||||
|
||||
####################
|
||||
# - Actions
|
||||
# - Methods
|
||||
####################
|
||||
def bl_select(self) -> None:
|
||||
"""Synchronizes the managed object to the preview, by manipulating
|
||||
|
|
|
@ -98,7 +98,7 @@ class ManagedBLMesh(base.ManagedObj):
|
|||
bpy.data.meshes.remove(bl_object.data)
|
||||
|
||||
####################
|
||||
# - Actions
|
||||
# - Methods
|
||||
####################
|
||||
def show_preview(self) -> None:
|
||||
"""Moves the managed Blender object to the preview collection.
|
||||
|
|
|
@ -12,8 +12,6 @@ from . import base
|
|||
|
||||
log = logger.get(__name__)
|
||||
|
||||
ModifierType: typ.TypeAlias = typx.Literal['NODES', 'ARRAY']
|
||||
NodeTreeInterfaceID: typ.TypeAlias = str
|
||||
UnitSystem: typ.TypeAlias = typ.Any
|
||||
|
||||
|
||||
|
@ -33,7 +31,7 @@ class ModifierAttrsNODES(typ.TypedDict):
|
|||
|
||||
node_group: bpy.types.GeometryNodeTree
|
||||
unit_system: UnitSystem
|
||||
inputs: dict[NodeTreeInterfaceID, typ.Any]
|
||||
inputs: dict[ct.BLNodeTreeInterfaceID, typ.Any]
|
||||
|
||||
|
||||
class ModifierAttrsARRAY(typ.TypedDict):
|
||||
|
@ -222,7 +220,7 @@ class ManagedBLModifier(base.ManagedObj):
|
|||
def bl_modifier(
|
||||
self,
|
||||
bl_object: bpy.types.Object,
|
||||
modifier_type: ModifierType,
|
||||
modifier_type: ct.BLModifierType,
|
||||
modifier_attrs: ModifierAttrs,
|
||||
):
|
||||
"""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.
|
||||
## If NO: Queue re-adding the link (safe since the sockets exist)
|
||||
## 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:
|
||||
link_corrections['to_add'].append((from_socket, to_socket))
|
||||
|
||||
|
@ -354,12 +354,14 @@ class MaxwellSimTree(bpy.types.NodeTree):
|
|||
# Retrieve Link Reference
|
||||
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.
|
||||
## 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:
|
||||
link_corrections['to_remove'].append(link)
|
||||
else:
|
||||
link.to_socket.on_link_added(link)
|
||||
|
||||
# Link Corrections
|
||||
## ADD: Links that 'to_socket' don't want removed.
|
||||
|
|
|
@ -121,28 +121,26 @@ class MaxwellSimNode(bpy.types.Node):
|
|||
|
||||
@classmethod
|
||||
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:
|
||||
- 'Event methods' must have an attribute 'action_type' in order to be picked up.
|
||||
- 'Event methods' must have an attribute 'action_type'.
|
||||
- 'Event methods' must have an attribute 'event' in order to be picked up.
|
||||
- 'Event methods' must have an attribute 'event'.
|
||||
|
||||
Returns:
|
||||
Event methods, indexed by the action that (maybe) triggers them.
|
||||
Event methods, indexed by the event that (maybe) triggers them.
|
||||
"""
|
||||
event_methods = [
|
||||
method
|
||||
for attr_name in dir(cls)
|
||||
if hasattr(method := getattr(cls, attr_name), 'action_type')
|
||||
and method.action_type in set(ct.DataFlowAction)
|
||||
if hasattr(method := getattr(cls, attr_name), 'event')
|
||||
and method.event in set(ct.FlowEvent)
|
||||
]
|
||||
event_methods_by_action = {
|
||||
action_type: [] for action_type in set(ct.DataFlowAction)
|
||||
}
|
||||
event_methods_by_event = {event: [] for event in set(ct.FlowEvent)}
|
||||
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
|
||||
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)
|
||||
|
||||
## Event Method Callbacks
|
||||
cls.event_methods_by_action = cls._gather_event_methods()
|
||||
cls.event_methods_by_event = cls._gather_event_methods()
|
||||
|
||||
## Active Socket Set
|
||||
if len(cls.input_socket_sets) + len(cls.output_socket_sets) > 0:
|
||||
|
@ -483,25 +481,22 @@ class MaxwellSimNode(bpy.types.Node):
|
|||
# - Event Methods
|
||||
####################
|
||||
@property
|
||||
def _event_method_filter_by_action(self) -> dict[ct.DataFlowAction, typ.Callable]:
|
||||
"""Compute a map of DataFlowActions, to a function that filters its event methods.
|
||||
def _event_method_filter_by_event(self) -> dict[ct.FlowEvent, typ.Callable]:
|
||||
"""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`.
|
||||
They may use attributes of `self`, always return `True` or `False`, or something different.
|
||||
|
||||
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:
|
||||
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 {
|
||||
ct.DataFlowAction.EnableLock: lambda *_: True,
|
||||
ct.DataFlowAction.DisableLock: lambda *_: True,
|
||||
ct.DataFlowAction.DataChanged: lambda event_method,
|
||||
socket_name,
|
||||
prop_name,
|
||||
_: (
|
||||
ct.FlowEvent.EnableLock: lambda *_: True,
|
||||
ct.FlowEvent.DisableLock: lambda *_: True,
|
||||
ct.FlowEvent.DataChanged: lambda event_method, socket_name, prop_name, _: (
|
||||
(
|
||||
socket_name
|
||||
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
|
||||
)
|
||||
),
|
||||
ct.DataFlowAction.OutputRequested: lambda output_socket_method,
|
||||
ct.FlowEvent.OutputRequested: lambda output_socket_method,
|
||||
output_socket_name,
|
||||
_,
|
||||
kind: (
|
||||
|
@ -526,26 +521,26 @@ class MaxwellSimNode(bpy.types.Node):
|
|||
== output_socket_method.callback_info.output_socket_name
|
||||
)
|
||||
),
|
||||
ct.DataFlowAction.ShowPreview: lambda *_: True,
|
||||
ct.DataFlowAction.ShowPlot: lambda *_: True,
|
||||
ct.FlowEvent.ShowPreview: lambda *_: True,
|
||||
ct.FlowEvent.ShowPlot: lambda *_: True,
|
||||
}
|
||||
|
||||
def filtered_event_methods_by_action(
|
||||
def filtered_event_methods_by_event(
|
||||
self,
|
||||
action: ct.DataFlowAction,
|
||||
event: ct.FlowEvent,
|
||||
_filter: tuple[ct.SocketName, str],
|
||||
) -> list[typ.Callable]:
|
||||
"""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:
|
||||
All `event_method`s that should run, as callable objects (they can be run using `event_method(self)`).
|
||||
"""
|
||||
return [
|
||||
event_method
|
||||
for event_method in self.event_methods_by_action[action]
|
||||
if self._event_method_filter_by_action[action](event_method, *_filter)
|
||||
for event_method in self.event_methods_by_event[event]
|
||||
if self._event_method_filter_by_event[event](event_method, *_filter)
|
||||
]
|
||||
|
||||
####################
|
||||
|
@ -591,7 +586,7 @@ class MaxwellSimNode(bpy.types.Node):
|
|||
raise ValueError(msg)
|
||||
|
||||
####################
|
||||
# - Compute Action: Output Socket
|
||||
# - Compute Event: Output Socket
|
||||
####################
|
||||
@bl_cache.keyed_cache(
|
||||
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"
|
||||
raise RuntimeError(msg)
|
||||
|
||||
output_socket_methods = self.filtered_event_methods_by_action(
|
||||
ct.DataFlowAction.OutputRequested,
|
||||
output_socket_methods = self.filtered_event_methods_by_event(
|
||||
ct.FlowEvent.OutputRequested,
|
||||
(output_socket_name, None, kind),
|
||||
)
|
||||
|
||||
|
@ -636,7 +631,7 @@ class MaxwellSimNode(bpy.types.Node):
|
|||
raise ValueError(msg)
|
||||
|
||||
####################
|
||||
# - Action Trigger
|
||||
# - Event Trigger
|
||||
####################
|
||||
def _should_recompute_output_socket(
|
||||
self,
|
||||
|
@ -657,25 +652,25 @@ class MaxwellSimNode(bpy.types.Node):
|
|||
)
|
||||
)
|
||||
|
||||
def trigger_action(
|
||||
def trigger_event(
|
||||
self,
|
||||
action: ct.DataFlowAction,
|
||||
event: ct.FlowEvent,
|
||||
socket_name: ct.SocketName | None = None,
|
||||
prop_name: ct.SocketName | 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:
|
||||
This can be an unpredictably heavy function, depending on the node graph topology.
|
||||
|
||||
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.
|
||||
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
|
||||
|
||||
# Invalidate Input Socket Cache
|
||||
|
@ -687,8 +682,8 @@ class MaxwellSimNode(bpy.types.Node):
|
|||
)
|
||||
|
||||
# Invalidate Output Socket Cache
|
||||
for output_socket_method in self.event_methods_by_action[
|
||||
ct.DataFlowAction.OutputRequested
|
||||
for output_socket_method in self.event_methods_by_event[
|
||||
ct.FlowEvent.OutputRequested
|
||||
]:
|
||||
method_info = output_socket_method.callback_info
|
||||
if self._should_recompute_output_socket(
|
||||
|
@ -701,24 +696,24 @@ class MaxwellSimNode(bpy.types.Node):
|
|||
|
||||
# Run Triggered Event Methods
|
||||
stop_propagation = False
|
||||
triggered_event_methods = self.filtered_event_methods_by_action(
|
||||
action, (socket_name, prop_name, None)
|
||||
triggered_event_methods = self.filtered_event_methods_by_event(
|
||||
event, (socket_name, prop_name, None)
|
||||
)
|
||||
for event_method in triggered_event_methods:
|
||||
stop_propagation |= event_method.stop_propagation
|
||||
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/...
|
||||
if not stop_propagation:
|
||||
triggered_sockets = self._bl_sockets(
|
||||
direc=ct.DataFlowAction.trigger_direction(action)
|
||||
direc=ct.FlowEvent.flow_direction[event]
|
||||
)
|
||||
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:
|
||||
"""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.
|
||||
"""
|
||||
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:
|
||||
msg = f'Property {prop_name} not defined on node {self}'
|
||||
raise RuntimeError(msg)
|
||||
|
@ -864,9 +859,7 @@ class MaxwellSimNode(bpy.types.Node):
|
|||
## -> Compromise: Users explicitly say 'run_on_init' in @on_value_changed
|
||||
for event_method in [
|
||||
event_method
|
||||
for event_method in self.event_methods_by_action[
|
||||
ct.DataFlowAction.DataChanged
|
||||
]
|
||||
for event_method in self.event_methods_by_event[ct.FlowEvent.DataChanged]
|
||||
if event_method.callback_info.run_on_init
|
||||
]:
|
||||
event_method(self)
|
||||
|
@ -915,7 +908,7 @@ class MaxwellSimNode(bpy.types.Node):
|
|||
bl_socket.is_linked and bl_socket.locked
|
||||
for bl_socket in self.inputs.values()
|
||||
):
|
||||
self.trigger_action(ct.DataFlowAction.DisableLock)
|
||||
self.trigger_event(ct.FlowEvent.DisableLock)
|
||||
|
||||
# Free Managed Objects
|
||||
for managed_obj in self.managed_objs.values():
|
||||
|
|
|
@ -50,7 +50,7 @@ PropName: typ.TypeAlias = str
|
|||
|
||||
|
||||
def event_decorator(
|
||||
action_type: ct.DataFlowAction,
|
||||
event: ct.FlowEvent,
|
||||
callback_info: EventCallbackInfo | None,
|
||||
stop_propagation: bool = False,
|
||||
# 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.
|
||||
|
||||
Parameters:
|
||||
action_type: A name describing which event the decorator should respond to.
|
||||
Set to `return_method.action_type`
|
||||
callback_info: A dictionary that provides the caller with additional per-`action_type` 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.
|
||||
event: A name describing which event the decorator should respond to.
|
||||
Set to `return_method.event`
|
||||
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 events to take after running the callback.
|
||||
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.
|
||||
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`.
|
||||
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 = (
|
||||
{'self'}
|
||||
|
@ -252,14 +252,14 @@ def event_decorator(
|
|||
)
|
||||
|
||||
# Set Decorated Attributes and Return
|
||||
## Fix Introspection + Documentation
|
||||
## TODO: Fix Introspection + Documentation
|
||||
# decorated.__name__ = method.__name__
|
||||
# decorated.__module__ = method.__module__
|
||||
# decorated.__qualname__ = method.__qualname__
|
||||
# decorated.__doc__ = method.__doc__
|
||||
decorated.__doc__ = method.__doc__
|
||||
|
||||
## Add Spice
|
||||
decorated.action_type = action_type
|
||||
decorated.event = event
|
||||
decorated.callback_info = callback_info
|
||||
decorated.stop_propagation = stop_propagation
|
||||
|
||||
|
@ -275,7 +275,7 @@ def on_enable_lock(
|
|||
**kwargs,
|
||||
):
|
||||
return event_decorator(
|
||||
action_type=ct.DataFlowAction.EnableLock,
|
||||
event=ct.FlowEvent.EnableLock,
|
||||
callback_info=None,
|
||||
**kwargs,
|
||||
)
|
||||
|
@ -285,7 +285,7 @@ def on_disable_lock(
|
|||
**kwargs,
|
||||
):
|
||||
return event_decorator(
|
||||
action_type=ct.DataFlowAction.DisableLock,
|
||||
event=ct.FlowEvent.DisableLock,
|
||||
callback_info=None,
|
||||
**kwargs,
|
||||
)
|
||||
|
@ -300,7 +300,7 @@ def on_value_changed(
|
|||
**kwargs,
|
||||
):
|
||||
return event_decorator(
|
||||
action_type=ct.DataFlowAction.DataChanged,
|
||||
event=ct.FlowEvent.DataChanged,
|
||||
callback_info=InfoDataChanged(
|
||||
run_on_init=run_on_init,
|
||||
on_changed_sockets=(
|
||||
|
@ -320,7 +320,7 @@ def computes_output_socket(
|
|||
**kwargs,
|
||||
):
|
||||
return event_decorator(
|
||||
action_type=ct.DataFlowAction.OutputRequested,
|
||||
event=ct.FlowEvent.OutputRequested,
|
||||
callback_info=InfoOutputRequested(
|
||||
output_socket_name=output_socket_name,
|
||||
kind=kind,
|
||||
|
@ -342,7 +342,7 @@ def on_show_preview(
|
|||
**kwargs,
|
||||
):
|
||||
return event_decorator(
|
||||
action_type=ct.DataFlowAction.ShowPreview,
|
||||
event=ct.FlowEvent.ShowPreview,
|
||||
callback_info={},
|
||||
**kwargs,
|
||||
)
|
||||
|
@ -353,7 +353,7 @@ def on_show_plot(
|
|||
**kwargs,
|
||||
):
|
||||
return event_decorator(
|
||||
action_type=ct.DataFlowAction.ShowPlot,
|
||||
event=ct.FlowEvent.ShowPlot,
|
||||
callback_info={},
|
||||
stop_propagation=stop_propagation,
|
||||
**kwargs,
|
||||
|
|
|
@ -124,7 +124,7 @@ class ViewerNode(base.MaxwellSimNode):
|
|||
)
|
||||
def on_changed_plot_preview(self, props):
|
||||
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(
|
||||
socket_name='Data',
|
||||
|
@ -137,7 +137,7 @@ class ViewerNode(base.MaxwellSimNode):
|
|||
# Remove Non-Repreviewed Previews on Close
|
||||
with node_tree.repreview_all():
|
||||
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):
|
||||
if self.lock_tree:
|
||||
self.trigger_action(ct.DataFlowAction.EnableLock)
|
||||
self.trigger_event(ct.FlowEvent.EnableLock)
|
||||
self.locked = False
|
||||
for bl_socket in self.inputs:
|
||||
if bl_socket.name == 'FDTD Sim':
|
||||
|
@ -188,7 +188,7 @@ class Tidy3DWebExporterNode(base.MaxwellSimNode):
|
|||
bl_socket.locked = False
|
||||
|
||||
else:
|
||||
self.trigger_action(ct.DataFlowAction.DisableLock)
|
||||
self.trigger_event(ct.FlowEvent.DisableLock)
|
||||
|
||||
self.sync_prop('lock_tree', context)
|
||||
|
||||
|
|
|
@ -17,25 +17,68 @@ log = logger.get(__name__)
|
|||
# - SocketDef
|
||||
####################
|
||||
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
|
||||
|
||||
@abc.abstractmethod
|
||||
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
|
||||
####################
|
||||
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()]
|
||||
|
||||
@staticmethod
|
||||
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])
|
||||
for subclass in SocketDef.__subclasses__()
|
||||
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
|
||||
|
||||
# Options
|
||||
# link_limit: int = 0
|
||||
use_units: bool = False
|
||||
use_prelock: bool = False
|
||||
|
||||
|
@ -132,14 +174,10 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
|
|||
|
||||
# Setup Style
|
||||
cls.socket_color = ct.SOCKET_COLORS[cls.socket_type]
|
||||
cls.socket_shape = ct.SOCKET_SHAPES[cls.socket_type]
|
||||
|
||||
# Setup List
|
||||
cls.__annotations__['active_kind'] = bpy.props.StringProperty(
|
||||
name='Active Kind',
|
||||
description='The active Data Flow Kind',
|
||||
default=str(ct.FlowKind.Value),
|
||||
update=lambda self, _: self.sync_active_kind(),
|
||||
cls.set_prop(
|
||||
'active_kind', bpy.props.StringProperty, default=str(ct.FlowKind.Value)
|
||||
)
|
||||
|
||||
# Configure Use of Units
|
||||
|
@ -169,86 +207,116 @@ class MaxwellSimSocket(bpy.types.NodeSocket):
|
|||
)
|
||||
|
||||
####################
|
||||
# - Action Chain
|
||||
# - Event Chain
|
||||
####################
|
||||
def trigger_action(
|
||||
def trigger_event(
|
||||
self,
|
||||
action: ct.DataFlowAction,
|
||||
event: ct.FlowEvent,
|
||||
) -> None:
|
||||
"""Called whenever the socket's output value has changed.
|
||||
|
||||
This also invalidates any of the socket's caches.
|
||||
|
||||
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
|
||||
`trigger_action` method will be called.
|
||||
`trigger_event` method will be called.
|
||||
"""
|
||||
# Forwards Chains
|
||||
if action in {ct.DataFlowAction.DataChanged}:
|
||||
if event in {ct.FlowEvent.DataChanged}:
|
||||
## Input Socket
|
||||
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
|
||||
elif self.is_output and self.is_linked:
|
||||
for link in self.links:
|
||||
link.to_socket.trigger_action(action)
|
||||
link.to_socket.trigger_event(event)
|
||||
|
||||
# Backwards Chains
|
||||
elif action in {
|
||||
ct.DataFlowAction.EnableLock,
|
||||
ct.DataFlowAction.DisableLock,
|
||||
ct.DataFlowAction.OutputRequested,
|
||||
ct.DataFlowAction.DataChanged,
|
||||
ct.DataFlowAction.ShowPreview,
|
||||
ct.DataFlowAction.ShowPlot,
|
||||
elif event in {
|
||||
ct.FlowEvent.EnableLock,
|
||||
ct.FlowEvent.DisableLock,
|
||||
ct.FlowEvent.OutputRequested,
|
||||
ct.FlowEvent.DataChanged,
|
||||
ct.FlowEvent.ShowPreview,
|
||||
ct.FlowEvent.ShowPlot,
|
||||
}:
|
||||
if action == ct.DataFlowAction.EnableLock:
|
||||
if event == ct.FlowEvent.EnableLock:
|
||||
self.locked = True
|
||||
|
||||
if action == ct.DataFlowAction.DisableLock:
|
||||
if event == ct.FlowEvent.DisableLock:
|
||||
self.locked = False
|
||||
|
||||
## Output Socket
|
||||
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
|
||||
elif not self.is_output and self.is_linked:
|
||||
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):
|
||||
"""Called when the active data flow kind of the socket changes.
|
||||
def sync_prop(self, prop_name: str, _: bpy.types.Context) -> None:
|
||||
"""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 = {
|
||||
ct.FlowKind.Value: ct.SOCKET_SHAPES[self.socket_type],
|
||||
ct.FlowKind.ValueArray: 'SQUARE',
|
||||
ct.FlowKind.ValueSpectrum: 'SQUARE',
|
||||
ct.FlowKind.LazyValue: ct.SOCKET_SHAPES[self.socket_type],
|
||||
ct.FlowKind.LazyValueRange: 'SQUARE',
|
||||
ct.FlowKind.LazyValueSpectrum: 'SQUARE',
|
||||
}[self.active_kind] + ('_DOT' if self.use_units else '')
|
||||
# Property: Active Kind
|
||||
if prop_name == 'active_kind':
|
||||
self.display_shape(
|
||||
'SQUARE'
|
||||
if self.active_kind
|
||||
in {ct.FlowKind.LazyValue, ct.FlowKind.LazyValueRange}
|
||||
else 'CIRCLE'
|
||||
) + ('_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):
|
||||
"""Called when a property has been updated."""
|
||||
if hasattr(self, prop_name):
|
||||
self.trigger_action(ct.DataFlowAction.DataChanged)
|
||||
# Undefined Properties
|
||||
else:
|
||||
msg = f'Property {prop_name} not defined on socket {self}'
|
||||
raise RuntimeError(msg)
|
||||
|
||||
def sync_link_added(self, link) -> bool:
|
||||
"""Called when a link has been added to this (input) socket."""
|
||||
def allow_add_link(self, link: bpy.types.NodeLink) -> bool:
|
||||
"""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:
|
||||
log.error(
|
||||
'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,
|
||||
)
|
||||
return False
|
||||
|
||||
# Capability Check
|
||||
if not link.from_socket.capabilities.is_compatible_with(self.capabilities):
|
||||
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.capabilities,
|
||||
self.bl_label,
|
||||
self.capabilities,
|
||||
)
|
||||
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
|
||||
|
||||
def sync_link_removed(self, from_socket) -> bool:
|
||||
"""Called when a link has been removed from this (input) socket.
|
||||
def on_link_added(self, link: bpy.types.NodeLink) -> None:
|
||||
"""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:
|
||||
return False
|
||||
self.trigger_event(ct.FlowEvent.DataChanged)
|
||||
|
||||
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:
|
||||
msg = "Tried to sync 'link add' on output socket"
|
||||
raise RuntimeError(msg)
|
||||
|
||||
self.trigger_action(ct.DataFlowAction.DataChanged)
|
||||
# Lock Check
|
||||
if self.locked:
|
||||
return False
|
||||
|
||||
self.trigger_event(ct.FlowEvent.DataChanged)
|
||||
return True
|
||||
|
||||
####################
|
||||
|
|
|
@ -18,7 +18,7 @@ class MaxwellMonitorSocketDef(base.SocketDef):
|
|||
|
||||
def init(self, bl_socket: MaxwellMonitorBLSocket) -> None:
|
||||
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