nodebpy 520.30.1__tar.gz → 520.31.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 (92) hide show
  1. {nodebpy-520.30.1 → nodebpy-520.31.0}/PKG-INFO +1 -1
  2. {nodebpy-520.30.1 → nodebpy-520.31.0}/pyproject.toml +1 -1
  3. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/builder/accessor.py +15 -0
  4. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/builder/asset.py +4 -4
  5. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/builder/mixins.py +50 -38
  6. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/builder/node.py +24 -15
  7. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/export/codegen.py +11 -5
  8. {nodebpy-520.30.1 → nodebpy-520.31.0}/README.md +0 -0
  9. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/__init__.py +0 -0
  10. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/assets/__init__.py +0 -0
  11. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/assets/__main__.py +0 -0
  12. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/assets/_codegen.py +0 -0
  13. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/assets/_library.py +0 -0
  14. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/assets/_pipeline.py +0 -0
  15. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/builder/__init__.py +0 -0
  16. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/builder/_registry.py +0 -0
  17. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/builder/_socket_order.py +0 -0
  18. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/builder/_utils.py +0 -0
  19. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/builder/items.py +0 -0
  20. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/builder/layout.py +0 -0
  21. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/builder/socket.py +0 -0
  22. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/builder/tree.py +0 -0
  23. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/export/__init__.py +0 -0
  24. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/export/_floats.py +0 -0
  25. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/export/diagram.py +0 -0
  26. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/export/parity.py +0 -0
  27. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/export/plot.py +0 -0
  28. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/export/web_render.py +0 -0
  29. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/lib/__init__.py +0 -0
  30. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/lib/nodearrange/VENDORED.md +0 -0
  31. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/lib/nodearrange/__init__.py +0 -0
  32. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/lib/nodearrange/arrange/__init__.py +0 -0
  33. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/lib/nodearrange/arrange/balancing.py +0 -0
  34. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/lib/nodearrange/arrange/graph.py +0 -0
  35. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/lib/nodearrange/arrange/ordering.py +0 -0
  36. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/lib/nodearrange/arrange/ranking.py +0 -0
  37. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/lib/nodearrange/arrange/realize.py +0 -0
  38. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/lib/nodearrange/arrange/stacking.py +0 -0
  39. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/lib/nodearrange/arrange/structs.py +0 -0
  40. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/lib/nodearrange/arrange/sugiyama.py +0 -0
  41. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/lib/nodearrange/arrange/x_coords.py +0 -0
  42. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/lib/nodearrange/arrange/y_coords.py +0 -0
  43. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/lib/nodearrange/config.py +0 -0
  44. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/lib/nodearrange/utils.py +0 -0
  45. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/live.py +0 -0
  46. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/nodes/__init__.py +0 -0
  47. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/nodes/_mixins.py +0 -0
  48. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/nodes/compositor/__init__.py +0 -0
  49. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/nodes/compositor/assets.py +0 -0
  50. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/nodes/compositor/color.py +0 -0
  51. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/nodes/compositor/converter.py +0 -0
  52. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/nodes/compositor/distort.py +0 -0
  53. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/nodes/compositor/filter.py +0 -0
  54. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/nodes/compositor/group.py +0 -0
  55. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/nodes/compositor/input.py +0 -0
  56. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/nodes/compositor/interface.py +0 -0
  57. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/nodes/compositor/manual.py +0 -0
  58. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/nodes/compositor/matte.py +0 -0
  59. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/nodes/compositor/output.py +0 -0
  60. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/nodes/compositor/vector.py +0 -0
  61. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/nodes/geometry/__init__.py +0 -0
  62. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/nodes/geometry/assets.py +0 -0
  63. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/nodes/geometry/attribute.py +0 -0
  64. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/nodes/geometry/color.py +0 -0
  65. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/nodes/geometry/converter.py +0 -0
  66. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/nodes/geometry/geometry.py +0 -0
  67. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/nodes/geometry/grid.py +0 -0
  68. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/nodes/geometry/group.py +0 -0
  69. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/nodes/geometry/groups.py +0 -0
  70. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/nodes/geometry/input.py +0 -0
  71. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/nodes/geometry/interface.py +0 -0
  72. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/nodes/geometry/manual.py +0 -0
  73. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/nodes/geometry/output.py +0 -0
  74. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/nodes/geometry/texture.py +0 -0
  75. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/nodes/geometry/utilities.py +0 -0
  76. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/nodes/geometry/vector.py +0 -0
  77. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/nodes/geometry/zone.py +0 -0
  78. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/nodes/shader/__init__.py +0 -0
  79. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/nodes/shader/assets.py +0 -0
  80. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/nodes/shader/color.py +0 -0
  81. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/nodes/shader/converter.py +0 -0
  82. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/nodes/shader/grid.py +0 -0
  83. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/nodes/shader/group.py +0 -0
  84. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/nodes/shader/input.py +0 -0
  85. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/nodes/shader/manual.py +0 -0
  86. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/nodes/shader/output.py +0 -0
  87. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/nodes/shader/script.py +0 -0
  88. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/nodes/shader/shader.py +0 -0
  89. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/nodes/shader/texture.py +0 -0
  90. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/nodes/shader/vector.py +0 -0
  91. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/py.typed +0 -0
  92. {nodebpy-520.30.1 → nodebpy-520.31.0}/src/nodebpy/types.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: nodebpy
