nodebpy 520.14.0__tar.gz → 520.16.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. {nodebpy-520.14.0 → nodebpy-520.16.0}/PKG-INFO +1 -1
  2. {nodebpy-520.14.0 → nodebpy-520.16.0}/pyproject.toml +1 -1
  3. {nodebpy-520.14.0 → nodebpy-520.16.0}/src/nodebpy/builder/__init__.py +2 -1
  4. {nodebpy-520.14.0 → nodebpy-520.16.0}/src/nodebpy/builder/items.py +63 -2
  5. {nodebpy-520.14.0 → nodebpy-520.16.0}/src/nodebpy/builder/tree.py +42 -0
  6. {nodebpy-520.14.0 → nodebpy-520.16.0}/src/nodebpy/export/codegen.py +15 -1
  7. {nodebpy-520.14.0 → nodebpy-520.16.0}/src/nodebpy/nodes/compositor/manual.py +8 -8
  8. {nodebpy-520.14.0 → nodebpy-520.16.0}/src/nodebpy/nodes/geometry/manual.py +141 -48
  9. {nodebpy-520.14.0 → nodebpy-520.16.0}/src/nodebpy/nodes/geometry/zone.py +16 -0
  10. {nodebpy-520.14.0 → nodebpy-520.16.0}/src/nodebpy/nodes/shader/manual.py +10 -10
  11. {nodebpy-520.14.0 → nodebpy-520.16.0}/src/nodebpy/types.py +1 -0
  12. {nodebpy-520.14.0 → nodebpy-520.16.0}/README.md +0 -0
  13. {nodebpy-520.14.0 → nodebpy-520.16.0}/src/nodebpy/__init__.py +0 -0
  14. {nodebpy-520.14.0 → nodebpy-520.16.0}/src/nodebpy/assets/__init__.py +0 -0
  15. {nodebpy-520.14.0 → nodebpy-520.16.0}/src/nodebpy/assets/__main__.py +0 -0
  16. {nodebpy-520.14.0 → nodebpy-520.16.0}/src/nodebpy/assets/_codegen.py +0 -0
  17. {nodebpy-520.14.0 → nodebpy-520.16.0}/src/nodebpy/builder/_registry.py +0 -0
  18. {nodebpy-520.14.0 → nodebpy-520.16.0}/src/nodebpy/builder/_utils.py +0 -0
  19. {nodebpy-520.14.0 → nodebpy-520.16.0}/src/nodebpy/builder/accessor.py +0 -0
  20. {nodebpy-520.14.0 → nodebpy-520.16.0}/src/nodebpy/builder/arrange.py +0 -0
  21. {nodebpy-520.14.0 → nodebpy-520.16.0}/src/nodebpy/builder/asset.py +0 -0
  22. {nodebpy-520.14.0 → nodebpy-520.16.0}/src/nodebpy/builder/mixins.py +0 -0
  23. {nodebpy-520.14.0 → nodebpy-520.16.0}/src/nodebpy/builder/node.py +0 -0
  24. {nodebpy-520.14.0 → nodebpy-520.16.0}/src/nodebpy/builder/socket.py +0 -0
  25. {nodebpy-520.14.0 → nodebpy-520.16.0}/src/nodebpy/export/__init__.py +0 -0
  26. {nodebpy-520.14.0 → nodebpy-520.16.0}/src/nodebpy/export/diagram.py +0 -0
  27. {nodebpy-520.14.0 → nodebpy-520.16.0}/src/nodebpy/export/web_render.py +0 -0
  28. {nodebpy-520.14.0 → nodebpy-520.16.0}/src/nodebpy/lib/nodearrange/__init__.py +0 -0
  29. {nodebpy-520.14.0 → nodebpy-520.16.0}/src/nodebpy/lib/nodearrange/arrange/__init__.py +0 -0
  30. {nodebpy-520.14.0 → nodebpy-520.16.0}/src/nodebpy/lib/nodearrange/arrange/graph.py +0 -0
  31. {nodebpy-520.14.0 → nodebpy-520.16.0}/src/nodebpy/lib/nodearrange/arrange/ordering.py +0 -0
  32. {nodebpy-520.14.0 → nodebpy-520.16.0}/src/nodebpy/lib/nodearrange/arrange/ranking.py +0 -0
  33. {nodebpy-520.14.0 → nodebpy-520.16.0}/src/nodebpy/lib/nodearrange/arrange/realize.py +0 -0
  34. {nodebpy-520.14.0 → nodebpy-520.16.0}/src/nodebpy/lib/nodearrange/arrange/stacking.py +0 -0
  35. {nodebpy-520.14.0 → nodebpy-520.16.0}/src/nodebpy/lib/nodearrange/arrange/structs.py +0 -0
  36. {nodebpy-520.14.0 → nodebpy-520.16.0}/src/nodebpy/lib/nodearrange/arrange/sugiyama.py +0 -0
  37. {nodebpy-520.14.0 → nodebpy-520.16.0}/src/nodebpy/lib/nodearrange/arrange/x_coords.py +0 -0
  38. {nodebpy-520.14.0 → nodebpy-520.16.0}/src/nodebpy/lib/nodearrange/arrange/y_coords.py +0 -0
  39. {nodebpy-520.14.0 → nodebpy-520.16.0}/src/nodebpy/lib/nodearrange/config.py +0 -0
  40. {nodebpy-520.14.0 → nodebpy-520.16.0}/src/nodebpy/lib/nodearrange/utils.py +0 -0
  41. {nodebpy-520.14.0 → nodebpy-520.16.0}/src/nodebpy/nodes/__init__.py +0 -0
  42. {nodebpy-520.14.0 → nodebpy-520.16.0}/src/nodebpy/nodes/_mixins.py +0 -0
  43. {nodebpy-520.14.0 → nodebpy-520.16.0}/src/nodebpy/nodes/compositor/__init__.py +0 -0
  44. {nodebpy-520.14.0 → nodebpy-520.16.0}/src/nodebpy/nodes/compositor/assets.py +0 -0
  45. {nodebpy-520.14.0 → nodebpy-520.16.0}/src/nodebpy/nodes/compositor/color.py +0 -0
  46. {nodebpy-520.14.0 → nodebpy-520.16.0}/src/nodebpy/nodes/compositor/converter.py +0 -0
  47. {nodebpy-520.14.0 → nodebpy-520.16.0}/src/nodebpy/nodes/compositor/distort.py +0 -0
  48. {nodebpy-520.14.0 → nodebpy-520.16.0}/src/nodebpy/nodes/compositor/filter.py +0 -0
  49. {nodebpy-520.14.0 → nodebpy-520.16.0}/src/nodebpy/nodes/compositor/group.py +0 -0
  50. {nodebpy-520.14.0 → nodebpy-520.16.0}/src/nodebpy/nodes/compositor/input.py +0 -0
  51. {nodebpy-520.14.0 → nodebpy-520.16.0}/src/nodebpy/nodes/compositor/interface.py +0 -0
  52. {nodebpy-520.14.0 → nodebpy-520.16.0}/src/nodebpy/nodes/compositor/matte.py +0 -0
  53. {nodebpy-520.14.0 → nodebpy-520.16.0}/src/nodebpy/nodes/compositor/output.py +0 -0
  54. {nodebpy-520.14.0 → nodebpy-520.16.0}/src/nodebpy/nodes/compositor/vector.py +0 -0
  55. {nodebpy-520.14.0 → nodebpy-520.16.0}/src/nodebpy/nodes/geometry/__init__.py +0 -0
  56. {nodebpy-520.14.0 → nodebpy-520.16.0}/src/nodebpy/nodes/geometry/assets.py +0 -0
  57. {nodebpy-520.14.0 → nodebpy-520.16.0}/src/nodebpy/nodes/geometry/attribute.py +0 -0
  58. {nodebpy-520.14.0 → nodebpy-520.16.0}/src/nodebpy/nodes/geometry/color.py +0 -0
  59. {nodebpy-520.14.0 → nodebpy-520.16.0}/src/nodebpy/nodes/geometry/converter.py +0 -0
  60. {nodebpy-520.14.0 → nodebpy-520.16.0}/src/nodebpy/nodes/geometry/geometry.py +0 -0
  61. {nodebpy-520.14.0 → nodebpy-520.16.0}/src/nodebpy/nodes/geometry/grid.py +0 -0
  62. {nodebpy-520.14.0 → nodebpy-520.16.0}/src/nodebpy/nodes/geometry/group.py +0 -0
  63. {nodebpy-520.14.0 → nodebpy-520.16.0}/src/nodebpy/nodes/geometry/groups.py +0 -0
  64. {nodebpy-520.14.0 → nodebpy-520.16.0}/src/nodebpy/nodes/geometry/input.py +0 -0
  65. {nodebpy-520.14.0 → nodebpy-520.16.0}/src/nodebpy/nodes/geometry/interface.py +0 -0
  66. {nodebpy-520.14.0 → nodebpy-520.16.0}/src/nodebpy/nodes/geometry/output.py +0 -0
  67. {nodebpy-520.14.0 → nodebpy-520.16.0}/src/nodebpy/nodes/geometry/texture.py +0 -0
  68. {nodebpy-520.14.0 → nodebpy-520.16.0}/src/nodebpy/nodes/geometry/utilities.py +0 -0
  69. {nodebpy-520.14.0 → nodebpy-520.16.0}/src/nodebpy/nodes/geometry/vector.py +0 -0
  70. {nodebpy-520.14.0 → nodebpy-520.16.0}/src/nodebpy/nodes/shader/__init__.py +0 -0
  71. {nodebpy-520.14.0 → nodebpy-520.16.0}/src/nodebpy/nodes/shader/assets.py +0 -0
  72. {nodebpy-520.14.0 → nodebpy-520.16.0}/src/nodebpy/nodes/shader/color.py +0 -0
  73. {nodebpy-520.14.0 → nodebpy-520.16.0}/src/nodebpy/nodes/shader/converter.py +0 -0
  74. {nodebpy-520.14.0 → nodebpy-520.16.0}/src/nodebpy/nodes/shader/grid.py +0 -0
  75. {nodebpy-520.14.0 → nodebpy-520.16.0}/src/nodebpy/nodes/shader/group.py +0 -0
  76. {nodebpy-520.14.0 → nodebpy-520.16.0}/src/nodebpy/nodes/shader/input.py +0 -0
  77. {nodebpy-520.14.0 → nodebpy-520.16.0}/src/nodebpy/nodes/shader/output.py +0 -0
  78. {nodebpy-520.14.0 → nodebpy-520.16.0}/src/nodebpy/nodes/shader/script.py +0 -0
  79. {nodebpy-520.14.0 → nodebpy-520.16.0}/src/nodebpy/nodes/shader/shader.py +0 -0
  80. {nodebpy-520.14.0 → nodebpy-520.16.0}/src/nodebpy/nodes/shader/texture.py +0 -0
  81. {nodebpy-520.14.0 → nodebpy-520.16.0}/src/nodebpy/nodes/shader/vector.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: nodebpy
