nodebpy 0.7.0__tar.gz → 0.7.2__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 (63) hide show
  1. {nodebpy-0.7.0 → nodebpy-0.7.2}/PKG-INFO +1 -1
  2. {nodebpy-0.7.0 → nodebpy-0.7.2}/pyproject.toml +1 -1
  3. {nodebpy-0.7.0 → nodebpy-0.7.2}/src/nodebpy/builder.py +64 -30
  4. {nodebpy-0.7.0 → nodebpy-0.7.2}/src/nodebpy/types.py +4 -3
  5. {nodebpy-0.7.0 → nodebpy-0.7.2}/README.md +0 -0
  6. {nodebpy-0.7.0 → nodebpy-0.7.2}/src/nodebpy/__init__.py +0 -0
  7. {nodebpy-0.7.0 → nodebpy-0.7.2}/src/nodebpy/arrange.py +0 -0
  8. {nodebpy-0.7.0 → nodebpy-0.7.2}/src/nodebpy/lib/nodearrange/__init__.py +0 -0
  9. {nodebpy-0.7.0 → nodebpy-0.7.2}/src/nodebpy/lib/nodearrange/arrange/graph.py +0 -0
  10. {nodebpy-0.7.0 → nodebpy-0.7.2}/src/nodebpy/lib/nodearrange/arrange/ordering.py +0 -0
  11. {nodebpy-0.7.0 → nodebpy-0.7.2}/src/nodebpy/lib/nodearrange/arrange/ranking.py +0 -0
  12. {nodebpy-0.7.0 → nodebpy-0.7.2}/src/nodebpy/lib/nodearrange/arrange/realize.py +0 -0
  13. {nodebpy-0.7.0 → nodebpy-0.7.2}/src/nodebpy/lib/nodearrange/arrange/stacking.py +0 -0
  14. {nodebpy-0.7.0 → nodebpy-0.7.2}/src/nodebpy/lib/nodearrange/arrange/structs.py +0 -0
  15. {nodebpy-0.7.0 → nodebpy-0.7.2}/src/nodebpy/lib/nodearrange/arrange/sugiyama.py +0 -0
  16. {nodebpy-0.7.0 → nodebpy-0.7.2}/src/nodebpy/lib/nodearrange/arrange/x_coords.py +0 -0
  17. {nodebpy-0.7.0 → nodebpy-0.7.2}/src/nodebpy/lib/nodearrange/arrange/y_coords.py +0 -0
  18. {nodebpy-0.7.0 → nodebpy-0.7.2}/src/nodebpy/lib/nodearrange/config.py +0 -0
  19. {nodebpy-0.7.0 → nodebpy-0.7.2}/src/nodebpy/lib/nodearrange/utils.py +0 -0
  20. {nodebpy-0.7.0 → nodebpy-0.7.2}/src/nodebpy/nodes/__init__.py +0 -0
  21. {nodebpy-0.7.0 → nodebpy-0.7.2}/src/nodebpy/nodes/compositor/__init__.py +0 -0
  22. {nodebpy-0.7.0 → nodebpy-0.7.2}/src/nodebpy/nodes/compositor/color.py +0 -0
  23. {nodebpy-0.7.0 → nodebpy-0.7.2}/src/nodebpy/nodes/compositor/converter.py +0 -0
  24. {nodebpy-0.7.0 → nodebpy-0.7.2}/src/nodebpy/nodes/compositor/distort.py +0 -0
  25. {nodebpy-0.7.0 → nodebpy-0.7.2}/src/nodebpy/nodes/compositor/filter.py +0 -0
  26. {nodebpy-0.7.0 → nodebpy-0.7.2}/src/nodebpy/nodes/compositor/group.py +0 -0
  27. {nodebpy-0.7.0 → nodebpy-0.7.2}/src/nodebpy/nodes/compositor/input.py +0 -0
  28. {nodebpy-0.7.0 → nodebpy-0.7.2}/src/nodebpy/nodes/compositor/interface.py +0 -0
  29. {nodebpy-0.7.0 → nodebpy-0.7.2}/src/nodebpy/nodes/compositor/manual.py +0 -0
  30. {nodebpy-0.7.0 → nodebpy-0.7.2}/src/nodebpy/nodes/compositor/matte.py +0 -0
  31. {nodebpy-0.7.0 → nodebpy-0.7.2}/src/nodebpy/nodes/compositor/output.py +0 -0
  32. {nodebpy-0.7.0 → nodebpy-0.7.2}/src/nodebpy/nodes/compositor/texture.py +0 -0
  33. {nodebpy-0.7.0 → nodebpy-0.7.2}/src/nodebpy/nodes/compositor/vector.py +0 -0
  34. {nodebpy-0.7.0 → nodebpy-0.7.2}/src/nodebpy/nodes/geometry/__init__.py +0 -0
  35. {nodebpy-0.7.0 → nodebpy-0.7.2}/src/nodebpy/nodes/geometry/attribute.py +0 -0
  36. {nodebpy-0.7.0 → nodebpy-0.7.2}/src/nodebpy/nodes/geometry/color.py +0 -0
  37. {nodebpy-0.7.0 → nodebpy-0.7.2}/src/nodebpy/nodes/geometry/converter.py +0 -0
  38. {nodebpy-0.7.0 → nodebpy-0.7.2}/src/nodebpy/nodes/geometry/geometry.py +0 -0
  39. {nodebpy-0.7.0 → nodebpy-0.7.2}/src/nodebpy/nodes/geometry/grid.py +0 -0
  40. {nodebpy-0.7.0 → nodebpy-0.7.2}/src/nodebpy/nodes/geometry/group.py +0 -0
  41. {nodebpy-0.7.0 → nodebpy-0.7.2}/src/nodebpy/nodes/geometry/input.py +0 -0
  42. {nodebpy-0.7.0 → nodebpy-0.7.2}/src/nodebpy/nodes/geometry/interface.py +0 -0
  43. {nodebpy-0.7.0 → nodebpy-0.7.2}/src/nodebpy/nodes/geometry/manual.py +0 -0
  44. {nodebpy-0.7.0 → nodebpy-0.7.2}/src/nodebpy/nodes/geometry/output.py +0 -0
  45. {nodebpy-0.7.0 → nodebpy-0.7.2}/src/nodebpy/nodes/geometry/texture.py +0 -0
  46. {nodebpy-0.7.0 → nodebpy-0.7.2}/src/nodebpy/nodes/geometry/vector.py +0 -0
  47. {nodebpy-0.7.0 → nodebpy-0.7.2}/src/nodebpy/nodes/geometry/zone.py +0 -0
  48. {nodebpy-0.7.0 → nodebpy-0.7.2}/src/nodebpy/nodes/shader/__init__.py +0 -0
  49. {nodebpy-0.7.0 → nodebpy-0.7.2}/src/nodebpy/nodes/shader/color.py +0 -0
  50. {nodebpy-0.7.0 → nodebpy-0.7.2}/src/nodebpy/nodes/shader/converter.py +0 -0
  51. {nodebpy-0.7.0 → nodebpy-0.7.2}/src/nodebpy/nodes/shader/grid.py +0 -0
  52. {nodebpy-0.7.0 → nodebpy-0.7.2}/src/nodebpy/nodes/shader/group.py +0 -0
  53. {nodebpy-0.7.0 → nodebpy-0.7.2}/src/nodebpy/nodes/shader/input.py +0 -0
  54. {nodebpy-0.7.0 → nodebpy-0.7.2}/src/nodebpy/nodes/shader/interface.py +0 -0
  55. {nodebpy-0.7.0 → nodebpy-0.7.2}/src/nodebpy/nodes/shader/manual.py +0 -0
  56. {nodebpy-0.7.0 → nodebpy-0.7.2}/src/nodebpy/nodes/shader/output.py +0 -0
  57. {nodebpy-0.7.0 → nodebpy-0.7.2}/src/nodebpy/nodes/shader/script.py +0 -0
  58. {nodebpy-0.7.0 → nodebpy-0.7.2}/src/nodebpy/nodes/shader/shader.py +0 -0
  59. {nodebpy-0.7.0 → nodebpy-0.7.2}/src/nodebpy/nodes/shader/texture.py +0 -0
  60. {nodebpy-0.7.0 → nodebpy-0.7.2}/src/nodebpy/nodes/shader/vector.py +0 -0
  61. {nodebpy-0.7.0 → nodebpy-0.7.2}/src/nodebpy/nodes/shader/zone.py +0 -0
  62. {nodebpy-0.7.0 → nodebpy-0.7.2}/src/nodebpy/screenshot.py +0 -0
  63. {nodebpy-0.7.0 → nodebpy-0.7.2}/src/nodebpy/sockets.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: nodebpy