3
- Version: 520.30.1
3
+ Version: 520.31.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.30.1"
3
+ version = "520.31.0"
4
4
  description = "Build nodes trees in Blender more elegantly with code"
5
5
  readme = "README.md"
6
6
  authors = [
@@ -180,6 +180,21 @@ class SocketAccessor:
180
180
  s for s in self._collection if self._ignore_visibility or s.is_icon_visible
181
181
  ]
182
182
 
183
+ @property
184
+ def _inactive_fallback(self) -> list[NodeSocket]:
185
+ """Inactive inputs that may still be linked, for when no active input
186
+ fits. Only nodes on the ``_allow_innactive_sockets`` allowlist qualify:
187
+ a group input used behind an internal switch (``FadeGeometry(fade=0)``)
188
+ polls inactive but is still shown and linkable in the UI.
189
+ """
190
+ if self._direction != "input" or not _allow_innactive_sockets(self._node):
191
+ return []
192
+ return [
193
+ s
194
+ for s in self._collection
195
+ if s.is_inactive and s.is_icon_visible and (not s.links or s.is_multi_input)
196
+ ]
197
+
183
198
  def _best_match(self, socket_type: str) -> NodeSocket:
184
199
  """Find the best compatible socket for the given type."""
185
200
  from ..types import SOCKET_COMPATIBILITY
@@ -110,8 +110,7 @@ class _AssetGroupMixin:
110
110
  (a merged dump class) rather than the appending-only placeholder."""
111
111
  return cls._build_group is not _AssetGroupMixin._build_group
112
112
 
113
- @classmethod
114
- def create_group(cls) -> Any:
113
+ def _create_group(self) -> Any:
115
114
  """Append the named asset group from the library and return its tree,
116
115
  reusing a previously-appended group of the same name and tree type.
117
116
 
@@ -125,15 +124,16 @@ class _AssetGroupMixin:
125
124
  when the ``.blend`` is not on disk (a source checkout that hasn't
126
125
  built it yet).