3
- Version: 520.14.0
3
+ Version: 520.16.0
4
4
  Summary: Build nodes trees in Blender more elegantly with code
5
5
  Author: Brady Johnston
6
6
  Author-email: Brady Johnston <brady.johnston@me.com>
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "nodebpy"
3
- version = "520.14.0"
3
+ version = "520.16.0"
4
4
  description = "Build nodes trees in Blender more elegantly with code"
5
5
  readme = "README.md"
6
6
  authors = [
@@ -9,7 +9,7 @@ from .asset import (
9
9
  PackageLibrary,
10
10
  asset_group_base,
11
11
  )
12
- from .items import Item, ItemsMixin
12
+ from .items import Item, ItemsMixin, MenuItem
13
13
  from .mixins import LinkingMixin, OperatorMixin
14
14
  from .node import (
15
15
  BaseNode,
@@ -118,6 +118,7 @@ __all__ = [
118
118
  "MaterialSocketList",
119
119
  "MatrixSocket",
120
120
  "MatrixSocketList",
121
+ "MenuItem",
121
122
  "MenuSocket",
122
123
  "MenuSocketList",
123
124
  "NodeGroupBuilder",
@@ -10,7 +10,7 @@ from ..types import _is_default_value
10
10
  from ._registry import _wrap_socket
11
11
  from ._utils import _SocketLike
12
12
  from .node import DynamicInputsMixin
13
- from .socket import Socket
13
+ from .socket import BaseSocket, Socket
14
14
 
15
15
  if TYPE_CHECKING:
16
16
  from ..types import (
@@ -21,6 +21,7 @@ if TYPE_CHECKING:
21
21
  InputCollection,
22
22
  InputColor,
23
23
  InputFloat,
24
+ InputFont,
24
25
  InputGeometry,
25
26
  InputImage,
26
27
  InputInteger,
@@ -30,6 +31,7 @@ if TYPE_CHECKING:
30
31
  InputMenu,
31
32
  InputObject,
32
33
  InputRotation,
34
+ InputSound,
33
35
  InputString,
34
36
  InputVector,
35
37
  _SocketShapeStructureType,
@@ -41,6 +43,7 @@ if TYPE_CHECKING:
41
43
  CollectionSocket,
42
44
  ColorSocket,
43
45
  FloatSocket,
46
+ FontSocket,
44
47
  GeometrySocket,
45
48
  ImageSocket,
46
49
  IntegerSocket,
@@ -49,6 +52,7 @@ if TYPE_CHECKING:
49
52
  MenuSocket,
50
53
  ObjectSocket,
51
54
  RotationSocket,
55
+ SoundSocket,
52
56
  StringSocket,
53
57
  VectorSocket,
54
58
  )
@@ -96,7 +100,7 @@ def _infer_value_type(value: Any) -> str | None:
96
100
  return None
97
101
 
98
102
 
99
- _SocketT = TypeVar("_SocketT", bound=Socket, default=Socket)
103
+ _SocketT = TypeVar("_SocketT", bound=BaseSocket, default=Socket)
100
104
 
101
105
 
102
106
  class Item(Generic[_SocketT]):
@@ -152,6 +156,29 @@ class Item(Generic[_SocketT]):
152
156
  )
153
157
 
154
158
 
159
+ class MenuItem(Item[_SocketT]):
160
+ """Handle for one Menu Switch enum item.
161
+
162
+ ``input`` is the item's input socket (typed to the switch's data
163
+ type), ``is_selected`` the item's boolean output socket, and
164
+ ``description`` the tooltip Blender shows for the item in the menu.
165
+ """
166
+
167
+ @property
168
+ def is_selected(self) -> BooleanSocket:
169
+ """Boolean output socket — True when this item is selected."""
170
+ return cast("BooleanSocket", self.output)
171
+
172
+ @property
173
+ def description(self) -> str:
174
+ """Tooltip shown for this item in the drop-down menu."""
175
+ return self._item.description
176
+
177
+ @description.setter
178
+ def description(self, value: str) -> None:
179
+ self._item.description = value
180
+
181
+
155
182
  class ItemsMixin(DynamicInputsMixin):
156
183
  """Socket machinery for nodes whose sockets are driven by a bpy item
157
184
  collection (``capture_items``, ``bake_items``, ``format_items``, ...).
@@ -511,6 +538,22 @@ class _SocketItemFactory(_TypedItemFactory):
511
538
  ) -> MaterialSocket:
512
539
  return cast("MaterialSocket", self._declare(name, "MATERIAL", structure_type))
513
540
 
541
+ def font(
542
+ self,
543
+ name: str = "Font",
544
+ *,
545
+ structure_type: _SocketShapeStructureType = "AUTO",
546
+ ) -> FontSocket:
547
+ return cast("FontSocket", self._declare(name, "FONT", structure_type))
548
+
549
+ def sound(
550
+ self,
551
+ name: str = "Sound",
552
+ *,
553
+ structure_type: _SocketShapeStructureType = "AUTO",
554
+ ) -> SoundSocket:
555
+ return cast("SoundSocket", self._declare(name, "SOUND", structure_type))
556
+
514
557
  def bundle(
515
558
  self,
516
559
  name: str = "Bundle",
@@ -686,6 +729,24 @@ class _SocketValueItemFactory(_TypedItemFactory):
686
729
  "MaterialSocket", self._declare(name, value, "MATERIAL", structure_type)
687
730
  )
688
731
 
732
+ def font(
733
+ self,
734
+ name: str = "Font",
735
+ value: InputFont = None,
736
+ *,
737
+ structure_type: _SocketShapeStructureType = "AUTO",
738
+ ) -> FontSocket:
739
+ return cast("FontSocket", self._declare(name, value, "FONT", structure_type))
740
+
741
+ def sound(
742
+ self,
743
+ name: str = "Sound",
744
+ value: InputSound = None,
745
+ *,
746
+ structure_type: _SocketShapeStructureType = "AUTO",
747
+ ) -> SoundSocket:
748
+ return cast("SoundSocket", self._declare(name, value, "SOUND", structure_type))
749
+
689
750
  def bundle(
690
751
  self,
691
752
  name: str = "Bundle",
@@ -33,6 +33,7 @@ from .socket import (
33
33
  CollectionSocket,
34
34
  ColorSocket,
35
35
  FloatSocket,
36
+ FontSocket,
36
37
  GeometrySocket,
37
38
  ImageSocket,
38
39
  IntegerSocket,
@@ -43,6 +44,7 @@ from .socket import (
43
44
  RotationSocket,
44
45
  ShaderSocket,
45
46
  Socket,
47
+ SoundSocket,
46
48
  StringSocket,
47
49
  VectorSocket,
48
50
  )
@@ -518,6 +520,46 @@ class SocketContext:
518
520
  )
519
521
  return self._wrap(MaterialSocket, iface)
520
522
 
523
+ def font(
524
+ self,
525
+ name: str = "Font",
526
+ default_value: bpy.types.VectorFont | None = None,
527
+ description: str = "",
528
+ *,
529
+ optional_label: bool = False,
530
+ hide_value: bool = False,
531
+ hide_in_modifier: bool = False,
532
+ ) -> FontSocket:
533
+ iface = self._add_socket("NodeSocketFont", name, description)
534
+ self._set_props(
535
+ iface,
536
+ default_value=default_value,
537
+ optional_label=optional_label,
538
+ hide_value=hide_value,
539
+ hide_in_modifier=hide_in_modifier,
540
+ )
541
+ return self._wrap(FontSocket, iface)
542
+
543
+ def sound(
544
+ self,
545
+ name: str = "Sound",
546
+ default_value: bpy.types.Sound | None = None,
547
+ description: str = "",
548
+ *,
549
+ optional_label: bool = False,
550
+ hide_value: bool = False,
551
+ hide_in_modifier: bool = False,
552
+ ) -> SoundSocket:
553
+ iface = self._add_socket("NodeSocketSound", name, description)
554
+ self._set_props(
555
+ iface,
556
+ default_value=default_value,
557
+ optional_label=optional_label,
558
+ hide_value=hide_value,
559
+ hide_in_modifier=hide_in_modifier,
560
+ )
561
+ return self._wrap(SoundSocket, iface)
562
+
521
563
  def bundle(
522
564
  self,
523
565
  name: str = "Bundle",
@@ -436,6 +436,8 @@ _INTERFACE_TYPE_METHOD: dict[str, str] = {
436
436
  "NodeSocketCollection": "collection",
437
437
  "NodeSocketImage": "image",
438
438
  "NodeSocketMaterial": "material",
439
+ "NodeSocketFont": "font",
440
+ "NodeSocketSound": "sound",
439
441
  "NodeSocketBundle": "bundle",
440
442
  "NodeSocketClosure": "closure",
441
443
  "NodeSocketShader": "shader",
@@ -448,6 +450,8 @@ _NO_DEFAULT_VALUE_TYPES = frozenset(
448
450
  "NodeSocketCollection",
449
451
  "NodeSocketImage",
450
452
  "NodeSocketMaterial",
453
+ "NodeSocketFont",
454
+ "NodeSocketSound",
451
455
  "NodeSocketBundle",
452
456
  "NodeSocketClosure",
453
457
  "NodeSocketShader",
@@ -3875,6 +3879,8 @@ _SWITCH_FACTORY_NAMES = {
3875
3879
  "BUNDLE": "bundle",
3876
3880
  "CLOSURE": "closure",
3877
3881
  "SHADER": "shader",
3882
+ "FONT": "font",
3883
+ "SOUND": "sound",
3878
3884
  }
3879
3885
 
3880
3886
 
@@ -3904,6 +3910,7 @@ def _emit_menu_switch(node, ctx: EmitContext) -> Expr | _Val | None:
3904
3910
  """MenuSwitch emits the factory dict form
3905
3911
  ``g.MenuSwitch.geometry(menu, {"Name": value, ...})`` — the plain
3906
3912
  constructor's per-socket kwargs cannot recreate the enum item names.
3913
+ An item with a tooltip emits the ``(value, description)`` pair form.
3907
3914
  Each tree type has its own MenuSwitch class (with tree-specific factories
3908
3915
  such as ``shader``), so the alias follows the tree being exported."""
3909
3916
  factory = _SWITCH_FACTORY_NAMES.get(node.data_type)
@@ -3911,7 +3918,14 @@ def _emit_menu_switch(node, ctx: EmitContext) -> Expr | _Val | None:
3911
3918
  return None
3912
3919
  alias = _TREE_ALIAS.get(ctx.node_tree.bl_idname, "g")
3913
3920
  ctx.used_aliases.add(alias)
3914
- items = DictExpr(dict(_switch_item_exprs(node, ctx, "Menu")))
3921
+ item_exprs: dict[str, Expr] = {}
3922
+ for (name, expr), item in zip(
3923
+ _switch_item_exprs(node, ctx, "Menu"), node.enum_items
3924
+ ):
3925
+ if item.description:
3926
+ expr = TupleExpr([expr, Lit(item.description)])
3927
+ item_exprs[name] = expr
3928
+ items = DictExpr(item_exprs)
3915
3929
  menu_link = ctx.input_link(node, "Menu")
3916
3930
  if menu_link is not None:
3917
3931
  return Call(
@@ -30,7 +30,7 @@ from ...types import (
30
30
  InputString,
31
31
  InputVector,
32
32
  )
33
- from ..geometry.manual import Float, Frame, _MenuSwitchBase
33
+ from ..geometry.manual import Float, Frame, MenuItems, _MenuSwitchBase
34
34
 
35
35
  __all__ = ["Float", "Frame", "MenuSwitch", "tree"]
36
36
 
@@ -52,43 +52,43 @@ class MenuSwitch[T: BaseSocket](_MenuSwitchBase[T]):
52
52
 
53
53
  @classmethod
54
54
  def float(
55
- cls, menu: InputMenu = None, items: dict[str, InputFloat] | None = None
55
+ cls, menu: InputMenu = None, items: MenuItems[InputFloat] | None = None
56
56
  ) -> "MenuSwitch[FloatSocket]":
57
57
  return MenuSwitch(menu, items, data_type="FLOAT")
58
58
 
59
59
  @classmethod
60
60
  def integer(
61
- cls, menu: InputMenu = None, items: dict[str, InputInteger] | None = None
61
+ cls, menu: InputMenu = None, items: MenuItems[InputInteger] | None = None
62
62
  ) -> "MenuSwitch[IntegerSocket]":
63
63
  return MenuSwitch(menu, items, data_type="INT")
64
64
 
65
65
  @classmethod
66
66
  def boolean(
67
- cls, menu: InputMenu = None, items: dict[str, InputBoolean] | None = None
67
+ cls, menu: InputMenu = None, items: MenuItems[InputBoolean] | None = None
68
68
  ) -> "MenuSwitch[BooleanSocket]":
69
69
  return MenuSwitch(menu, items, data_type="BOOLEAN")
70
70
 
71
71
  @classmethod
72
72
  def vector(
73
- cls, menu: InputMenu = None, items: dict[str, InputVector] | None = None
73
+ cls, menu: InputMenu = None, items: MenuItems[InputVector] | None = None
74
74
  ) -> "MenuSwitch[VectorSocket]":
75
75
  return MenuSwitch(menu, items, data_type="VECTOR")
76
76
 
77
77
  @classmethod
78
78
  def color(
79
- cls, menu: InputMenu = None, items: dict[str, InputColor] | None = None
79
+ cls, menu: InputMenu = None, items: MenuItems[InputColor] | None = None
80
80
  ) -> "MenuSwitch[ColorSocket]":
81
81
  return MenuSwitch(menu, items, data_type="RGBA")
82
82
 
83
83
  @classmethod
84
84
  def string(
85
- cls, menu: InputMenu = None, items: dict[str, InputString] | None = None
85
+ cls, menu: InputMenu = None, items: MenuItems[InputString] | None = None
86
86
  ) -> "MenuSwitch[StringSocket]":
87
87
  return MenuSwitch(menu, items, data_type="STRING")
88
88
 
89
89
  @classmethod
90
90
  def menu(
91
- cls, menu: InputMenu = None, items: dict[str, InputMenu] | None = None
91
+ cls, menu: InputMenu = None, items: MenuItems[InputMenu] | None = None
92
92
  ) -> "MenuSwitch[MenuSocket]":
93
93
  return MenuSwitch(menu, items, data_type="MENU")
94
94
 
@@ -51,6 +51,7 @@ from ...builder import Socket as SocketLinker
51
51
  from ...builder._registry import _wrap_socket
52
52
  from ...builder.items import (
53
53
  GridItem,
54
+ MenuItem,
54
55
  _apply_item_value,
55
56
  _FieldItemFactory,
56
57
  _socket_for_item,
@@ -128,6 +129,9 @@ __all__ = (
128
129
  "IndexSwitch",
129
130
  "JoinGeometry",
130
131
  "JoinStrings",
132
+ # "MenuItems" is deliberately not exported: quartodoc cannot document a
133
+ # PEP 695 `type` alias (griffe Kind.TYPE_ALIAS); import it from this
134
+ # module directly for annotations.
131
135
  "MenuSwitch",
132
136
  "MeshBoolean",
133
137
  "RepeatInput",
@@ -1423,6 +1427,18 @@ class IndexSwitch[T: BaseSocket](ItemsMixin, BaseNode):
1423
1427
  ) -> "IndexSwitch[ClosureSocket]":
1424
1428
  return IndexSwitch(index=index, items=items, data_type="CLOSURE")
1425
1429
 
1430
+ @classmethod
1431
+ def font(
1432
+ cls, index: InputInteger = 0, items: Iterable[InputFont] = ()
1433
+ ) -> "IndexSwitch[FontSocket]":
1434
+ return IndexSwitch(index=index, items=items, data_type="FONT")
1435
+
1436
+ @classmethod
1437
+ def sound(
1438
+ cls, index: InputInteger = 0, items: Iterable[InputSound] = ()
1439
+ ) -> "IndexSwitch[SoundSocket]":
1440
+ return IndexSwitch(index=index, items=items, data_type="SOUND")
1441
+
1426
1442
  class _Inputs(SocketAccessor):
1427
1443
  index: IntegerSocket
1428
1444
 
@@ -1501,6 +1517,22 @@ class IndexSwitch[T: BaseSocket](ItemsMixin, BaseNode):
1501
1517
  self.node.data_type = value
1502
1518
 
1503
1519
 
1520
+ type MenuItems[V] = Mapping[str, None | V | tuple[V | None, str]]
1521
+ """Menu Switch ``items`` mapping: item name → value, where the value may be
1522
+ a ``(value, description)`` pair to also set the item's tooltip."""
1523
+
1524
+
1525
+ def _split_menu_item(
1526
+ value: InputAny | tuple[InputAny, str],
1527
+ ) -> tuple[InputAny, str | None]:
1528
+ """Split a ``(value, description)`` menu item pair. A 2-tuple whose
1529
+ second element is a string can never be a socket default value, so the
1530
+ form is unambiguous."""
1531
+ if isinstance(value, tuple) and len(value) == 2 and isinstance(value[1], str):
1532
+ return value[0], value[1]
1533
+ return cast("InputAny", value), None
1534
+
1535
+
1504
1536
  class _MenuSwitchBase[T: BaseSocket](ItemsMixin, BaseNode):
1505
1537
  """Base class for MenuSwitch nodes across all tree types."""
1506
1538
 
@@ -1525,30 +1557,42 @@ class _MenuSwitchBase[T: BaseSocket](ItemsMixin, BaseNode):
1525
1557
  def __init__(
1526
1558
  self,
1527
1559
  menu: InputMenu = None,
1528
- items: Mapping[str, InputAny] | None = None,
1560
+ items: MenuItems[InputAny] | None = None,
1529
1561
  *,
1530
1562
  data_type: SOCKET_TYPES = "FLOAT",
1531
1563
  ):
1532
1564
  super().__init__()
1533
1565
  self.data_type = data_type
1534
1566
  self.node.enum_items.clear()
1535
- key_args = {"Menu": menu}
1567
+ # a plain string `menu` is an explicit selection; otherwise the
1568
+ # selection defaults to the first item
1569
+ self._explicit_selection = isinstance(menu, str)
1536
1570
  self._link_args(**(items or {}))
1537
- self._establish_links(**key_args)
1538
- # a plain string `menu` is an explicit selection; otherwise default
1539
- # the selection to the first item
1540
-
1541
- if self.node.enum_items and not isinstance(menu, str):
1571
+ if isinstance(menu, str) and not self.node.enum_items:
1572
+ # a selection named before any items exist (they will be added via
1573
+ # item()) can't be set yet; defer it to context exit
1542
1574
  assert self.node.inputs is not None
1543
- try:
1544
- menu_socket = self.node.inputs["Menu"]
1545
- menu_socket.default_value = self.node.enum_items[0].name
1546
- except TypeError: # pragma: no cover - rare Blender enum-refresh quirk
1547
- # the socket is a NodeSocketMenu whose enum hasn't refreshed yet, so
1548
- # the default_value isn't settable here; defer it to context exit.
1549
- self.tree._menu_defaults.append(
1550
- _MenuDefault(self.node.inputs["Menu"], self.node.enum_items[0].name)
1551
- )
1575
+ self.tree._menu_defaults.append(
1576
+ _MenuDefault(self.node.inputs["Menu"], menu)
1577
+ )
1578
+ else:
1579
+ self._establish_links(Menu=menu)
1580
+
1581
+ if self.node.enum_items and not self._explicit_selection:
1582
+ self._default_selection_to_first()
1583
+
1584
+ def _default_selection_to_first(self) -> None:
1585
+ """Default the menu selection to the first enum item."""
1586
+ assert self.node.inputs is not None
1587
+ try:
1588
+ menu_socket = self.node.inputs["Menu"]
1589
+ menu_socket.default_value = self.node.enum_items[0].name
1590
+ except TypeError: # pragma: no cover - rare Blender enum-refresh quirk
1591
+ # the socket is a NodeSocketMenu whose enum hasn't refreshed yet, so
1592
+ # the default_value isn't settable here; defer it to context exit.
1593
+ self.tree._menu_defaults.append(
1594
+ _MenuDefault(self.node.inputs["Menu"], self.node.enum_items[0].name)
1595
+ )
1552
1596
 
1553
1597
  @property
1554
1598
  def _socket_data_types(self) -> tuple[str, ...]:
@@ -1560,24 +1604,57 @@ class _MenuSwitchBase[T: BaseSocket](ItemsMixin, BaseNode):
1560
1604
  # menu items are untyped; .new() takes only a name
1561
1605
  return self._items.new(name)
1562
1606
 
1563
- def _link_args(self, **kwargs: InputAny):
1607
+ def _link_args(self, **kwargs: InputAny | tuple[InputAny, str]):
1564
1608
  for key, value in kwargs.items():
1565
- socket = self._add_socket(name=key, type=self.data_type)
1566
- if value is None:
1567
- continue # item declared but left unlinked
1568
- if _is_default_value(value):
1569
- if isinstance(socket, bpy.types.NodeSocketMenu) and isinstance(
1570
- value, str
1571
- ):
1572
- # the socket is a NodeSocketMenu, but the default_value is not settable
1573
- # until the full tree is built and menu items are known. We need to defer
1574
- # the setting of the default values until after tree construction.
1575
- self.tree._menu_defaults.append(_MenuDefault(socket, value))
1576
- else:
1577
- socket.default_value = value # ty: ignore[unresolved-attribute]
1609
+ value, description = _split_menu_item(value)
1610
+ item = self._new_item(key, self.data_type)
1611
+ if description:
1612
+ item.description = description
1613
+ self._apply_item_input(self._item_socket(item), value)
1614
+
1615
+ def _apply_item_input(self, socket: NodeSocket, value: InputAny) -> None:
1616
+ if value is None:
1617
+ return # item declared but left unlinked
1618
+ if _is_default_value(value):
1619
+ if isinstance(socket, bpy.types.NodeSocketMenu) and isinstance(value, str):
1620
+ # the socket is a NodeSocketMenu, but the default_value is not settable
1621
+ # until the full tree is built and menu items are known. We need to defer
1622
+ # the setting of the default values until after tree construction.
1623
+ self.tree._menu_defaults.append(_MenuDefault(socket, value))
1578
1624
  else:
1579
- source = self._source_socket(value) # type: ignore
1580
- self._link(source, socket)
1625
+ socket.default_value = value # ty: ignore[unresolved-attribute]
1626
+ else:
1627
+ source = self._source_socket(value) # type: ignore
1628
+ self._link(source, socket)
1629
+
1630
+ def item(
1631
+ self,
1632
+ name: str,
1633
+ value: InputAny | tuple[InputAny, str] = None,
1634
+ *,
1635
+ description: str | None = None,
1636
+ ) -> MenuItem[T]:
1637
+ """Declare a menu item and return its handle.
1638
+
1639
+ ``value`` may be a linkable (linked into the item's input socket), a
1640
+ plain default value, or a ``(value, description)`` pair; omit it to
1641
+ declare the item unlinked. ``description`` sets the tooltip Blender
1642
+ shows for the item in the menu.
1643
+
1644
+ Unless the menu selection was set explicitly, declaring the first
1645
+ item also defaults the selection to it (as the constructor does).
1646
+ """
1647
+ value, pair_description = _split_menu_item(value)
1648
+ if description is None:
1649
+ description = pair_description
1650
+ bpy_item = self._new_item(name, self.data_type)
1651
+ if description:
1652
+ bpy_item.description = description
1653
+ handle = cast("MenuItem[T]", MenuItem(self, bpy_item))
1654
+ self._apply_item_input(self._item_socket(bpy_item), value)
1655
+ if len(self.node.enum_items) == 1 and not self._explicit_selection:
1656
+ self._default_selection_to_first()
1657
+ return handle
1581
1658
 
1582
1659
  def is_selected(self, name: str) -> BooleanSocket:
1583
1660
  """Gets the boolean output socket that is True when the named menu item is selected.
@@ -1616,7 +1693,7 @@ class MenuSwitch[T: BaseSocket](_MenuSwitchBase[T]):
1616
1693
  def float(
1617
1694
  cls,
1618
1695
  menu: InputMenu = None,
1619
- items: dict[str, InputFloat] | None = None,
1696
+ items: MenuItems[InputFloat] | None = None,
1620
1697
  ) -> "MenuSwitch[FloatSocket]":
1621
1698
  return MenuSwitch(menu, items, data_type="FLOAT")
1622
1699
 
@@ -1624,7 +1701,7 @@ class MenuSwitch[T: BaseSocket](_MenuSwitchBase[T]):
1624
1701
  def integer(
1625
1702
  cls,
1626
1703
  menu: InputMenu = None,
1627
- items: dict[str, InputInteger] | None = None,
1704
+ items: MenuItems[InputInteger] | None = None,
1628
1705
  ) -> "MenuSwitch[IntegerSocket]":
1629
1706
  return MenuSwitch(menu, items, data_type="INT")
1630
1707
 
@@ -1632,7 +1709,7 @@ class MenuSwitch[T: BaseSocket](_MenuSwitchBase[T]):
1632
1709
  def boolean(
1633
1710
  cls,
1634
1711
  menu: InputMenu = None,
1635
- items: dict[str, InputBoolean] | None = None,
1712
+ items: MenuItems[InputBoolean] | None = None,
1636
1713
  ) -> "MenuSwitch[BooleanSocket]":
1637
1714
  return MenuSwitch(menu, items, data_type="BOOLEAN")
1638
1715
 
@@ -1640,7 +1717,7 @@ class MenuSwitch[T: BaseSocket](_MenuSwitchBase[T]):
1640
1717
  def vector(
1641
1718
  cls,
1642
1719
  menu: InputMenu = None,
1643
- items: dict[str, InputVector] | None = None,
1720
+ items: MenuItems[InputVector] | None = None,
1644
1721
  ) -> "MenuSwitch[VectorSocket]":
1645
1722
  return MenuSwitch(menu, items, data_type="VECTOR")
1646
1723
 
@@ -1648,7 +1725,7 @@ class MenuSwitch[T: BaseSocket](_MenuSwitchBase[T]):
1648
1725
  def color(
1649
1726
  cls,
1650
1727
  menu: InputMenu = None,
1651
- items: dict[str, InputColor] | None = None,
1728
+ items: MenuItems[InputColor] | None = None,
1652
1729
  ) -> "MenuSwitch[ColorSocket]":
1653
1730
  return MenuSwitch(menu, items, data_type="RGBA")
1654
1731
 
@@ -1656,7 +1733,7 @@ class MenuSwitch[T: BaseSocket](_MenuSwitchBase[T]):
1656
1733
  def rotation(
1657
1734
  cls,
1658
1735
  menu: InputMenu = None,
1659
- items: dict[str, InputRotation] | None = None,
1736
+ items: MenuItems[InputRotation] | None = None,
1660
1737
  ) -> "MenuSwitch[RotationSocket]":
1661
1738
  return MenuSwitch(menu, items, data_type="ROTATION")
1662
1739
 
@@ -1664,7 +1741,7 @@ class MenuSwitch[T: BaseSocket](_MenuSwitchBase[T]):
1664
1741
  def matrix(
1665
1742
  cls,
1666
1743
  menu: InputMenu = None,
1667
- items: dict[str, InputMatrix] | None = None,
1744
+ items: MenuItems[InputMatrix] | None = None,
1668
1745
  ) -> "MenuSwitch[MatrixSocket]":
1669
1746
  return MenuSwitch(menu, items, data_type="MATRIX")
1670
1747
 
@@ -1672,7 +1749,7 @@ class MenuSwitch[T: BaseSocket](_MenuSwitchBase[T]):
1672
1749
  def string(
1673
1750
  cls,
1674
1751
  menu: InputMenu = None,
1675
- items: dict[str, InputString] | None = None,
1752
+ items: MenuItems[InputString] | None = None,
1676
1753
  ) -> "MenuSwitch[StringSocket]":
1677
1754
  return MenuSwitch(menu, items, data_type="STRING")
1678
1755
 
@@ -1680,7 +1757,7 @@ class MenuSwitch[T: BaseSocket](_MenuSwitchBase[T]):
1680
1757
  def menu(
1681
1758
  cls,
1682
1759
  menu: InputMenu = None,
1683
- items: dict[str, InputMenu] | None = None,
1760
+ items: MenuItems[InputMenu] | None = None,
1684
1761
  ) -> "MenuSwitch[MenuSocket]":
1685
1762
  return MenuSwitch(menu, items, data_type="MENU")
1686
1763
 
@@ -1688,7 +1765,7 @@ class MenuSwitch[T: BaseSocket](_MenuSwitchBase[T]):
1688
1765
  def object(
1689
1766
  cls,
1690
1767
  menu: InputMenu = None,
1691
- items: dict[str, InputObject] | None = None,
1768
+ items: MenuItems[InputObject] | None = None,
1692
1769
  ) -> "MenuSwitch[ObjectSocket]":
1693
1770
  return MenuSwitch(menu, items, data_type="OBJECT")
1694
1771
 
@@ -1696,7 +1773,7 @@ class MenuSwitch[T: BaseSocket](_MenuSwitchBase[T]):
1696
1773
  def geometry(
1697
1774
  cls,
1698
1775
  menu: InputMenu = None,
1699
- items: dict[str, InputGeometry] | None = None,
1776
+ items: MenuItems[InputGeometry] | None = None,
1700
1777
  ) -> "MenuSwitch[GeometrySocket]":
1701
1778
  return MenuSwitch(menu, items, data_type="GEOMETRY")
1702
1779
 
@@ -1704,7 +1781,7 @@ class MenuSwitch[T: BaseSocket](_MenuSwitchBase[T]):
1704
1781
  def collection(
1705
1782
  cls,
1706
1783
  menu: InputMenu = None,
1707
- items: dict[str, InputCollection] | None = None,
1784
+ items: MenuItems[InputCollection] | None = None,
1708
1785
  ) -> "MenuSwitch[CollectionSocket]":
1709
1786
  return MenuSwitch(menu, items, data_type="COLLECTION")
1710
1787
 
@@ -1712,7 +1789,7 @@ class MenuSwitch[T: BaseSocket](_MenuSwitchBase[T]):
1712
1789
  def image(
1713
1790
  cls,
1714
1791
  menu: InputMenu = None,
1715
- items: dict[str, InputImage] | None = None,
1792
+ items: MenuItems[InputImage] | None = None,
1716
1793
  ) -> "MenuSwitch[ImageSocket]":
1717
1794
  return MenuSwitch(menu, items, data_type="IMAGE")
1718
1795
 
@@ -1720,7 +1797,7 @@ class MenuSwitch[T: BaseSocket](_MenuSwitchBase[T]):
1720
1797
  def material(
1721
1798
  cls,
1722
1799
  menu: InputMenu = None,
1723
- items: dict[str, InputMaterial] | None = None,
1800
+ items: MenuItems[InputMaterial] | None = None,
1724
1801
  ) -> "MenuSwitch[MaterialSocket]":
1725
1802
  return MenuSwitch(menu, items, data_type="MATERIAL")
1726
1803
 
@@ -1728,7 +1805,7 @@ class MenuSwitch[T: BaseSocket](_MenuSwitchBase[T]):
1728
1805
  def bundle(
1729
1806
  cls,
1730
1807
  menu: InputMenu = None,
1731
- items: dict[str, InputBundle] | None = None,
1808
+ items: MenuItems[InputBundle] | None = None,
1732
1809
  ) -> "MenuSwitch[BundleSocket]":
1733
1810
  return MenuSwitch(menu, items, data_type="BUNDLE")
1734
1811
 
@@ -1736,10 +1813,26 @@ class MenuSwitch[T: BaseSocket](_MenuSwitchBase[T]):
1736
1813
  def closure(
1737
1814
  cls,
1738
1815
  menu: InputMenu = None,
1739
- items: dict[str, InputClosure] | None = None,
1816
+ items: MenuItems[InputClosure] | None = None,
1740
1817
  ) -> "MenuSwitch[ClosureSocket]":
1741
1818
  return MenuSwitch(menu, items, data_type="CLOSURE")
1742
1819
 
1820
+ @classmethod
1821
+ def font(
1822
+ cls,
1823
+ menu: InputMenu = None,
1824
+ items: dict[str, InputFont] | None = None,
1825
+ ) -> "MenuSwitch[FontSocket]":
1826
+ return MenuSwitch(menu, items, data_type="FONT")
1827
+
1828
+ @classmethod
1829
+ def sound(
1830
+ cls,
1831
+ menu: InputMenu = None,
1832
+ items: dict[str, InputSound] | None = None,
1833
+ ) -> "MenuSwitch[SoundSocket]":
1834
+ return MenuSwitch(menu, items, data_type="SOUND")
1835
+
1743
1836
 
1744
1837
  class CaptureAttribute(ItemsMixin, BaseNode):
1745
1838
  """
@@ -22,6 +22,7 @@ from ...builder import (
22
22
  CollectionSocket,
23
23
  ColorSocket,
24
24
  FloatSocket,
25
+ FontSocket,
25
26
  GeometrySocket,
26
27
  ImageSocket,
27
28
  IntegerSocket,
@@ -32,6 +33,7 @@ from ...builder import (
32
33
  MenuSocket,
33
34
  ObjectSocket,
34
35
  RotationSocket,
36
+ SoundSocket,
35
37
  StringSocket,
36
38
  VectorSocket,
37
39
  )
@@ -48,6 +50,7 @@ from ...types import (
48
50
  InputCollection,
49
51
  InputColor,
50
52
  InputFloat,
53
+ InputFont,
51
54
  InputGeometry,
52
55
  InputImage,
53
56
  InputInteger,
@@ -57,6 +60,7 @@ from ...types import (
57
60
  InputMenu,
58
61
  InputObject,
59
62
  InputRotation,
63
+ InputSound,
60
64
  InputString,
61
65
  InputVector,
62
66
  _AttributeDomains,
@@ -331,6 +335,16 @@ class _RepeatZoneItems(_StateZoneItems):
331
335
  ) -> "ZoneItem[ClosureSocket]":
332
336
  return cast("ZoneItem[ClosureSocket]", self._declare(name, initial, "CLOSURE"))
333
337
 
338
+ def font(
339
+ self, name: str = "Font", initial: InputFont = None
340
+ ) -> "ZoneItem[FontSocket]":
341
+ return cast("ZoneItem[FontSocket]", self._declare(name, initial, "FONT"))
342
+
343
+ def sound(
344
+ self, name: str = "Sound", initial: InputSound = None
345
+ ) -> "ZoneItem[SoundSocket]":
346
+ return cast("ZoneItem[SoundSocket]", self._declare(name, initial, "SOUND"))
347
+
334
348
 
335
349
  class BaseSimulationZone(BaseZone):
336
350
  _items_collection = "state_items"
@@ -419,6 +433,8 @@ class BaseRepeatZone(BaseZone):
419
433
  "MATERIAL",
420
434
  "BUNDLE",
421
435
  "CLOSURE",
436
+ "FONT",
437
+ "SOUND",
422
438
  )
423
439
 
424
440
  _type_map: ClassVar[dict[str, str]] = {"VALUE": "FLOAT"}
@@ -30,7 +30,7 @@ from ...types import (
30
30
  InputVector,
31
31
  )
32
32
  from ..geometry import Frame, RepeatInput, RepeatOutput, RepeatZone
33
- from ..geometry.manual import Float, _MenuSwitchBase
33
+ from ..geometry.manual import Float, MenuItems, _MenuSwitchBase
34
34
 
35
35
  __all__ = [
36
36
  "Attribute",
@@ -74,55 +74,55 @@ class MenuSwitch[T: BaseSocket](_MenuSwitchBase[T]):
74
74
 
75
75
  @classmethod
76
76
  def float(
77
- cls, menu: InputMenu = None, items: dict[str, InputFloat] | None = None
77
+ cls, menu: InputMenu = None, items: MenuItems[InputFloat] | None = None
78
78
  ) -> "MenuSwitch[FloatSocket]":
79
79
  return MenuSwitch(menu, items, data_type="FLOAT")
80
80
 
81
81
  @classmethod
82
82
  def integer(
83
- cls, menu: InputMenu = None, items: dict[str, InputInteger] | None = None
83
+ cls, menu: InputMenu = None, items: MenuItems[InputInteger] | None = None
84
84
  ) -> "MenuSwitch[IntegerSocket]":
85
85
  return MenuSwitch(menu, items, data_type="INT")
86
86
 
87
87
  @classmethod
88
88
  def boolean(
89
- cls, menu: InputMenu = None, items: dict[str, InputBoolean] | None = None
89
+ cls, menu: InputMenu = None, items: MenuItems[InputBoolean] | None = None
90
90
  ) -> "MenuSwitch[BooleanSocket]":
91
91
  return MenuSwitch(menu, items, data_type="BOOLEAN")
92
92
 
93
93
  @classmethod
94
94
  def vector(
95
- cls, menu: InputMenu = None, items: dict[str, InputVector] | None = None
95
+ cls, menu: InputMenu = None, items: MenuItems[InputVector] | None = None
96
96
  ) -> "MenuSwitch[VectorSocket]":
97
97
  return MenuSwitch(menu, items, data_type="VECTOR")
98
98
 
99
99
  @classmethod
100
100
  def color(
101
- cls, menu: InputMenu = None, items: dict[str, InputColor] | None = None
101
+ cls, menu: InputMenu = None, items: MenuItems[InputColor] | None = None
102
102
  ) -> "MenuSwitch[ColorSocket]":
103
103
  return MenuSwitch(menu, items, data_type="RGBA")
104
104
 
105
105
  @classmethod
106
106
  def menu(
107
- cls, menu: InputMenu = None, items: dict[str, InputMenu] | None = None
107
+ cls, menu: InputMenu = None, items: MenuItems[InputMenu] | None = None
108
108
  ) -> "MenuSwitch[MenuSocket]":
109
109
  return MenuSwitch(menu, items, data_type="MENU")
110
110
 
111
111
  @classmethod
112
112
  def closure(
113
- cls, menu: InputMenu = None, items: dict[str, InputClosure] | None = None
113
+ cls, menu: InputMenu = None, items: MenuItems[InputClosure] | None = None
114
114
  ) -> "MenuSwitch[ClosureSocket]":
115
115
  return MenuSwitch(menu, items, data_type="CLOSURE")
116
116
 
117
117
  @classmethod
118
118
  def bundle(
119
- cls, menu: InputMenu = None, items: dict[str, InputBundle] | None = None
119
+ cls, menu: InputMenu = None, items: MenuItems[InputBundle] | None = None
120
120
  ) -> "MenuSwitch[BundleSocket]":
121
121
  return MenuSwitch(menu, items, data_type="BUNDLE")
122
122
 
123
123
  @classmethod
124
124
  def shader(
125
- cls, menu: InputMenu = None, items: dict[str, InputShader] | None = None
125
+ cls, menu: InputMenu = None, items: MenuItems[InputShader] | None = None
126
126
  ) -> "MenuSwitch[ShaderSocket]":
127
127
  return MenuSwitch(menu, items, data_type="SHADER")
128
128
 
@@ -369,6 +369,7 @@ SOCKET_COMPATIBILITY: dict[str, tuple[str, ...]] = {
369
369
  "BUNDLE": ("BUNDLE",),
370
370
  "CLOSURE": ("CLOSURE",),
371
371
  "SHADER": ("SHADER", "RGBA"),
372
+ "FONT": ("FONT",),
372
373
  "SOUND": ("SOUND",),
373
374
  }
374
375
 
File without changes