nodebpy 520.36.0__tar.gz → 520.37.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 (117) hide show
  1. {nodebpy-520.36.0 → nodebpy-520.37.0}/PKG-INFO +1 -1
  2. {nodebpy-520.36.0 → nodebpy-520.37.0}/pyproject.toml +1 -1
  3. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/builder/_utils.py +29 -2
  4. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/builder/node.py +21 -1
  5. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/export/codegen.py +53 -12
  6. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/nodes/compositor/color.py +97 -63
  7. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/nodes/compositor/distort.py +14 -11
  8. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/nodes/compositor/filter.py +200 -125
  9. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/nodes/compositor/input.py +24 -18
  10. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/nodes/compositor/matte.py +48 -36
  11. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/nodes/geometry/color.py +9 -6
  12. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/nodes/geometry/converter.py +71 -47
  13. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/nodes/geometry/geometry.py +81 -71
  14. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/nodes/geometry/input.py +21 -19
  15. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/nodes/geometry/manual.py +14 -10
  16. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/nodes/geometry/texture.py +12 -12
  17. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/nodes/geometry/vector.py +9 -6
  18. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/nodes/shader/color.py +25 -16
  19. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/nodes/shader/input.py +97 -4
  20. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/nodes/shader/manual.py +1 -83
  21. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/nodes/shader/shader.py +31 -21
  22. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/nodes/shader/texture.py +2 -2
  23. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/skills/nodebpy/references/shader-compositor.md +2 -2
  24. {nodebpy-520.36.0 → nodebpy-520.37.0}/README.md +0 -0
  25. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/__init__.py +0 -0
  26. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/__main__.py +0 -0
  27. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/_lookup.py +0 -0
  28. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/_skill.py +0 -0
  29. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/assets/__init__.py +0 -0
  30. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/assets/__main__.py +0 -0
  31. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/assets/_codegen.py +0 -0
  32. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/assets/_library.py +0 -0
  33. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/assets/_pipeline.py +0 -0
  34. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/assets/_textconv.py +0 -0
  35. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/builder/__init__.py +0 -0
  36. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/builder/_combine.py +0 -0
  37. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/builder/_infer.py +0 -0
  38. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/builder/_registry.py +0 -0
  39. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/builder/_socket_order.py +0 -0
  40. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/builder/accessor.py +0 -0
  41. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/builder/asset.py +0 -0
  42. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/builder/items.py +0 -0
  43. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/builder/mixins.py +0 -0
  44. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/builder/socket.py +0 -0
  45. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/builder/tree.py +0 -0
  46. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/export/__init__.py +0 -0
  47. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/export/_floats.py +0 -0
  48. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/export/diagram.py +0 -0
  49. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/export/parity.py +0 -0
  50. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/export/plot.py +0 -0
  51. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/export/web_render.py +0 -0
  52. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/layout/DESIGN.md +0 -0
  53. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/layout/__init__.py +0 -0
  54. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/layout/api.py +0 -0
  55. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/layout/apply.py +0 -0
  56. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/layout/balancing.py +0 -0
  57. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/layout/build.py +0 -0
  58. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/layout/common.py +0 -0
  59. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/layout/config.py +0 -0
  60. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/layout/digraph.py +0 -0
  61. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/layout/dna.py +0 -0
  62. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/layout/edits.py +0 -0
  63. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/layout/extract.py +0 -0
  64. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/layout/feeders.py +0 -0
  65. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/layout/long_links.py +0 -0
  66. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/layout/model.py +0 -0
  67. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/layout/node_size.py +0 -0
  68. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/layout/ordering.py +0 -0
  69. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/layout/packing.py +0 -0
  70. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/layout/pipeline.py +0 -0
  71. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/layout/placement.py +0 -0
  72. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/layout/priority.py +0 -0
  73. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/layout/ranking.py +0 -0
  74. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/layout/realize.py +0 -0
  75. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/layout/reroutes.py +0 -0
  76. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/layout/routing.py +0 -0
  77. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/layout/snapping.py +0 -0
  78. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/layout/spacing.py +0 -0
  79. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/layout/stacking.py +0 -0
  80. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/layout/sugiyama.py +0 -0
  81. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/layout/zones.py +0 -0
  82. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/live.py +0 -0
  83. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/nodes/__init__.py +0 -0
  84. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/nodes/_mixins.py +0 -0
  85. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/nodes/compositor/__init__.py +0 -0
  86. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/nodes/compositor/assets.py +0 -0
  87. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/nodes/compositor/converter.py +0 -0
  88. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/nodes/compositor/group.py +0 -0
  89. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/nodes/compositor/interface.py +0 -0
  90. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/nodes/compositor/manual.py +0 -0
  91. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/nodes/compositor/output.py +0 -0
  92. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/nodes/compositor/vector.py +0 -0
  93. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/nodes/geometry/__init__.py +0 -0
  94. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/nodes/geometry/assets.py +0 -0
  95. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/nodes/geometry/attribute.py +0 -0
  96. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/nodes/geometry/grid.py +0 -0
  97. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/nodes/geometry/group.py +0 -0
  98. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/nodes/geometry/groups.py +0 -0
  99. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/nodes/geometry/interface.py +0 -0
  100. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/nodes/geometry/output.py +0 -0
  101. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/nodes/geometry/utilities.py +0 -0
  102. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/nodes/geometry/zone.py +0 -0
  103. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/nodes/shader/__init__.py +1 -1
  104. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/nodes/shader/assets.py +0 -0
  105. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/nodes/shader/converter.py +0 -0
  106. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/nodes/shader/grid.py +0 -0
  107. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/nodes/shader/group.py +0 -0
  108. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/nodes/shader/output.py +0 -0
  109. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/nodes/shader/script.py +0 -0
  110. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/nodes/shader/vector.py +0 -0
  111. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/py.typed +0 -0
  112. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/skills/nodebpy/SKILL.md +0 -0
  113. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/skills/nodebpy/references/existing-trees.md +0 -0
  114. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/skills/nodebpy/references/verifying.md +0 -0
  115. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/skills/nodebpy/references/writing-trees.md +0 -0
  116. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/skills/nodebpy/references/zones-and-items.md +0 -0
  117. {nodebpy-520.36.0 → nodebpy-520.37.0}/src/nodebpy/types.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: nodebpy