3
- Version: 0.7.0
3
+ Version: 0.7.2
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 = "0.7.0"
3
+ version = "0.7.2"
4
4
  description = "Build nodes trees in Blender more elegantly with code"
5
5
  readme = "README.md"
6
6
  authors = [
@@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Any, ClassVar, Iterable, Literal
4
4
 
5
5
  if TYPE_CHECKING:
6
6
  from .nodes.geometry import IntegerMath, Math, VectorMath
7
- from .nodes.geometry.converter import BooleanMath, MultiplyMatrices
7
+ from .nodes.geometry.converter import BooleanMath, MultiplyMatrices, TransformPoint
8
8
  from .nodes.geometry.manual import Compare
9
9
 
10
10
  import bpy
@@ -139,7 +139,7 @@ class SocketContext:
139
139
  class DirectionalContext(SocketContext):
140
140
  """Base class for directional socket contexts"""
141
141
 
142
- _direction: Literal["INPUT", "OUTPUT"] = "INPUT"
142
+ _direction = "INPUT"
143
143
  _active_context = None
144
144
 
145
145
 
@@ -249,14 +249,14 @@ class TreeBuilder:
249
249
 
250
250
  @fake_user.setter
251
251
  def fake_user(self, value: bool) -> None:
252
- self.tree.use_extra_user = value
252
+ self.tree.use_fake_user = value
253
253
 
254
254
  def activate_tree(self) -> None:
255
255
  """Make this tree the active tree for all new node creation."""
256
256
  TreeBuilder._tree_contexts.append(self)
257
257
 
258
258
  def deactivate_tree(self) -> None:
259
- """Whatever tree ws previously active is set to be the active one (or None if no previously active tree)."""
259
+ """Whatever tree was previously active is set to be the active one (or None if no previously active tree)."""
260
260
  TreeBuilder._tree_contexts.pop()
261
261
 
262
262
  def __enter__(self):
@@ -271,9 +271,9 @@ class TreeBuilder:
271
271
 
272
272
  def _apply_input_defaults(self) -> None:
273
273
  for key, value in self._menu_defaults.items():
274
- for item in self.tree.interface.items_tree:
275
- if item.identifier == key:
276
- item.default_value = value
274
+ for item in self.tree.interface.items_tree: # type: ignore
275
+ if item.identifier == key: # type: ignore
276
+ item.default_value = value # type: ignore
277
277
  break
278
278
 
279
279
  def __len__(self) -> int:
@@ -335,9 +335,13 @@ class TreeBuilder:
335
335
  # the warning message should report which sockets from which nodes were linked and which were innactive
336
336
  for socket in [socket1, socket2]:
337
337
  # we want to be loud about it if we end up linking an inactive socket to a node that is not a switch
338
- if socket.is_inactive and socket.node.bl_idname not in ( # type: ignore
339
- "GeometryNodeIndexSwitch",
340
- "GeometryNodeMenuSwitch",
338
+ if socket.is_inactive and (
339
+ socket.node.bl_idname # type: ignore
340
+ not in ( # type: ignore
341
+ "GeometryNodeIndexSwitch",
342
+ "GeometryNodeMenuSwitch",
343
+ "ShaderNodeMixShader",
344
+ )
341
345
  ):
342
346
  message = f"Socket {socket.name} from node {socket.node.name} is inactive." # type: ignore
343
347
  message += f" It is linked to socket {socket2.name} from node {socket2.node.name}."
@@ -356,13 +360,14 @@ class TreeBuilder:
356
360
  class NodeBuilder:
357
361
  """Base class for all geometry node wrappers."""
358
362
 
359
- node: Any
363
+ node: bpy.types.Node
360
364
  _bl_idname: str
361
365
  _tree: "TreeBuilder"
362
366
  _link_target: str | None = None
363
367
  _from_socket: NodeSocket | None = None
364
368
  _default_input_id: str | None = None
365
369
  _default_output_id: str | None = None
370
+ _placeholder_inputs: list[str]
366
371
  __array_ufunc__ = None
367
372
 
368
373
  def __init__(self):
@@ -380,6 +385,7 @@ class NodeBuilder:
380
385
 
381
386
  self._tree = tree
382
387
  self._link_target = None
388
+ self._placeholder_inputs = []
383
389
  if self.__class__.name is not None:
384
390
  self.node = self._tree.add(self.__class__._bl_idname)
385
391
  else:
@@ -502,7 +508,7 @@ class NodeBuilder:
502
508
  return sorted(possible_combos, key=lambda x: x[0])[0][1]
503
509
 
504
510
  raise SocketError(
505
- f"Cannot link any output from {source.node.name} to any input of {target.node.name}. "
511
+ f"Cannot link any output from {source.node.name} to any input of {target.node.name}. " # type: ignore
506
512
  f"Available output types: {[f'{o.name}:{o.type}' for o in outputs]}, "
507
513
  f"Available input types: {[f'{i.name}:{i.type}' for i in inputs]}"
508
514
  )
@@ -526,7 +532,10 @@ class NodeBuilder:
526
532
  if identifier in input_names:
527
533
  return input_names.index(identifier)
528
534
 
529
- raise RuntimeError()
535
+ raise RuntimeError(
536
+ f"Input '{identifier}' not found on {self.node.bl_idname}. "
537
+ f"Available inputs: {input_names}"
538
+ )
530
539
 
531
540
  def _output_idx(self, identifier: str) -> int:
532
541
  output_ids = [output.identifier for output in self.node.outputs]
@@ -589,10 +598,11 @@ class NodeBuilder:
589
598
  value = node
590
599
 
591
600
  if value is ...:
592
- # Ellipsis indicates this input should receive links from >> operator
593
- # which can potentially target multiple inputs on the new node
601
+ # Ellipsis marks this input as a placeholder for the >> operator
602
+ self._placeholder_inputs.append(name)
594
603
  if self._from_socket is not None:
595
604
  self._link_from(self._from_socket, name)
605
+ continue
596
606
 
597
607
  elif isinstance(value, SocketLinker):
598
608
  self._link_from(value, name)
@@ -624,6 +634,14 @@ class NodeBuilder:
624
634
  source = self._default_output_socket
625
635
  target = other.socket
626
636
  other._from_socket = source
637
+ elif getattr(other, "_placeholder_inputs", None):
638
+ # Link to the first placeholder input marked with ...
639
+ name = other._placeholder_inputs.pop(0)
640
+ try:
641
+ target = other.node.inputs[name]
642
+ except KeyError:
643
+ target = other.node.inputs[other._input_idx(name)]
644
+ source = self._best_output_socket(target.type)
627
645
  else:
628
646
  try:
629
647
  source, target = self._find_best_socket_pair(self, other)
@@ -697,7 +715,7 @@ class NodeBuilder:
697
715
 
698
716
  # only the Geometry Node Tree supports integer math currently, potential
699
717
  # to support other trees when Blender supports it
700
- is_geometry_tree = self._tree.tree.bl_idname in ["GeometryNodeTree"]
718
+ is_geometry_tree = self._tree.tree.bl_idname == "GeometryNodeTree"
701
719
  if (
702
720
  is_geometry_tree
703
721
  and isinstance(other, int)
@@ -842,16 +860,16 @@ class NodeBuilder:
842
860
  result = Math.subtract(1.0, result._default_output_socket)
843
861
  return result
844
862
 
845
- def __lt__(self, other: Any) -> "Compare":
863
+ def __lt__(self, other: Any) -> "Compare | Math":
846
864
  return self._apply_compare_operation(other, "less_than")
847
865
 
848
- def __gt__(self, other: Any) -> "Compare":
866
+ def __gt__(self, other: Any) -> "Compare | Math":
849
867
  return self._apply_compare_operation(other, "greater_than")
850
868
 
851
- def __le__(self, other: Any) -> "Compare":
869
+ def __le__(self, other: Any) -> "Compare | Math":
852
870
  return self._apply_compare_operation(other, "less_equal")
853
871
 
854
- def __ge__(self, other: Any) -> "Compare":
872
+ def __ge__(self, other: Any) -> "Compare | Math":
855
873
  return self._apply_compare_operation(other, "greater_equal")
856
874
 
857
875
  def _apply_boolean_operation(self, other: Any, operation: str) -> "BooleanMath":
@@ -898,15 +916,31 @@ class NodeBuilder:
898
916
  else:
899
917
  return value
900
918
 
901
- def __matmul__(self, other: Any) -> "MultiplyMatrices":
902
- from .nodes.geometry.converter import MultiplyMatrices
919
+ def __matmul__(self, other: Any) -> "MultiplyMatrices | TransformPoint":
920
+ from .nodes.geometry.converter import MultiplyMatrices, TransformPoint
903
921
 
904
- return MultiplyMatrices(self, self._cast_to_matrix(other))
922
+ other = self._cast_to_matrix(other)
923
+ socket = self._default_output_socket
924
+ other_type = getattr(other, "type", None)
925
+
926
+ # matrix @ vector → TransformPoint (standard M @ v)
927
+ if socket.type == "MATRIX" and other_type == "VECTOR":
928
+ return TransformPoint(other, socket)
929
+
930
+ return MultiplyMatrices(self, other)
931
+
932
+ def __rmatmul__(self, other: Any) -> "MultiplyMatrices | TransformPoint":
933
+ from .nodes.geometry.converter import MultiplyMatrices, TransformPoint
934
+
935
+ other = self._cast_to_matrix(other)
936
+ socket = self._default_output_socket
937
+ other_type = getattr(other, "type", None)
905
938
 
906
- def __rmatmul__(self, other: Any) -> "MultiplyMatrices":
907
- from .nodes.geometry.converter import MultiplyMatrices
939
+ # matrix @ vector: other is matrix (non-NodeBuilder cast), self is vector
940
+ if socket.type == "VECTOR" and other_type == "MATRIX":
941
+ return TransformPoint(socket, other)
908
942
 
909
- return MultiplyMatrices(self._cast_to_matrix(other), self)
943
+ return MultiplyMatrices(other, self)
910
944
 
911
945
 
912
946
  class DynamicInputsMixin:
@@ -934,7 +968,7 @@ class DynamicInputsMixin:
934
968
  self, source: NodeBuilder | NodeSocket, target: NodeBuilder | NodeSocket
935
969
  ) -> tuple[NodeSocket, NodeSocket]:
936
970
  try:
937
- return super()._find_best_socket_pair(source, target)
971
+ return super()._find_best_socket_pair(source, target) # type: ignore
938
972
  except SocketError:
939
973
  if target == self:
940
974
  target_name, source_socket = list(target._add_inputs(source).items())[0]
@@ -1026,7 +1060,7 @@ class SocketBase(SocketLinker):
1026
1060
  def __init__(self, name: str, description: str = ""):
1027
1061
  self.description = description
1028
1062
 
1029
- self._socket_context: SocketContext = SocketContext._active_context
1063
+ self._socket_context = SocketContext._active_context
1030
1064
  self.interface_socket = self._socket_context._create_socket(self, name)
1031
1065
  self._tree = self._socket_context.builder
1032
1066
  if self._socket_context._direction == "INPUT":
@@ -1056,7 +1090,7 @@ class SocketBase(SocketLinker):
1056
1090
  raise AttributeError(
1057
1091
  f"'{self.__class__.__name__}' object has no attribute 'default_value'"
1058
1092
  )
1059
- return self.interface_socket.default_value
1093
+ return getattr(self.interface_socket, "default_value")
1060
1094
 
1061
1095
  @default_value.setter
1062
1096
  def default_value(self, value):
@@ -1064,7 +1098,7 @@ class SocketBase(SocketLinker):
1064
1098
  raise AttributeError(
1065
1099
  f"'{self.__class__.__name__}' object has no attribute 'default_value'"
1066
1100
  )
1067
- self.interface_socket.default_value = value
1101
+ setattr(self.interface_socket, "default_value", value)
1068
1102
 
1069
1103
 
1070
1104
  class SocketFloat(SocketBase):
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import typing
4
+ from types import EllipsisType
4
5
  from typing import Literal
5
6
 
6
7
  from bpy.types import (
@@ -35,7 +36,7 @@ def _is_default_value(value: TYPE_INPUT_ALL):
35
36
 
36
37
 
37
38
  # Type aliases for node inputs using typing.Union for runtime compatibility
38
- LINKABLE = typing.Union["NodeBuilder", "SocketLinker", NodeSocket, None]
39
+ LINKABLE = typing.Union["NodeBuilder", "SocketLinker", NodeSocket, None, EllipsisType]
39
40
  TYPE_INPUT_ROTATION = typing.Union[
40
41
  tuple[float, float, float], float, int, Euler, LINKABLE
41
42
  ]
@@ -316,7 +317,7 @@ SOCKET_COMPATIBILITY: dict[str, tuple[str]] = {
316
317
  "INT",
317
318
  "BOOLEAN",
318
319
  ),
319
- "RGBA": ("RGBA", "VECTOR", "VALUE", "INT", "BOOLEAN", "SHADER"),
320
+ "RGBA": ("RGBA", "VECTOR", "VALUE", "INT", "BOOLEAN"),
320
321
  "ROTATION": (
321
322
  "ROTATION",
322
323
  "MATRIX",
@@ -335,7 +336,7 @@ SOCKET_COMPATIBILITY: dict[str, tuple[str]] = {
335
336
  "MATERIAL": ("MATERIAL",),
336
337
  "BUNDLE": ("BUNDLE",),
337
338
  "CLOSURE": ("CLOSURE",),
338
- "SHADER": ("SHADER",),
339
+ "SHADER": ("SHADER", "RGBA"),
339
340
  }
340
341
 
341
342
 
File without changes
File without changes
File without changes
File without changes