nodebpy 520.28.0__tar.gz → 520.29.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 (90) hide show
  1. {nodebpy-520.28.0 → nodebpy-520.29.0}/PKG-INFO +1 -1
  2. {nodebpy-520.28.0 → nodebpy-520.29.0}/pyproject.toml +1 -1
  3. nodebpy-520.29.0/src/nodebpy/export/_floats.py +114 -0
  4. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/export/codegen.py +8 -106
  5. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/compositor/color.py +39 -39
  6. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/compositor/converter.py +51 -42
  7. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/compositor/distort.py +27 -27
  8. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/compositor/filter.py +81 -67
  9. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/compositor/input.py +6 -5
  10. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/compositor/interface.py +4 -4
  11. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/compositor/matte.py +21 -20
  12. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/compositor/output.py +1 -1
  13. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/geometry/assets.py +9 -8
  14. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/geometry/attribute.py +2 -2
  15. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/geometry/color.py +2 -2
  16. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/geometry/converter.py +75 -66
  17. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/geometry/geometry.py +157 -146
  18. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/geometry/grid.py +14 -14
  19. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/geometry/input.py +1 -1
  20. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/geometry/interface.py +9 -9
  21. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/geometry/texture.py +38 -35
  22. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/geometry/utilities.py +1 -1
  23. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/geometry/vector.py +88 -58
  24. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/shader/color.py +3 -3
  25. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/shader/converter.py +25 -17
  26. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/shader/grid.py +9 -9
  27. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/shader/input.py +7 -7
  28. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/shader/output.py +3 -3
  29. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/shader/shader.py +58 -58
  30. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/shader/texture.py +11 -10
  31. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/shader/vector.py +27 -27
  32. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/types.py +8 -1
  33. {nodebpy-520.28.0 → nodebpy-520.29.0}/README.md +0 -0
  34. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/__init__.py +0 -0
  35. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/assets/__init__.py +0 -0
  36. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/assets/__main__.py +0 -0
  37. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/assets/_codegen.py +0 -0
  38. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/assets/_library.py +0 -0
  39. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/assets/_pipeline.py +0 -0
  40. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/builder/__init__.py +0 -0
  41. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/builder/_registry.py +0 -0
  42. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/builder/_socket_order.py +0 -0
  43. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/builder/_utils.py +0 -0
  44. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/builder/accessor.py +0 -0
  45. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/builder/asset.py +0 -0
  46. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/builder/items.py +0 -0
  47. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/builder/layout.py +0 -0
  48. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/builder/mixins.py +0 -0
  49. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/builder/node.py +0 -0
  50. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/builder/socket.py +0 -0
  51. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/builder/tree.py +0 -0
  52. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/export/__init__.py +0 -0
  53. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/export/diagram.py +0 -0
  54. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/export/parity.py +0 -0
  55. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/export/plot.py +0 -0
  56. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/export/web_render.py +0 -0
  57. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/lib/nodearrange/VENDORED.md +0 -0
  58. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/lib/nodearrange/__init__.py +0 -0
  59. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/lib/nodearrange/arrange/__init__.py +0 -0
  60. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/lib/nodearrange/arrange/balancing.py +0 -0
  61. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/lib/nodearrange/arrange/graph.py +0 -0
  62. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/lib/nodearrange/arrange/ordering.py +0 -0
  63. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/lib/nodearrange/arrange/ranking.py +0 -0
  64. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/lib/nodearrange/arrange/realize.py +0 -0
  65. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/lib/nodearrange/arrange/stacking.py +0 -0
  66. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/lib/nodearrange/arrange/structs.py +0 -0
  67. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/lib/nodearrange/arrange/sugiyama.py +0 -0
  68. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/lib/nodearrange/arrange/x_coords.py +0 -0
  69. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/lib/nodearrange/arrange/y_coords.py +0 -0
  70. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/lib/nodearrange/config.py +0 -0
  71. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/lib/nodearrange/utils.py +0 -0
  72. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/live.py +0 -0
  73. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/__init__.py +0 -0
  74. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/_mixins.py +0 -0
  75. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/compositor/__init__.py +0 -0
  76. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/compositor/assets.py +0 -0
  77. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/compositor/group.py +0 -0
  78. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/compositor/manual.py +0 -0
  79. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/compositor/vector.py +0 -0
  80. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/geometry/__init__.py +0 -0
  81. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/geometry/group.py +0 -0
  82. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/geometry/groups.py +0 -0
  83. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/geometry/manual.py +0 -0
  84. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/geometry/output.py +0 -0
  85. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/geometry/zone.py +0 -0
  86. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/shader/__init__.py +0 -0
  87. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/shader/assets.py +0 -0
  88. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/shader/group.py +0 -0
  89. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/shader/manual.py +0 -0
  90. {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/shader/script.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: nodebpy
3
- Version: 520.28.0
3
+ Version: 520.29.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.28.0"
3
+ version = "520.29.0"
4
4
  description = "Build nodes trees in Blender more elegantly with code"
5
5
  readme = "README.md"
6
6
  authors = [
@@ -0,0 +1,114 @@
1
+ """Float formatting shared by ``to_python`` export and the node-class generator.
2
+
3
+ Blender stores socket values as float32. This module renders such a value as
4
+ the shortest Python literal that rebuilds the identical float32 (``0.1`` rather
5
+ than ``0.10000000149011612``), or as a ``math.pi`` / ``math.tau`` / ``math.e``
6
+ expression for the rational multiples of those constants (``3 * math.pi / 4``).
7
+
8
+ It is a leaf: stdlib and numpy only, no ``nodebpy`` imports. The generator in
9
+ ``gen/`` loads it straight from this file (see ``gen/config.py``) so that a
10
+ broken generated tree can never block regenerating it.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import math
16
+
17
+ import numpy as np
18
+
19
+
20
+ def within_one_ulp(candidate: float, f32) -> bool:
21
+ """Whether ``candidate`` lands on ``f32`` or its immediate float32
22
+ neighbour. One ULP is the round-off a value picks up crossing precisions
23
+ (an authored ``0.15`` surviving as ``0.14999999``), so snapping across it
24
+ restores the authored constant without moving any genuinely different
25
+ value."""
26
+ # A candidate beyond float32 range (a huge clamp bound) overflows the
27
+ # cast to inf with a RuntimeWarning; it is never within one ULP.
28
+ with np.errstate(over="ignore"):
29
+ c32 = np.float32(candidate)
30
+ if not np.isfinite(c32):
31
+ return False
32
+ return bool(c32 == f32 or np.nextafter(f32, c32) == c32)
33
+
34
+
35
+ def math_constant_expression(f32) -> str | None:
36
+ """A ``math.pi``/``math.tau``/``math.e`` expression for a rational
37
+ multiple of one of those constants, or None.
38
+
39
+ Constants authored as expressions (``2 * math.pi``, ``pi / 3``, ``tau``,
40
+ ``e``) survive in a blend only as float32 values; matching small-fraction
41
+ multiples (within one ULP) restores the readable form. Numerators up to 48
42
+ over denominators up to 12 cover the usual turns and subdivisions while
43
+ keeping a chance coincidence with an ordinary decimal essentially
44
+ impossible. An even multiple of pi renders in tau form (``math.tau``,
45
+ ``math.tau / 3``) — with the fraction reduced, halving the numerator is
46
+ always the simpler expression.
47
+ """
48
+ magnitude = abs(float(f32))
49
+ if magnitude == 0.0:
50
+ return None
51
+ for name, const in (("pi", math.pi), ("e", math.e)):
52
+ for den in range(1, 13):
53
+ num = round(magnitude * den / const)
54
+ if not 1 <= num <= 48 or math.gcd(num, den) != 1:
55
+ continue
56
+ if not within_one_ulp(num * const / den, abs(f32)):
57
+ continue
58
+ if name == "pi" and num % 2 == 0:
59
+ name, num = "tau", num // 2
60
+ expr = f"math.{name}" if num == 1 else f"{num} * math.{name}"
61
+ if den > 1:
62
+ expr = f"{expr} / {den}"
63
+ return f"-{expr}" if float(f32) < 0 else expr
64
+ return None
65
+
66
+
67
+ def group_digits(text: str) -> str:
68
+ """Underscore-group a positional float literal's integer part when it has
69
+ five or more digits (``10000.0`` → ``10_000.0``)."""
70
+ sign = ""
71
+ if text.startswith("-"):
72
+ sign, text = "-", text[1:]
73
+ int_part, dot, frac = text.partition(".")
74
+ if len(int_part) < 5:
75
+ return sign + text
76
+ return sign + f"{int(int_part):_}" + dot + frac
77
+
78
+
79
+ def fmt_float(value: float, *, snap: bool = True) -> str:
80
+ """Shortest readable literal for a float32-backed value.
81
+
82
+ Blender stores socket values as float32, so reading them back through
83
+ Python gives noisy float64 reprs (``0.10000000149011612``); the shortest
84
+ decimal that uniquely identifies the float32 (``0.1``) rebuilds the
85
+ identical socket value. On top of that, rational multiples of pi, tau
86
+ and e render as ``math.*`` expressions, a value one ULP off a much shorter decimal
87
+ snaps to it (``0.14999999`` → ``0.15``), and long integer parts get
88
+ underscore grouping (``-10_000.0``). ``snap=False`` keeps the literal that
89
+ rebuilds exactly this float32 (a generated default must be Blender's own
90
+ value, not its prettier neighbour).
91
+ """
92
+ if not math.isfinite(value):
93
+ return repr(value)
94
+ f32 = np.float32(value)
95
+ if float(f32) != value:
96
+ return repr(value) # genuine float64 — keep full precision
97
+ const_expr = math_constant_expression(f32)
98
+ if const_expr is not None:
99
+ return const_expr
100
+ text = np.format_float_positional(f32, unique=True, trim="0")
101
+ # Snap to the shortest decimal within one ULP: fewer significant digits
102
+ # first, so ``0.14999999`` becomes ``0.15`` rather than ``0.1499999``.
103
+ digits = sum(c.isdigit() for c in text.lstrip("-0.")) if snap else 0
104
+ for sig in range(1, digits):
105
+ candidate = float(f"%.{sig}g" % float(f32))
106
+ if not within_one_ulp(candidate, f32):
107
+ continue
108
+ snapped = np.format_float_positional(
109
+ np.float32(candidate), unique=True, trim="0"
110
+ )
111
+ if len(snapped) < len(text):
112
+ text = snapped
113
+ break
114
+ return group_digits(text)
@@ -31,8 +31,13 @@ from collections.abc import Callable, Collection, Mapping
31
31
  from dataclasses import dataclass, field
32
32
  from typing import TYPE_CHECKING, Any, Literal, NamedTuple
33
33
 
34
+ import numpy as np
34
35
  from bpy.types import ID, FunctionNodeCompare, NodeTree
35
36
 
37
+ from ._floats import (
38
+ fmt_float as _fmt_float,
39
+ )
40
+
36
41
  if TYPE_CHECKING:
37
42
  from ..builder.tree import TreeBuilder
38
43
 
@@ -354,111 +359,6 @@ def _stmt_lines(
354
359
  # ---------------------------------------------------------------------------
355
360
 
356
361
 
357
- def _within_one_ulp(candidate: float, f32) -> bool:
358
- """Whether ``candidate`` lands on ``f32`` or its immediate float32
359
- neighbour. One ULP is the round-off a value picks up crossing precisions
360
- (an authored ``0.15`` surviving as ``0.14999999``), so snapping across it
361
- restores the authored constant without moving any genuinely different
362
- value."""
363
- import numpy as np
364
-
365
- # A candidate beyond float32 range (a huge clamp bound) overflows the
366
- # cast to inf with a RuntimeWarning; it is never within one ULP.
367
- with np.errstate(over="ignore"):
368
- c32 = np.float32(candidate)
369
- if not np.isfinite(c32):
370
- return False
371
- return bool(c32 == f32 or np.nextafter(f32, c32) == c32)
372
-
373
-
374
- def _math_constant_expression(f32) -> str | None:
375
- """A ``math.pi``/``math.tau``/``math.e`` expression for a rational
376
- multiple of one of those constants, or None.
377
-
378
- Constants authored as expressions (``2 * math.pi``, ``pi / 3``, ``tau``,
379
- ``e``) survive in a blend only as float32 values; matching small-fraction
380
- multiples (within one ULP) restores the readable form. Numerators up to 48
381
- over denominators up to 12 cover the usual turns and subdivisions while
382
- keeping a chance coincidence with an ordinary decimal essentially
383
- impossible. An even multiple of pi renders in tau form (``math.tau``,
384
- ``math.tau / 3``) — with the fraction reduced, halving the numerator is
385
- always the simpler expression.
386
- """
387
- import math
388
-
389
- magnitude = abs(float(f32))
390
- if magnitude == 0.0:
391
- return None
392
- for name, const in (("pi", math.pi), ("e", math.e)):
393
- for den in range(1, 13):
394
- num = round(magnitude * den / const)
395
- if not 1 <= num <= 48 or math.gcd(num, den) != 1:
396
- continue
397
- if not _within_one_ulp(num * const / den, abs(f32)):
398
- continue
399
- if name == "pi" and num % 2 == 0:
400
- name, num = "tau", num // 2
401
- expr = f"math.{name}" if num == 1 else f"{num} * math.{name}"
402
- if den > 1:
403
- expr = f"{expr} / {den}"
404
- return f"-{expr}" if float(f32) < 0 else expr
405
- return None
406
-
407
-
408
- def _group_digits(text: str) -> str:
409
- """Underscore-group a positional float literal's integer part when it has
410
- five or more digits (``10000.0`` → ``10_000.0``)."""
411
- sign = ""
412
- if text.startswith("-"):
413
- sign, text = "-", text[1:]
414
- int_part, dot, frac = text.partition(".")
415
- if len(int_part) < 5:
416
- return sign + text
417
- return sign + f"{int(int_part):_}" + dot + frac
418
-
419
-
420
- def _fmt_float(value: float) -> str:
421
- """Shortest readable literal for a float32-backed value.
422
-
423
- Blender stores socket values as float32, so reading them back through
424
- Python gives noisy float64 reprs (``0.10000000149011612``); the shortest
425
- decimal that uniquely identifies the float32 (``0.1``) rebuilds the
426
- identical socket value. On top of that, rational multiples of pi, tau
427
- and e render as ``math.*`` expressions, a value one ULP off a much shorter decimal
428
- snaps to it (``0.14999999`` → ``0.15``), and long integer parts get
429
- underscore grouping (``-10_000.0``).
430
- """
431
- import math
432
-
433
- if not math.isfinite(value):
434
- return repr(value)
435
- try:
436
- import numpy as np
437
- except ImportError: # pragma: no cover - Blender ships numpy
438
- return repr(value)
439
- f32 = np.float32(value)
440
- if float(f32) != value:
441
- return repr(value) # genuine float64 — keep full precision
442
- const_expr = _math_constant_expression(f32)
443
- if const_expr is not None:
444
- return const_expr
445
- text = np.format_float_positional(f32, unique=True, trim="0")
446
- # Snap to the shortest decimal within one ULP: fewer significant digits
447
- # first, so ``0.14999999`` becomes ``0.15`` rather than ``0.1499999``.
448
- digits = sum(c.isdigit() for c in text.lstrip("-0."))
449
- for sig in range(1, digits):
450
- candidate = float(f"%.{sig}g" % float(f32))
451
- if not _within_one_ulp(candidate, f32):
452
- continue
453
- snapped = np.format_float_positional(
454
- np.float32(candidate), unique=True, trim="0"
455
- )
456
- if len(snapped) < len(text):
457
- text = snapped
458
- break
459
- return _group_digits(text)
460
-
461
-
462
362
  def _fmt(value: Any) -> str:
463
363
  """Format a value as a Python literal using double-quoted strings."""
464
364
  if isinstance(value, bool):
@@ -507,7 +407,9 @@ def _eq(a: Any, b: Any) -> bool:
507
407
  return False
508
408
  return all(_eq(x, y) for x, y in zip(a, b))
509
409
  if isinstance(a, (int, float)) and isinstance(b, (int, float)):
510
- return float(a) == float(b)
410
+ # Sockets store float32: a Python default of ``0.05`` reads back
411
+ # as ``0.05000000074505806``, so compare at that precision.
412
+ return bool(np.float32(a) == np.float32(b))
511
413
  return a == b
512
414
  except Exception: # noqa: BLE001
513
415
  return False
@@ -84,8 +84,8 @@ class AlphaOver(BaseNode):
84
84
 
85
85
  def __init__(
86
86
  self,
87
- background: InputColor = None,
88
- foreground: InputColor = None,
87
+ background: InputColor = (1.0, 1.0, 1.0, 1.0),
88
+ foreground: InputColor = (1.0, 1.0, 1.0, 1.0),
89
89
  fac: InputFloat = 1.0,
90
90
  type: InputMenu | Literal["Over", "Disjoint Over", "Conjoint Over"] = "Over",
91
91
  straight_alpha: InputBoolean = False,
@@ -104,8 +104,8 @@ class AlphaOver(BaseNode):
104
104
  @classmethod
105
105
  def over(
106
106
  cls,
107
- background: InputColor = None,
108
- foreground: InputColor = None,
107
+ background: InputColor = (1.0, 1.0, 1.0, 1.0),
108
+ foreground: InputColor = (1.0, 1.0, 1.0, 1.0),
109
109
  fac: InputFloat = 1.0,
110
110
  straight_alpha: InputBoolean = False,
111
111
  ) -> "AlphaOver":
@@ -121,8 +121,8 @@ class AlphaOver(BaseNode):
121
121
  @classmethod
122
122
  def disjoint_over(
123
123
  cls,
124
- background: InputColor = None,
125
- foreground: InputColor = None,
124
+ background: InputColor = (1.0, 1.0, 1.0, 1.0),
125
+ foreground: InputColor = (1.0, 1.0, 1.0, 1.0),
126
126
  fac: InputFloat = 1.0,
127
127
  straight_alpha: InputBoolean = False,
128
128
  ) -> "AlphaOver":
@@ -138,8 +138,8 @@ class AlphaOver(BaseNode):
138
138
  @classmethod
139
139
  def conjoint_over(
140
140
  cls,
141
- background: InputColor = None,
142
- foreground: InputColor = None,
141
+ background: InputColor = (1.0, 1.0, 1.0, 1.0),
142
+ foreground: InputColor = (1.0, 1.0, 1.0, 1.0),
143
143
  fac: InputFloat = 1.0,
144
144
  straight_alpha: InputBoolean = False,
145
145
  ) -> "AlphaOver":
@@ -205,7 +205,7 @@ class BrightnessContrast(BaseNode):
205
205
 
206
206
  def __init__(
207
207
  self,
208
- image: InputColor = None,
208
+ image: InputColor = (1.0, 1.0, 1.0, 1.0),
209
209
  bright: InputFloat = 0.0,
210
210
  contrast: InputFloat = 0.0,
211
211
  ):
@@ -363,24 +363,24 @@ class ColorBalance(BaseNode):
363
363
 
364
364
  def __init__(
365
365
  self,
366
- image: InputColor = None,
366
+ image: InputColor = (1.0, 1.0, 1.0, 1.0),
367
367
  fac: InputFloat = 1.0,
368
368
  type: InputMenu
369
369
  | Literal[
370
370
  "Lift/Gamma/Gain", "Offset/Power/Slope (ASC-CDL)", "White Point"
371
371
  ] = "Lift/Gamma/Gain",
372
372
  base_lift: InputFloat = 0.0,
373
- color_lift: InputColor = None,
373
+ color_lift: InputColor = (1.0, 1.0, 1.0, 1.0),
374
374
  base_gamma: InputFloat = 1.0,
375
- color_gamma: InputColor = None,
375
+ color_gamma: InputColor = (1.0, 1.0, 1.0, 1.0),
376
376
  base_gain: InputFloat = 1.0,
377
- color_gain: InputColor = None,
377
+ color_gain: InputColor = (1.0, 1.0, 1.0, 1.0),
378
378
  base_offset: InputFloat = 0.0,
379
- color_offset: InputColor = None,
379
+ color_offset: InputColor = (0.0, 0.0, 0.0, 1.0),
380
380
  base_power: InputFloat = 1.0,
381
- color_power: InputColor = None,
381
+ color_power: InputColor = (1.0, 1.0, 1.0, 1.0),
382
382
  base_slope: InputFloat = 1.0,
383
- color_slope: InputColor = None,
383
+ color_slope: InputColor = (1.0, 1.0, 1.0, 1.0),
384
384
  input_temperature: InputFloat = 6500.0,
385
385
  input_tint: InputFloat = 10.0,
386
386
  output_temperature: InputFloat = 6500.0,
@@ -418,14 +418,14 @@ class ColorBalance(BaseNode):
418
418
  @classmethod
419
419
  def lift_gamma_gain(
420
420
  cls,
421
- image: InputColor = None,
421
+ image: InputColor = (1.0, 1.0, 1.0, 1.0),
422
422
  fac: InputFloat = 1.0,
423
423
  base_lift: InputFloat = 0.0,
424
- color_lift: InputColor = None,
424
+ color_lift: InputColor = (1.0, 1.0, 1.0, 1.0),
425
425
  base_gamma: InputFloat = 1.0,
426
- color_gamma: InputColor = None,
426
+ color_gamma: InputColor = (1.0, 1.0, 1.0, 1.0),
427
427
  base_gain: InputFloat = 1.0,
428
- color_gain: InputColor = None,
428
+ color_gain: InputColor = (1.0, 1.0, 1.0, 1.0),
429
429
  ) -> "ColorBalance":
430
430
  """Create Color Balance node with type 'Lift/Gamma/Gain'."""
431
431
  return cls(
@@ -443,14 +443,14 @@ class ColorBalance(BaseNode):
443
443
  @classmethod
444
444
  def offset_power_slope_asc_cdl(
445
445
  cls,
446
- image: InputColor = None,
446
+ image: InputColor = (1.0, 1.0, 1.0, 1.0),
447
447
  fac: InputFloat = 1.0,
448
448
  base_offset: InputFloat = 0.0,
449
- color_offset: InputColor = None,
449
+ color_offset: InputColor = (0.0, 0.0, 0.0, 1.0),
450
450
  base_power: InputFloat = 1.0,
451
- color_power: InputColor = None,
451
+ color_power: InputColor = (1.0, 1.0, 1.0, 1.0),
452
452
  base_slope: InputFloat = 1.0,
453
- color_slope: InputColor = None,
453
+ color_slope: InputColor = (1.0, 1.0, 1.0, 1.0),
454
454
  ) -> "ColorBalance":
455
455
  """Create Color Balance node with type 'Offset/Power/Slope (ASC-CDL)'."""
456
456
  return cls(
@@ -468,7 +468,7 @@ class ColorBalance(BaseNode):
468
468
  @classmethod
469
469
  def white_point(
470
470
  cls,
471
- image: InputColor = None,
471
+ image: InputColor = (1.0, 1.0, 1.0, 1.0),
472
472
  fac: InputFloat = 1.0,
473
473
  input_temperature: InputFloat = 6500.0,
474
474
  input_tint: InputFloat = 10.0,
@@ -699,7 +699,7 @@ class ColorCorrection(BaseNode):
699
699
 
700
700
  def __init__(
701
701
  self,
702
- image: InputColor = None,
702
+ image: InputColor = (1.0, 1.0, 1.0, 1.0),
703
703
  mask: InputFloat = 1.0,
704
704
  master_saturation: InputFloat = 1.0,
705
705
  master_contrast: InputFloat = 1.0,
@@ -835,9 +835,9 @@ class DepthCombine(BaseNode):
835
835
 
836
836
  def __init__(
837
837
  self,
838
- a: InputColor = None,
838
+ a: InputColor = (1.0, 1.0, 1.0, 1.0),
839
839
  depth_a: InputFloat = 1.0,
840
- b: InputColor = None,
840
+ b: InputColor = (1.0, 1.0, 1.0, 1.0),
841
841
  depth_b: InputFloat = 1.0,
842
842
  use_alpha: InputBoolean = False,
843
843
  anti_alias: InputBoolean = True,
@@ -901,7 +901,7 @@ class Exposure(BaseNode):
901
901
 
902
902
  def __init__(
903
903
  self,
904
- image: InputColor = None,
904
+ image: InputColor = (1.0, 1.0, 1.0, 1.0),
905
905
  exposure: InputFloat = 0.0,
906
906
  ):
907
907
  super().__init__()
@@ -956,7 +956,7 @@ class HueCorrect(BaseNode):
956
956
 
957
957
  def __init__(
958
958
  self,
959
- image: InputColor = None,
959
+ image: InputColor = (1.0, 1.0, 1.0, 1.0),
960
960
  fac: InputFloat = 1.0,
961
961
  ):
962
962
  super().__init__()
@@ -1029,7 +1029,7 @@ class HueSaturationValue(BaseNode):
1029
1029
 
1030
1030
  def __init__(
1031
1031
  self,
1032
- image: InputColor = None,
1032
+ image: InputColor = (1.0, 1.0, 1.0, 1.0),
1033
1033
  hue: InputFloat = 0.5,
1034
1034
  saturation: InputFloat = 1.0,
1035
1035
  value: InputFloat = 1.0,
@@ -1105,7 +1105,7 @@ class InvertColor(BaseNode):
1105
1105
 
1106
1106
  def __init__(
1107
1107
  self,
1108
- color: InputColor = None,
1108
+ color: InputColor = (1.0, 1.0, 1.0, 1.0),
1109
1109
  fac: InputFloat = 1.0,
1110
1110
  invert_color: InputBoolean = True,
1111
1111
  invert_alpha: InputBoolean = False,
@@ -1167,7 +1167,7 @@ class Posterize(BaseNode):
1167
1167
 
1168
1168
  def __init__(
1169
1169
  self,
1170
- image: InputColor = None,
1170
+ image: InputColor = (1.0, 1.0, 1.0, 1.0),
1171
1171
  steps: InputFloat = 8.0,
1172
1172
  ):
1173
1173
  super().__init__()
@@ -1234,10 +1234,10 @@ class RGBCurves(BaseNode):
1234
1234
 
1235
1235
  def __init__(
1236
1236
  self,
1237
- image: InputColor = None,
1237
+ image: InputColor = (1.0, 1.0, 1.0, 1.0),
1238
1238
  fac: InputFloat = 1.0,
1239
- black_level: InputColor = None,
1240
- white_level: InputColor = None,
1239
+ black_level: InputColor = (0.0, 0.0, 0.0, 1.0),
1240
+ white_level: InputColor = (1.0, 1.0, 1.0, 1.0),
1241
1241
  ):
1242
1242
  super().__init__()
1243
1243
  key_args = {
@@ -1338,7 +1338,7 @@ class Tonemap(BaseNode):
1338
1338
 
1339
1339
  def __init__(
1340
1340
  self,
1341
- image: InputColor = None,
1341
+ image: InputColor = (1.0, 1.0, 1.0, 1.0),
1342
1342
  type: InputMenu
1343
1343
  | Literal["R/D Photoreceptor", "Rh Simple"] = "R/D Photoreceptor",
1344
1344
  key: InputFloat = 0.18,
@@ -1367,7 +1367,7 @@ class Tonemap(BaseNode):
1367
1367
  @classmethod
1368
1368
  def r_d_photoreceptor(
1369
1369
  cls,
1370
- image: InputColor = None,
1370
+ image: InputColor = (1.0, 1.0, 1.0, 1.0),
1371
1371
  intensity: InputFloat = 0.0,
1372
1372
  contrast: InputFloat = 0.0,
1373
1373
  light_adaptation: InputFloat = 0.0,
@@ -1386,7 +1386,7 @@ class Tonemap(BaseNode):
1386
1386
  @classmethod
1387
1387
  def rh_simple(
1388
1388
  cls,
1389
- image: InputColor = None,
1389
+ image: InputColor = (1.0, 1.0, 1.0, 1.0),
1390
1390
  key: InputFloat = 0.18,
1391
1391
  balance: InputFloat = 1.0,
1392
1392
  gamma: InputFloat = 1.0,