3
- Version: 520.36.0
3
+ Version: 520.37.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.36.0"
3
+ version = "520.37.0"
4
4
  description = "Build nodes trees in Blender more elegantly with code"
5
5
  readme = "README.md"
6
6
  authors = [
@@ -5,8 +5,9 @@ import keyword
5
5
  import re
6
6
  import unicodedata
7
7
  import warnings
8
- from functools import cache
9
- from typing import TYPE_CHECKING, Any, Protocol, runtime_checkable
8
+ from collections.abc import Callable
9
+ from functools import cache, wraps
10
+ from typing import TYPE_CHECKING, Any, Protocol, cast, runtime_checkable
10
11
 
11
12
  import bpy
12
13
  from bpy.types import NodeSocket
@@ -248,3 +249,29 @@ def _deprecated(old: str, new: str) -> None:
248
249
  DeprecationWarning,
249
250
  stacklevel=3,
250
251
  )
252
+
253
+
254
+ def _accept_renamed[**P, R](
255
+ func: Callable[P, R], renames: dict[str, str], label: str
256
+ ) -> Callable[P, R]:
257
+ """Wrap ``func`` so each old keyword in ``renames`` (old -> new) still
258
+ works, with a deprecation warning, as its new name."""
259
+
260
+ @wraps(func)
261
+ def wrapper(*args: Any, **kwargs: Any) -> R:
262
+ for old, new in renames.items():
263
+ if old in kwargs:
264
+ if new in kwargs:
265
+ raise TypeError(f"{label}() got both {old}= and {new}=")
266
+ _deprecated(f"{label}({old}=...)", f"{new}=")
267
+ kwargs[new] = kwargs.pop(old)
268
+ return func(*args, **kwargs)
269
+
270
+ return cast("Callable[P, R]", wrapper)
271
+
272
+
273
+ def _renamed_params[**P, R](
274
+ renames: dict[str, str], label: str
275
+ ) -> Callable[[Callable[P, R]], Callable[P, R]]:
276
+ """Decorator form of :func:`_accept_renamed`."""
277
+ return lambda func: _accept_renamed(func, renames, label)
@@ -27,7 +27,13 @@ from bpy.types import (
27
27
 
28
28
  from ..types import SOCKET_COMPATIBILITY, Default, InputAny
29
29
  from ._combine import holds_sockets, link_combined
30
- from ._utils import SocketError, _NodeLike, _SocketLike, enum_value_error
30
+ from ._utils import (
31
+ SocketError,
32
+ _accept_renamed,
33
+ _NodeLike,
34
+ _SocketLike,
35
+ enum_value_error,
36
+ )
31
37
  from .accessor import SocketAccessor
32
38
  from .mixins import LinkingMixin, OperatorMixin
33
39
  from .tree import TreeBuilder
@@ -97,6 +103,20 @@ class BaseNode(_NodeLike, OperatorMixin, LinkingMixin):
97
103
  _default_input_id: str | None = None
98
104
  _default_output_id: str | None = None
99
105
  _placeholder_inputs: list[str]
106
+ # Parameters renamed after their socket's display name, per method (old
107
+ # name -> new name); the old name keeps working with a DeprecationWarning.
108
+ _renamed_params: ClassVar[dict[str, dict[str, str]]] = {}
109
+
110
+ def __init_subclass__(cls, **kwargs: Any) -> None:
111
+ super().__init_subclass__(**kwargs)
112
+ for method, renames in cls.__dict__.get("_renamed_params", {}).items():
113
+ label = cls.__name__ if method == "__init__" else f"{cls.__name__}.{method}"
114
+ attr = cls.__dict__[method]
115
+ if isinstance(attr, classmethod):
116
+ func = _accept_renamed(attr.__func__, renames, label)
117
+ setattr(cls, method, classmethod(func))
118
+ else:
119
+ setattr(cls, method, _accept_renamed(attr, renames, label))
100
120
 
101
121
  def __init__(self, node: Node | None = None):
102
122
  tree = (
@@ -639,6 +639,38 @@ def _tree_alias(ctx: EmitContext, name: str) -> str:
639
639
  # Signatures are read for every constructor call emitted; they do not change.
640
640
  _signature = cache(inspect.signature)
641
641
 
642
+ # Constant input nodes, whose value is passed positionally whatever else the
643
+ # call carries (``g.Integer(5)``, ``g.Vector((1.0, 0.0, 0.0), vector_dimensions=2)``).
644
+ _CONSTANT_INPUT_NODES = frozenset(
645
+ {
646
+ "FunctionNodeInputBool",
647
+ "FunctionNodeInputColor",
648
+ "FunctionNodeInputInt",
649
+ "FunctionNodeInputIntVector",
650
+ "FunctionNodeInputRotation",
651
+ "FunctionNodeInputString",
652
+ "FunctionNodeInputVector",
653
+ }
654
+ )
655
+
656
+
657
+ def _constructor_call(func: str, cls: type, node, kwargs: dict[str, Expr]) -> Call:
658
+ """``func(**kwargs)``, passing the constructor's first parameter
659
+ positionally for a constant input node or when it is the only argument
660
+ (``g.Points(count)``, but ``g.SetPosition(offset=...)``)."""
661
+ if kwargs and (node.bl_idname in _CONSTANT_INPUT_NODES or len(kwargs) == 1):
662
+ params = list(_signature(cls.__init__).parameters.values())[1:]
663
+ if (
664
+ params
665
+ and params[0].kind is inspect.Parameter.POSITIONAL_OR_KEYWORD
666
+ and params[0].name in kwargs
667
+ ):
668
+ kwargs = dict(kwargs)
669
+ first = kwargs.pop(params[0].name)
670
+ return Call(func, args=[first], kwargs=kwargs)
671
+ return Call(func, kwargs=kwargs)
672
+
673
+
642
674
  # Probe trees kept open inside probe_trees(), by tree type.
643
675
  _PROBE_TREES: dict[str, Any] = {}
644
676
  _KEEP_PROBE_TREES = False
@@ -1262,7 +1294,7 @@ class EmitContext:
1262
1294
  for link in self.incoming.get(node.name, ()):
1263
1295
  if skip_input_id and link.to_socket.identifier == skip_input_id:
1264
1296
  continue
1265
- name = _normalize(link.to_socket.identifier)
1297
+ name = _socket_param(cls, link.to_socket.identifier)
1266
1298
  if getattr(link.to_socket, "is_multi_input", False):
1267
1299
  multi_inputs.add(name)
1268
1300
  by_socket.setdefault(name, []).append(
@@ -1303,7 +1335,7 @@ class EmitContext:
1303
1335
  kwargs = dict(socket_kwargs)
1304
1336
  for name, value in prop_values.items():
1305
1337
  kwargs[name] = Lit(value)
1306
- return Call(f"{alias}.{cls.__name__}", kwargs=kwargs)
1338
+ return _constructor_call(f"{alias}.{cls.__name__}", cls, node, kwargs)
1307
1339
 
1308
1340
  def _literal_kwargs(
1309
1341
  self, node, cls: type, existing: dict, skip_input_id: str | None
@@ -1337,7 +1369,7 @@ class EmitContext:
1337
1369
  continue
1338
1370
  if not hasattr(socket, "default_value"):
1339
1371
  continue
1340
- kwarg_name = _normalize(socket.identifier)
1372
+ kwarg_name = _socket_param(cls, socket.identifier)
1341
1373
  if kwarg_name in existing:
1342
1374
  continue
1343
1375
  try:
@@ -1599,7 +1631,7 @@ def _non_default_props(node, cls: type) -> dict[str, Any]:
1599
1631
  # property — even when its name happens to match a bpy property.
1600
1632
  # AxesToRotation's ``primary_axis`` is the Vector socket; the enum of
1601
1633
  # the same bpy name rides on the separate ``primary`` param instead.
1602
- if _input_socket_by_kwarg(node, name) is not None:
1634
+ if _input_socket_by_kwarg(node, cls, name) is not None:
1603
1635
  continue
1604
1636
  # A constructor param represents a node setting only if it (or the
1605
1637
  # property it proxies) names an actual bpy property. This excludes
@@ -1866,15 +1898,24 @@ def _class_factories(cls: type) -> list[_Factory]:
1866
1898
  return factories
1867
1899
 
1868
1900
 
1869
- def _input_socket_by_kwarg(node, kwarg_name: str):
1901
+ def _socket_param(cls: type, identifier: str) -> str:
1902
+ """The constructor parameter for input socket ``identifier``: its
1903
+ normalized identifier, unless the class renamed it after the socket's
1904
+ display name (Color Ramp's ``Fac`` is ``factor``)."""
1905
+ param = _normalize(identifier)
1906
+ renames = getattr(cls, "_renamed_params", {}).get("__init__", {})
1907
+ return renames.get(param, param)
1908
+
1909
+
1910
+ def _input_socket_by_kwarg(node, cls: type, kwarg_name: str):
1870
1911
  normalized = _normalize(kwarg_name)
1871
1912
  for socket in node.inputs:
1872
- if _normalize(socket.identifier) == normalized:
1913
+ if _socket_param(cls, socket.identifier) == normalized:
1873
1914
  return socket
1874
1915
  return None
1875
1916
 
1876
1917
 
1877
- def _factory_state_matches(node, props: dict[str, Any]) -> bool:
1918
+ def _factory_state_matches(node, cls: type, props: dict[str, Any]) -> bool:
1878
1919
  """True if every constant the factory sets equals the node's current state."""
1879
1920
  rna_props = getattr(getattr(node, "bl_rna", None), "properties", None)
1880
1921
  for key, value in props.items():
@@ -1884,7 +1925,7 @@ def _factory_state_matches(node, props: dict[str, Any]) -> bool:
1884
1925
  continue
1885
1926
  # Not an rna property — may be a constant on an input socket (menu
1886
1927
  # "type" sockets). Verify against the socket's default value.
1887
- socket = _input_socket_by_kwarg(node, key)
1928
+ socket = _input_socket_by_kwarg(node, cls, key)
1888
1929
  if (
1889
1930
  socket is None
1890
1931
  or socket.is_linked
@@ -1921,11 +1962,11 @@ def _factory_call(
1921
1962
  parameters, and unlinked sockets whose factory-parameter default differs
1922
1963
  from the node's value get explicit literal kwargs.
1923
1964
  """
1924
- skip_key = _normalize(skip_input_id) if skip_input_id else None
1965
+ skip_key = _socket_param(cls, skip_input_id) if skip_input_id else None
1925
1966
  best: tuple[_Factory, dict[str, Expr], tuple[bool, int, int]] | None = None
1926
1967
 
1927
1968
  for factory in _class_factories(cls):
1928
- if not _factory_state_matches(node, factory.props):
1969
+ if not _factory_state_matches(node, cls, factory.props):
1929
1970
  continue
1930
1971
  covered = {_normalize(key) for key in factory.props}
1931
1972
  passed = {
@@ -1967,7 +2008,7 @@ def _factory_call(
1967
2008
  or isinstance(default, Default)
1968
2009
  ):
1969
2010
  continue # None / Default.* mean "leave the socket untouched"
1970
- socket = _input_socket_by_kwarg(node, key)
2011
+ socket = _input_socket_by_kwarg(node, cls, key)
1971
2012
  if socket is None:
1972
2013
  faithful = False
1973
2014
  break
@@ -5549,7 +5590,7 @@ def _items_node_ctor(
5549
5590
  if method is not None:
5550
5591
  return Call(f"{alias}.{cls.__name__}.{method}", kwargs=kwargs)
5551
5592
  kwargs[spec.factory_prop] = Lit(prop_value)
5552
- return Call(f"{alias}.{cls.__name__}", kwargs=kwargs)
5593
+ return _constructor_call(f"{alias}.{cls.__name__}", cls, node, kwargs)
5553
5594
 
5554
5595
 
5555
5596
  def _emit_items_node(node, ctx: EmitContext) -> Expr | _Val | None: