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.
- {nodebpy-520.28.0 → nodebpy-520.29.0}/PKG-INFO +1 -1
- {nodebpy-520.28.0 → nodebpy-520.29.0}/pyproject.toml +1 -1
- nodebpy-520.29.0/src/nodebpy/export/_floats.py +114 -0
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/export/codegen.py +8 -106
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/compositor/color.py +39 -39
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/compositor/converter.py +51 -42
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/compositor/distort.py +27 -27
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/compositor/filter.py +81 -67
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/compositor/input.py +6 -5
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/compositor/interface.py +4 -4
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/compositor/matte.py +21 -20
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/compositor/output.py +1 -1
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/geometry/assets.py +9 -8
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/geometry/attribute.py +2 -2
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/geometry/color.py +2 -2
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/geometry/converter.py +75 -66
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/geometry/geometry.py +157 -146
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/geometry/grid.py +14 -14
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/geometry/input.py +1 -1
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/geometry/interface.py +9 -9
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/geometry/texture.py +38 -35
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/geometry/utilities.py +1 -1
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/geometry/vector.py +88 -58
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/shader/color.py +3 -3
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/shader/converter.py +25 -17
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/shader/grid.py +9 -9
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/shader/input.py +7 -7
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/shader/output.py +3 -3
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/shader/shader.py +58 -58
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/shader/texture.py +11 -10
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/shader/vector.py +27 -27
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/types.py +8 -1
- {nodebpy-520.28.0 → nodebpy-520.29.0}/README.md +0 -0
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/__init__.py +0 -0
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/assets/__init__.py +0 -0
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/assets/__main__.py +0 -0
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/assets/_codegen.py +0 -0
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/assets/_library.py +0 -0
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/assets/_pipeline.py +0 -0
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/builder/__init__.py +0 -0
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/builder/_registry.py +0 -0
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/builder/_socket_order.py +0 -0
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/builder/_utils.py +0 -0
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/builder/accessor.py +0 -0
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/builder/asset.py +0 -0
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/builder/items.py +0 -0
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/builder/layout.py +0 -0
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/builder/mixins.py +0 -0
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/builder/node.py +0 -0
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/builder/socket.py +0 -0
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/builder/tree.py +0 -0
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/export/__init__.py +0 -0
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/export/diagram.py +0 -0
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/export/parity.py +0 -0
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/export/plot.py +0 -0
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/export/web_render.py +0 -0
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/lib/nodearrange/VENDORED.md +0 -0
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/lib/nodearrange/__init__.py +0 -0
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/lib/nodearrange/arrange/__init__.py +0 -0
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/lib/nodearrange/arrange/balancing.py +0 -0
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/lib/nodearrange/arrange/graph.py +0 -0
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/lib/nodearrange/arrange/ordering.py +0 -0
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/lib/nodearrange/arrange/ranking.py +0 -0
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/lib/nodearrange/arrange/realize.py +0 -0
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/lib/nodearrange/arrange/stacking.py +0 -0
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/lib/nodearrange/arrange/structs.py +0 -0
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/lib/nodearrange/arrange/sugiyama.py +0 -0
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/lib/nodearrange/arrange/x_coords.py +0 -0
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/lib/nodearrange/arrange/y_coords.py +0 -0
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/lib/nodearrange/config.py +0 -0
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/lib/nodearrange/utils.py +0 -0
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/live.py +0 -0
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/__init__.py +0 -0
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/_mixins.py +0 -0
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/compositor/__init__.py +0 -0
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/compositor/assets.py +0 -0
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/compositor/group.py +0 -0
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/compositor/manual.py +0 -0
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/compositor/vector.py +0 -0
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/geometry/__init__.py +0 -0
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/geometry/group.py +0 -0
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/geometry/groups.py +0 -0
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/geometry/manual.py +0 -0
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/geometry/output.py +0 -0
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/geometry/zone.py +0 -0
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/shader/__init__.py +0 -0
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/shader/assets.py +0 -0
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/shader/group.py +0 -0
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/shader/manual.py +0 -0
- {nodebpy-520.28.0 → nodebpy-520.29.0}/src/nodebpy/nodes/shader/script.py +0 -0
|
@@ -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
|
-
|
|
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 =
|
|
88
|
-
foreground: InputColor =
|
|
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 =
|
|
108
|
-
foreground: InputColor =
|
|
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 =
|
|
125
|
-
foreground: InputColor =
|
|
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 =
|
|
142
|
-
foreground: InputColor =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
373
|
+
color_lift: InputColor = (1.0, 1.0, 1.0, 1.0),
|
|
374
374
|
base_gamma: InputFloat = 1.0,
|
|
375
|
-
color_gamma: InputColor =
|
|
375
|
+
color_gamma: InputColor = (1.0, 1.0, 1.0, 1.0),
|
|
376
376
|
base_gain: InputFloat = 1.0,
|
|
377
|
-
color_gain: InputColor =
|
|
377
|
+
color_gain: InputColor = (1.0, 1.0, 1.0, 1.0),
|
|
378
378
|
base_offset: InputFloat = 0.0,
|
|
379
|
-
color_offset: InputColor =
|
|
379
|
+
color_offset: InputColor = (0.0, 0.0, 0.0, 1.0),
|
|
380
380
|
base_power: InputFloat = 1.0,
|
|
381
|
-
color_power: InputColor =
|
|
381
|
+
color_power: InputColor = (1.0, 1.0, 1.0, 1.0),
|
|
382
382
|
base_slope: InputFloat = 1.0,
|
|
383
|
-
color_slope: InputColor =
|
|
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 =
|
|
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 =
|
|
424
|
+
color_lift: InputColor = (1.0, 1.0, 1.0, 1.0),
|
|
425
425
|
base_gamma: InputFloat = 1.0,
|
|
426
|
-
color_gamma: InputColor =
|
|
426
|
+
color_gamma: InputColor = (1.0, 1.0, 1.0, 1.0),
|
|
427
427
|
base_gain: InputFloat = 1.0,
|
|
428
|
-
color_gain: InputColor =
|
|
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 =
|
|
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 =
|
|
449
|
+
color_offset: InputColor = (0.0, 0.0, 0.0, 1.0),
|
|
450
450
|
base_power: InputFloat = 1.0,
|
|
451
|
-
color_power: InputColor =
|
|
451
|
+
color_power: InputColor = (1.0, 1.0, 1.0, 1.0),
|
|
452
452
|
base_slope: InputFloat = 1.0,
|
|
453
|
-
color_slope: InputColor =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
838
|
+
a: InputColor = (1.0, 1.0, 1.0, 1.0),
|
|
839
839
|
depth_a: InputFloat = 1.0,
|
|
840
|
-
b: InputColor =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
1237
|
+
image: InputColor = (1.0, 1.0, 1.0, 1.0),
|
|
1238
1238
|
fac: InputFloat = 1.0,
|
|
1239
|
-
black_level: InputColor =
|
|
1240
|
-
white_level: InputColor =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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,
|