From e7957c75d0e9639a8953ea1f5a3c1604a91c6071 Mon Sep 17 00:00:00 2001 From: Carson Katri Date: Sun, 12 Feb 2023 11:43:16 -0500 Subject: [PATCH] Add curve support --- api/node_mapper.py | 8 +++ api/static/curve.py | 54 ++++++++++++++++++ api/tree.py | 1 + book/src/SUMMARY.md | 1 + book/src/api/advanced-scripting/curves.md | 32 +++++++++++ .../api/advanced-scripting/float_curve.png | Bin 0 -> 36448 bytes 6 files changed, 96 insertions(+) create mode 100644 api/static/curve.py create mode 100644 book/src/api/advanced-scripting/curves.md create mode 100644 book/src/api/advanced-scripting/float_curve.png diff --git a/api/node_mapper.py b/api/node_mapper.py index 876540b..736bc5c 100644 --- a/api/node_mapper.py +++ b/api/node_mapper.py @@ -6,6 +6,7 @@ import re from .state import State from .types import * from .static.input_group import InputGroup +from .static.curve import Curve from ..absolute_path import absolute_path class OutputsList(dict): @@ -28,6 +29,13 @@ def build_node(node_type): argname = prop.identifier.lower().replace(' ', '_') if argname in kwargs: value = kwargs[argname] + if isinstance(value, list) and len(value) > 0 and isinstance(value[0], Curve): + for i, curve in enumerate(value): + curve.apply(getattr(node, prop.identifier).curves[i]) + continue + if isinstance(value, Curve): + value.apply(getattr(node, prop.identifier).curves[0]) + continue if isinstance(value, enum.Enum): value = value.value setattr(node, prop.identifier, value) diff --git a/api/static/curve.py b/api/static/curve.py new file mode 100644 index 0000000..e53a282 --- /dev/null +++ b/api/static/curve.py @@ -0,0 +1,54 @@ +from typing import List +import enum + +class HandleType(enum.Enum): + AUTO = 'AUTO' + VECTOR = 'VECTOR' + AUTO_CLAMPED = 'AUTO_CLAMPED' + +class Point: + """ + A single point on a curve + """ + + x: float + y: float + handle_type: HandleType + + def __init__(self, x: float, y: float, handle_type: HandleType = HandleType.AUTO): + self.x = x + self.y = y + self.handle_type = handle_type + +class Curve: + """ + A class that represents a curve. + + Create a curve from a set of `Point`s. + ```python + my_curve = Curve( + Point(0, 0, Handle.AUTO_CLAMPED), + Point(0.2, 0.3, Handle.AUTO), + Point(1, 1, Handle.VECTOR) + ) + ``` + """ + + points: List[Point] + + def __init__(self, *points: Point): + if len(points) == 1 and isinstance(points[0], list): + self.points = points[0] + else: + self.points = list(points) + + def apply(self, curve): + """ + Apply the points to a curve object. + """ + for i, point in enumerate(self.points): + if len(curve.points) > i: + curve.points[i].location = (point.x, point.y) + curve.points[i].handle_type = point.handle_type.value + else: + curve.points.new(point.x, point.y).handle_type = point.handle_type.value \ No newline at end of file diff --git a/api/tree.py b/api/tree.py index a85e80d..e732b53 100644 --- a/api/tree.py +++ b/api/tree.py @@ -8,6 +8,7 @@ from .state import State from .types import * from .node_mapper import * from .static.attribute import * +from .static.curve import * from .static.expression import * from .static.input_group import * from .static.sample_mode import * diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index 64be5ea..45b7f80 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -21,6 +21,7 @@ - [Input Groups](./api/advanced-scripting/input-groups.md) - [Attributes](./api/advanced-scripting/attributes.md) - [Boolean Math](./api/advanced-scripting/boolean-math.md) + - [Curves](./api/advanced-scripting/curves.md) - [Drivers](./api/advanced-scripting/drivers.md) - [Simulation](./api/advanced-scripting/simulation.md) diff --git a/book/src/api/advanced-scripting/curves.md b/book/src/api/advanced-scripting/curves.md new file mode 100644 index 0000000..3a007c3 --- /dev/null +++ b/book/src/api/advanced-scripting/curves.md @@ -0,0 +1,32 @@ +# Curves + +Some nodes, such as *Float Curve* take a curve as a property. You can create a curve with the `Curve` class. + +```python +float_curve( + mapping=Curve( + Point(0, 0), + Point(0.5, 0.25), + Point(1, 1, HandleType.VECTOR), # Optionally specify a handle type, such as `AUTO`, `VECTOR`, or `AUTO_CLAMPED`. + ) +) +``` + +![](./float_curve.png) + +You can also pass the points as a list to `Curve`. + +```python +points = [Point(0, 0), Point(1, 1)] +float_curve( + mapping=Curve(points) +) +``` + +If a node has multiple curve properties, such as the *Vector Curves* node, pass a list of curves to the node. + +```python +vector_curves( + mapping=[x_curve, y_curve, z_curve] +) +``` \ No newline at end of file diff --git a/book/src/api/advanced-scripting/float_curve.png b/book/src/api/advanced-scripting/float_curve.png new file mode 100644 index 0000000000000000000000000000000000000000..53bb1c9c1b5be91fa241ffc3eeecdeece8ad172a GIT binary patch literal 36448 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