127
126
  """
127
+ cls = type(self)
128
128
  if _build_from_source_depth and cls._has_build_source():
129
- return super().create_group() # ty: ignore[unresolved-attribute]
129
+ return super()._create_group() # ty: ignore[unresolved-attribute]
130
130
  existing = bpy.data.node_groups.get(cls._asset_name)
131
131
  if existing is not None and existing.bl_idname == cls._tree_idname:
132
132
  return existing
133
133
  path = cls._library.path()
134
134
  if not os.path.exists(path):
135
135
  if cls._has_build_source():
136
- return super().create_group() # ty: ignore[unresolved-attribute]
136
+ return super()._create_group() # ty: ignore[unresolved-attribute]
137
137
  raise FileNotFoundError(f"Asset library not found: {path}")
138
138
  with bpy.data.libraries.load(path, link=True, pack=True, assets_only=True) as ( # ty: ignore[invalid-context-manager]
139
139
  src,
@@ -259,7 +259,6 @@ class LinkingMixin:
259
259
  from ..builder.socket import Socket
260
260
  from ..types import PREFER_FIRST_SOCKET, SOCKET_COMPATIBILITY
261
261
 
262
- possible_combos = []
263
262
  if isinstance(source, BaseNode):
264
263
  outputs = source.o._available
265
264
  elif isinstance(source, NodeSocket):
@@ -286,44 +285,57 @@ class LinkingMixin:
286
285
  ):
287
286
  return inputs[0], outputs[0]
288
287
 
289
- # Try first available input first — if the output type matches it exactly,
290
- # or is a "preferred" implicit conversion (e.g. float→color, vector→color),
291
- # use the first socket rather than searching for a better-typed later one.
292
- # This keeps float→Image working in the compositor instead of drifting to
293
- # a float Factor socket that scores higher on raw compatibility.
294
- # Pairs not in PREFER_FIRST_SOCKET (e.g. VALUE→BOOLEAN, VECTOR→ROTATION)
295
- # fall through to the ranked search below.
296
- if inputs:
297
- first_input = inputs[0]
288
+ def best_pair(inputs: list[NodeSocket]) -> tuple[NodeSocket, NodeSocket] | None:
289
+ possible_combos = []
290
+ # Try first available input first — if the output type matches it exactly,
291
+ # or is a "preferred" implicit conversion (e.g. float→color, vector→color),
292
+ # use the first socket rather than searching for a better-typed later one.
293
+ # This keeps float→Image working in the compositor instead of drifting to
294
+ # a float Factor socket that scores higher on raw compatibility.
295
+ # Pairs not in PREFER_FIRST_SOCKET (e.g. VALUE→BOOLEAN, VECTOR→ROTATION)
296
+ # fall through to the ranked search below.
297
+ if inputs:
298
+ first_input = inputs[0]
299
+ for output in outputs:
300
+ if first_input.type == output.type:
301
+ return first_input, output
302
+ if (output.type, first_input.type) in PREFER_FIRST_SOCKET:
303
+ return first_input, output
304
+
298
305
  for output in outputs:
299
- if first_input.type == output.type:
300
- return first_input, output
301
- if (output.type, first_input.type) in PREFER_FIRST_SOCKET:
302
- return first_input, output
303
-
304
- for output in outputs:
305
- compat_sockets = SOCKET_COMPATIBILITY.get(output.type, ())
306
- for input in inputs:
307
- if input.type == output.type:
308
- return input, output
309
-
310
- if input.type in compat_sockets:
311
- possible_combos.append(
312
- (compat_sockets.index(input.type), (input, output))
313
- )
314
-
315
- if possible_combos:
316
- return min(possible_combos, key=lambda x: x[0])[1]
317
-
318
- # A node with a virtual ``__extend__`` input (Viewer, …) accepts any
319
- # source: linking to it makes Blender create a typed socket. Fall back
320
- # to it when nothing else matched, pairing with the source's first
321
- # available output.
322
- extend = next(
323
- (i for i in inputs if i.identifier.startswith("__extend__")), None
324
- )
325
- if extend is not None and outputs:
326
- return extend, outputs[0]
306
+ compat_sockets = SOCKET_COMPATIBILITY.get(output.type, ())
307
+ for input in inputs:
308
+ if input.type == output.type:
309
+ return input, output
310
+
311
+ if input.type in compat_sockets:
312
+ possible_combos.append(
313
+ (compat_sockets.index(input.type), (input, output))
314
+ )
315
+
316
+ if possible_combos:
317
+ return min(possible_combos, key=lambda x: x[0])[1]
318
+
319
+ # A node with a virtual ``__extend__`` input (Viewer, …) accepts any
320
+ # source: linking to it makes Blender create a typed socket. Fall back
321
+ # to it when nothing else matched, pairing with the source's first
322
+ # available output.
323
+ extend = next(
324
+ (i for i in inputs if i.identifier.startswith("__extend__")), None
325
+ )
326
+ if extend is not None and outputs:
327
+ return extend, outputs[0]
328
+ return None
329
+
330
+ pair = best_pair(inputs)
331
+ # Nothing active fits: fall back to inactive inputs Blender still lets
332
+ # the user link (a group input unused at the current values).
333
+ if pair is None and isinstance(target, BaseNode):
334
+ fallback = target.i._inactive_fallback
335
+ if fallback:
336
+ pair = best_pair(fallback)
337
+ if pair is not None:
338
+ return pair
327
339
 
328
340
  src_name = getattr(getattr(source, "node", None), "name", repr(source))
329
341
  tgt_name = getattr(getattr(target, "node", None), "name", repr(target))
@@ -474,28 +474,37 @@ class NodeGroupBuilder[T: bpy.types.NodeTree](BaseNode, ABC):
474
474
  directly (e.g. assigned to a node's ``node_tree``) instead of being
475
475
  created by constructing the class inside a tree.
476
476
  """
477
- existing = bpy.data.node_groups.get(cls._name)
477
+ # Only the inner tree is needed (no group *node*), so skip __init__,
478
+ # which would require an active context to create a node.
479
+ return cls.__new__(cls)._create_group()
480
+
481
+ def _group_name(self) -> str:
482
+ """Name of the inner tree. Override when instance state set before
483
+ ``super().__init__()`` (e.g. a data type) selects a variant, so each
484
+ variant builds and reuses its own tree."""
485
+ return self._name
486
+
487
+ def _create_group(self) -> T:
488
+ name = self._group_name()
489
+ existing = bpy.data.node_groups.get(name)
478
490
  if existing is not None:
479
- if existing.bl_idname != cls._tree_idname:
491
+ if existing.bl_idname != self._tree_idname:
480
492
  raise TypeError(
481
- f"Node group '{cls._name}' already exists as "
482
- f"{existing.bl_idname}, not {cls._tree_idname}. "
493
+ f"Node group '{name}' already exists as "
494
+ f"{existing.bl_idname}, not {self._tree_idname}. "
483
495
  f"Use a unique _name for this group."
484
496
  )
485
497
  return cast(T, existing)
486
- # Only the inner tree is needed (no group *node*), so skip __init__,
487
- # which would require an active context to create a node.
488
- builder = cls.__new__(cls)
489
- with TreeBuilder(cls._name, tree_type=cls._tree_idname) as tree:
490
- builder._build_group(tree)
491
- tree.tree.color_tag = cls._color_tag
492
- for key, value in cls._tree_properties.items():
498
+ with TreeBuilder(name, tree_type=self._tree_idname) as tree:
499
+ self._build_group(tree)
500
+ tree.tree.color_tag = self._color_tag
501
+ for key, value in self._tree_properties.items():
493
502
  try:
494
503
  setattr(tree.tree, key, value)
495
504
  except (AttributeError, TypeError):
496
505
  # A property this Blender version doesn't have (or types
497
506
  # differently) — skip rather than fail the whole build.
498
- print(f" {cls._name}: skipping tree property {key!r}")
507
+ print(f" {name}: skipping tree property {key!r}")
499
508
  return cast(T, tree.tree)
500
509
 
501
510
 
@@ -512,7 +521,7 @@ class CustomGeometryGroup(NodeGroupBuilder[GeometryNodeTree]):
512
521
  return self.node.node_tree
513
522
 
514
523
  def _setup_node_group(self) -> None:
515
- self.node.node_tree = self.create_group()
524
+ self.node.node_tree = self._create_group()
516
525
  self.node.warning_propagation = self._warning_propagation
517
526
 
518
527
 
@@ -529,7 +538,7 @@ class CustomShaderGroup(NodeGroupBuilder[ShaderNodeTree]):
529
538
  return self.node.node_tree
530
539
 
531
540
  def _setup_node_group(self) -> None:
532
- self.node.node_tree = self.create_group()
541
+ self.node.node_tree = self._create_group()
533
542
 
534
543
 
535
544
  class CustomCompositorGroup(NodeGroupBuilder[CompositorNodeTree]):
@@ -545,4 +554,4 @@ class CustomCompositorGroup(NodeGroupBuilder[CompositorNodeTree]):
545
554
  return self.node.node_tree
546
555
 
547
556
  def _setup_node_group(self) -> None:
548
- self.node.node_tree = self.create_group()
557
+ self.node.node_tree = self._create_group()
@@ -1757,6 +1757,7 @@ def _factory_state_matches(node, props: dict[str, Any]) -> bool:
1757
1757
  # baking behavioural props (``operation``, ``mode``, …) keep requiring a
1758
1758
  # non-default state, as before.
1759
1759
  _TYPE_FACTORY_PROPS = frozenset({"data_type", "domain", "input_type", "socket_type"})
1760
+ _DATA_TYPE_PROPS = frozenset({"data_type", "input_type", "socket_type"})
1760
1761
 
1761
1762
 
1762
1763
  def _factory_call(
@@ -1775,7 +1776,7 @@ def _factory_call(
1775
1776
  from the node's value get explicit literal kwargs.
1776
1777
  """
1777
1778
  skip_key = _normalize(skip_input_id) if skip_input_id else None
1778
- best: tuple[_Factory, dict[str, Expr], tuple[int, int]] | None = None
1779
+ best: tuple[_Factory, dict[str, Expr], tuple[bool, int, int]] | None = None
1779
1780
 
1780
1781
  for factory in _class_factories(cls):
1781
1782
  if not _factory_state_matches(node, factory.props):
@@ -1828,10 +1829,15 @@ def _factory_call(
1828
1829
  continue
1829
1830
  if not _eq(socket.default_value, default):
1830
1831
  call_kwargs[param] = Lit(socket.default_value)
1831
- # Prefer the factory baking the most constants; on a tie, the one
1832
- # passing fewest properties through (``DeleteGeometry.edge()`` over
1833
- # ``DeleteGeometry.all(domain="EDGE")``).
1834
- rank = (len(factory.props), -len(passed))
1832
+ # Prefer a factory baking the data type (``GetBundleItem.float(...,
1833
+ # structure_type="SINGLE")`` over ``GetBundleItem.single(...)``),
1834
+ # then the most constants baked, then the fewest properties passed
1835
+ # through (``DeleteGeometry.edge()`` over ``.all(domain="EDGE")``).
1836
+ rank = (
1837
+ bool(_DATA_TYPE_PROPS & set(factory.props)),
1838
+ len(factory.props),
1839
+ -len(passed),
1840
+ )
1835
1841
  if faithful and (best is None or rank > best[2]):
1836
1842
  ordered = {
1837
1843
  param: call_kwargs[param]
File without changes