From 55c13e23f97c5641e1ec6154569589ddf9265fae Mon Sep 17 00:00:00 2001 From: keithlostracco Date: Sat, 25 Jul 2026 20:27:15 -0700 Subject: [PATCH 01/18] chore: prepare build for public release Update the anim submodule to v0.1.2 and adopt the TouchDesigner headers that expose per-node save/load of arbitrary byte data. - Bump ext/anim 61e6168 -> v0.1.2. The previous pin was the pre-squash form of the same work and is not reachable from anim's main, so a recursive clone would have failed once the prep branch is deleted. v0.1.2 also carries two fixes: an out-of-bounds iterator dereference in Channel::evaluate() at the last keyframe, and insert_keyframe() falling through after an in-place replace and inserting a moved-from keyframe. - Replace ext/td/include with the headers adding saveData()/loadData(). CHOP_PluginInfo::apiVersion is now private, so both FillCHOPPluginInfo functions call setAPIVersion() and return early when the running build does not support the version. This raises the minimum TouchDesigner version, as the new virtuals change the vtable layout. - Stop tracking td/Plugins. The binaries are published as release assets, and Plugins.json is a per-machine TouchDesigner trust file holding a host name and absolute paths. --- .gitignore | 5 + ext/anim | 2 +- ext/td/include/CHOP_CPlusPlusBase.h | 97 ++- ext/td/include/CPlusPlus_Common.h | 1095 +++++++++++++++++++++++++-- src/animation_chop.cpp | 7 +- src/animation_view_chop.cpp | 7 +- td/Plugins/AnimationCHOP.dll | Bin 310784 -> 0 bytes td/Plugins/AnimationViewCHOP.dll | Bin 93696 -> 0 bytes td/Plugins/Plugins.json | 41 - 9 files changed, 1118 insertions(+), 136 deletions(-) delete mode 100644 td/Plugins/AnimationCHOP.dll delete mode 100644 td/Plugins/AnimationViewCHOP.dll delete mode 100644 td/Plugins/Plugins.json diff --git a/.gitignore b/.gitignore index c173329..ce0f5f7 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,8 @@ build *.ilk *.dmp .DS_Store + +# Local plugin build output. CMake copies the built libraries here post-build, +# and TouchDesigner writes a per-machine Plugins.json trust file alongside them. +# Released binaries are published as release assets instead. +td/Plugins/ diff --git a/ext/anim b/ext/anim index 61e6168..bd5f9f9 160000 --- a/ext/anim +++ b/ext/anim @@ -1 +1 @@ -Subproject commit 61e616845df8142c399a9db8984cb36a77ddeebf +Subproject commit bd5f9f9327746238ee5d355b714b525b912463c9 diff --git a/ext/td/include/CHOP_CPlusPlusBase.h b/ext/td/include/CHOP_CPlusPlusBase.h index 02c71d5..7ad116c 100644 --- a/ext/td/include/CHOP_CPlusPlusBase.h +++ b/ext/td/include/CHOP_CPlusPlusBase.h @@ -31,14 +31,14 @@ * Derivative Developers:: Make sure the virtual function order * stays the same, otherwise changes won't be backwards compatible */ -#pragma warning(push) -#pragma warning(disable : 4100) #ifndef __CHOP_CPlusPlusBase__ #define __CHOP_CPlusPlusBase__ #include "CPlusPlus_Common.h" +class CHOP_CPlusPlus; + namespace TD { #pragma pack(push, 8) @@ -52,22 +52,65 @@ class CHOP_CPlusPlusBase; // from the samples folder in a newer TouchDesigner installation. // You may need to upgrade your plugin code in that case, to match // the new API requirements -const int CHOPCPlusPlusAPIVersion = 9; +const int CHOPCPlusPlusAPIVersion = 10 | (OP_CommonAPIVersion << 16); class CHOP_PluginInfo { -public: - // Must be set to CHOPCPlusPlusAPIVersion in FillCHOPPluginInfo +private: + // Set it by calling setAPIVersion() int32_t apiVersion = 0; +public: + + // Returns false if the API version is not supported + [[nodiscard]] + int32_t + getAPIVersion() const + { + return apiVersion; + } + + // Should be called with a value of CHOPCPlusPlusAPIVersion + [[nodiscard]] + bool + setAPIVersion(int32_t version) + { + apiVersion = version; + if (!isAPIVersionSupported(version)) + return false; + + return true; + } + [[nodiscard]] + bool + isAPIVersionSupported(int32_t version) + { + return checkAPIVersionSupported(version, MinAPIVersion, MaxAPIVersion); + } int32_t reserved[100]; // Information used to describe this plugin as a custom OP. OP_CustomOPInfo customOPInfo; - int32_t reserved2[20]; + int32_t reserved2[18]; + + static constexpr bool checkPrivateOffsets(); + +private: + // Will be set by the caller of FillTOPPluginInfo() + const int32_t MinAPIVersion = 0; + const int32_t MaxAPIVersion = 0; + friend class ::CHOP_CPlusPlus; }; +constexpr bool +CHOP_PluginInfo::checkPrivateOffsets() +{ + return offsetof(CHOP_PluginInfo, apiVersion) == 0 && + offsetof(CHOP_PluginInfo, MinAPIVersion) == 408 + sizeof(customOPInfo) + 18 * 4 && + offsetof(CHOP_PluginInfo, MaxAPIVersion) == 408 + sizeof(customOPInfo) + 18 * 4 + 4; +} + class CHOP_GeneralInfo { public: @@ -133,7 +176,7 @@ class CHOP_OutputInfo uint32_t startIndex; // Specify the sample rate of the channel data - // DEFAULT : whatever the timeline FPS is ($FPS) + // DEFAULT : whatever the component timeline FPS is. (me.time.rate) float sampleRate; void* reserved1; @@ -178,8 +221,9 @@ class CHOP_Output /***** FUNCTION CALL ORDER DURING INITIALIZATION ******/ /* - When the TOP loads the dll the functions will be called in this order + When the CHOP loads the dll the functions will be called in this order + loadData(const OP_NodeSaveState* saver); setupParameters(OP_ParameterManager* m); */ @@ -347,21 +391,44 @@ class CHOP_CPlusPlusBase } // This is called whenever a dynamic menu type custom parameter needs to have it's content's - // updated. It may happen often, so this could should be efficient. + // updated. It may happen often, so this call should be efficient. virtual void buildDynamicMenu(const OP_Inputs* inputs, OP_BuildDynamicMenuInfo* info, void* reserved1) { } + // Override this method if you want to save arbitrary bytedata with this operator into the toe file. + // `OP_NodeSaveState* saver` has methods like `saveEntry()` that are used to add key, value pairs to be saved. + // This is called whenever the project file is saved or the custom operator is unloaded. + // Usage example in Samples/CPlusPlus/CHOP sample project. + virtual void + saveData(OP_NodeSaveState* saver, void* reserved1) + { + } + + // Override this method if you want to load the bytedata that was saved into the toe file through `saveData()`. + // `OP_NodeLoadState* loader` has methods like `getKey()`, `getKeyCount()`, `loadEntry()` that are used to retrieve key, value pairs. + // This is called during startup of the project file, or whenever the custom operator is loaded/reloaded. + // Usage example in Samples/CPlusPlus/CHOP sample project. + virtual void + loadData(const OP_NodeLoadState* loader, void* reserved1) + { + } + + // Override this method if you want to specify a descriptor string when hovering over the input connectors of the node. + // `inputLabel->label->setString()` sets the label for the input index. + // Usage example in Samples/CPlusPlus/CHOP sample project. + virtual void + inputConnectorLabel(int index, OP_InputLabel* inputLabel, void* reserved1) + { + } + // END PUBLIC INTERFACE private: // Reserved for future features - virtual int32_t reservedFunc6() { return 0; } - virtual int32_t reservedFunc7() { return 0; } - virtual int32_t reservedFunc8() { return 0; } virtual int32_t reservedFunc9() { return 0; } virtual int32_t reservedFunc10() { return 0; } virtual int32_t reservedFunc11() { return 0; } @@ -381,7 +448,7 @@ class CHOP_CPlusPlusBase #pragma pack(pop) -static_assert(offsetof(CHOP_PluginInfo, apiVersion) == 0, "Incorrect Alignment"); +static_assert(CHOP_PluginInfo::checkPrivateOffsets(), "Incorrect Alignment"); static_assert(offsetof(CHOP_PluginInfo, customOPInfo) == 408, "Incorrect Alignment"); static_assert(sizeof(CHOP_PluginInfo) == 944, "Incorrect Size"); @@ -405,8 +472,6 @@ static_assert(offsetof(CHOP_Output, startIndex) == 12, "Incorrect Alignment"); static_assert(offsetof(CHOP_Output, names) == 16, "Incorrect Alignment"); static_assert(offsetof(CHOP_Output, channels) == 24, "Incorrect Alignment"); static_assert(sizeof(CHOP_Output) == 112, "Incorrect Size"); -#endif }; // namespace TD - -#pragma warning(pop) \ No newline at end of file +#endif diff --git a/ext/td/include/CPlusPlus_Common.h b/ext/td/include/CPlusPlus_Common.h index 2269952..0c9b0d1 100644 --- a/ext/td/include/CPlusPlus_Common.h +++ b/ext/td/include/CPlusPlus_Common.h @@ -16,8 +16,7 @@ Derivative Developers: Make sure the virtual function order stays the same, otherwise changes won't be backwards compatible ********/ -#pragma warning(push) -#pragma warning(disable : 4100) + #ifndef __CPlusPlus_Common__ #define __CPlusPlus_Common__ @@ -51,10 +50,41 @@ struct CUstream_st; typedef struct CUstream_st* cudaStream_t; class TOP_CPlusPlus; +class POP_CPlusPlus; namespace TD { +const int OP_CommonAPIVersion = 2; + +inline int32_t +extractFamilyAPIVersion(int32_t version) +{ + return version & 0xFFFF; +} + +inline int32_t +extractCommonAPIVersion(int32_t version) +{ + return (version >> 16) & 0xFFFF; +} + +inline bool +checkAPIVersionSupported(int32_t version, int32_t minVersion, int32_t maxVersion) +{ + if (extractFamilyAPIVersion(version) < extractFamilyAPIVersion(minVersion) || + extractFamilyAPIVersion(version) > extractFamilyAPIVersion(maxVersion)) + { + return false; + } + if (extractCommonAPIVersion(version) < extractCommonAPIVersion(minVersion) || + extractCommonAPIVersion(version) > extractCommonAPIVersion(maxVersion)) + { + return false; + } + return true; +} + class CHOP_PluginInfo; class CHOP_CPlusPlusBase; class DAT_PluginInfo; @@ -62,8 +92,11 @@ class DAT_CPlusPlusBase; class TOP_PluginInfo; class TOP_CPlusPlusBase; class TOP_Context; +class POP_Context; class SOP_PluginInfo; class SOP_CPlusPlusBase; +class POP_PluginInfo; +class POP_CPlusPlusBase; #pragma pack(push, 8) @@ -102,16 +135,155 @@ enum class OP_PixelFormat : int32_t MonoA16Float, MonoA32Float, - // sRGB. use SBGRA if possible since that's what most GPUs use - SBGRA8Fixed = 600, - SRGBA8Fixed, + // Previously sRGB textures, but not supported this way anymore. Use the + // OP_ColorSpace workflow instead. + UnusedReserved1 = 600, + UnusedReserved2 = 601, RGB10A2Fixed = 700, // 11-bit float, positive values only. B is actually 10 bits RGB11Float, +}; +inline bool +isFloatFormat(OP_PixelFormat f) +{ + switch (f) + { + default: + case OP_PixelFormat::Invalid: + case OP_PixelFormat::BGRA8Fixed: + case OP_PixelFormat::RGBA8Fixed: + case OP_PixelFormat::RGBA16Fixed: + case OP_PixelFormat::Mono8Fixed: + case OP_PixelFormat::Mono16Fixed: + case OP_PixelFormat::RG8Fixed: + case OP_PixelFormat::RG16Fixed: + case OP_PixelFormat::A8Fixed: + case OP_PixelFormat::A16Fixed: + case OP_PixelFormat::MonoA8Fixed: + case OP_PixelFormat::MonoA16Fixed: + case OP_PixelFormat::RGB10A2Fixed: + return false; + case OP_PixelFormat::Mono16Float: + case OP_PixelFormat::Mono32Float: + case OP_PixelFormat::RG16Float: + case OP_PixelFormat::RG32Float: + case OP_PixelFormat::A16Float: + case OP_PixelFormat::A32Float: + case OP_PixelFormat::MonoA16Float: + case OP_PixelFormat::MonoA32Float: + case OP_PixelFormat::RGBA16Float: + case OP_PixelFormat::RGBA32Float: + case OP_PixelFormat::RGB11Float: + return true; + } +} -}; +inline bool +isBGRFormat(OP_PixelFormat f) +{ + switch (f) + { + default: + case OP_PixelFormat::Invalid: + case OP_PixelFormat::RGBA8Fixed: + case OP_PixelFormat::RGBA16Fixed: + case OP_PixelFormat::Mono8Fixed: + case OP_PixelFormat::Mono16Fixed: + case OP_PixelFormat::RG8Fixed: + case OP_PixelFormat::RG16Fixed: + case OP_PixelFormat::A8Fixed: + case OP_PixelFormat::A16Fixed: + case OP_PixelFormat::MonoA8Fixed: + case OP_PixelFormat::MonoA16Fixed: + case OP_PixelFormat::RGB10A2Fixed: + case OP_PixelFormat::Mono16Float: + case OP_PixelFormat::Mono32Float: + case OP_PixelFormat::RG16Float: + case OP_PixelFormat::RG32Float: + case OP_PixelFormat::A16Float: + case OP_PixelFormat::A32Float: + case OP_PixelFormat::MonoA16Float: + case OP_PixelFormat::MonoA32Float: + case OP_PixelFormat::RGBA16Float: + case OP_PixelFormat::RGBA32Float: + case OP_PixelFormat::RGB11Float: + return false; + case OP_PixelFormat::BGRA8Fixed: + return true; + } +} + +inline bool +isMonoAlphaFormat(OP_PixelFormat f) +{ + switch (f) + { + default: + case OP_PixelFormat::Invalid: + case OP_PixelFormat::RGBA8Fixed: + case OP_PixelFormat::RGBA16Fixed: + case OP_PixelFormat::Mono8Fixed: + case OP_PixelFormat::Mono16Fixed: + case OP_PixelFormat::RG8Fixed: + case OP_PixelFormat::RG16Fixed: + case OP_PixelFormat::A8Fixed: + case OP_PixelFormat::A16Fixed: + case OP_PixelFormat::RGB10A2Fixed: + case OP_PixelFormat::Mono16Float: + case OP_PixelFormat::Mono32Float: + case OP_PixelFormat::RG16Float: + case OP_PixelFormat::RG32Float: + case OP_PixelFormat::A16Float: + case OP_PixelFormat::A32Float: + case OP_PixelFormat::RGBA16Float: + case OP_PixelFormat::RGBA32Float: + case OP_PixelFormat::RGB11Float: + case OP_PixelFormat::BGRA8Fixed: + return false; + case OP_PixelFormat::MonoA8Fixed: + case OP_PixelFormat::MonoA16Fixed: + case OP_PixelFormat::MonoA16Float: + case OP_PixelFormat::MonoA32Float: + return true; + } +} + +inline bool +isAlphaFormat(OP_PixelFormat f) +{ + switch (f) + { + default: + case OP_PixelFormat::Invalid: + case OP_PixelFormat::RGBA8Fixed: + case OP_PixelFormat::RGBA16Fixed: + case OP_PixelFormat::Mono8Fixed: + case OP_PixelFormat::Mono16Fixed: + case OP_PixelFormat::RG8Fixed: + case OP_PixelFormat::RG16Fixed: + case OP_PixelFormat::RGB10A2Fixed: + case OP_PixelFormat::Mono16Float: + case OP_PixelFormat::Mono32Float: + case OP_PixelFormat::RG16Float: + case OP_PixelFormat::RG32Float: + case OP_PixelFormat::RGBA16Float: + case OP_PixelFormat::RGBA32Float: + case OP_PixelFormat::RGB11Float: + case OP_PixelFormat::BGRA8Fixed: + case OP_PixelFormat::MonoA8Fixed: + case OP_PixelFormat::MonoA16Fixed: + case OP_PixelFormat::MonoA16Float: + case OP_PixelFormat::MonoA32Float: + return false; + case OP_PixelFormat::A8Fixed: + case OP_PixelFormat::A16Fixed: + case OP_PixelFormat::A16Float: + case OP_PixelFormat::A32Float: + return true; + } +} typedef OP_PixelFormat OP_CPUMemPixelType; @@ -124,6 +296,56 @@ enum class OP_TexDim : int32_t eCube, }; +enum class OP_WorkingColorSpace : int32_t +{ + // There is no working color space, colors are passed around as-is without any conversion. + Passthrough, + + // All colors provided and held in textures will be in ACEScg gamut (ACES AP1) with linear transfer function. + ACEScg, + + // For the below 3 color spaces, RGBA/BGRA 8-bit textures will use a sRGB transfer to store the data, + // so accessing the data in shaders etc gives a linearlized version of it. + // Otherwise it will be linear. + // All colors provided will be in sRGB gamut with linear transfer function (sRGB for 8-bit textures). + // This is the same as Rec.709 + SRGBLinear, + // All colors provided will be in Rec.2020 gamut with linear transfer function (sRGB for 8-bit textures).. + Rec2020Linear, + // All colors provided will be in DCI-P3 gamut with linear transfer function (sRGB for 8-bit textures).. + DCIP3Linear, + + // All colors provided will be in ACES2065_1 with linear transfer function. + ACES2065_1 +}; + +inline void +getWorkingColorSpacePrimaries(OP_WorkingColorSpace wcs, + float* rx, float *ry, float* gx, float* gy, float* bx, float* by, float* wx, float* wy) +{ + switch (wcs) + { + case OP_WorkingColorSpace::SRGBLinear: + *rx = 0.64f; *ry = 0.33f; *gx = 0.3f; *gy = 0.6f; *bx = 0.15f; *by = 0.06f; *wx = 0.3127f; *wy = 0.3290f; + break; + case OP_WorkingColorSpace::Rec2020Linear: + *rx = 0.708f; *ry = 0.292f; *gx = 0.170f; *gy = 0.797f; *bx = 0.131f; *by = 0.046f; *wx = 0.3127f; *wy = 0.3290f; + break; + case OP_WorkingColorSpace::ACES2065_1: + *rx = 0.7347f; *ry = 0.2653f; *gx = 0.0f; *gy = 1.0f; *bx = 0.0001f; *by = -0.077f; *wx = 0.32168f; *wy = 0.33767f; + break; + case OP_WorkingColorSpace::ACEScg: + *rx = 0.713f; *ry = 0.293f; *gx = 0.165f; *gy = 0.830f; *bx = 0.128f; *by = 0.044f; *wx = 0.32168f; *wy = 0.33767f; + break; + case OP_WorkingColorSpace::DCIP3Linear: + *rx = 0.680f; *ry = 0.320f; *gx = 0.265f; *gy = 0.690f; *bx = 0.150f; *by = 0.060f; *wx = 0.314f; *wy = 0.351f; + break; + default: + *rx = 0.0f; *ry = 0.0f; *gx = 0.0f; *gy = 0.0f; *bx = 0.0f; *by = 0.0f; *wx = 0.0f; *wy = 0.0f; + break; + } +} + class OP_String; class OP_TOPInputOpenGL; class OP_TOPInputDownloadOptionsOpenGL; @@ -140,7 +362,7 @@ class PY_GetInfo // the node's state to be up-to-date before doing it's work. bool autoCook; - int32_t reserved[50]; + int32_t reserved[50] = {}; }; class PY_Context @@ -158,7 +380,7 @@ class PY_Context // you should call this at the end of your python code. virtual void makeNodeDirty(void* reserved = nullptr) = 0; - int32_t reserved[50]; + int32_t reserved[50] = {}; }; #define OP_STRUCT_HEADER_ENTRIES 256 @@ -206,26 +428,18 @@ template class OP_SmartRef { public: - OP_SmartRef() : myTarget(nullptr) { } - OP_SmartRef(T* t) - { - if (t) - t->acquire(); - myTarget = t; - } - OP_SmartRef(const OP_SmartRef& t) : myTarget(nullptr) { operator=(t); } - OP_SmartRef(OP_SmartRef&& t) : + OP_SmartRef(OP_SmartRef&& t) noexcept : myTarget(nullptr) { operator=(std::move(t)); @@ -236,6 +450,14 @@ class OP_SmartRef release(); } + // Takes ownership, the caller should *not* call release() on the object. + void + takeOwnership(T* t) + { + release(); + myTarget = t; + } + void operator=(const OP_SmartRef& t) { @@ -250,7 +472,7 @@ class OP_SmartRef } void - operator=(OP_SmartRef&& t) + operator=(OP_SmartRef&& t) noexcept { if (this == &t || myTarget == t.myTarget) return; @@ -286,6 +508,7 @@ class OP_SmartRef T* myTarget; friend class ::TOP_CPlusPlus; + friend class ::POP_CPlusPlus; }; // Used to describe this Plugin so it can be used as a custom OP. @@ -376,12 +599,16 @@ class OP_CustomOPInfo // then fill in the stub code for the DAT here. // This will cause a Callbacks DAT parameter to be added to the first page of // your node's parameters. - // This should be setup with empty/stub functions along with comments, + // This should be setup with empty/stub functions along with comments, // similar to the way other Callback DATs are pre-filled in other nodes in TouchDesigner. // Note: This only works when the .dll is installed as a Custom OP, not as a C++ OP. const char* pythonCallbacksDAT = nullptr; - int32_t reserved[88]; + // If you want to specify a website URL to direct to when the Operator Help button is pressed + // set this to that URL + OP_String* opHelpURL = nullptr; + + int32_t reserved[85] = {}; }; // This class is used to provide direct access to the instance of a Custom OP @@ -505,13 +732,60 @@ class OP_NodeInfo HINSTANCE processHInstance; #endif + // If the project is set to have a working color space, this will be set. + // Otherwise it will be OP_WorkingColorSpace::Passthrough. + OP_WorkingColorSpace workingColorSpace; + #ifdef _WIN32 - int32_t reserved[12]; + int32_t reserved[11] = {}; #else - int32_t reserved[14]; + int32_t reserved[13] = {}; #endif }; +class OP_Parameters +{ +public: + // Returns true on success, false if the parameter does not exist. + // Note, use getParRGB and getParRGBA for RGB and RGBA parameters. + virtual bool getParDouble(const char* name, double& v, int32_t index = 0) const = 0; + virtual bool getParDouble2(const char* name, double& v0, double& v1) const = 0; + virtual bool getParDouble3(const char* name, double& v0, double& v1, double& v2) const = 0; + virtual bool getParDouble4(const char* name, double& v0, double& v1, double& v2, double& v3) const = 0; + + // Returns true on success, false if the parameter does not exist. + virtual bool getParInt(const char* name, int32_t& v, int32_t index = 0) const = 0; + virtual bool getParInt2(const char* name, int32_t& v0, int32_t& v1) const = 0; + virtual bool getParInt3(const char* name, int32_t& v0, int32_t& v1, int32_t& v2) const = 0; + virtual bool getParInt4(const char* name, int32_t& v0, int32_t& v1, int32_t& v2, int32_t& v3) const = 0; + + // Returns the requested value + // The return value is valid until the parameters are rebuilt or it is called with the same parameter name. + // Return value usable for life of parameter + // The returned string will be in UTF-8 encoding. + // Can be used to get menu entries as well. + virtual const char* getParString(const char* name) const = 0; + + // This is similar to getParString, but will return an absolute path if it exists, with + // slash direction consistent with O/S requirements. + // To get the original parameter value, use getParString. + // Return value usable for life of parameter. + // The returned string will be in UTF-8 encoding. + virtual const char* getParFilePath(const char* name) const = 0; + + // Will take the working color space into account (if enabled), and return the values in that, + // using the Parameter Color Space parameter to interpret the values. + // That is, the values written in the parameters are treated as what is set in the 'Parameter Color Space', + // parameter, and the values you get here will be in the working color space. + // Therefore, they will always be returns as one of the spaces in OP_WorkingColorSpace, + // which are always linear transfer. + virtual bool getParRGB(const char* name, double& r, double& g, double& b) const = 0; + virtual bool getParRGBA(const char* name, double& r, double& g, double& b, double& a) const = 0; +private: + + int32_t reserved[200] = {}; +}; + class OP_DATInput { public: @@ -537,29 +811,105 @@ class OP_DATInput // The number of times this node has cooked int64_t totalCooks; - // See documentation for OPCustomOPInstance + // See comments that preceed the declaration of the OP_CustomOPInstance class + // for more information const OP_CustomOPInstance* customOP; - int32_t reserved[16]; + // This can be used to read parameters from this node. + const OP_Parameters* parms = nullptr; + +private: + + int32_t reserved[14] = {}; +}; + +enum class OP_ColorSpace : uint32_t +{ + // For DefaultForWorkingColorSpace, the data is in whatever color space the project's Working Color Space. + // The Working Color Space can be obtained from OP_NodeInfo.workingColorSpace member. + // Note the special treatment for when 4-channel 8-bit textures some color spaces though. + // + // OP_WorkingColorSpace::SRGBLinear + // OP_WorkingColorSpace::Rec2020Linear + // OP_WorkingColorSpace::DCIP3Linear + // When the working color space is one of the above, and the OP_PixelFormat is RGBA8Fixed or BGRA8Fixed, + // the data will have an sRGB transfer, to maintain the extra detail for the darker colors. + // Note that this is a non-standard transfer for DCIP3, which is usually just gamma 2.6. + // But GPUs can do sRGB natively so we use that. + // All other formats are the native gamut with a linear transfer, + // including other 8-bit formats such as RG8Fixed. + DefaultForWorkingColorSpace = 0, + + // When there is no working color space or if the color space is unknown, this will be set as the color space. + // If you have data you are providing that you don't want converted to the working color space, use this color space. + Passthrough, + + // sRGB gamut with sRGB transfer function. + SRGB, + // sRGB gamut with linear transfer function. + SRGBLinear, + // ACES AP0 gamut with linear transfer function. + ACES2065_1, + // ACES AP1 gamut with linear transfer function. + ACEScg, + // ACES AP1 gamut with a log transfer function. + ACESproxy, + // These all have non-linear transfer functions, as specified by their specs + Rec601PAL, + Rec601NTSC, + Rec709, + Rec2020, + DCIP3, + DCIP3D60, + DisplayP3D65, + Rec2020ST2084PQ, + Rec2020HLG, + // Linear transfers versions of the above + DisplayP3D65Linear, + DCIP3Linear, + Rec2020Linear, +}; + +enum class OP_ReferenceWhite : uint32_t +{ + DefaultForColorSpace = 0, + + SDR, + HDR, + UI, }; class OP_TOPInputDownloadOptions { public: - OP_TOPInputDownloadOptions() - { - verticalFlip = false; - pixelFormat = OP_PixelFormat::Invalid; - } - // Set this to true if you want the image vertically flipped in the // downloaded data - bool verticalFlip; + bool verticalFlip = false; // Set this to how you want the pixel data to be give to you in CPU memory. // Leave this as Invalid if you want to download the texture in it's GPU native format. // Only 2D textures can be converted to other formats. 3D/Cube/2DArray all must have this set as Invalid. - OP_PixelFormat pixelFormat; + OP_PixelFormat pixelFormat = OP_PixelFormat::Invalid; + + // Only has an effect when the Working Color Space is not passthrough. + // Can be set to something other than OP_ColorSpace::WorkingColorSpace + // if the data should be converted to a specific color space before it's downloaded. + OP_ColorSpace colorSpace = OP_ColorSpace::DefaultForWorkingColorSpace; + + // Only has an effect when the Working Color Space is not passthrough. + OP_ReferenceWhite referenceWhite = OP_ReferenceWhite::DefaultForColorSpace; + +private: + int32_t reserved[30] = {}; +}; + +// This is seperate from TOP_InputDownloadOptions to avoid break backwards compatibility with +class OP_TOPExtraInputDownloadOptions +{ +public: + +private: + int32_t reserved[30] = {}; }; class OP_TextureDesc @@ -572,7 +922,7 @@ class OP_TextureDesc uint32_t width = 0; uint32_t height = 0; - // Depth for 3D and 2D_ARRAY textures, 1 for other texture types + // Depth for e3D and e2DArray textures, 1 for other texture types uint32_t depth = 1; OP_TexDim texDim = OP_TexDim::eInvalid; @@ -605,12 +955,15 @@ class OP_TOPDownloadResult : public OP_RefCount // and start working on the data as soon as it's ready (such as outputting to an external device). virtual void* getData() = 0; - // The size in bytes of the data. + // The size in bytes of the data. uint64_t size = 0; OP_TextureDesc textureDesc; - int32_t reserved[32]; + // When there is a working color space, this will be set to the color space of the data. + OP_ColorSpace colorSpace; + + int32_t reserved[31]; }; @@ -660,6 +1013,8 @@ class OP_TOPInput // Can only be called from a C++ TOP/Custom TOP that is working in TOP_ExecuteMode::CUDA. Will error/return nullptr in other // cases. Should only be called from within execute(), and the returned pointer will remain valids until execute() returns. // Returns a OP_CUDArrayInfo* that can be used to get the cudaArray* pointer for the texture memory for this TOP. + // This call should be done before beginCUDAOperations(), but the returned object should not be used until + // beginCUDAOperations() has been called afterwards. virtual const OP_CUDAArrayInfo* getCUDAArray(const OP_CUDAAcquireInfo& info, void* reserved2) const = 0; const char* opPath; @@ -670,10 +1025,14 @@ class OP_TOPInput // The number of times this node has cooked int64_t totalCooks; - // See documentation for OPCustomOPInstance + // See comments that preceed the declaration of the OP_CustomOPInstance class + // for more information const OP_CustomOPInstance* customOP; - int32_t reserved[12]; + // This can be used to read parameters from this node. + const OP_Parameters* parms = nullptr; + + int32_t reserved[10] = {}; protected: virtual void* reserved0() = 0; @@ -683,6 +1042,413 @@ class OP_TOPInput virtual void* reserved4() = 0; }; +enum class POP_AttributeClass : uint32_t +{ + Vertex = 0, + Point, + Primitive +}; + +enum class POP_AttributeType : uint32_t +{ + Float = 0, + Double, + Int32, + UInt32, +}; + +enum class POP_AttributeQualifier : uint32_t +{ + None, + // Treat this attribute as a direction. + // Needed for attributes such as Normals to be transformed correctly. + Direction, + + // Treat a matrix attribute as a transform matrix. + TransformMatrix, + + // Treat this attribute as a Color + Color, + + // Treat this attribute as a Quaternion + Quaternion, +}; + +class POP_AttributeInfo +{ +public: + const char* name = ""; + uint32_t numComponents = 4; + // Set this above 1 to make a matrix. + // numComponents will be the number of rows + uint32_t numColumns = 1; + + // Controls if the attribute is an array attribute instead of just a single + // attribute be element. + // 0 means not an array, >= 1 means an array of that size. + uint32_t arraySize = 0; + + POP_AttributeType type = POP_AttributeType::Float; + POP_AttributeQualifier qualifier = POP_AttributeQualifier::None; + POP_AttributeClass attribClass = POP_AttributeClass::Point; + +private: + int32_t reserved[30] = {}; +}; + +class POP_PointInfo +{ +public: + // The number of point attribute elements that have been provided in the buffers + uint32_t numPoints = 0; + +private: + int32_t reserved[20] = {}; +}; + +class POP_TopologyInfo +{ +public: + // 3 vertex triangles + // *StartIndex is the location in the index buffer that the primitives of this type start at + uint32_t trianglesStartIndex = 0; + // The number of triangles (not the number of vertices/indices) + uint32_t trianglesCount = 0; + + // 4 vertex quads + uint32_t quadsStartIndex = 0; + uint32_t quadsCount = 0; + + // Line strips that can be made up of any number of points. + // Each line strip must be terminated with an entry in the + // index buffer that is 0xFFFFFFFF, + uint32_t lineStripsStartIndex = 0; + uint32_t lineStripsCount = 0; + // This must include the restart indices as well + uint32_t lineStripsNumVertices = 0; + + // 2 vertex lines + uint32_t linesStartIndex = 0; + uint32_t linesCount = 0; + + // 1 vertex points + uint32_t pointPrimitivesStartIndex = 0; + uint32_t pointPrimitivesCount = 0; + + uint32_t + getNumPrimitives() const + { + return trianglesCount + quadsCount + lineStripsCount + linesCount + pointPrimitivesCount; + } + + uint32_t + getNumVerticies() const + { + return trianglesCount * 3 + quadsCount * 4 + lineStripsNumVertices + linesCount * 2 + pointPrimitivesCount; + } + +private: + int32_t reserved[20] = {}; +}; + +class POP_GridInfo +{ +public: + // Because we are using a 'flexible array member' at the end of this class + // to hold the gridDimensions, you should allocate your buffer using + // this function, instead of using sizeof(POP_GridInfo) + static uint64_t + getRequiredSize(uint32_t numDims) + { + // Avoid underflow + if (numDims == 0) + numDims = 1; + return sizeof(POP_GridInfo) + sizeof(uint32_t) * (numDims - 1); + } + // This can be optionally passed an array of uint32_t that denote the dimension size + // of N-dimensionalal grid metadata. + // Some POPs make use of this data to interpret grids of points. + uint32_t gridDimensionsCount = 0; + + int32_t reserved[20] = {}; + + // This class should be allocated using createBuffer() or malloc(), using the size + // which is obtained from getRequiredSize(). + // This allows us to read into this array with values larger than 1 without + // an memory overflow. + uint32_t gridDimensions[1]; +}; + +enum class POP_BufferLocation : uint32_t +{ + CPU = 0, + +#ifdef _WIN32 + CUDA = 20, + // Return the buffer where it currently resides. If it's currently on the CPU + // it will return it there, if it's currently on the GPU then it will return it + // as a CUDA buffer. + // This is only valid when getting buffers, not for creating them. + CPUOrCUDA = 21, +#endif +}; + +enum class POP_BufferMode : uint32_t +{ + // You should write to the buffer sequentially. + // Avoid random access writes, or any reads as it may have + // a high impact on performance. + SequentialWrite = 0, + + // Freely read/write the memory + ReadWrite, +}; + +enum class POP_BufferUsage : uint32_t +{ + Attribute = 0, + IndexBuffer, + PointInfoBuffer, + TopologyInfoBuffer, + LineStripsInfoBuffer, + GridInfoBuffer, +}; + +class POP_BufferInfo +{ +public: + uint64_t size = 0; + POP_BufferMode mode = POP_BufferMode::SequentialWrite; + POP_BufferUsage usage = POP_BufferUsage::Attribute; + POP_BufferLocation location = POP_BufferLocation::CPU; + + // If the location is CUDA, then this should be set to the cudaStream_t that will used the buffer + cudaStream_t stream = 0; + +private: + int32_t reserved[18] = {}; +}; + +class POP_Buffer : public OP_RefCount +{ +protected: + POP_Buffer() {} + virtual ~POP_Buffer() {} + +public: + POP_BufferInfo info; + + // When this buffer is retrieved from an input, if if the location i POP_BufferLocation::CPU, + // then getData() will stall until the GPU->CPU download has completed. + // If the location is CUDA, then you will be immediately be given the CUDA device pointer. + // + // For CPU data, you can use this to have another thread stall waiting for the data to be ready before processing. + // Or you can hold onto the POP_Buffer until a later time (to avoid the stall) and consume the data + // on the next cook. + virtual void* getData(void* reserved) = 0; + +private: + int32_t reserved[50] = {}; +}; + +class POP_GetBufferInfo +{ +public: + // Specifies where you want the data to be located when returned. If the data is not currently + // where it is requested, it will be transfered to that location. + // + // Note that when requesting CUDA memory, it must be done before beginCUDAOperations() is called, + // and only inside of a POP. + POP_BufferLocation location = POP_BufferLocation::CPU; + + // This should be set to the cudaStream_t that will be used for operations that will use + // the buffer. + cudaStream_t stream = 0; + + // You can optionally supply a previously used buffer you own as an candidate for the output buffer. + // If this buffer is suitable, the data will be copied into it instead of allocating a new + // buffer. You should not use this buffer agani after giving it to this function. + // It may get returned by the getBuffer() call. + // Currently only used if this is for a CUDA buffer. + OP_SmartRef outputBufferCandidate; +private: + int32_t reserved[18] = {}; +}; + +// When Point and/or Topology is only known on the GPU, we can't know the actual values on the CPU. +// This Info is used to keep track of the upper bounds of possible points and/or primitives. +class POP_MaxInfo +{ +public: + // Point attributes (not point primitives, that is 'pointPrims'). + uint32_t points = 0; + + // The maximum number of each prim type that may be defined in the topology. + // The actual number must be less than or equal to this maximum. + uint32_t triangles = 0; + uint32_t quads = 0; + + // The maximum number of line strips that may be defined in the topology + uint32_t lineStrips = 0; + // The maximum number of vertices any single line strip may have. + uint32_t lineStripVertices = 0; + + uint32_t lines = 0; + uint32_t pointPrims = 0; + +private: + int32_t reserved[30] = {}; +}; + +class POP_InfoBuffers +{ +public: + + // The data format of this should be POP_PointInfo + OP_SmartRef pointInfo; + + // The data format of this should be POP_TopologyInfo + OP_SmartRef topoInfo; + + // If the topology info is provided in a buffer that resides on the GPU (such as CUDA) + // Then you must also provide some maximum information. + // This will be ignored if the topoInfo is on the CPU. + POP_MaxInfo maxInfo; + + // This must be a buffer with pairs of uint32_t values, one pair for each line strip + // that is being given. The values are + // { lineStripStartIndex, lineStripNumVertices } + // where: + // lineStripStartIndex: Is the 0-based location in the index buffer where this line strip starts. + // 0 means the index where the line strips first appear in the index buffer. + // So the number of triangle/quad indices that appear before it don't matter. + // lineStripNumVertices: the number of vertices in the line strip, including the restart index. + // E.g A 5 vertex line strip followed by a 10 vertex line strip would have the entries: + // It's acceptable to have gaps where indices inthe buffer are skipped, such as in cases + // where a line strip has some vertices deleted. + // [0, 5], [5, 10] + OP_SmartRef lineStripsInfo; + + // This must be a buffer of uint32_t with the same number of entries as POP_TopologyInfo.lineStripsNumVertices + // Each entry should be the line strip primitive index that index buffer entry matches up with. + // Restart index entries should be incldued as well. + // E.g A 3 point line strip followed by a 4 point line strip would be + // [0, 0, 0, 0, 1, 1, 1, 1, 1] + OP_SmartRef lineStripsPrimIndices; + + // The data format of this should be POP_GridInfo. This should always be provided + // via a buffer on the CPU. + OP_SmartRef gridInfo; + +private: + int32_t reserved[200] = {}; +}; + +class POP_Attribute +{ +protected: + POP_Attribute() {} + virtual ~POP_Attribute() {} +public: + + POP_AttributeInfo info; + + virtual OP_SmartRef getBuffer(const POP_GetBufferInfo& info, void* reserved) const = 0; + +private: + int32_t reserved[20] = {}; +}; + +enum class POP_IndexType : uint32_t +{ + UInt32 = 0, +}; + +class POP_IndexBufferInfo +{ +public: + POP_IndexType type = POP_IndexType::UInt32; + +private: + int reserved[20] = {}; +}; + +class POP_IndexBuffer +{ +protected: + POP_IndexBuffer() {} + virtual ~POP_IndexBuffer() {} +public: + + POP_IndexBufferInfo info; + + // Get the actual index buffer, an array of uint32_t values. + virtual OP_SmartRef getBuffer(const POP_GetBufferInfo& info, void* reserved) const = 0; + +private: + int32_t reserved[20] = {}; +}; + +class OP_POPInput +{ +protected: + virtual ~OP_POPInput() + { + } +public: + const char* opPath; + uint32_t opId; + + // The number of times this node has cooked + int64_t totalCooks; + + // See comments that preceed the declaration of the OP_CustomOPInstance class + // for more information + const OP_CustomOPInstance* customOP; + + // This can be used to read parameters from this node. + const OP_Parameters* parms = nullptr; + + // Gets the number of attributes in that particular attribute class + virtual uint32_t getNumAttributes(POP_AttributeClass) const = 0; + // These calls are fast, so you can safely loop over them multiple times to query attributes. + // The same pointer is returned from multiple calls with the same arguments. + // Returns nullptr if the attribute doesn't exist + virtual const POP_Attribute* getAttribute(POP_AttributeClass, uint32_t index, void* reserved) const = 0; + virtual const POP_Attribute* getAttribute(POP_AttributeClass, const char* name, void* reserved) const = 0; + virtual const POP_IndexBuffer* getIndexBuffer(void* reserved) const = 0; + + // Get the TopologyInfo. This may be coming from the GPU or the CPU, depending on the source POP. + // Cast the resulting data to POP_TopologyInfo + virtual OP_SmartRef getTopologyInfo(const POP_GetBufferInfo& info, void* reserved) const = 0; + // Get the TopologyInfo. This may be coming from the GPU or the CPU, depending on the source POP. + // Cast the resulting data to POP_PointInfo + virtual OP_SmartRef getPointInfo(const POP_GetBufferInfo& info, void* reserved) const = 0; + + // See documentation in POP_InfoBuffers for the format of this buffer + virtual OP_SmartRef getLineStripsInfo(const POP_GetBufferInfo& info, void* reserved) const = 0; + + // See documentation in POP_InfoBuffers for the format of this buffer + virtual OP_SmartRef getLineStripsPrimIndices(const POP_GetBufferInfo& info, void* reserved) const = 0; + + // See documentation in POP_GridInfo for the format of this buffer. + // Note that this data will always be returned on the CPU. We return an empty buffer if it's + // requested to be as CUDA memory. + virtual OP_SmartRef getGridInfo(const POP_GetBufferInfo& info, void* reserved) const = 0; + + virtual void getMaxInfo(POP_MaxInfo* maxInfo, void* reserved) const = 0; + + // Helper function to get all of the Info buffers in one call, if you know you need them all. + // Note that some of the info buffers are always on the CPU, so you must ask for the data as + // POP_BufferLocation::CPU or POP_BufferLocation::CPUOrCUDA. Will return false if this fails. + virtual bool getAllInfoBuffers(POP_InfoBuffers* buffers, const POP_GetBufferInfo& info, + void* reserved) const = 0; + +protected: + + int32_t reserved[10]; +}; + class OP_String { protected: @@ -740,10 +1506,16 @@ class OP_CHOPInput // The number of times this node has cooked int64_t totalCooks; - // See documentation for OPCustomOPInstance + // See comments that preceed the declaration of the OP_CustomOPInstance class + // for more information const OP_CustomOPInstance* customOP; - int32_t reserved[16]; + // This can be used to read parameters from this node. + const OP_Parameters* parms = nullptr; + +private: + + int32_t reserved[14]; }; class OP_ObjectInput @@ -752,17 +1524,26 @@ class OP_ObjectInput const char* opPath; uint32_t opId; - // Use these methods to calculate object transforms + // These matrices are in column-vector convention. They are addressed via [r][c], + // and the translate is located in [0][3], [1][3] and [2][3]. + // The memory layout is row-by-row though. Most APIs expect the memory layout + // to be vector-by-vector (so column-by-column for a column-vector matrix), + // So it may need to be converted for your API. double worldTransform[4][4]; double localTransform[4][4]; // The number of times this node has cooked int64_t totalCooks; - int32_t reserved[18]; + // This can be used to read parameters from this node. + const OP_Parameters* parms = nullptr; + +private: + int32_t reserved[16] = {}; }; -// The type of data the attribute holds +// The type of data the attribute holds. +// For SOPs only enum class AttribType : int32_t { // One or more floats @@ -772,18 +1553,22 @@ enum class AttribType : int32_t Int, }; -// Right now we only support point attributes. +// The type of data the attribute holds. +// For SOPs only enum class AttribSet : int32_t { - Invalid, + Invalid = -1, Point = 0, + Vertex, + Primitive, }; // The type of the primitives, currently only Polygon type -// is supported +// is supported. +// For SOPs only enum class PrimitiveType : int32_t { - Invalid, + Invalid = -1, Polygon = 0, }; @@ -1118,27 +1903,27 @@ class BoundingBox // returns the bounding box length in x axis: float - sizeX() + sizeX() const { return maxX - minX; } // returns the bounding box length in y axis: float - sizeY() + sizeY() const { return maxY - minY; } // returns the bounding box length in z axis: float - sizeZ() + sizeZ() const { return maxZ - minZ; } bool - getCenter(Position* pos) + getCenter(Position* pos) const { if (!pos) return false; @@ -1150,7 +1935,7 @@ class BoundingBox // verifies if the input position (pos) is inside the current bounding box or not: bool - isInside(const Position& pos) + isInside(const Position& pos) const { if (pos.x >= minX && pos.x <= maxX && pos.y >= minY && pos.y <= maxY && @@ -1171,6 +1956,11 @@ class BoundingBox }; + +// SOP_PrimitiveInfo, all the required data for each primitive +// this info can be queried by calling getPrimitive() which accepts +// a valid index of a primitive as an input argument + class SOP_NormalInfo { public: @@ -1287,6 +2077,7 @@ class SOP_PrimitiveInfo type = PrimitiveType::Invalid; pointIndicesOffset = 0; isClosed = true; + memset(reserved, 0, sizeof(reserved)); } // number of vertices of this prim @@ -1333,16 +2124,16 @@ class OP_SOPInput // Returns an array of point positions. This array is getNumPoints() long. virtual const Position* getPointPositions() const = 0; - // Returns an array of normals. + // Returns an array of point normals. // // Returns nullptr if no normals are present virtual const SOP_NormalInfo* getNormals() const = 0; - // Returns an array of colors. + // Returns an array of point colors. // Returns nullptr if no colors are present virtual const SOP_ColorInfo* getColors() const = 0; - // Returns an array of texture coordinates. + // Returns an array of point texture coordinates. // If multiple texture coordinate layers are present, they will be placed // interleaved back-to-back. // E.g layer0 followed by layer1 followed by layer0 etc. @@ -1356,11 +2147,11 @@ class OP_SOPInput // Returns the custom attribute data with its name virtual const SOP_CustomAttribData* getCustomAttribute(const char* customAttribName) const = 0; - // Returns true if the SOP has a normal attribute of the given source + // Returns true if the SOP has a normal point attribute of the given source // attribute 'N' virtual bool hasNormals() const = 0; - // Returns true if the SOP has a color the given source + // Returns true if the SOP has a color point attribute of the given source // attribute 'Cd' virtual bool hasColors() const = 0; @@ -1373,7 +2164,7 @@ class OP_SOPInput float &hitU, float &hitV, int &hitPrimitiveIndex) = 0; // Returns the SOP_PrimitiveInfo with primIndex - const SOP_PrimitiveInfo + const SOP_PrimitiveInfo& getPrimitive(int32_t primIndex) const { return myPrimsInfo[primIndex]; @@ -1387,16 +2178,45 @@ class OP_SOPInput return myPrimPointIndices; } + // Returns an array of vertex colors. + // Returns nullptr if no colors are present + virtual const SOP_ColorInfo* getVtxColors() const = 0; + + // Returns an array of vertex texture coordinates. + // If multiple texture coordinate layers are present, they will be placed + // interleaved back-to-back. + // E.g layer0 followed by layer1 followed by layer0 etc. + // + // Returns nullptr if no texture layers are present + virtual const SOP_TextureInfo* getVtxTextures() const = 0; + + // Returns an array of primitive colors. + // Returns nullptr if no colors are present + virtual const SOP_ColorInfo* getPrimColors() const = 0; + + // Returns true if the SOP has a color vertex attribute of the given source +// attribute 'Cd' + virtual bool hasVtxColors() const = 0; + + // Returns true if the SOP has a color primitive attribute of the given source + // attribute 'Cd' + virtual bool hasPrimColors() const = 0; + SOP_PrimitiveInfo* myPrimsInfo; const int32_t* myPrimPointIndices; // The number of times this node has cooked int64_t totalCooks; - // See documentation for OPCustomOPInstance + // See comments that preceed the declaration of the OP_CustomOPInstance class + // for more information const OP_CustomOPInstance* customOP; - int32_t reserved[95]; + // This can be used to read parameters from this node. + const OP_Parameters* parms = nullptr; + +private: + int32_t reserved[93]; }; class OP_TimeInfo @@ -1439,10 +2259,6 @@ class OP_TimeInfo class OP_Inputs { public: - // NOTE: When writting a TOP, none of these functions should - // be called inside a beginGLCommands()/endGLCommands() section - // as they may require GL themselves to complete execution. - // Inputs that are wired into the node. Note that since some inputs // may not be connected this number doesn't mean that that the first N // inputs are connected. For example on a 3 input node if the 3rd input @@ -1458,6 +2274,7 @@ class OP_Inputs virtual const OP_CHOPInput* getInputCHOP(int32_t index) const = 0; // getInputSOP() declared later on in the class // getInputDAT() declared later on in the class + // getInputPOP() declared later on in the class // these are defined by parameters. // may return nullptr when invalid input @@ -1470,6 +2287,7 @@ class OP_Inputs virtual const OP_CHOPInput* getParCHOP(const char *name) const = 0; virtual const OP_ObjectInput* getParObject(const char *name) const = 0; // getParSOP() declared later on in the class + // getParPOP() declared later on in the class // these work on any type of parameter and can be interchanged // for menu types, int returns the menu selection index, string returns the item @@ -1478,11 +2296,11 @@ class OP_Inputs virtual double getParDouble(const char* name, int32_t index = 0) const = 0; // for multiple values: returns True on success/false otherwise + // Note, use getParRGB and getParRGBA for RGB and RGBA parameters. virtual bool getParDouble2(const char* name, double &v0, double &v1) const = 0; virtual bool getParDouble3(const char* name, double &v0, double &v1, double &v2) const = 0; virtual bool getParDouble4(const char* name, double &v0, double &v1, double &v2, double &v3) const = 0; - // returns the requested value virtual int32_t getParInt(const char* name, int32_t index = 0) const = 0; @@ -1552,6 +2370,20 @@ class OP_Inputs virtual const OP_TOPInput* getTOP(const char* path) const = 0; virtual const OP_TOPInput* getInputTOP(int32_t index) const = 0; virtual const OP_TOPInput* getParTOP(const char *name) const = 0; + + virtual const OP_POPInput* getInputPOP(int32_t index) const = 0; + virtual const OP_POPInput* getParPOP(const char *name) const = 0; + + // Will take the working color space into account (if enabled), and return the values in that, + // using the Parameter Color Space parameter to interpret the values. + // That is, the values written in the parameters are treated as what is set in the 'Parameter Color Space', + // parameter, and the values you get here will be in the working color space. + // Therefore, they will always be returns as one of the spaces in OP_WorkingColorSpace, + // which are always linear transfer. + virtual bool getParRGB(const char* name, double &r, double &g, double &b) const = 0; + virtual bool getParRGBA(const char* name, double &r, double &g, double &b, double &a) const = 0; + + virtual const OP_POPInput* getPOP(const char* path) const = 0; }; class OP_InfoCHOPChan @@ -1592,6 +2424,45 @@ class OP_InfoDATEntries int32_t reserved[10]; }; +// Class for specifying labels for the node input connectors. +class OP_InputLabel +{ +public: + OP_String* label; + + int32_t reserved[10] = {}; +}; + +// Class for saving and loading arbitrary bytedata into and from the toe file. +class OP_NodeSaveState +{ +public: + // Save a key, value pair into the toe file. + // `data` is assumed to be a bytedata array. + // `dataByteSize` is the size in bytes of the bytedata array `data`. + // e.g saveEntry("entry1","value1",6); + virtual void saveEntry(const char* key, const void* data, int64_t dataByteSize) = 0; + + int32_t reserved[20] = {}; +}; + +class OP_NodeLoadState +{ +public: + // Returns the value stored under `key`. + // `dataByteSize` outputs the size of the stored bytedata array. + virtual const void* loadEntry(const char* key, int64_t* dataByteSize) const = 0; + + // Return number of stored key,value pairs. + virtual int getKeyCount() const = 0; + + // Return a key based on an index. + // Use the key returned from `getKey()` to get the value via `loadEntry()`. + virtual const char* getKey(int n) const = 0; + + int32_t reserved[20] = {}; +}; + class OP_NumericParameter { public: @@ -1599,6 +2470,7 @@ class OP_NumericParameter { name = iname; label = page = nullptr; + help = nullptr; for (int i = 0; i<4; i++) { @@ -1612,7 +2484,12 @@ class OP_NumericParameter clampMins[i] = false; clampMaxes[i] = false; + + size = 1; + + section = false; } + memset(reserved, 0, sizeof(reserved)); } // Any char* values passed are copied immediately by the append parameter functions, @@ -1632,7 +2509,17 @@ class OP_NumericParameter double minSliders[4]; double maxSliders[4]; - int32_t reserved[20]; + // Set the number of values associated with the parameter. When greater than 1, the parameter will be shown as multiple adjacent fields. + // size is supported only for appendFloat(), appendInt(), appendToggle(), appendMomentary(). Will error if used otherwise. + int32_t size; + + // Set the parameter's separator status. When True, a visible separator is drawn between this parameter and the ones preceding it. + bool section; + + // Set the parameter's help text. To see any parameter's help, rollover the parameter while holding the Alt key. + const char* help; + + int32_t reserved[16]; }; @@ -1644,6 +2531,10 @@ class OP_StringParameter name = iname; label = page = nullptr; defaultValue = nullptr; + size = 1; + section = false; + help = nullptr; + memset(reserved, 0, sizeof(reserved)); } // Any char* values passed are copied immediately by the append parameter functions, @@ -1657,7 +2548,17 @@ class OP_StringParameter // This should be in UTF-8 encoding. const char* defaultValue; - int32_t reserved[20]; + // Set the number of values associated with the parameter. When greater than 1, the parameter will be shown as multiple adjacent fields. + // size is supported only for appendMenu(). Will error if used otherwise. + int32_t size; + + // Set the parameter's separator status. When True, a visible separator is drawn between this parameter and the ones preceding it. + bool section; + + // Set the parameter's help text. To see any parameter's help, rollover the parameter while holding the Alt key. + const char* help; + + int32_t reserved[16]; }; enum class OP_ParAppendResult : int32_t @@ -1686,7 +2587,6 @@ class OP_BuildDynamicMenuInfo class OP_ParameterManager { - public: // Returns OP_ParAppendResult::Success on success virtual OP_ParAppendResult appendFloat(const OP_NumericParameter &np, int32_t size = 1) = 0; @@ -1694,10 +2594,13 @@ class OP_ParameterManager virtual OP_ParAppendResult appendXY(const OP_NumericParameter &np) = 0; virtual OP_ParAppendResult appendXYZ(const OP_NumericParameter &np) = 0; + // appendXYZW() added further down virtual OP_ParAppendResult appendUV(const OP_NumericParameter &np) = 0; virtual OP_ParAppendResult appendUVW(const OP_NumericParameter &np) = 0; + // These should be evaluted with getParRGB and getParRGBA, so you get the + // values in the working color space (if any). virtual OP_ParAppendResult appendRGB(const OP_NumericParameter &np) = 0; virtual OP_ParAppendResult appendRGBA(const OP_NumericParameter &np) = 0; @@ -1706,6 +2609,7 @@ class OP_ParameterManager virtual OP_ParAppendResult appendString(const OP_StringParameter &sp) = 0; virtual OP_ParAppendResult appendFile(const OP_StringParameter &sp) = 0; + // appendFileSave() located further down in the class virtual OP_ParAppendResult appendFolder(const OP_StringParameter &sp) = 0; virtual OP_ParAppendResult appendDAT(const OP_StringParameter &sp) = 0; @@ -1714,13 +2618,20 @@ class OP_ParameterManager virtual OP_ParAppendResult appendObject(const OP_StringParameter &sp) = 0; // appendSOP() located further down in the class - + // Add a menu that will always return a value that is present as one of the entries. + // This is different from a StringMenu, which can hold values that arn't one of the entries. + // If the entry selected is not longer in the menu's entries, it will default to the first entry. + // This can happen for example if the entries in the menu change between versions of the oeprator. // Any char* values passed are copied immediately by the append parameter functions, // and do not need to be retained by the calling function. virtual OP_ParAppendResult appendMenu(const OP_StringParameter &sp, int32_t nitems, const char **names, const char **labels) = 0; + // Add a string parameter that has a > dropdown on the right with menu entries. + // This allows for a string parameter with some quick auto-fill options. + // The parameter can still be set to values that are different from any of the + // entries though. // Any char* values passed are copied immediately by the append parameter functions, // and do not need to be retained by the calling function. virtual OP_ParAppendResult appendStringMenu(const OP_StringParameter &sp, @@ -1742,15 +2653,32 @@ class OP_ParameterManager virtual OP_ParAppendResult appendMomentary(const OP_NumericParameter &np) = 0; virtual OP_ParAppendResult appendWH(const OP_NumericParameter &np) = 0; - // The buildDynamicMenu() function will be called in your class instance when required, allowing you to + // This has a confusing name, since it actually creates a menu that looks like what appendDynamicMenu() adds. + // It does not look like the one appendStringMenu adds. + // This is different from appendDynamicMenu() because the value of the menu may not match any of the entries. + // This can occur for example if the menu is a device list, and the project is loaded on another machine that + // doesn't have the device that thet the project was saved as. In that case the menu entries will show the + // devices the new machine has, but the value of the parameter stays as what was saved in the project, + // until the users picks a new entry in the dropdown for one of the current entries. + // + // The buildDynamicMenu() callback function will be called in your class instance when required, allowing you to // fill the menu with custom entries based on other parameters or external state (such as available devices). virtual OP_ParAppendResult appendDynamicStringMenu(const OP_StringParameter &sp) = 0; + // Behaves like the appendMenu() type parameter, but with a dynamic list of entries. virtual OP_ParAppendResult appendDynamicMenu(const OP_NumericParameter &np) = 0; + virtual OP_ParAppendResult appendXYZW(const OP_NumericParameter& np) = 0; + + virtual OP_ParAppendResult appendFileSave(const OP_StringParameter& sp) = 0; + + + virtual OP_ParAppendResult appendPOP(const OP_StringParameter& sp) = 0; + }; #pragma pack(pop) +#ifndef __CUDACC__ static_assert(offsetof(OP_CustomOPInfo, opType) == 0, "Incorrect Alignment"); static_assert(offsetof(OP_CustomOPInfo, opLabel) == 8, "Incorrect Alignment"); static_assert(offsetof(OP_CustomOPInfo, opIcon) == 16, "Incorrect Alignment"); @@ -1780,10 +2708,6 @@ static_assert(offsetof(OP_DATInput, cellData) == 24, "Incorrect Alignment"); static_assert(offsetof(OP_DATInput, totalCooks) == 32, "Incorrect Alignment"); static_assert(sizeof(OP_DATInput) == 112, "Incorrect Size"); -static_assert(offsetof(OP_TOPInput, opPath) == 8, "Incorrect Alignment"); -static_assert(offsetof(OP_TOPInput, opId) == 16, "Incorrect Alignment"); -static_assert(offsetof(OP_TOPInput, textureDesc) == 20, "Incorrect Alignment"); -static_assert(offsetof(OP_TOPInput, totalCooks) == 156 + 20, "Incorrect Alignment"); static_assert(sizeof(OP_TOPInput) == 156 + 28 + 56, "Incorrect Size"); static_assert(offsetof(OP_CHOPInput, opPath) == 0, "Incorrect Alignment"); @@ -1869,6 +2793,11 @@ static_assert(sizeof(OP_InfoDATSize) == 52, "Incorrect Size"); static_assert(offsetof(OP_InfoDATEntries, values) == 0, "Incorrect Alignment"); static_assert(sizeof(OP_InfoDATEntries) == 48, "Incorrect Size"); +static_assert(sizeof(OP_InputLabel) == 48, "Incorrect Size"); + +static_assert(sizeof(OP_NodeSaveState) == 88, "Incorrect Size"); +static_assert(sizeof(OP_NodeLoadState) == 88, "Incorrect Size"); + static_assert(offsetof(OP_NumericParameter, name) == 0, "Incorrect Alignment"); static_assert(offsetof(OP_NumericParameter, label) == 8, "Incorrect Alignment"); static_assert(offsetof(OP_NumericParameter, page) == 16, "Incorrect Alignment"); @@ -1900,6 +2829,21 @@ static_assert(offsetof(PY_GetInfo, autoCook) == 0, "Incorrect Alignment"); static_assert(sizeof(PY_GetInfo) == 204, "Incorrect Size"); static_assert(sizeof(PY_Context) == 208, "Incorrect Size"); static_assert(offsetof(PY_Struct, context) == OP_STRUCT_HEADER_ENTRIES * sizeof(int32_t), "Incorrect Alignment"); + +static_assert(offsetof(POP_BufferInfo, size) == 0, "Incorrect Alignment"); +static_assert(offsetof(POP_BufferInfo, mode) == 8, "Incorrect Alignment"); +static_assert(offsetof(POP_BufferInfo, usage) == 12, "Incorrect Alignment"); +static_assert(offsetof(POP_BufferInfo, location) == 16, "Incorrect Alignment"); +static_assert(offsetof(POP_BufferInfo, stream) == 24, "Incorrect Alignment"); +static_assert(sizeof(POP_BufferInfo) == 104, "Incorrect Size"); + +static_assert(offsetof(POP_GetBufferInfo, location) == 0, "Incorrect Alignment"); +static_assert(offsetof(POP_GetBufferInfo, stream) == 8, "Incorrect Alignment"); +static_assert(sizeof(POP_GetBufferInfo) == 96, "Incorrect Size"); + +static_assert(sizeof(OP_TOPDownloadResult) == sizeof(OP_RefCount) + sizeof(OP_TextureDesc) + 12 + 32 * sizeof(int32_t), "Incorrect Size"); + +#endif // CUDACC }; // These are the definitions for the C-functions that are used to @@ -1916,7 +2860,8 @@ typedef void (__cdecl *DESTROYTOPINSTANCE)(TD::TOP_CPlusPlusBase*, TD::TOP_Conte typedef void(__cdecl *FILLSOPPLUGININFO)(TD::SOP_PluginInfo *info); typedef TD::SOP_CPlusPlusBase* (__cdecl *CREATESOPINSTANCE)(const TD::OP_NodeInfo*); typedef void(__cdecl *DESTROYSOPINSTANCE)(TD::SOP_CPlusPlusBase*); +typedef void(__cdecl *FILLPOPPLUGININFO)(TD::POP_PluginInfo *info); +typedef TD::POP_CPlusPlusBase* (__cdecl *CREATEPOPINSTANCE)(const TD::OP_NodeInfo*, TD::POP_Context*); +typedef void(__cdecl *DESTROYPOPINSTANCE)(TD::POP_CPlusPlusBase*); #endif - -#pragma warning(pop) \ No newline at end of file diff --git a/src/animation_chop.cpp b/src/animation_chop.cpp index 71da97c..c3e0723 100644 --- a/src/animation_chop.cpp +++ b/src/animation_chop.cpp @@ -116,8 +116,11 @@ DLLEXPORT void FillCHOPPluginInfo(CHOP_PluginInfo *info) { - // Always set this to CHOPCPlusPlusAPIVersion. - info->apiVersion = CHOPCPlusPlusAPIVersion; + // Always set this to CHOPCPlusPlusAPIVersion. The version is recorded even + // when unsupported, so bailing out here lets TouchDesigner report the + // mismatch rather than loading a half-filled plugin info. + if (!info->setAPIVersion(CHOPCPlusPlusAPIVersion)) + return; // The opType is the unique name for this BasicCHOP. It must start with a // capital A-Z character, and all the following characters must lower case diff --git a/src/animation_view_chop.cpp b/src/animation_view_chop.cpp index e484eea..f774101 100644 --- a/src/animation_view_chop.cpp +++ b/src/animation_view_chop.cpp @@ -125,7 +125,12 @@ static PyMethodDef viewMethods[] = { DLLEXPORT void FillCHOPPluginInfo(CHOP_PluginInfo* info) { - info->apiVersion = CHOPCPlusPlusAPIVersion; + // The version is recorded even when unsupported, so bailing out here lets + // TouchDesigner report the mismatch rather than loading a half-filled + // plugin info. + if (!info->setAPIVersion(CHOPCPlusPlusAPIVersion)) + return; + info->customOPInfo.opType->setString("Animationview"); info->customOPInfo.opLabel->setString("Animation View"); info->customOPInfo.opIcon->setString("AMV"); diff --git a/td/Plugins/AnimationCHOP.dll b/td/Plugins/AnimationCHOP.dll deleted file mode 100644 index 760373e8c1f0ee4e108eda7cf3ec78a86b9ef5b3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 310784 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 HpRqqMdx5vpU;ddE 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 Date: Sat, 25 Jul 2026 20:37:16 -0700 Subject: [PATCH 02/18] deps: update anim to v0.3.0 v0.3.0 makes the Id constructor private and returns references rather than pointers from the Animation Id lookups. Both reach py_channel.cpp: - Build the PY_Channel id by copying Channel::id() instead of rebuilding an Id from its raw value, which the library no longer permits. - Take the address of the channel(Id) result now that it returns a reference. Also translate a missed Id lookup into a null ChannelData. channel(Id) has always thrown std::out_of_range on a miss, but getChannelData() never caught it while every one of its callers tests for a null channel, so a Python Channel outliving a remove_channel() would unwind a C++ exception through the CPython boundary instead of raising. The two renames in v0.2.0 (Id::isValid to is_valid, and the GrabbedHandle enumerators to PascalCase) need no changes here; neither symbol is used. --- ext/anim | 2 +- src/py_anim_bindings/py_channel.cpp | 19 ++++++++++++++++--- src/py_anim_bindings/py_channel.h | 5 ++++- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/ext/anim b/ext/anim index bd5f9f9..85de526 160000 --- a/ext/anim +++ b/ext/anim @@ -1 +1 @@ -Subproject commit bd5f9f9327746238ee5d355b714b525b912463c9 +Subproject commit 85de52649f87e225cedf49ea93428cd5f26ec7fb diff --git a/src/py_anim_bindings/py_channel.cpp b/src/py_anim_bindings/py_channel.cpp index fc33ebb..7c8764a 100644 --- a/src/py_anim_bindings/py_channel.cpp +++ b/src/py_anim_bindings/py_channel.cpp @@ -4,6 +4,7 @@ #include "py_extend.h" // For Extend enum support #include #include +#include // Allocation/deallocation functions static PyObject* PY_Channel_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { @@ -1135,7 +1136,10 @@ PyObject* ChannelToPY_Object(anim::Channel* channel, PyObject* parent) { self->parent = nullptr; try { - new (&self->channel_id) anim::Id(channel->id().id); // Use placement new to initialize id + // Placement new to initialize the const id member. Copy the Id itself + // rather than rebuilding one from its raw value: anim only hands ids + // out from the library, so the raw constructor is private. + new (&self->channel_id) anim::Id(channel->id()); Py_XINCREF(parent); // Increase reference count for parent self->parent = parent; // Assign parent } catch (const std::exception& e) { @@ -1195,6 +1199,15 @@ ChannelData getChannelData(PY_Channel *self, bool autoCook) { PyErr_SetString(PyExc_RuntimeError, "Cannot retrieve anim::Animation instance."); return ChannelData(); } - // anim::Animation would need a method like getChannelById - return { animation->channel(self->channel_id), inst, td_struct }; + // channel(Id) returns a reference and throws std::out_of_range when the + // channel is gone -- which is reachable, since Python can hold a Channel + // past a remove_channel(). Every caller tests for a null channel, so + // translate the miss into that rather than letting a C++ exception escape + // through the CPython boundary. + try { + return { &animation->channel(self->channel_id), inst, td_struct }; + } catch (const std::out_of_range&) { + PyErr_SetString(PyExc_RuntimeError, "Channel no longer exists."); + return ChannelData(); + } } \ No newline at end of file diff --git a/src/py_anim_bindings/py_channel.h b/src/py_anim_bindings/py_channel.h index 045d9a0..9e1cd4c 100644 --- a/src/py_anim_bindings/py_channel.h +++ b/src/py_anim_bindings/py_channel.h @@ -22,7 +22,10 @@ namespace TD { struct PY_Struct; } typedef struct { PyObject_HEAD - anim::Id channel_id; // Unique identifier for the channel - the internal Id is const and anim::Id has explicit constructor so we need to set on construction + // Unique identifier for the channel. Id holds a const member and its raw + // constructor is private to the library, so this is placement-new copied + // from Channel::id() rather than assigned. + anim::Id channel_id; PyObject* parent; // Reference to the parent AnimationCHOP to keep it alive } PY_Channel; From 48866e78be771d14c1de3fdfe9c51b4521a9debf Mon Sep 17 00:00:00 2001 From: keithlostracco Date: Sat, 25 Jul 2026 20:47:53 -0700 Subject: [PATCH 03/18] test: add a standalone pytest suite for the Python bindings The bindings had no automated coverage: the only tests were scripts that have to be run by hand inside TouchDesigner. tests/python builds a small CPython extension that compiles the real operator sources and stands in for TouchDesigner with a fake PY_Context. That is the whole TouchDesigner surface the bindings touch -- they cast self to a PY_Struct, read ->context, and call getNodeInstance()/makeNodeDirty() -- so the module under test is the real binding code rather than a reimplementation of it. The operator's own method and getset tables are bound to the stand-in node object, which is why they are now externally linked. 77 tests cover Point, Keyframe, Channel and the animation as a whole: construction, evaluation, extend behaviour, state round-trips, error handling and node invalidation. Run them with `cmake --workflow --preset dev`, which configures, builds and runs the suite through ctest. uv supplies the Python 3.11 and pytest when present, so there is nothing to install first. Three behaviours are pinned by tests because they surprise: - keyframe(i) returns a detached copy, so assigning to its properties updates only that copy and never the channel; - the operator's range is its own setting rather than the span of its channels, and clear() does not reset it; - Channel.num_samples takes a sample rate, unlike the operator's property. No Catch2 suite yet: anim carries its own, and what remains here is bound to either TouchDesigner or CPython. The saveData()/loadData() codec is the natural subject and should bring tests/cpp with it. --- .gitignore | 6 + CMakeLists.txt | 53 +++++ CMakePresets.json | 42 ++++ src/animation_chop.cpp | 12 +- src/animation_chop.h | 8 + tests/python/CMakeLists.txt | 74 +++++++ tests/python/conftest.py | 48 +++++ .../python/extension/animationchop_module.cpp | 162 +++++++++++++++ tests/python/requirements.txt | 6 + tests/python/test_animation.py | 184 +++++++++++++++++ tests/python/test_channel.py | 187 ++++++++++++++++++ tests/python/test_errors.py | 68 +++++++ tests/python/test_keyframe.py | 99 ++++++++++ tests/python/test_point.py | 44 +++++ 14 files changed, 989 insertions(+), 4 deletions(-) create mode 100644 CMakePresets.json create mode 100644 tests/python/CMakeLists.txt create mode 100644 tests/python/conftest.py create mode 100644 tests/python/extension/animationchop_module.cpp create mode 100644 tests/python/requirements.txt create mode 100644 tests/python/test_animation.py create mode 100644 tests/python/test_channel.py create mode 100644 tests/python/test_errors.py create mode 100644 tests/python/test_keyframe.py create mode 100644 tests/python/test_point.py diff --git a/.gitignore b/.gitignore index ce0f5f7..07facd0 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,9 @@ build # and TouchDesigner writes a per-machine Plugins.json trust file alongside them. # Released binaries are published as release assets instead. td/Plugins/ + +# The pytest extension is copied next to its tests so `import animationchop` +# resolves; it is a build artifact. +tests/python/*.pyd +tests/python/*.so +.pytest_cache/ diff --git a/CMakeLists.txt b/CMakeLists.txt index b081c4a..907ed87 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -226,3 +226,56 @@ install(TARGETS animation_chop animation_view_chop LIBRARY DESTINATION lib ARCHIVE DESTINATION lib ) + +# --------------------------------------------------------------------------- +# Tests +# +# tests/python runs pytest against an extension that compiles the real operator +# sources against a fake PY_Context. No TouchDesigner needed, so it runs in CI. +# +# There is deliberately no Catch2 suite yet: anim carries its own (5026 +# assertions), and what remains here is bound to TouchDesigner or to CPython, +# so a C++ suite today would either test nothing meaningful or drag Python in. +# The saveData()/loadData() codec is the natural subject and should bring +# tests/cpp with it. +# +# The TouchDesigner integration test is separate and local-only (TouchDesigner +# needs a licence and a GPU, so it cannot run on a cloud runner); see +# run_td_tests.ps1 and TESTING.md. +# +# Configure with -DANIMATIONCHOP_BUILD_TESTS=ON, or use the `dev` preset. +# --------------------------------------------------------------------------- +option(ANIMATIONCHOP_BUILD_TESTS "Build the Catch2 and pytest unit tests" OFF) + +if(ANIMATIONCHOP_BUILD_TESTS) + enable_testing() + + # TouchDesigner embeds CPython 3.11, so the extension must be built and + # imported by 3.11. uv is preferred: it can supply both the interpreter and + # pytest without anything being installed globally. + find_program(UV_EXECUTABLE uv) + if(UV_EXECUTABLE) + message(STATUS "AnimationCHOP: using uv at ${UV_EXECUTABLE} to run pytest") + else() + find_package(Python3 3.11 COMPONENTS Interpreter REQUIRED) + set(ANIMATIONCHOP_PYTHON_EXECUTABLE ${Python3_EXECUTABLE}) + message(STATUS "AnimationCHOP: uv not found; running pytest with ${Python3_EXECUTABLE}") + endif() + + # A CPython extension is .pyd on Windows and .so everywhere else, including + # macOS -- CMake's default MODULE suffix there is .so already, but .dylib + # would not be importable, so be explicit. + if(WIN32) + set(ANIMATIONCHOP_PYEXT_SUFFIX ".pyd") + # Windows builds against the vendored headers/import library, matching + # the operator targets above. + set(ANIMATIONCHOP_PYTHON_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/ext/Python/Include) + else() + set(ANIMATIONCHOP_PYEXT_SUFFIX ".so") + find_package(Python3 3.11 COMPONENTS Development.Module REQUIRED) + set(ANIMATIONCHOP_PYTHON_INCLUDE_DIRS ${Python3_INCLUDE_DIRS}) + set(ANIMATIONCHOP_PYTHON_LIBRARIES Python3::Module) + endif() + + add_subdirectory(tests/python) +endif() diff --git a/CMakePresets.json b/CMakePresets.json new file mode 100644 index 0000000..9116d3e --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,42 @@ +{ + "version": 6, + "cmakeMinimumRequired": { "major": 3, "minor": 25, "patch": 0 }, + "configurePresets": [ + { + "name": "dev", + "displayName": "Dev (operators + unit tests)", + "description": "Build both operators and the pytest binding suite. Uses uv for the Python 3.11 that runs pytest, when available.", + "binaryDir": "${sourceDir}/build", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release", + "ANIMATIONCHOP_BUILD_TESTS": "ON" + } + } + ], + "buildPresets": [ + { + "name": "dev", + "configurePreset": "dev", + "configuration": "Release" + } + ], + "testPresets": [ + { + "name": "dev", + "configurePreset": "dev", + "configuration": "Release", + "output": { "outputOnFailure": true } + } + ], + "workflowPresets": [ + { + "name": "dev", + "displayName": "Configure, build, and run all unit tests", + "steps": [ + { "type": "configure", "name": "dev" }, + { "type": "build", "name": "dev" }, + { "type": "test", "name": "dev" } + ] + } + ] +} diff --git a/src/animation_chop.cpp b/src/animation_chop.cpp index c3e0723..e910af7 100644 --- a/src/animation_chop.cpp +++ b/src/animation_chop.cpp @@ -49,7 +49,10 @@ static PyObject* py_get_state_method(PyObject* self, PyObject* args); static PyObject* py_set_state_method(PyObject* self, PyObject* args); // --- Python method table for AnimationCHOP --- -static PyMethodDef methods[] = { +// Externally linked (and declared in animation_chop.h) so the pytest extension +// under tests/python can expose the very same table, rather than a copy that +// would drift. +PyMethodDef AnimationCHOP_pythonMethods[] = { {"create_channel", (PyCFunction)py_create_channel, METH_VARARGS, "Create a new channel."}, {"remove_channel", (PyCFunction)py_remove_channel, METH_VARARGS, "Remove a channel by name or index."}, {"has_channel", (PyCFunction)py_has_channel, METH_VARARGS, "Check if a channel exists."}, @@ -75,7 +78,8 @@ static PyObject* py_get_state(PyObject* self, void* closure); static int py_set_state(PyObject* self, PyObject* value, void* closure); // This struct lists the different getters and/or settings the Custom Operator will expose. -static PyGetSetDef getSets[] = +// Externally linked for the same reason as AnimationCHOP_pythonMethods above. +PyGetSetDef AnimationCHOP_pythonGetSets[] = { {"Point", get_point_type, nullptr, "Point type for representing time-value pairs.", nullptr}, {"HandleMode", get_handle_mode_enum, nullptr, "HandleMode enum for keyframe handle behavior.", nullptr}, @@ -144,8 +148,8 @@ FillCHOPPluginInfo(CHOP_PluginInfo *info) info->customOPInfo.maxInputs = 1; info->customOPInfo.pythonVersion->setString(PY_VERSION); - info->customOPInfo.pythonMethods = methods; - info->customOPInfo.pythonGetSets = getSets; + info->customOPInfo.pythonMethods = AnimationCHOP_pythonMethods; + info->customOPInfo.pythonGetSets = AnimationCHOP_pythonGetSets; info->customOPInfo.pythonCallbacksDAT = PythonCallbacksDATStubs; } diff --git a/src/animation_chop.h b/src/animation_chop.h index db1b026..df114d6 100644 --- a/src/animation_chop.h +++ b/src/animation_chop.h @@ -7,9 +7,17 @@ #include #include +#include + using namespace TD; +// The Python method/getset tables this operator registers with TouchDesigner. +// Defined in animation_chop.cpp and exposed so the pytest extension under +// tests/python can bind the identical tables without TouchDesigner. +extern PyMethodDef AnimationCHOP_pythonMethods[]; +extern PyGetSetDef AnimationCHOP_pythonGetSets[]; + class AnimationCHOP : public CHOP_CPlusPlusBase { public: diff --git a/tests/python/CMakeLists.txt b/tests/python/CMakeLists.txt new file mode 100644 index 0000000..31958aa --- /dev/null +++ b/tests/python/CMakeLists.txt @@ -0,0 +1,74 @@ +# Python extension + pytest suite. Exposes the operator bindings without +# TouchDesigner by compiling the real operator sources against a fake +# PY_Context (see extension/animationchop_module.cpp). + +add_library(animationchop_testext MODULE + extension/animationchop_module.cpp + ${CMAKE_SOURCE_DIR}/src/animation_chop.cpp + ${CMAKE_SOURCE_DIR}/src/py_anim_bindings/py_channel.cpp + ${CMAKE_SOURCE_DIR}/src/py_anim_bindings/py_extend.cpp + ${CMAKE_SOURCE_DIR}/src/py_anim_bindings/py_function.cpp + ${CMAKE_SOURCE_DIR}/src/py_anim_bindings/py_handle_mode.cpp + ${CMAKE_SOURCE_DIR}/src/py_anim_bindings/py_keyframe.cpp + ${CMAKE_SOURCE_DIR}/src/py_anim_bindings/py_point.cpp +) + +# A CPython extension is a .pyd on Windows and a .so elsewhere -- never .dylib. +set_target_properties(animationchop_testext PROPERTIES + OUTPUT_NAME "animationchop" + PREFIX "" + SUFFIX "${ANIMATIONCHOP_PYEXT_SUFFIX}" +) + +target_include_directories(animationchop_testext PRIVATE + ${CMAKE_SOURCE_DIR}/src + ${CMAKE_SOURCE_DIR}/ext/td/include + ${CMAKE_SOURCE_DIR}/ext/anim/include + ${ANIMATIONCHOP_PYTHON_INCLUDE_DIRS} +) + +target_link_libraries(animationchop_testext PRIVATE anim) +target_compile_features(animationchop_testext PRIVATE cxx_std_20) + +if(WIN32) + target_include_directories(animationchop_testext PRIVATE + ${CMAKE_SOURCE_DIR}/ext/Python/Include/PC) + target_link_directories(animationchop_testext PRIVATE + ${CMAKE_SOURCE_DIR}/ext/Python/lib/x64) + target_link_libraries(animationchop_testext PRIVATE python311) + target_compile_options(animationchop_testext PRIVATE /wd4100 /wd4189) + target_compile_definitions(animationchop_testext PRIVATE _CRT_SECURE_NO_WARNINGS) +else() + # Extension modules leave Python's symbols undefined; the host interpreter + # supplies them at import. + target_link_libraries(animationchop_testext PRIVATE ${ANIMATIONCHOP_PYTHON_LIBRARIES}) + target_compile_definitions(animationchop_testext PRIVATE __cdecl=) + target_compile_options(animationchop_testext PRIVATE + -Wno-unused-parameter -Wno-unused-variable) +endif() + +# Drop the module next to the pytest files so `import animationchop` resolves +# regardless of the per-configuration build subdirectory. +add_custom_command(TARGET animationchop_testext POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + "$" + "${CMAKE_CURRENT_SOURCE_DIR}/" + COMMENT "Copying animationchop test extension to tests/python/" +) + +# Run pytest as a ctest test. Prefer `uv run`, which supplies pytest +# ephemerally so nothing has to be installed first; otherwise call pytest in +# the discovered interpreter (see requirements.txt). +if(UV_EXECUTABLE) + add_test( + NAME python_bindings + COMMAND ${UV_EXECUTABLE} run --no-project --python 3.11 --with pytest + pytest -q "${CMAKE_CURRENT_SOURCE_DIR}" + ) +else() + add_test( + NAME python_bindings + COMMAND ${ANIMATIONCHOP_PYTHON_EXECUTABLE} -m pytest -q "${CMAKE_CURRENT_SOURCE_DIR}" + ) +endif() +set_tests_properties(python_bindings PROPERTIES DEPENDS animationchop_testext) diff --git a/tests/python/conftest.py b/tests/python/conftest.py new file mode 100644 index 0000000..ff8309d --- /dev/null +++ b/tests/python/conftest.py @@ -0,0 +1,48 @@ +"""Shared fixtures for the AnimationCHOP binding tests. + +The `animationchop` extension creates a single operator instance at import and +keeps it for the session -- the same lifetime it has inside TouchDesigner, where +the node owns its anim::Animation. So the animation is reset between tests +rather than rebuilt, and tests must not rely on state from one another. +""" + +import pytest + +import animationchop + + +# The operator's range defaults to 0..30 and is independent of the channels, +# so clear() does not restore it -- the fixture has to, or a test that sets the +# range leaks it into every test that follows. +DEFAULT_START_TIME = 0.0 +DEFAULT_END_TIME = 30.0 + + +def _reset(node): + node.clear() + node.start_time = DEFAULT_START_TIME + node.end_time = DEFAULT_END_TIME + + +@pytest.fixture +def op(): + """The operator: no channels, default range, dirty counter zeroed.""" + _reset(animationchop.op) + animationchop.reset_dirty_count() + yield animationchop.op + _reset(animationchop.op) + + +@pytest.fixture +def channel(op): + """An empty channel named 'tx'.""" + return op.create_channel("tx") + + +@pytest.fixture +def ramp(op): + """A channel ramping 0 -> 100 over frames 0 -> 30.""" + ch = op.create_channel("ramp") + ch.create_keyframe(0, 0) + ch.create_keyframe(30, 100) + return ch diff --git a/tests/python/extension/animationchop_module.cpp b/tests/python/extension/animationchop_module.cpp new file mode 100644 index 0000000..e020fea --- /dev/null +++ b/tests/python/extension/animationchop_module.cpp @@ -0,0 +1,162 @@ +// Test-only CPython extension that exposes the AnimationCHOP Python bindings +// as an importable module, so they can be exercised with pytest WITHOUT +// TouchDesigner. +// +// It compiles the real operator sources (animation_chop.cpp and the +// py_anim_bindings/*.cpp) and stands in for TouchDesigner with a fake +// PY_Context. That is the entire TouchDesigner surface the bindings touch: +// they cast their `self` to a TD::PY_Struct, read `->context`, and call +// getNodeInstance() / makeNodeDirty() on it. +// +// The module exposes a single `op` object bound to the operator's own method +// and getset tables, so `op.create_channel(...)`, `op.state`, `op.Point`, etc. +// behave exactly as they do inside TouchDesigner. + +#include "animation_chop.h" +#include "CPlusPlus_Common.h" + +#include +#include + +using namespace TD; + +namespace { + +AnimationCHOP* g_instance = nullptr; +long g_dirtyCount = 0; + +// The only TouchDesigner interface the bindings use. +class FakeContext : public PY_Context +{ +public: + void* getNodeInstance(const PY_GetInfo&, void*) override + { + return g_instance; + } + + void makeNodeDirty(void*) override + { + ++g_dirtyCount; + } +}; + +FakeContext g_context; + +// A PyObject laid out like TD::PY_Struct. +// +// Inside TouchDesigner the node's Python object *is* a PY_Struct: an opaque +// header (where CPython's own object header lives), then a PY_Context*. The +// bindings cast to that layout and read `context`, so the stand-in has to put +// its context pointer at the same offset. The static_asserts below are what +// actually guarantee that; the padding is just arithmetic to get there. +struct FakeNode +{ + PyObject_HEAD + char headerPad[sizeof(int32_t) * OP_STRUCT_HEADER_ENTRIES - sizeof(PyObject)]; + PY_Context* context; +}; + +static_assert(offsetof(FakeNode, context) == offsetof(PY_Struct, context), + "FakeNode must place `context` where TD::PY_Struct does, since the " + "bindings cast between them."); +static_assert(sizeof(FakeNode) <= sizeof(PY_Struct), + "FakeNode must not claim more storage than a real PY_Struct."); + +PyObject* +FakeNode_new(PyTypeObject* type, PyObject*, PyObject*) +{ + PyObject* self = type->tp_alloc(type, 0); + if (!self) + return nullptr; + reinterpret_cast(self)->context = &g_context; + return self; +} + +PyTypeObject FakeNodeType = { + PyVarObject_HEAD_INIT(nullptr, 0) + "animationchop.AnimationCHOP", // tp_name + sizeof(FakeNode), // tp_basicsize +}; + +// --- module-level helpers, for assertions the bindings themselves cannot make --- + +PyObject* +tm_dirty_count(PyObject*, PyObject*) +{ + return PyLong_FromLong(g_dirtyCount); +} + +PyObject* +tm_reset_dirty_count(PyObject*, PyObject*) +{ + g_dirtyCount = 0; + Py_RETURN_NONE; +} + +PyMethodDef module_methods[] = { + {"dirty_count", tm_dirty_count, METH_NOARGS, + "Number of times the bindings have marked the node dirty."}, + {"reset_dirty_count", tm_reset_dirty_count, METH_NOARGS, + "Reset the makeNodeDirty() counter."}, + {nullptr, nullptr, 0, nullptr}, +}; + +PyModuleDef animationchop_module = { + PyModuleDef_HEAD_INIT, + "animationchop", + "Test-only extension exposing the AnimationCHOP bindings (no TouchDesigner).", + -1, + module_methods, + nullptr, nullptr, nullptr, nullptr, +}; + +} // namespace + +PyMODINIT_FUNC +PyInit_animationchop(void) +{ + // Bind the operator's own tables, so the module under test is the real + // binding surface rather than a reimplementation of it. + FakeNodeType.tp_flags = Py_TPFLAGS_DEFAULT; + FakeNodeType.tp_doc = "Stand-in for an AnimationCHOP node outside TouchDesigner."; + FakeNodeType.tp_methods = AnimationCHOP_pythonMethods; + FakeNodeType.tp_getset = AnimationCHOP_pythonGetSets; + FakeNodeType.tp_new = FakeNode_new; + + if (PyType_Ready(&FakeNodeType) < 0) + return nullptr; + + PyObject* module = PyModule_Create(&animationchop_module); + if (!module) + return nullptr; + + // One operator instance, alive for the session. The bindings reach it + // through FakeContext::getNodeInstance(). + if (!g_instance) { + static OP_NodeInfo nodeInfo{}; + nodeInfo.opPath = "/test/animation1"; + nodeInfo.opId = 1; + g_instance = new AnimationCHOP(&nodeInfo); + } + + PyObject* node = FakeNode_new(&FakeNodeType, nullptr, nullptr); + if (!node) { + Py_DECREF(module); + return nullptr; + } + + Py_INCREF(&FakeNodeType); + if (PyModule_AddObject(module, "AnimationCHOP", (PyObject*)&FakeNodeType) < 0) { + Py_DECREF(&FakeNodeType); + Py_DECREF(node); + Py_DECREF(module); + return nullptr; + } + if (PyModule_AddObject(module, "op", node) < 0) { + Py_DECREF(node); + Py_DECREF(module); + return nullptr; + } + + return module; +} diff --git a/tests/python/requirements.txt b/tests/python/requirements.txt new file mode 100644 index 0000000..95a8e99 --- /dev/null +++ b/tests/python/requirements.txt @@ -0,0 +1,6 @@ +# Test dependencies for the pytest binding suite. +# Only needed when uv is unavailable -- otherwise `uv run --with pytest` +# supplies these ephemerally. Install into the Python 3.11 that built the +# extension, e.g.: +# uv pip install -r tests/python/requirements.txt +pytest>=8,<9 diff --git a/tests/python/test_animation.py b/tests/python/test_animation.py new file mode 100644 index 0000000..3098651 --- /dev/null +++ b/tests/python/test_animation.py @@ -0,0 +1,184 @@ +"""The operator itself: channel collection, bounds and whole-animation state.""" + +import pytest + +import animationchop + + +# --- channel management ----------------------------------------------------- + +def test_starts_empty(op): + assert op.num_channels == 0 + assert op.channel_names == [] + + +def test_create_channel(op): + ch = op.create_channel("tx") + assert ch.name == "tx" + assert op.num_channels == 1 + assert op.channel_names == ["tx"] + + +def test_create_channel_at_index(op): + op.create_channel("a") + op.create_channel("b") + op.create_channel("mid", 1) + assert op.channel_names == ["a", "mid", "b"] + + +def test_has_channel(op): + op.create_channel("tx") + assert op.has_channel("tx") is True + assert op.has_channel("ty") is False + + +def test_get_channel_by_name_and_index(op): + op.create_channel("tx") + op.create_channel("ty") + assert op.get_channel("ty").name == "ty" + assert op.get_channel(0).name == "tx" + + +def test_remove_channel_by_name(op): + op.create_channel("tx") + op.create_channel("ty") + op.remove_channel("tx") + assert op.channel_names == ["ty"] + + +def test_remove_channel_by_index(op): + op.create_channel("tx") + op.create_channel("ty") + op.remove_channel(0) + assert op.channel_names == ["ty"] + + +def test_channels_returns_every_channel(op): + op.create_channel("a") + op.create_channel("b") + assert [c.name for c in op.channels] == ["a", "b"] + + +def test_clear_removes_everything(op): + op.create_channel("a") + op.create_channel("b") + op.clear() + assert op.num_channels == 0 + assert op.channel_names == [] + + +# --- bounds ----------------------------------------------------------------- + +def test_animation_range_is_independent_of_channel_content(op): + """The operator's range is its own setting, not the span of its channels. + + Adding a channel reaching frame 60 leaves the range at its 0..30 default, + so the range has to be set deliberately. + """ + long = op.create_channel("long") + long.create_keyframe(5, 0) + long.create_keyframe(60, 1) + + assert long.end_time == pytest.approx(60.0) + assert op.end_time == pytest.approx(30.0) + + +def test_animation_range_is_settable(op): + op.start_time = 10.0 + op.end_time = 70.0 + assert op.start_time == pytest.approx(10.0) + assert op.end_time == pytest.approx(70.0) + assert op.length == pytest.approx(60.0) + + +def test_clear_leaves_the_range_alone(op): + """clear() removes channels but keeps the configured range.""" + op.start_time = 10.0 + op.end_time = 70.0 + op.create_channel("tx") + + op.clear() + + assert op.num_channels == 0 + assert op.start_time == pytest.approx(10.0) + assert op.end_time == pytest.approx(70.0) + + +def test_animation_num_samples_is_empty_without_channels(op): + assert op.num_samples == 0 + + +def test_animation_num_samples_covers_the_range(ramp, op): + # 0..30 at 60 fps, inclusive of both ends. + assert op.num_samples == 30 * 60 + 1 + + +def test_channel_num_samples_takes_a_sample_rate(ramp): + """Channel.num_samples(rate) is a method because it is rate-dependent, + unlike the operator's num_samples, which reads the configured range.""" + assert ramp.num_samples(60.0) == 30 * 60 + 1 + assert ramp.num_samples(30.0) == 30 * 30 + 1 + + +# --- state ------------------------------------------------------------------ + +def test_state_shape(ramp, op): + state = op.state + assert set(state) == { + "channels", "start_time", "end_time", "length", "num_channels", + } + assert len(state["channels"]) == 1 + + +def test_state_round_trips(op): + tx = op.create_channel("tx") + tx.create_keyframe(0, 0) + tx.create_keyframe(30, 100) + ty = op.create_channel("ty") + ty.create_keyframe(0, 5) + ty.create_keyframe(10, 15) + + saved = op.state + before = (op.channel_names, tx.evaluate(15), ty.evaluate(5)) + + op.clear() + assert op.num_channels == 0 + + op.state = saved + + assert op.channel_names == before[0] + assert op.get_channel("tx").evaluate(15) == pytest.approx(before[1]) + assert op.get_channel("ty").evaluate(5) == pytest.approx(before[2]) + + +def test_set_state_replaces_existing_channels(op): + src = op.create_channel("keep") + src.create_keyframe(0, 0) + saved = op.state + + op.clear() + op.create_channel("stale") + op.set_state(saved) + + assert op.channel_names == ["keep"] + + +def test_get_state_matches_property(ramp, op): + assert op.get_state() == op.state + + +# --- node invalidation ------------------------------------------------------ + +def test_mutating_marks_the_node_dirty(op): + animationchop.reset_dirty_count() + ch = op.create_channel("tx") + ch.create_keyframe(0, 0) + assert animationchop.dirty_count() > 0 + + +def test_reading_does_not_mark_the_node_dirty(ramp): + animationchop.reset_dirty_count() + _ = ramp.num_keyframes + _ = ramp.evaluate(15) + _ = ramp.state + assert animationchop.dirty_count() == 0 diff --git a/tests/python/test_channel.py b/tests/python/test_channel.py new file mode 100644 index 0000000..526982b --- /dev/null +++ b/tests/python/test_channel.py @@ -0,0 +1,187 @@ +"""Channel: keyframe management, evaluation, extend behaviour and state.""" + +import pytest + + +# --- identity and size ------------------------------------------------------ + +def test_new_channel_is_named_and_empty(channel): + assert channel.name == "tx" + assert channel.empty is True + assert channel.num_keyframes == 0 + + +def test_size_tracks_num_keyframes(channel): + channel.create_keyframe(0, 0) + channel.create_keyframe(10, 1) + assert channel.num_keyframes == 2 + assert channel.size == channel.num_keyframes + assert channel.empty is False + + +# --- create_keyframe overloads --------------------------------------------- + +def test_create_keyframe_time_value(channel): + kf = channel.create_keyframe(12, 34) + assert kf.time == pytest.approx(12.0) + assert kf.value == pytest.approx(34.0) + + +def test_create_keyframe_with_function_and_handle_mode(op, channel): + kf = channel.create_keyframe(0, 0, op.Function.LINEAR, op.HandleMode.FLAT) + assert kf.function == op.Function.LINEAR + assert kf.handle_mode == op.HandleMode.FLAT + + +def test_create_keyframe_from_state(op, channel): + channel.create_keyframe(0, 0) + source = channel.keyframe(0).state + + other = op.create_channel("copy") + made = other.create_keyframe_from_state(source) + assert made.time == pytest.approx(0.0) + assert made.state["function"] == source["function"] + + +def test_keyframes_stay_sorted_by_time(channel): + for time in (30, 0, 15): + channel.create_keyframe(time, time) + times = [channel.keyframe(i).time for i in range(channel.num_keyframes)] + assert times == sorted(times) + + +# --- bounds ----------------------------------------------------------------- + +def test_start_end_and_length(ramp): + assert ramp.start_time == pytest.approx(0.0) + assert ramp.end_time == pytest.approx(30.0) + assert ramp.length == pytest.approx(30.0) + + +# --- evaluation ------------------------------------------------------------- + +def test_evaluate_hits_the_keyframes(ramp): + assert ramp.evaluate(0) == pytest.approx(0.0) + assert ramp.evaluate(30) == pytest.approx(100.0) + + +def test_evaluate_midpoint_is_between(ramp): + assert 0.0 < ramp.evaluate(15) < 100.0 + + +def test_evaluate_at_last_keyframe_is_exact(ramp): + """Regression: the end-of-range lookup used to dereference end().""" + assert ramp.evaluate(30.0) == pytest.approx(100.0) + + +def test_evaluate_range_length(ramp): + samples = ramp.evaluate_range(0, 30, 31) + assert len(samples) == 31 + assert samples[0] == pytest.approx(0.0) + assert samples[-1] == pytest.approx(100.0) + + +def test_evaluate_range_by_rate(ramp): + samples = ramp.evaluate_range_by_rate(0, 30, 1.0) + assert len(samples) > 1 + assert samples[0] == pytest.approx(0.0) + + +# --- navigation ------------------------------------------------------------- + +def test_next_and_prev_keyframe(ramp): + assert ramp.next_keyframe(10).time == pytest.approx(30.0) + assert ramp.prev_keyframe(10).time == pytest.approx(0.0) + + +def test_closest_keyframe(ramp): + assert ramp.closest_keyframe(2).time == pytest.approx(0.0) + assert ramp.closest_keyframe(28).time == pytest.approx(30.0) + + +# --- mutation --------------------------------------------------------------- + +def test_delete_keyframe(ramp): + ramp.delete_keyframe(0) + assert ramp.num_keyframes == 1 + assert ramp.keyframe(0).time == pytest.approx(30.0) + + +def test_set_keyframe_value(ramp): + ramp.set_keyframe_value(1, 250.0) + assert ramp.keyframe(1).value == pytest.approx(250.0) + + +def test_set_keyframe_time(ramp): + ramp.set_keyframe_time(1, 60.0) + assert ramp.keyframe(1).time == pytest.approx(60.0) + assert ramp.end_time == pytest.approx(60.0) + + +def test_set_keyframe_position(ramp): + ramp.set_keyframe_position(1, 45.0, 75.0) + assert ramp.keyframe(1).time == pytest.approx(45.0) + assert ramp.keyframe(1).value == pytest.approx(75.0) + + +# --- extend ----------------------------------------------------------------- + +def test_extend_defaults_to_hold(op, ramp): + assert ramp.extend_start == op.Extend.HOLD + assert ramp.extend_end == op.Extend.HOLD + + +def test_extend_hold_clamps_outside_the_range(ramp): + assert ramp.evaluate(-50) == pytest.approx(0.0) + assert ramp.evaluate(500) == pytest.approx(100.0) + + +def test_extend_is_settable(op, ramp): + ramp.extend_end = op.Extend.REPEAT + assert ramp.extend_end == op.Extend.REPEAT + + +def test_extend_repeat_wraps(op, ramp): + ramp.extend_end = op.Extend.REPEAT + # One full period past the end lands back at the start of the cycle. + assert ramp.evaluate(45) == pytest.approx(ramp.evaluate(15), abs=1e-6) + + +# --- state ------------------------------------------------------------------ + +def test_state_shape(ramp): + state = ramp.state + assert set(state) == { + "name", "start_time", "end_time", "length", "num_keyframes", + "empty", "extend_start", "extend_end", "keyframes", + } + assert len(state["keyframes"]) == 2 + + +def test_state_round_trips_through_a_second_channel(op, ramp): + other = op.create_channel("other") + other.state = ramp.state + + assert other.num_keyframes == ramp.num_keyframes + assert other.evaluate(15) == pytest.approx(ramp.evaluate(15)) + + +def test_set_state_replaces_rather_than_appends(op, ramp): + other = op.create_channel("other") + other.create_keyframe(0, 0) + other.create_keyframe(5, 5) + other.create_keyframe(9, 9) + + other.set_state(ramp.state) + assert other.num_keyframes == 2 + + +def test_state_preserves_extend(op, ramp): + ramp.extend_start = op.Extend.MIRROR + other = op.create_channel("other") + other.state = ramp.state + assert other.extend_start == op.Extend.MIRROR + + +def test_get_state_matches_property(ramp): + assert ramp.get_state() == ramp.state diff --git a/tests/python/test_errors.py b/tests/python/test_errors.py new file mode 100644 index 0000000..b9a8b7f --- /dev/null +++ b/tests/python/test_errors.py @@ -0,0 +1,68 @@ +"""Error handling: bad input should raise, never crash the host.""" + +import pytest + + +def test_get_missing_channel_raises(op): + with pytest.raises(Exception): + op.get_channel("nope") + + +def test_get_channel_out_of_range_raises(op): + op.create_channel("tx") + with pytest.raises(Exception): + op.get_channel(99) + + +def test_remove_missing_channel_raises(op): + with pytest.raises(Exception): + op.remove_channel("nope") + + +def test_keyframe_index_out_of_range_raises(ramp): + with pytest.raises(Exception): + ramp.keyframe(99) + + +def test_delete_keyframe_out_of_range_raises(ramp): + with pytest.raises(Exception): + ramp.delete_keyframe(99) + + +def test_set_state_rejects_non_mapping(ramp): + with pytest.raises(TypeError): + ramp.state = ["not", "a", "dict"] + + +def test_animation_set_state_rejects_non_mapping(op): + with pytest.raises(TypeError): + op.state = 42 + + +def test_animation_set_state_requires_channels(op): + with pytest.raises(ValueError): + op.state = {"start_time": 0.0} + + +def test_channel_outliving_its_removal_raises(op): + """A Python Channel held past remove_channel() must raise, not crash. + + The id lookup behind this throws std::out_of_range; letting that unwind + through the CPython boundary would take the host process down instead of + raising, so it is translated at the binding edge. + """ + ch = op.create_channel("doomed") + ch.create_keyframe(0, 0) + + op.remove_channel("doomed") + + with pytest.raises(RuntimeError): + _ = ch.num_keyframes + + +def test_channel_outliving_a_clear_raises(op): + ch = op.create_channel("doomed") + op.clear() + + with pytest.raises(RuntimeError): + _ = ch.name diff --git a/tests/python/test_keyframe.py b/tests/python/test_keyframe.py new file mode 100644 index 0000000..b91c1da --- /dev/null +++ b/tests/python/test_keyframe.py @@ -0,0 +1,99 @@ +"""Keyframe: position, handles, interpolation function and handle mode.""" + +import pytest + + +def test_time_and_value(ramp): + first, last = ramp.keyframe(0), ramp.keyframe(1) + assert first.time == pytest.approx(0.0) + assert first.value == pytest.approx(0.0) + assert last.time == pytest.approx(30.0) + assert last.value == pytest.approx(100.0) + + +def test_keyframe_is_a_detached_copy(ramp): + """keyframe(i) hands back a fresh object each call, not a live view.""" + assert ramp.keyframe(1) is not ramp.keyframe(1) + + +def test_assigning_to_a_keyframe_does_not_reach_the_channel(ramp): + """Setting a property on a returned Keyframe updates only that copy. + + The assignment succeeds and reads back on the copy, but the channel is + untouched -- Channel.set_keyframe_* are the mutators that persist. This + pins current behaviour; the silent divergence is a sharp edge worth + revisiting. + """ + kf = ramp.keyframe(1) + kf.value = 55.0 + + assert kf.value == pytest.approx(55.0) + assert ramp.keyframe(1).value == pytest.approx(100.0) + + +def test_set_keyframe_value_persists(ramp): + ramp.set_keyframe_value(1, 55.0) + assert ramp.keyframe(1).value == pytest.approx(55.0) + + +def test_handles_are_points(ramp): + kf = ramp.keyframe(0) + assert hasattr(kf.in_handle, "time") + assert hasattr(kf.out_handle, "value") + + +def test_defaults_are_bezier_and_smooth(op, ramp): + kf = ramp.keyframe(0) + assert kf.function == op.Function.BEZIER + assert kf.handle_mode == op.HandleMode.SMOOTH + + +def test_function_is_settable(op, ramp): + ramp.set_keyframe_function(0, op.Function.LINEAR) + assert ramp.keyframe(0).function == op.Function.LINEAR + + +def test_handle_mode_is_settable(op, ramp): + ramp.set_keyframe_handle_mode(0, op.HandleMode.FLAT) + assert ramp.keyframe(0).handle_mode == op.HandleMode.FLAT + + +def test_state_shape(ramp): + state = ramp.keyframe(0).state + assert set(state) == { + "position", "in_handle", "out_handle", "function", "handle_mode", + } + assert set(state["position"]) == {"time", "value"} + + +def test_state_encodes_enums_as_names(ramp): + state = ramp.keyframe(0).state + assert state["function"] == "Function.BEZIER" + assert state["handle_mode"] == "HandleMode.SMOOTH" + + +def test_state_round_trips(op, ramp): + kf = ramp.keyframe(0) + original = kf.state + + kf.function = op.Function.CONSTANT + assert kf.state["function"] == "Function.CONSTANT" + + kf.state = original + assert kf.state == original + + +def test_get_state_matches_property(ramp): + kf = ramp.keyframe(0) + assert kf.get_state() == kf.state + + +def test_linear_function_interpolates_linearly(op, ramp): + ramp.set_keyframe_function(0, op.Function.LINEAR) + assert ramp.evaluate(15) == pytest.approx(50.0, abs=1e-6) + + +def test_constant_function_holds_the_start_value(op, ramp): + ramp.set_keyframe_function(0, op.Function.CONSTANT) + assert ramp.evaluate(15) == pytest.approx(0.0) + assert ramp.evaluate(29.9) == pytest.approx(0.0) diff --git a/tests/python/test_point.py b/tests/python/test_point.py new file mode 100644 index 0000000..875e737 --- /dev/null +++ b/tests/python/test_point.py @@ -0,0 +1,44 @@ +"""Point: the time/value pair used for keyframe positions and handles.""" + +import pytest + + +def test_handles_expose_time_and_value(ramp): + handle = ramp.keyframe(0).out_handle + assert isinstance(handle.time, float) + assert isinstance(handle.value, float) + + +def test_state_is_time_and_value(ramp): + state = ramp.keyframe(0).out_handle.state + assert set(state) == {"time", "value"} + assert state["time"] == pytest.approx(ramp.keyframe(0).out_handle.time) + assert state["value"] == pytest.approx(ramp.keyframe(0).out_handle.value) + + +def test_get_state_matches_property(ramp): + handle = ramp.keyframe(0).in_handle + assert handle.get_state() == handle.state + + +def test_set_state_round_trips(ramp): + handle = ramp.keyframe(0).out_handle + original = handle.state + + handle.set_state({"time": 5.0, "value": 42.0}) + assert handle.time == pytest.approx(5.0) + assert handle.value == pytest.approx(42.0) + + handle.set_state(original) + assert handle.state == original + + +def test_state_setter_property(ramp): + handle = ramp.keyframe(0).out_handle + handle.state = {"time": 3.5, "value": -7.25} + assert handle.state["time"] == pytest.approx(3.5) + assert handle.state["value"] == pytest.approx(-7.25) + + +def test_repr_names_the_type(ramp): + assert "Point" in repr(ramp.keyframe(0).out_handle) From c2885c026545eea445f985cdd2bda28d689080da Mon Sep 17 00:00:00 2001 From: keithlostracco Date: Sun, 26 Jul 2026 10:47:28 -0700 Subject: [PATCH 04/18] chore: update keyframer and widgets modules for improved functionality and error handling --- td/AnimationCHOP.toe | Bin 101938 -> 99602 bytes td/modules/keyframer.py | 8 +++++++- td/modules/widgets.py | 30 +++++++++++++++++------------- 3 files changed, 24 insertions(+), 14 deletions(-) diff --git a/td/AnimationCHOP.toe b/td/AnimationCHOP.toe index b18c67fc2b36d21912b61aed9efdd57c2e0b7dfc..497278787b6a8e2e2845ca8a0cf0157831d84a98 100644 GIT binary patch literal 99602 zcmV(yKR)N$biX|ejzB7uN6Z;?H{*Y)lve!#il3H2;6OSq^yePFZ7tejIt8feS!r$}zgzlh#w?pj%Kt=@pA?^vH&E?X&QC1^ErG zXbG-JI3~RPLUq z(?n2#=97IH3KOC~N|RQKhUPU;dY^s7K!gYX@W$5&w@4W_Fz{d&-=A|CvR5(TTN@f$ z*R<(zw;H!-fOe3l%d2rp7-6-GGl)!R9=skR2|mf`_u!l-19EgIf=dDVhnDNb+bSY% z`%RKPa$R%&LMV#Q%71ADSn z?QA(dghV2vXEzI}mc2Jzx}&$LD^z;u$CMD3hUofrM5uvu1g1}>A<|VE@<5k%`3h>t zSyaxYsD?I8;DV`KIo*e3rNTMuy;``85{mzQi`A?jn(;2}FGlp5pX(34mX$}4%UCdq zZ=;mIZuAby4AqK2Bir~Mf-R7b-O|0GE!m`24qFJ~fDn~B?&(fa34$2p83^yPK~So( z$K=Va*F1*RHxZ`oMEEoqo<|LmL?BMnd5{6wse&^DXc!`eoX~{F0EfyG;?AvaBZ)d%B*t~?0&%Js`6LVn4wi$;Wlk7|T&+iMmH<5ukV_pr_^7in2wO7mbhI;(=;r|JtgPSt;Mar}W} zOH-VBwD!mZ4fml#3-ce^L|0bIDGi^eZq5f)`<>pv#j8mfM7-C&$9n)Fd%^s?dBarV z?DT@4CJ)wsDMx`680V!vFbdNFPcY%YLG++Z{{fr%82a`fimu4ai2lP?$WOevdxW@^ zJU^^*ha{XPAgz{xWY;U+Ef#brE--Ivp+!b-^qB&eyw0mBQ`#h*el}J}5HZ!d8ZYcm z&UmL%N~M#Kk)JI~U{CWQjfN$Ege|m@1*-k{;Ez5 z7#qZ@k%G~n@%!Lo2vzw<+H%$OPiYFftRf^Z0NcpY)^6-X#?a01LsbKaHvVpE9Z|4E zGo*1MpmGsEeH5vZH8-?egX{S`lfPl{u#ZY(_m$raqVVwT-7*Qve`E3_EEZvOZQup& zhzMJ-#^@f1A^O(|J)bWrTkLhHS&H$d%Y1B(HEe2g;aaIt`r14tuh+aQQB9#^XQuZK>icrmZ# zcig;ctt5~xQAxP(82Hxxqx0<(T(sui@r=wKrH3HntSkBBRp-rI0?P^-ca)R$4X-YU zas8xX0bDa` zzUoY(0Q5_D)lv)@_j5de>l<~7HUy+MeL~YqW*oChlW3h}*64gnHJ)sq@9Rgr`gn+9 zTpLH@3zbk(j1s#FGdG|qmuq`TAzLh6@!CvkC6UC;uZx0{SbPZiC&lR1TmP&g^IW=5 zqY;Rr8run^SFL;9UWg(ks5(ton|DLAw0;<#67YHY<+Wbs#DoiW^QjIcO0cXTB=V&I zdwcCE;Tz9(x3Fe_{;OB^PzUux4i|}bGL{&(z<-O0zIJ$jLn1Ni53?o}WN_8PoH#I+ z6crG>`u#!dPl!n1&a9HxMd!yGd#BlMfY)c@ZMY1ls8N7e>@r z3{@18Ua6%(G(DmS-Bw_}b85Q{=R2d@@P;2ztE$C!CV2$ead6UBHzxGx;>!rFptcNZ zsc0a8@X*o9BgZ3fF2YelB63awZT&<~Ux-e5g? zbga!RT=YVxT*-^4QMe^eW(r<-1gSw`$@KN>Ut zA`)kKx4o_`FjZ^CK->)dE7MHNIsQ|3zP%i)P>c)!!2LxZhr2#uq2)7 zm0_q9!+xR}C%$GG++t?Y0-b)MKfiQhW?}>XCuYdRo>OS<@sqG!DauUq&q60)e%sPewG4 zi>=!VcGCj=*(n{h_KCZhEy1_X0j=jim`G7L0`dPLrS44VX zpl}ZJC#K-ewY$sxWq>88#YVC<38~5HY~OK*R&5^31_hZg$B*n*9Z%}5id`@ZR2mzL zPVKtt_jmyj5)1lz00zT!DgT&$!AE=U*}JyoCTGnQR2~ggC6E!0E@<9^n!1Le`|&y< z4jgtdQrqtBU-x41)~ecor;gJd+if=VGB_4bl$4&OCH5OU|1sRuB?2dt_SToHMWfnp zh<>RIAjU4QsEF7q`@~r)b(p~fgRRn@fl*^4_R|Ik#f<QE|G5$i55W}iw!No=FX-7nc0p8=xNiE-3lk7k0@+7a z+%Q3HhdB%S-9gCT;`^$JVYdS%tr{bcoPPfvMPd-U^qq;TwD}fT2dIIzEL6iOPbb># z@{ION!G@C6h%SN7`%W5kBSkivs=uroFIuQ;wbL(9oK9sb z%TOj9Ey|L&6GjdTx_8ddu%YP1wFwg5+w0KS)>`QM@czJZzsaD{C3?v52<;O|Y+4S& zqKmT@oCwl;W}eRi#!}`uGYeFw7FneF9*+o90fer|HhCK4Osekh+gs0r*aXIXNc=Qa zO*{Ljm*gXuL(pD|?*&6xN_1_3*_^~brw9pQpK0Y`SNaY(@KRJr9z1;N#%aat^E!9IlrLj{u<% z@Y9h0rjbjS$qAi5EnA;Y3rP$nRqCJAGN?^AOAMxv{hWxFlt28c-;}wg{C? z?GTp|kUu-A`zHoz@IHeLIP0N|i7B9Z+{yOFxNpJ0b+R7!vZT`2d&}7lOFi)z976{s ziHJ}PALa2~crK$_a{1${f0VB0dB@>aRR7E9!L0%$DC#&2%kt;A(n<8QPe0~1=>MbE zqji}kUi$%pM}cIib^pw(82>|@+N<}R@|z~=4D}^{-6Q9guiNlN+@W~3K4q?7hh{Rq zqHX}hv_y^#l5r~oDR%+6Jx6Ja=Qd`GwQMz#l%mZ@Stm!}eaGw2ge*$SQNT~b8YKra z#0Jw7V_Fdxy^OF{r5vU4ANHuf)Dk@(hXmx*omSz07e-SG=D*wE+ihSea?vh&w{}8# zXX8L2kzU(6Ju)jk)Lb`VHP|tJEei4J2_2m;m2Uz&{*F7xhcLaTLWircRCcsv_CE|! ztJF9!l%wty`FOoq+^Z%Ob~H2|gBh&#IhTsKjRC#RJ_5d^t{+HemNalfv7*9`s#xb1&cZ%q5F6jW3?3L`l}4UlGQ0~@ z+L$Vkbrw$J-n6OjCyBs@kVKQCi3wAiK8i4a%(96WXG~z<6*SBU-~0F1RFnWFmca#tp3L0?!A19jY-duo*bB@w%#V zMul)@`bcf)KR3tu4f|??n&S<)(0qq3w3C)!ov89PhgEmHrEOe&)CwS=JzS8I zJt?HIQ+(jI_V<@b#nGC!Y332P{FqP*yUl8|z*XB_?RYM)Fgj7k4B9N84D;#2@%DZr z(i`&eLhJn1;loPROQTBT3#wAxLhZ*^LU3^f-}h>zeOb?k8FN;8>du)>&HO*0hqFNd zeTWs5#hG+5a6!yzJ zC;Kfv!_ePCerO*9l}(PI%xvNl&8E8?hh<~7DpeClT1*7EpZytMR`&gh zsIj0hW(JBtQ?4$iVXYD#g3z+UF^{=Sm!KKBt4Sp<4`&JiXDM&VEGL zY}X~xPYV>~wcKXM#S*e_cr-^ogEZ#U_+?|cSun);lO z)z+}YZSWK%?aX#%#<$1OyrIUr68F8iHoY&EA;a$8mQns|tTX@e)57KzfN-zZiEVCg z3^Q!kn|36|;DfJP;oUJY*DeCjm%bQF@IHYNoD}^}pJN=+a(S~F&Q_0QK_j#?+j>1` zFv#jiAC`Oz2n6xx3ip*s4UK#m5D&95BPS7O51E3?kleOQG%^Dyv<>}t;OOIWAHDK# zu`D?Kw<6l;2JkJSeLrUJF_+?*@uT#QT#r`@J-4C9lZ($dL&=<;BtbJsH62bAr7?y? zlCwel%6Fpe_N?CoHTB-@6zwC8=7>(5gcn)$*Ivssay!}20sQ?@S(>rE^kS!mYi{hp zg)u$7pO)C?Oe}${BkVV&3X`pz=3bAdMz>m^0b?1%!QZlwcn%FO{(I%q1pLT?!cx+o z2z|qYX1VM&z#SzG1amFA8qRn6&Yq6zaAoV>p1qPuLQe1GL1MIfdRr$N?h^}2ZKhuD zW-$CxK5l@!w)H;`L+Uvl*I!4gO$8rFQN2z4vKZy6i>`tuM1&PDHa*^L!iy606)z)F zOHF!5GFbAeFI{~TADoTZ1QWuwK(ewq4UzFiHkx_B6v%GrCuxGFwkO=@Nhog$OcVav zOfCH3UdA%*??CH{YKe1l^LUjUYXtKFZg$oC7_i(~Nf_TiTR&zX-g{48XNT|9b(=btf~0*7Nqa{DF&m>C^{bvN<|0Y?;(*Z2{1KEhL~1gH*h> zbp%)f-<0av6J`_yPmEbpmPiV8f&j>p2A)FKm0xIPTwZ6@^;{c_O>dZZKH5QJQY;ID zfFq3fJ58@>4W6T#CV7u!)s8JY8$4KAoLSf|&4KSHlaW@C4fRo-f?))M5fNa_auQYFs=&#pXX!5RtpFg*YlmUe4Nqw0bqA5H1{>+!&!W*+3Z=?2 z!*(ry``Q6wc`+m+WiG?Mx0<`3Whc>VQWBLF#oN#xJxHjMUl`u(q)dzkrcOubynBSz z=6qLrlcb@Kq0zsmreM=M92Tv$8^w1p;#cooHXK#3+3s@Ch^7e9L#<9}`Ns`_H4nCt zXHe~I1jU|LC0>}4R&YUfzRA+a@F|{c-HVF5-JXfpN%Jlh)3@<=UB(=d!W7c6(j&%7 z`LU?SJ=U)-Oz7Hsk5Cw4UTxl=5UAkzQSSY8uj#xa365{5QrBqmiZRyw z;+^cK{1(?xscaoGR%j5_|F=9f43FoK?D%8K6h3}Clw_n3_N)^680p9O*1|A77S69Es`T1dkC>av#sJ1BH*a24upCx6Dz>?HITOMj3$UJ0c z1n5#saN$O)x4p^E*a1Vj!$2B4MJwJ`d|zAr1qH0A651ddDGXP>d6dlHQqs_Z=wv+X1G_ujp`aRg;DubCzjbfbPFmS7?b{Kl0IDi!9nN1kO4Ppv(AjveEF|L-eljcSUJGDcO!3*Bv81a86~)d*hpx4 z-qoFp?1i^G#LWEYF2i`P9rc#~FQHLac5t%_0eP23yX@n}l1s_dx|)(v!B=hOi|L?q z0wP;&zvmUf7T8<&2p;+Gd9VeNFxY9_l4Eos>M);#V-d+g!E`>aOmjY zn3%Ma+`K-{xV0aDgYP`4anI)Dy&@i#ypgKNXtwh>_PcY4Wz_Gw#?TU%s!)&B1E2cA z?e`5DffMV*L#fRMy)>7OFPv@ZgVHIHKavu3B02MifI1iEe>3%<5iED9-bp(7kjiK9 zn3n=Z!pFs&>*L9Yy&Z9w3A+(4#I3-le&Jr_yvg=L4)P`LcnJQRNVFWO)fy16?-#N+%*Z1e zd}AWM*}bnEKZ>HUVy8C{VoZJJohbq-rjsvW5P>na>KA?GsJLhAVVC;lplfc5Y3JtP z$r!PaS0aUdoJ{s1g%*#w4kU;$>RFv9w6hK%F^zUPWzE7v9pP`c>gaD!Z9SzHU= zbet6i_Ix(iy)<^ki$3KWwl7^bU!UxvS3iZAxI+z zUN7)3cEl}ch{_7nO7|u3#1h$3WY@+hh-`UK9UJJtf;PoREaD}{c@eyc{vzVnZ)_$2 zt@D@o&}k%u-$r+BnUj7vS5k}syxeo}>~cAXPx6|xlY9xzxvCXPEnYJ95h&KfKZ|@% zz;Mm@R{@Qy4Z!2aU}JbPGzo{2>|t6bhh*a}??E&P)c?Bn$&b`|VZ;BRX;3^9DH>C6 zUAM#2woXL`gc|83q)lR4wD6NgZyfL~){ln2FL0Uu*r_6U#6IQ76)^)v;K*35gjdM< zdPV%mkd=o`Oxej#M0Ws4uL)y$oDon!alo1mdC~2q zUhq@agm#X@iRy34L@t?y_~R)BrUD-1I+WbBVvda#7#27K{6L%|f^ajmTAMRW2=vd!SrFs!EF9T~;sLds*zVG{A&!or zpjRXrQFl7!XvaXCUMyu>t3yGafx4ijXmWrP@o!tXn(ZjV3AT}ksy2T$wG1m5Kv6n$>K8Jx)=BTa=E}wlJ1ar%>`0V#>hrd3Dfv%FiLvTg_Y~ln1((S?vc|q+bWdPcx_ly^&SdBsDU19A+x*&KCNE+AS7$tM+qs!M3^iF@0YU?$jpTvCl#x zhxn2E&v0%b5|7i$1%UPcg^qSo1-B2{{0Y*-@Rr3#Z5*ypm-4BAn=lkSIKdAg zBDFnYKCRnKc#XOIzoe1`L9wgWuC4Uya`lte2Em+yn;` zRWo^+*_AFvZmfLNLLcW=cK$MnL_Obra3zuoDV0s!z z4+@#HE&ug0^OFn9*5C7%Isa^W7k?# z;Aid~V+0x+jCP)tSIOB}NMi4J7LCGfN_w?B;p>^<)*y;@>nn*J1mMrK31A?linM#< z@U7CSV)>D{7xWgK-kMM0ARqK|_x|DEHG;Z~QtlP`c!CzpDc$91H5zlBUc~EAwdOOzJ$l-vr@tIagFLIAXN??y1N{g6r z__7AxVbEt@B0i?@Xy1zF8EI$^d3yAI7R-XkFir<#6G{W2wE(0>F)^@9@aP5={r`PV zID|fq65T@?+)EbHHuP&*Vz1%CTKV~oCMbL7IU*ZE?gF#LbWYoVoWg zY);0^-4<($VNx;eAHE^Ap+SB4M&LevCO5KDFh{Ufa^!yXa^B|ey*h6Kr$awfo5U?3 zOc@At3H@#MUYzecc~xd#>#UJDVL2P8_CFF#Gs)6u?Twnoqr zas8uuSImiZsQ7-Y5x?46lamEj?fygrVfj3=EZBHo1+;G+;WJC6tU-(=ld@kOT!W z6>wP=%K_Iopd`n}2=+>Q)Oof&^*~xJe-nOwv)>LvrhX-TEQjyfa%%e%0D5~*D$ne{ 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�k!LUU7=dw38 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 A2><{9 diff --git a/td/modules/keyframer.py b/td/modules/keyframer.py index 18f92bb..3b51d5e 100644 --- a/td/modules/keyframer.py +++ b/td/modules/keyframer.py @@ -1,3 +1,7 @@ +from cProfile import run +import traceback + + vMath = op('vsu').module.VMath() TD_CHOP_CHANNEL = Channel pop_menu = op.TDResources.PopMenu @@ -100,9 +104,11 @@ def __init__(self, ownerComp): ] } } - self.init() + + + @property def AnimationChop(self): if self.ownerComp.par.Animationchop.eval() is not None: diff --git a/td/modules/widgets.py b/td/modules/widgets.py index 4b38730..4320ed5 100644 --- a/td/modules/widgets.py +++ b/td/modules/widgets.py @@ -153,11 +153,12 @@ def __init__(self, ownerComp, updateElements): self.doUndo = ownerComp.par.Doundo.eval() else: for i in range(vsu.getOpDepth(ownerComp)): - if hasattr(ownerComp.parent(i).par, 'Doundo'): - self.doUndo = ownerComp.parent(i).par.Doundo.eval() - break - else: - self.doUndo = True + if ownerComp.parent(i): + if hasattr(ownerComp.parent(i).par, 'Doundo'): + self.doUndo = ownerComp.parent(i).par.Doundo.eval() + break + else: + self.doUndo = True def ExpandVal(self, low, high, value, Type): value = ((high - low) * (value)) / 1 + low @@ -769,23 +770,26 @@ def OpenList(self, droplistWidget): compH = self.listItems.numRows * self.DroplistWidget.ItemHeight height = min(maxHeight, compH) width = self.DroplistWidget.ListWidth + self.ownerComp.par.w = width self.listContainer.par.w = width - if self.listContainer.par.pvscrollbar.eval(): - w2 = width - 12 - self.list.par.w = w2 + scrollbar_active = self.listContainer.par.pvscrollbar.eval() != 'off' + scrollbar_width = 12 + if scrollbar_active: + self.list.par.w = width - scrollbar_width else: self.list.par.w = width # self.list.par.w = width self.listContainer.par.h = height if opened == False: + absMouseX = self.absMouse['tx'].eval() absMouseY = self.absMouse['ty'].eval() - mouseX = (self.DroplistWidget.panel.insideu - * self.DroplistWidget.width) + mouseX = (self.DroplistWidget.op('Button').panel.insideu + * self.DroplistWidget.op('Button').width) mouseY = (self.DroplistWidget.panel.insidev * self.DroplistWidget.height) - x = width - mouseX - 8 + x = width * .5 - mouseX if absMouseY >= height + self.DroplistWidget.ItemHeight: y = - height * 0.5 - mouseY else: @@ -874,9 +878,9 @@ def GetToggles(self, init=False): if n < len(prevToggles[i]): row.append(prevToggles[i][n]) else: - row.append(self.ownerComp.par.Defaulttogglevalue.eval()) + row.append(False) else: - row.append(self.ownerComp.par.Defaulttogglevalue.eval()) + row.append(False) default.append(row) if not init and numRows != len(self.Toggles): From 6c15919bb11ec87e22ff586ad48cda3b2ce87956 Mon Sep 17 00:00:00 2001 From: keithlostracco Date: Sun, 26 Jul 2026 11:32:59 -0700 Subject: [PATCH 05/18] docs: document keyframe value semantics and add channel[i] = kf Keyframes and Points come out of a channel as detached copies, so setting a property on one leaves the channel untouched. This is forced by anim: keyframes have no id and are exposed only as const&, because every edit has to clamp the time between its neighbours, re-solve their handles and invalidate the eval cache. Channel is the opposite -- a live handle that re-resolves by id on each access. Nothing said so, and channel[0].value = x reads like an assignment while doing nothing. Document the split in the type docstrings, on each keyframe accessor and in docs/, and add channel[index] = keyframe as a synonym for update_keyframe so the read-modify-write round trip is discoverable from the subscript a user already reached for. Deletion by subscript raises rather than being added silently. Also pin the sequence protocol, which had no coverage, and correct the assumption that a time edit re-sorts the channel: it clamps, so keyframes never reorder and an index stays valid until a create or delete. --- docs/Channel.md | 33 +++++++-- docs/Keyframe.md | 16 +++- docs/Point.md | 5 ++ docs/README.md | 48 ++++++++++++ src/py_anim_bindings/py_channel.cpp | 105 +++++++++++++++++++++++++-- src/py_anim_bindings/py_keyframe.cpp | 12 ++- src/py_anim_bindings/py_point.cpp | 7 +- tests/python/test_channel.py | 93 ++++++++++++++++++++++++ tests/python/test_keyframe.py | 33 ++++++++- 9 files changed, 331 insertions(+), 21 deletions(-) diff --git a/docs/Channel.md b/docs/Channel.md index 4d6b9e5..dd21662 100644 --- a/docs/Channel.md +++ b/docs/Channel.md @@ -2,6 +2,12 @@ A Channel represents an individual animation curve containing keyframes. Channels support sequence operations for easy keyframe access and iteration. +A Channel is a **live handle**: it resolves to the operator's channel on every +access, so two Channel objects naming the same channel see each other's edits, +and one held past a `remove_channel()` raises `RuntimeError`. The keyframes it +returns are **detached copies** — see +[Keyframes are values, Channels are handles](README.md#keyframes-are-values-channels-are-handles). + ## Properties | Property | Type | Description | @@ -45,17 +51,21 @@ Insert an existing keyframe into the channel. ### Keyframe Access +All accessors below return a **detached copy**. Setting a property on the +returned Keyframe changes the copy only; write it back with +`channel[index] = kf` to apply it. + #### `keyframe(index: int) -> Keyframe` -Get keyframe at specified index. +Get a copy of the keyframe at specified index. #### `prev_keyframe(time: float) -> Keyframe | None` -Get the keyframe immediately before the specified time. +Get a copy of the keyframe immediately before the specified time. #### `next_keyframe(time: float) -> Keyframe | None` -Get the keyframe immediately after the specified time. +Get a copy of the keyframe immediately after the specified time. #### `closest_keyframe(time: float) -> Keyframe | None` -Get the keyframe closest to the specified time. +Get a copy of the keyframe closest to the specified time. #### `delete_keyframe(index: int) -> None` Remove keyframe at specified index. @@ -63,7 +73,10 @@ Remove keyframe at specified index. ### Keyframe Modification #### `update_keyframe(index: int, keyframe: Keyframe) -> None` -Replace keyframe at index with new keyframe. +Replace keyframe at index with new keyframe. Equivalent to +`channel[index] = keyframe`. The time is clamped between the neighbouring +keyframes — keyframes never reorder — and the neighbouring handles are re-solved +as needed. #### `set_keyframe_time(index: int, time: float) -> None` Set the time of keyframe at index. @@ -119,10 +132,15 @@ channel = anim_chop.get_channel("tx") # Length count = len(channel) -# Indexing +# Indexing -- returns a detached copy first_keyframe = channel[0] last_keyframe = channel[-1] +# Assignment -- writes a keyframe back, same as update_keyframe(index, kf) +kf = channel[0] +kf.value = 55.0 +channel[0] = kf + # Iteration for keyframe in channel: print(f"Time: {keyframe.time}, Value: {keyframe.value}") @@ -132,6 +150,9 @@ if keyframe in channel: print("Keyframe exists in channel") ``` +Indices may be negative. `del channel[index]` is not supported — use +`delete_keyframe(index)`. + ## Examples ```python diff --git a/docs/Keyframe.md b/docs/Keyframe.md index b7586aa..296e9c6 100644 --- a/docs/Keyframe.md +++ b/docs/Keyframe.md @@ -2,6 +2,12 @@ A Keyframe represents a single animation key with position, handles, and interpolation settings. +A Keyframe is a **value, not a handle**. One obtained from a channel is a +detached copy, so the property setters below change the copy alone — write it +back with `channel[index] = kf` to apply it, or use the channel's +`set_keyframe_*` methods to edit in place. See +[Keyframes are values, Channels are handles](README.md#keyframes-are-values-channels-are-handles). + ## Properties | Property | Type | Description | @@ -81,16 +87,22 @@ kf4 = anim_chop.Keyframe( handle_mode=anim_chop.HandleMode.FREE ) -# Modify properties +# Modify properties (kf1 is standalone, so these are all it needs) kf1.time = 15 kf1.value = 75 kf1.function = anim_chop.Function.BEZIER kf1.handle_mode = anim_chop.HandleMode.SMOOTH -# Handle manipulation +# Handle manipulation. Assign a whole Point -- kf1.in_handle is itself a copy, +# so kf1.in_handle.time = 10 would change nothing. kf1.in_handle = anim_chop.Point(10, 50) kf1.out_handle = anim_chop.Point(20, 100) +# Editing a keyframe that came from a channel needs the write-back step +kf = channel[0] +kf.value = 55 +channel[0] = kf + # State operations state = kf1.state # Save keyframe state kf_copy = anim_chop.Keyframe() diff --git a/docs/Point.md b/docs/Point.md index 781f136..60eecff 100644 --- a/docs/Point.md +++ b/docs/Point.md @@ -2,6 +2,11 @@ A Point represents a time-value pair used for keyframe positions and handle coordinates. +Like [Keyframe](Keyframe.md), a Point is a **value**: one read from a keyframe is +a detached copy, so `kf.in_handle.time = 5` changes nothing. Assign a whole Point +instead — `kf.in_handle = Point(5, 20)`, or +`channel.set_keyframe_in_handle(index, Point(5, 20))`. + ## Properties | Property | Type | Description | diff --git a/docs/README.md b/docs/README.md index ba4fe75..86e2aa9 100644 --- a/docs/README.md +++ b/docs/README.md @@ -32,6 +32,54 @@ for kf in channel: print(f"Time: {kf.time}, Value: {kf.value}") ``` +## Keyframes are values, Channels are handles + +This is the one piece of the API worth reading before you write against it. + +**`Channel` is a live handle.** It resolves to the operator's channel on every +access, so two `Channel` objects naming the same channel see each other's edits, +and a `Channel` held past a `remove_channel()` raises `RuntimeError` rather than +reading freed memory. + +**`Keyframe` and `Point` are values.** `channel.keyframe(i)`, `channel[i]`, +iteration, `prev_keyframe`, `next_keyframe` and `closest_keyframe` all hand back +a *detached copy*. Setting a property on that copy changes the copy alone: + +```python +kf = channel[0] +kf.value = 55.0 # updates the copy +channel[0].value # still the original value -- the channel is untouched +``` + +That is not an oversight. A keyframe has no identity of its own: they live in a +time-ordered list, and any edit has to clamp the keyframe's time between its +neighbours, re-solve their handles and invalidate the channel's evaluation +cache. Only the channel can do that, so only the channel exposes mutators. + +(Because times are clamped rather than re-sorted, keyframes never reorder — an +index stays valid until a keyframe is created or deleted.) + +There are two ways to edit a keyframe, and both are fine: + +```python +# In place -- best when you are changing one thing. +channel.set_keyframe_value(0, 55.0) + +# Round trip -- best when you are changing several at once. +kf = channel[0] +kf.value = 55.0 +kf.function = op.Function.LINEAR +channel[0] = kf # same as channel.update_keyframe(0, kf) +``` + +The same applies one level down: `kf.in_handle` is a copy too, so +`kf.in_handle.time = 5` does nothing. Assign a whole `Point` instead — +`kf.in_handle = op.Point(5, 20)`. + +The `Keyframe` property setters are not dead weight — they are how you build a +keyframe to hand *to* a channel, via `create_keyframe`, `emplace_keyframe` or +the round trip above. + ## State Management All objects support serializable state dictionaries for saving/loading: diff --git a/src/py_anim_bindings/py_channel.cpp b/src/py_anim_bindings/py_channel.cpp index 7c8764a..1dadd72 100644 --- a/src/py_anim_bindings/py_channel.cpp +++ b/src/py_anim_bindings/py_channel.cpp @@ -971,11 +971,30 @@ static PyMethodDef PY_Channel_methods[] = { {"create_keyframe_from_state", (PyCFunction)PY_Channel_create_keyframe_from_state, METH_VARARGS, "Create a keyframe from a state dictionary"}, {"emplace_keyframe", (PyCFunction)PY_Channel_emplace_keyframe, METH_VARARGS, "Emplace a keyframe (move) into the channel"}, {"delete_keyframe", (PyCFunction)PY_Channel_remove_keyframe, METH_VARARGS, "Delete a keyframe by index"}, - {"keyframe", (PyCFunction)PY_Channel_get_keyframe, METH_VARARGS, "Get a keyframe by index"}, - {"prev_keyframe", (PyCFunction)PY_Channel_prev_keyframe, METH_VARARGS, "Get previous keyframe before time"}, - {"next_keyframe", (PyCFunction)PY_Channel_next_keyframe, METH_VARARGS, "Get next keyframe after time"}, - {"closest_keyframe", (PyCFunction)PY_Channel_closest_keyframe, METH_VARARGS, "Get closest keyframe to time"}, - {"update_keyframe", (PyCFunction)PY_Channel_update_keyframe, METH_VARARGS, "Update a keyframe at index with a new keyframe object"}, + {"keyframe", (PyCFunction)PY_Channel_get_keyframe, METH_VARARGS, + "keyframe(index) -> Keyframe\n\n" + "Return a detached copy of the keyframe at index. Assigning to the copy's\n" + "properties does not touch the channel; write it back with\n" + "channel[index] = kf (or update_keyframe), or use the set_keyframe_*\n" + "methods to edit in place."}, + {"prev_keyframe", (PyCFunction)PY_Channel_prev_keyframe, METH_VARARGS, + "prev_keyframe(time) -> Keyframe\n\n" + "Return a detached copy of the keyframe immediately before time. See\n" + "keyframe() for how to write changes back."}, + {"next_keyframe", (PyCFunction)PY_Channel_next_keyframe, METH_VARARGS, + "next_keyframe(time) -> Keyframe\n\n" + "Return a detached copy of the keyframe immediately after time. See\n" + "keyframe() for how to write changes back."}, + {"closest_keyframe", (PyCFunction)PY_Channel_closest_keyframe, METH_VARARGS, + "closest_keyframe(time) -> Keyframe\n\n" + "Return a detached copy of the keyframe nearest to time. See keyframe()\n" + "for how to write changes back."}, + {"update_keyframe", (PyCFunction)PY_Channel_update_keyframe, METH_VARARGS, + "update_keyframe(index, keyframe) -> None\n\n" + "Replace the keyframe at index. This is the write half of the\n" + "read-modify-write round trip, and the same as channel[index] = keyframe.\n" + "The time is clamped between the neighbouring keyframes, so keyframes\n" + "never reorder, and the neighbouring handles are re-solved as needed."}, {"set_keyframe_time", (PyCFunction)PY_Channel_set_keyframe_time, METH_VARARGS, "Set keyframe time at index"}, {"set_keyframe_value", (PyCFunction)PY_Channel_set_keyframe_value, METH_VARARGS, "Set keyframe value at index"}, {"set_keyframe_position", (PyCFunction)PY_Channel_set_keyframe_position, METH_VARARGS, "Set keyframe position at index"}, @@ -1038,10 +1057,66 @@ static PyObject* PY_Channel_mp_subscript(PY_Channel *self, PyObject *key) { return NULL; } +// channel[index] = keyframe -- a synonym for update_keyframe(index, keyframe). +// Keyframes come out of a channel as detached copies (anim only exposes them as +// const&, since every edit has to clamp the time between its neighbours, +// re-solve the neighbouring handles and invalidate the eval cache), so +// read-modify-write is the only way to edit one through a Keyframe object. +// Assignment makes that round trip visible; without it the natural-looking +// channel[0].value = x silently updates the copy alone. +static int PY_Channel_mp_ass_subscript(PY_Channel *self, PyObject *key, PyObject *value) { + auto channelData = getChannelData(self, false); + if (!channelData.channel) { + PyErr_SetString(PyExc_RuntimeError, "Channel is not valid"); + return -1; + } + + if (!PyLong_Check(key)) { + PyErr_SetString(PyExc_TypeError, "Channel indices must be integers"); + return -1; + } + + if (value == NULL) { + PyErr_SetString(PyExc_TypeError, + "Channel does not support keyframe deletion by subscript; " + "use delete_keyframe(index)"); + return -1; + } + + Py_ssize_t index = PyLong_AsSsize_t(key); + if (index == -1 && PyErr_Occurred()) { + return -1; + } + + // Handle negative indices + if (index < 0) { + index += (Py_ssize_t)channelData.channel->num_keyframes(); + } + + anim::Keyframe kf; + if (!PY_ObjectToKeyframe(value, kf)) { + // PY_ObjectToKeyframe already sets the error + return -1; + } + + try { + channelData.channel->update_keyframe(static_cast(index), kf); + if (channelData.node_struct) { + channelData.node_struct->context->makeNodeDirty(); + } + return 0; + } catch (const std::exception& e) { + // update_keyframe throws out_of_range for a bad index, matching the + // IndexError that reading channel[index] raises for the same input. + PyErr_SetString(PyExc_IndexError, e.what()); + return -1; + } +} + static PyMappingMethods PY_Channel_as_mapping = { (lenfunc)PY_Channel_len, // mp_length (binaryfunc)PY_Channel_mp_subscript, // mp_subscript - 0, // mp_ass_subscript + (objobjargproc)PY_Channel_mp_ass_subscript, // mp_ass_subscript }; // --- Properties --- @@ -1081,7 +1156,23 @@ PyTypeObject PY_ChannelType = { PyObject_GenericSetAttr, // tp_setattro 0, // tp_as_buffer Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, // tp_flags - "Channel object", // tp_doc + // tp_doc + "A named animation curve: a time-sorted sequence of keyframes.\n\n" + "A Channel is a live handle. It resolves to the operator's channel on every\n" + "access, so two Channel objects naming the same channel see each other's\n" + "edits, and one held past a remove_channel() raises rather than reading\n" + "freed memory.\n\n" + "Keyframes are the opposite: values, not handles. keyframe(), the iterator\n" + "and channel[index] all hand back detached copies, because a keyframe has no\n" + "identity of its own -- editing one has to clamp its time between its\n" + "neighbours, re-solve their handles and invalidate the eval cache, which\n" + "only the channel can do. Edit them either in place:\n\n" + " channel.set_keyframe_value(0, 55.0)\n\n" + "or by round trip:\n\n" + " kf = channel[0]\n" + " kf.value = 55.0\n" + " channel[0] = kf\n\n" + "Setting a property on a copy alone leaves the channel unchanged.", 0, // tp_traverse 0, // tp_clear 0, // tp_richcompare diff --git a/src/py_anim_bindings/py_keyframe.cpp b/src/py_anim_bindings/py_keyframe.cpp index 70052ea..7860aea 100644 --- a/src/py_anim_bindings/py_keyframe.cpp +++ b/src/py_anim_bindings/py_keyframe.cpp @@ -521,7 +521,17 @@ PyTypeObject PY_KeyframeType = { PyObject_GenericSetAttr, // tp_setattro 0, // tp_as_buffer Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, // tp_flags - "Keyframe object", // tp_doc + // tp_doc + "A single key on an animation curve: position, Bezier handles,\n" + "interpolation function and handle mode.\n\n" + "Keyframes are values, not handles into a channel. One obtained from a\n" + "channel is a detached copy, so setting its properties changes the copy\n" + "alone; write it back with channel[index] = kf to apply it. Keyframes are\n" + "also constructible standalone, which is what the property setters are\n" + "mainly for:\n\n" + " kf = anim_chop.Keyframe(time=30, value=100)\n" + " kf.function = anim_chop.Function.LINEAR\n" + " channel.emplace_keyframe(kf)", // tp_doc 0, // tp_traverse 0, // tp_clear PY_Keyframe_richcompare, // tp_richcompare diff --git a/src/py_anim_bindings/py_point.cpp b/src/py_anim_bindings/py_point.cpp index 16da922..4d14553 100644 --- a/src/py_anim_bindings/py_point.cpp +++ b/src/py_anim_bindings/py_point.cpp @@ -226,7 +226,12 @@ PyTypeObject PY_PointType = { PyObject_GenericSetAttr, // tp_setattro 0, // tp_as_buffer Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, // tp_flags - "Point objects", // tp_doc + // tp_doc + "A (time, value) pair, used for keyframe positions and Bezier handles.\n\n" + "Like Keyframe, a Point is a value: one read from a keyframe is a detached\n" + "copy, so kf.in_handle.time = x changes nothing. Assign a whole Point\n" + "instead -- kf.in_handle = Point(x, y), or\n" + "channel.set_keyframe_in_handle(index, Point(x, y)).", // tp_doc 0, // tp_traverse 0, // tp_clear PY_Point_richcompare, // tp_richcompare diff --git a/tests/python/test_channel.py b/tests/python/test_channel.py index 526982b..6f302d4 100644 --- a/tests/python/test_channel.py +++ b/tests/python/test_channel.py @@ -124,6 +124,99 @@ def test_set_keyframe_position(ramp): assert ramp.keyframe(1).value == pytest.approx(75.0) +# --- sequence protocol ------------------------------------------------------ + +def test_len_is_the_keyframe_count(ramp): + assert len(ramp) == 2 + + +def test_subscript_reads_a_keyframe(ramp): + assert ramp[0].time == pytest.approx(0.0) + assert ramp[1].value == pytest.approx(100.0) + + +def test_subscript_accepts_negative_indices(ramp): + assert ramp[-1].time == pytest.approx(30.0) + assert ramp[-2].time == pytest.approx(0.0) + + +def test_iteration_yields_keyframes_in_time_order(ramp): + assert [kf.time for kf in ramp] == pytest.approx([0.0, 30.0]) + + +def test_subscript_out_of_range_raises_index_error(ramp): + with pytest.raises(IndexError): + ramp[99] + + +def test_subscript_assignment_writes_the_keyframe_back(ramp): + kf = ramp[0] + kf.value = 42.0 + ramp[0] = kf + + assert ramp[0].value == pytest.approx(42.0) + + +def test_subscript_assignment_matches_update_keyframe(op, ramp): + other = op.create_channel("other") + other.create_keyframe(0, 0) + + kf = ramp[0] + kf.value = 42.0 + kf.function = op.Function.LINEAR + + ramp[0] = kf + other.update_keyframe(0, kf) + + assert ramp[0].value == pytest.approx(other[0].value) + assert ramp[0].function == other[0].function + + +def test_subscript_assignment_accepts_negative_indices(ramp): + kf = ramp[-1] + kf.value = 7.0 + ramp[-1] = kf + + assert ramp[1].value == pytest.approx(7.0) + + +def test_subscript_assignment_clamps_time_to_the_neighbour(ramp): + """Keyframes never reorder: a time past a neighbour clamps to it. + + Writing frame 60 into keyframe 0 pins it at its neighbour's frame 30 + rather than swapping the two, so indices stay stable under time edits. + """ + kf = ramp[0] + kf.time = 60.0 + ramp[0] = kf + + assert [k.time for k in ramp] == pytest.approx([30.0, 30.0]) + + +def test_set_keyframe_time_clamps_the_same_way(ramp): + """The in-place mutator clamps identically -- it is the same code path.""" + ramp.set_keyframe_time(0, 60.0) + assert [k.time for k in ramp] == pytest.approx([30.0, 30.0]) + + +def test_subscript_assignment_out_of_range_raises_index_error(ramp): + kf = ramp[0] + with pytest.raises(IndexError): + ramp[99] = kf + + +def test_subscript_assignment_rejects_non_keyframes(ramp): + with pytest.raises(TypeError): + ramp[0] = 5.0 + + +def test_subscript_deletion_is_not_supported(ramp): + """del points at delete_keyframe rather than silently doing nothing.""" + with pytest.raises(TypeError): + del ramp[0] + assert ramp.num_keyframes == 2 + + # --- extend ----------------------------------------------------------------- def test_extend_defaults_to_hold(op, ramp): diff --git a/tests/python/test_keyframe.py b/tests/python/test_keyframe.py index b91c1da..40c312c 100644 --- a/tests/python/test_keyframe.py +++ b/tests/python/test_keyframe.py @@ -12,7 +12,12 @@ def test_time_and_value(ramp): def test_keyframe_is_a_detached_copy(ramp): - """keyframe(i) hands back a fresh object each call, not a live view.""" + """keyframe(i) hands back a fresh object each call, not a live view. + + Keyframes are values rather than handles: anim exposes them only as + const&, because any edit has to clamp the time between its neighbours and + re-solve their handles, so only the channel can perform one. + """ assert ramp.keyframe(1) is not ramp.keyframe(1) @@ -20,9 +25,9 @@ def test_assigning_to_a_keyframe_does_not_reach_the_channel(ramp): """Setting a property on a returned Keyframe updates only that copy. The assignment succeeds and reads back on the copy, but the channel is - untouched -- Channel.set_keyframe_* are the mutators that persist. This - pins current behaviour; the silent divergence is a sharp edge worth - revisiting. + untouched. This is the documented contract -- the setters exist so a + standalone Keyframe can be built to hand to the channel. The two ways to + persist an edit are covered by the two tests below. """ kf = ramp.keyframe(1) kf.value = 55.0 @@ -32,10 +37,30 @@ def test_assigning_to_a_keyframe_does_not_reach_the_channel(ramp): def test_set_keyframe_value_persists(ramp): + """Editing in place, via the channel's index-based mutators.""" ramp.set_keyframe_value(1, 55.0) assert ramp.keyframe(1).value == pytest.approx(55.0) +def test_round_tripping_a_copy_persists(ramp): + """Editing by round trip: read a copy, change it, write it back.""" + kf = ramp.keyframe(1) + kf.value = 55.0 + ramp[1] = kf + + assert ramp.keyframe(1).value == pytest.approx(55.0) + + +def test_a_handle_read_from_a_keyframe_is_also_a_copy(ramp): + """Point is a value too, so kf.in_handle.time = x changes nothing.""" + kf = ramp.keyframe(1) + original = kf.in_handle.time + + kf.in_handle.time = original + 5.0 + + assert kf.in_handle.time == pytest.approx(original) + + def test_handles_are_points(ramp): kf = ramp.keyframe(0) assert hasattr(kf.in_handle, "time") From cd163b66ffbfdcc86f858bbdedf351e0d8314626 Mon Sep 17 00:00:00 2001 From: keithlostracco Date: Sun, 26 Jul 2026 11:40:55 -0700 Subject: [PATCH 06/18] test: add the TouchDesigner integration harness Adds the local pre-release gate: run_td_tests.ps1 (and .sh) builds the operators, launches tests/td/test.toe, runs the suites inside TouchDesigner, waits for a results.json sentinel, terminates TouchDesigner and exits non-zero on failure. The suite moves from td/tests_scripts/basic_tests.py to tests/td/animation_test.py. It already took the operator as a parameter, so the changes are small: it no longer runs itself on import or looks the operator up by a hardcoded name, its suites are isolated from each other so one blowing up does not hide the rest, and assertions are recorded structurally as well as printed so the host script can report several hundred of them as a handful of lines plus the failures. Adds cooked-output checks, which is the part only an in-TouchDesigner run can do: configure the node's output parameters, let it cook, and verify the emitted samples match Channel.evaluate() across the range. td_test_runner schedules them a few frames after the API suites, since the node has to actually cook in between. CMake now copies the operators into tests/td/Plugins/ as well -- TouchDesigner only loads Custom Operators from a Plugins/ folder beside the .toe, and td/ is what gets packaged for release, so the test project needs its own copy rather than borrowing the example project's. tests/td/test.toe itself is not in the repo; TESTING.md documents the one-time wiring (module DATs synced to these files, plus a bootstrap Execute DAT). --- .gitattributes | 9 + .gitignore | 4 + CMakeLists.txt | 54 ++-- TESTING.md | 157 ++++++++++++ run_td_tests.ps1 | 137 ++++++++++ run_td_tests.sh | 102 ++++++++ .../td/animation_test.py | 233 ++++++++++++++---- tests/td/td_test_runner.py | 168 +++++++++++++ 8 files changed, 793 insertions(+), 71 deletions(-) create mode 100644 TESTING.md create mode 100644 run_td_tests.ps1 create mode 100644 run_td_tests.sh rename td/tests_scripts/basic_tests.py => tests/td/animation_test.py (88%) create mode 100644 tests/td/td_test_runner.py diff --git a/.gitattributes b/.gitattributes index dfe0770..818ec96 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,11 @@ # Auto detect text files and perform LF normalization * text=auto + +# Shell scripts must keep LF even when checked out on Windows -- a CRLF shebang +# line makes them unrunnable on macOS/Linux. +*.sh text eol=lf + +# TouchDesigner project and component files are binary; never touch line endings +# or try to merge them. +*.toe binary +*.tox binary diff --git a/.gitignore b/.gitignore index 07facd0..e89538d 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,10 @@ build # and TouchDesigner writes a per-machine Plugins.json trust file alongside them. # Released binaries are published as release assets instead. td/Plugins/ +tests/td/Plugins/ + +# Sentinel written by the in-TD integration run, consumed by run_td_tests.* +tests/td/results.json # The pytest extension is copied next to its tests so `import animationchop` # resolves; it is a build artifact. diff --git a/CMakeLists.txt b/CMakeLists.txt index 907ed87..5105a17 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -167,30 +167,42 @@ if(SUPPRESS_NOT_REFERENCED_WARNINGS) endforeach() endif() -# Add post-build commands to copy both DLLs to the Plugins folder +# Add post-build commands to copy both DLLs to the Plugins folders. +# +# TouchDesigner only loads Custom Operators from a Plugins/ folder beside the +# .toe, with no way to point it at a build directory, so every project that +# needs the operators gets its own copy: td/ for the example project, and +# tests/td/ for the integration-test project (see TESTING.md). +set(ANIMATIONCHOP_PLUGIN_DIRS + "${CMAKE_SOURCE_DIR}/td/Plugins" + "${CMAKE_SOURCE_DIR}/tests/td/Plugins" +) + foreach(TARGET animation_chop animation_view_chop) - add_custom_command(TARGET ${TARGET} POST_BUILD - COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_SOURCE_DIR}/td/Plugins" - COMMENT "Creating Plugins directory" - ) - - if(APPLE) - # On macOS, copy the entire .plugin bundle - add_custom_command(TARGET ${TARGET} POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_directory - "$" - "${CMAKE_SOURCE_DIR}/td/Plugins/$.plugin" - COMMENT "Copying ${TARGET}.plugin bundle to Plugins directory" - ) - else() - # On other platforms, copy the library file + foreach(PLUGIN_DIR ${ANIMATIONCHOP_PLUGIN_DIRS}) add_custom_command(TARGET ${TARGET} POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy - "$" - "${CMAKE_SOURCE_DIR}/td/Plugins/" - COMMENT "Copying ${TARGET} to Plugins directory" + COMMAND ${CMAKE_COMMAND} -E make_directory "${PLUGIN_DIR}" + COMMENT "Creating Plugins directory: ${PLUGIN_DIR}" ) - endif() + + if(APPLE) + # On macOS, copy the entire .plugin bundle + add_custom_command(TARGET ${TARGET} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory + "$" + "${PLUGIN_DIR}/$.plugin" + COMMENT "Copying ${TARGET}.plugin bundle to ${PLUGIN_DIR}" + ) + else() + # On other platforms, copy the library file + add_custom_command(TARGET ${TARGET} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + "$" + "${PLUGIN_DIR}/" + COMMENT "Copying ${TARGET} to ${PLUGIN_DIR}" + ) + endif() + endforeach() endforeach() # Add platform-specific settings for both targets diff --git a/TESTING.md b/TESTING.md new file mode 100644 index 0000000..673ef42 --- /dev/null +++ b/TESTING.md @@ -0,0 +1,157 @@ +# AnimationCHOP Testing + +Tests live under `tests/`: + +- `tests/python/` — pytest against a compiled test extension (no TouchDesigner needed). +- `tests/td/` — the TouchDesigner project, the in-network test modules, and the + local integration harness (`run_td_tests.ps1` / `run_td_tests.sh`). + +> Note: TouchDesigner cannot run in cloud CI (it needs a license and a GPU), so +> CI only builds the operators and runs the `tests/python` suite. The `tests/td` +> integration test runs against a local TouchDesigner install. + +There is no `tests/cpp` suite. The animation maths lives in the `anim` +submodule, which has its own Catch2 tests; what remains here is binding and +TouchDesigner glue, which the two suites below cover directly. + +## Unit tests (no TouchDesigner) + +```powershell +# Configure + build + run all unit tests in one command: +cmake --workflow --preset dev + +# Re-run just the unit tests after a change: +ctest --preset dev +``` + +CMake auto-detects the uv Python 3.11 (no paths to pass), and the suite runs +through `uv run`, so pytest is fetched automatically — nothing to install first. +(Without uv, install `tests/python/requirements.txt` into the interpreter and +ctest will call `pytest` there instead.) + +`tests/python` builds a small CPython extension (`animationchop`) that compiles +the *real* operator sources against a fake `PY_Context`, so the bindings are +exercised directly rather than through a copy. Once it is built you can also run +pytest on its own: + +```powershell +uv run --with pytest pytest tests/python +``` + +## Integration test (local TouchDesigner) + +`run_td_tests.ps1` is a local pre-release gate. Run the one script and wait for +pass/fail — it compiles the operators, copies them into `tests/td/Plugins/`, +launches TouchDesigner with `tests/td/test.toe`, runs the suites *inside* +TouchDesigner, writes a `results.json` sentinel, then parses the results, +terminates TouchDesigner, and exits non-zero if anything failed. + +```powershell +.\run_td_tests.ps1 # build + run + report +.\run_td_tests.ps1 -NoBuild # reuse the already-built operators +.\run_td_tests.ps1 -OpName animation1 -TimeoutSec 180 +``` + +```bash +./run_td_tests.sh # macOS +./run_td_tests.sh --no-build --op animation1 +``` + +Two modules implement it: + +- **`animation_test.py`** *is* the suite. `run_api_tests(anim_chop)` runs the + binding suites (Point, Keyframe, enums, animation core, channel, advanced, + error handling, state, state errors, state roundtrip) against the real + operator. `setup_cook_test()` / `check_cook_test()` then do what only an + in-TouchDesigner run can: configure the node's output parameters, let it cook, + and check that the samples it emits match what the channels evaluate to. +- **`td_test_runner.py`** drives them. It runs the API suites, schedules the + cook checks a few frames later (the node has to actually cook in between — + that is what `run(..., delayFrames=)` is for; sleeping would block the very + frames being waited on), and writes `results.json`. + +The operator is passed in, so neither module needs to know where it lives. +TouchDesigner is left running; the host script terminates it once the sentinel +appears. + +### One-time wiring + +`tests/td/test.toe` is not in the repo — create it once and save it. The steps: + +**1. Create the project and the operator.** + +- Make a new project and save it as `tests/td/test.toe`. +- Build first (`.\build.ps1`) so `tests/td/Plugins/` exists — TouchDesigner only + loads Custom Operators from a `Plugins/` folder beside the `.toe`, and CMake's + post-build step puts them there. +- Add an **AnimationCHOP** and name it `animation1`. +- **First load after a (re)build:** TouchDesigner shows a modal asking you to + approve/trust the newly built Custom Operator. Click to approve. This is + interactive, so the first integration run after a rebuild may need a manual + click. + +**2. Add the test modules as DATs under `/local/modules`.** + +Each is a Text DAT synced to its file on disk, so the repo stays the source of +truth: + +| Module DAT (`/local/modules/…`) | Synced to file | +| --- | --- | +| `td_test_runner` | `tests/td/td_test_runner.py` | +| `animation_test` | `tests/td/animation_test.py` | + +In each DAT, set the **File** parameter to the path above and use **Sync to +File** so TouchDesigner imports them by name. + +**3. Add the bootstrap Execute DAT.** + +This is the only place that needs to know where the operator is. + +- Enable the Execute DAT's **Start** flag (the `onStart` callback). +- Paste: + + ```python + def onStart(): + import td_test_runner + td_test_runner.start(op('animation1')) # point at your AnimationCHOP + return + ``` + +- **Save** `test.toe`. + +(If you omit the argument, `start()` resolves the operator from the +`ANIMATIONCHOP_OP` environment variable that the host script sets from +`-OpName` / `--op`, defaulting to `animation1`.) + +### Running the suite by hand + +Useful while iterating — watch the textport for the PASS/FAIL lines: + +```python +import animation_test +animation_test.run_api_tests(op('animation1')) +``` + +Or the whole thing including the cooked-output checks, which writes +`results.json` as well: + +```python +import td_test_runner +td_test_runner.start(op('animation1')) +``` + +### Troubleshooting + +**"no results.json after N s"** — the runner never finished. Most likely: +`test.toe` has no bootstrap Execute DAT (or its Start flag is off); TouchDesigner +is sitting on the trust modal for a freshly rebuilt operator; or the project was +saved with the timeline paused, in which case the frame-delayed cook checks never +fire. Open the project by hand once and check the textport — if the API suites +printed but nothing else did, it is the paused-timeline case. + +**The operator does not appear in the palette** — `tests/td/Plugins/` is missing +or empty. Run `.\build.ps1`. + +**Cook checks fail but the API suites pass** — the bindings are fine and the +node's output path is not. `check_cook_test()` prints the worst sample delta, +which distinguishes "wrong values" from "wrong sample count". diff --git a/run_td_tests.ps1 b/run_td_tests.ps1 new file mode 100644 index 0000000..a15b874 --- /dev/null +++ b/run_td_tests.ps1 @@ -0,0 +1,137 @@ +<# +.SYNOPSIS + TouchDesigner integration test: launch TouchDesigner, run the AnimationCHOP + test suites inside it, and gate the exit code on the result. + +.DESCRIPTION + TouchDesigner cannot run in cloud CI (it needs a license and a GPU), so this + is a LOCAL pre-release gate. It launches tests/td/test.toe, which must + contain a one-time bootstrap Execute DAT that calls td_test_runner.start() + on start (see TESTING.md). The in-TD runner writes results.json (and leaves + TouchDesigner running); this script waits for that sentinel, terminates + TouchDesigner, parses the results, and exits 0 (pass) or 1 (fail / timeout). + +.PARAMETER NoBuild Skip building; use the operator already in td/Plugins/. +.PARAMETER Toe Path to the .toe to run. Default: tests/td/test.toe +.PARAMETER OpName Name of the AnimationCHOP operator in the project. Default: animation1 +.PARAMETER TimeoutSec How long to wait for results.json. Default: 180 +.PARAMETER TdPath Path to TouchDesigner.exe. Default: newest install found. + +.EXAMPLE + .\run_td_tests.ps1 # builds, copies the plugin, runs, reports pass/fail +.EXAMPLE + .\run_td_tests.ps1 -NoBuild # reuse the already-built plugin +#> +[CmdletBinding()] +param( + [switch] $NoBuild, + [string] $Toe = (Join-Path $PSScriptRoot "tests/td/test.toe"), + [string] $OpName = "animation1", + [int] $TimeoutSec = 180, + [string] $TdPath = "" +) + +$ErrorActionPreference = "Stop" + +function Resolve-TouchDesigner { + param([string] $Explicit) + if ($Explicit) { + if (Test-Path $Explicit) { return $Explicit } + throw "TouchDesigner.exe not found at: $Explicit" + } + if ($env:ANIMATIONCHOP_TD -and (Test-Path $env:ANIMATIONCHOP_TD)) { return $env:ANIMATIONCHOP_TD } + $candidates = Get-ChildItem "C:/Program Files/Derivative/TouchDesigner*/bin/TouchDesigner.exe" -ErrorAction SilentlyContinue | + Sort-Object FullName -Descending + if ($candidates) { return $candidates[0].FullName } + throw "Could not find TouchDesigner.exe. Pass -TdPath or set ANIMATIONCHOP_TD." +} + +# Build by default so the operators are always freshly compiled AND copied into +# td/Plugins/ (CMake's POST_BUILD step handles the copy). The dev just runs this +# script; no manual build-or-copy step. +if (-not $NoBuild) { + Write-Host "Building AnimationCHOP (compiles + copies to td/Plugins/)..." -ForegroundColor Cyan + & (Join-Path $PSScriptRoot "build.ps1") + if ($LASTEXITCODE -ne 0) { throw "Build failed." } +} + +if (-not (Test-Path $Toe)) { + Write-Host "No project at: $Toe" -ForegroundColor Red + Write-Host "Create it and wire the bootstrap Execute DAT -- see TESTING.md." -ForegroundColor Yellow + exit 1 +} + +$toeFull = (Resolve-Path $Toe).Path +$td = Resolve-TouchDesigner -Explicit $TdPath +$resultsPath = Join-Path (Split-Path -Parent $toeFull) "results.json" + +Write-Host "TouchDesigner : $td" -ForegroundColor DarkGray +Write-Host "Project : $toeFull" -ForegroundColor DarkGray +Write-Host "Results file : $resultsPath" -ForegroundColor DarkGray + +# Clear any stale results so we only ever read this run's output. +if (Test-Path $resultsPath) { Remove-Item $resultsPath -Force } + +# The in-TD runner reads these (inherited by the child process). +$env:ANIMATIONCHOP_RESULTS = $resultsPath +$env:ANIMATIONCHOP_OP = $OpName + +Write-Host "Launching TouchDesigner..." -ForegroundColor Cyan +$proc = Start-Process -FilePath $td -ArgumentList "`"$toeFull`"" -PassThru + +# Wait for the sentinel or timeout. +$deadline = (Get-Date).AddSeconds($TimeoutSec) +$found = $false +while ((Get-Date) -lt $deadline) { + if (Test-Path $resultsPath) { $found = $true; break } + if ($proc.HasExited -and -not (Test-Path $resultsPath)) { + Start-Sleep -Milliseconds 500 # let a last-moment write flush + if (Test-Path $resultsPath) { $found = $true } + break + } + Start-Sleep -Milliseconds 500 +} + +# The in-TD runner leaves TouchDesigner running; terminate it now. +if (-not $proc.HasExited) { + Write-Host "Stopping TouchDesigner..." -ForegroundColor DarkGray + try { Stop-Process -Id $proc.Id -Force -ErrorAction SilentlyContinue } catch {} +} + +if (-not $found) { + Write-Host "" + Write-Host "FAILED: no results.json after $TimeoutSec s." -ForegroundColor Red + Write-Host "Is test.toe wired with the bootstrap Execute DAT? See TESTING.md." -ForegroundColor Yellow + Write-Host "A freshly rebuilt operator also needs a one-time trust click." -ForegroundColor Yellow + exit 1 +} + +$summary = Get-Content $resultsPath -Raw | ConvertFrom-Json +Write-Host "" +Write-Host "==== AnimationCHOP integration results ====" -ForegroundColor Cyan +foreach ($r in $summary.results) { + $tag = if ($r.passed) { "PASS" } else { "FAIL" } + $color = if ($r.passed) { "Green" } else { "Red" } + Write-Host (" [{0}] {1} -- {2}" -f $tag, $r.name, $r.detail) -ForegroundColor $color +} + +# The suites run several hundred assertions, so the per-suite lines above are the +# summary and these are the detail that actually tells you what broke. +if ($summary.failures -and $summary.failures.Count -gt 0) { + Write-Host "" + Write-Host "Failures:" -ForegroundColor Red + foreach ($f in $summary.failures) { + Write-Host (" [{0}] {1} -- {2}" -f $f.suite, $f.name, $f.detail) -ForegroundColor Red + } +} + +Write-Host "" +Write-Host ("Total: {0} passed, {1} failed" -f $summary.passed, $summary.failed) -ForegroundColor Cyan + +if ($summary.success) { + Write-Host "`nAll integration tests passed." -ForegroundColor Green + exit 0 +} else { + Write-Host "`nIntegration tests failed." -ForegroundColor Red + exit 1 +} diff --git a/run_td_tests.sh b/run_td_tests.sh new file mode 100644 index 0000000..fae46de --- /dev/null +++ b/run_td_tests.sh @@ -0,0 +1,102 @@ +#!/usr/bin/env bash +# TouchDesigner integration test (macOS). Builds + copies the operators, launches +# TouchDesigner with tests/td/test.toe (which must contain the bootstrap Execute +# DAT — see TESTING.md), waits for the results.json sentinel the in-TD runner +# writes, then parses it and exits 0 (pass) or 1 (fail / timeout). +# +# Usage: ./run_td_tests.sh [--no-build] [--op NAME] [--timeout SEC] +# [--toe PATH] [--td APP_PATH] +set -euo pipefail + +cd "$(dirname "$0")" + +no_build=0 +op="animation1" +timeout_sec=180 +toe="tests/td/test.toe" +td_app="${ANIMATIONCHOP_TD:-/Applications/TouchDesigner.app}" + +while [[ $# -gt 0 ]]; do + case "$1" in + --no-build) no_build=1; shift ;; + --op) op="$2"; shift 2 ;; + --timeout) timeout_sec="$2"; shift 2 ;; + --toe) toe="$2"; shift 2 ;; + --td) td_app="$2"; shift 2 ;; + *) echo "Unknown argument: $1" >&2; exit 2 ;; + esac +done + +if [[ ! -f "$toe" ]]; then + echo "No project at: $toe" >&2 + echo "Create it and wire the bootstrap Execute DAT — see TESTING.md." >&2 + exit 1 +fi + +toe_full="$(cd "$(dirname "$toe")" && pwd)/$(basename "$toe")" +td_bin="$td_app/Contents/MacOS/TouchDesigner" +results_path="$(dirname "$toe_full")/results.json" + +if [[ ! -x "$td_bin" ]]; then + echo "TouchDesigner not found at: $td_bin (set ANIMATIONCHOP_TD or pass --td)" >&2 + exit 1 +fi + +echo "TouchDesigner : $td_bin" +echo "Project : $toe_full" +echo "Results file : $results_path" + +if [[ $no_build -eq 0 ]]; then + echo "Building AnimationCHOP (compiles + copies to td/Plugins/)..." + ./build.sh +fi + +rm -f "$results_path" + +export ANIMATIONCHOP_RESULTS="$results_path" +export ANIMATIONCHOP_OP="$op" + +echo "Launching TouchDesigner..." +"$td_bin" "$toe_full" & +td_pid=$! + +deadline=$(( $(date +%s) + timeout_sec )) +found=0 +while [[ $(date +%s) -lt $deadline ]]; do + if [[ -f "$results_path" ]]; then found=1; break; fi + if ! kill -0 "$td_pid" 2>/dev/null; then + sleep 1 + [[ -f "$results_path" ]] && found=1 + break + fi + sleep 1 +done + +# The in-TD runner leaves TouchDesigner running; terminate it now. +kill "$td_pid" 2>/dev/null || true + +if [[ $found -eq 0 ]]; then + echo "" + echo "FAILED: no results.json after ${timeout_sec}s." >&2 + echo "Is test.toe wired with the bootstrap Execute DAT? See TESTING.md." >&2 + echo "A freshly rebuilt operator also needs a one-time trust click." >&2 + exit 1 +fi + +echo "" +echo "==== AnimationCHOP integration results ====" +python3 - "$results_path" <<'PY' +import json, sys +d = json.load(open(sys.argv[1])) +for r in d["results"]: + tag = "PASS" if r["passed"] else "FAIL" + detail = (" -- " + r["detail"]) if r.get("detail") else "" + print(f" [{tag}] {r['name']}{detail}") +failures = d.get("failures") or [] +if failures: + print("\nFailures:") + for f in failures: + print(f" [{f['suite']}] {f['name']} -- {f['detail']}") +print(f"\nTotal: {d['passed']} passed, {d['failed']} failed") +sys.exit(0 if d.get("success") else 1) +PY diff --git a/td/tests_scripts/basic_tests.py b/tests/td/animation_test.py similarity index 88% rename from td/tests_scripts/basic_tests.py rename to tests/td/animation_test.py index f212547..1371a15 100644 --- a/td/tests_scripts/basic_tests.py +++ b/tests/td/animation_test.py @@ -1,7 +1,13 @@ -""" -Test suite for AnimationCHOP Python API -This script tests all the exposed Python functions for channel and keyframe management. -Run this in TouchDesigner with an AnimationCHOP node. +"""In-TouchDesigner test suite for the AnimationCHOP Python API. + +This is the integration half of the test story. The pytest suite under +tests/python covers the same bindings without TouchDesigner, against a fake +node; this one runs against the real operator inside a real project, so it also +exercises the parts that only exist there -- the node cooking, its output +channels, and the parameters. + +Loaded as a DAT under /local/modules and driven by td_test_runner, which passes +the operator in. Importing this module runs nothing; call run_api_tests(). """ import traceback @@ -9,11 +15,30 @@ import sys class TestResult: + """Collects assertions, printing as it goes and recording for results.json. + + Assertions are grouped into suites so a run of several hundred can be + reported as a handful of lines plus the individual failures. + """ + def __init__(self): self.passed = 0 self.failed = 0 self.errors = [] - + self.records = [] + self.suite = "general" + + def begin_suite(self, name): + self.suite = name + + def _record(self, passed, message, detail=""): + self.records.append({ + "suite": self.suite, + "name": message, + "passed": bool(passed), + "detail": detail, + }) + def _get_caller_line(self): """Get the line number of the calling test function""" frame = inspect.currentframe() @@ -44,44 +69,53 @@ def assert_true(self, condition, message): line_no = self._get_caller_line() if condition: self.passed += 1 + self._record(True, message) print(f"✓ PASS: {message}") else: self.failed += 1 error_msg = f"✗ FAIL: {message} (line {line_no})" + self._record(False, message, f"line {line_no}") print(error_msg) self.errors.append(error_msg) - + def assert_false(self, condition, message): self.assert_true(not condition, message) - + def assert_equal(self, expected, actual, message): line_no = self._get_caller_line() if expected == actual: self.passed += 1 + self._record(True, message) print(f"✓ PASS: {message} (expected: {expected}, got: {actual})") else: self.failed += 1 error_msg = f"✗ FAIL: {message} (expected: {expected}, got: {actual}) (line {line_no})" + self._record(False, message, + f"expected {expected}, got {actual} (line {line_no})") print(error_msg) self.errors.append(error_msg) - + def assert_not_none(self, value, message): self.assert_true(value is not None, message) - + def assert_none(self, value, message): self.assert_true(value is None, message) - + def assert_near(self, expected, actual, tolerance, message): line_no = self._get_caller_line() if abs(expected - actual) <= tolerance: self.passed += 1 + self._record(True, message) print(f"✓ PASS: {message} (expected: {expected}, got: {actual}, tolerance: {tolerance})") else: self.failed += 1 error_msg = f"✗ FAIL: {message} (expected: {expected}, got: {actual}, tolerance: {tolerance}) (line {line_no})" + self._record(False, message, + f"expected {expected}, got {actual} " + f"(tolerance {tolerance}, line {line_no})") print(error_msg) self.errors.append(error_msg) - + def record_exception(self, test_name, exception): """Record an exception with its actual line number""" try: @@ -92,11 +126,12 @@ def record_exception(self, test_name, exception): error_msg = f"✗ FAIL: {test_name} - {exception}" except: error_msg = f"✗ FAIL: {test_name} - {exception}" - + self.failed += 1 + self._record(False, test_name, str(exception)) print(error_msg) self.errors.append(error_msg) - + def print_summary(self): total = self.passed + self.failed print(f"\n{'='*50}") @@ -1125,60 +1160,158 @@ def test_state_roundtrip(anim_chop, result): result.record_exception("State roundtrip test error", e) -def run_tests(cleanup=False): - # Get the current operator (this should be called from the AnimationCHOP node) +# --- cooked output ----------------------------------------------------------- +# +# Everything above tests the Python bindings, which the pytest suite also covers +# headlessly. These two do what only an in-TouchDesigner run can: check that the +# operator actually cooks, and that the samples it emits match what the channels +# evaluate to. They are split in half because the node has to cook between them, +# which takes a frame -- td_test_runner supplies the delay. + +COOK_START = 0.0 +COOK_END = 2.0 +COOK_RATE = 60.0 + +# Range mode sizes its output as end_time * sample_rate. Note it does NOT +# subtract start_time, so this expectation only holds while COOK_START is 0 -- +# see the note in check_cook_test(). +COOK_SAMPLES = int(COOK_END * COOK_RATE) + +# The samples themselves span start..end inclusive (Channel::evaluate_range +# steps by (end - start) / (n - 1)), so the sample spacing is not 1/rate. +COOK_STEP = (COOK_END - COOK_START) / (COOK_SAMPLES - 1) + + +def setup_cook_test(anim_chop): + """Put the operator in a known output configuration and key a ramp.""" + anim_chop.clear() + anim_chop.par.Outputmode = 'range' + anim_chop.par.Indexunit = 'seconds' + anim_chop.par.Timeslice = 0 + anim_chop.par.Samplerate = COOK_RATE + anim_chop.par.Range1 = COOK_START + anim_chop.par.Range2 = COOK_END + + ramp = anim_chop.create_channel('cook_ramp') + ramp.create_keyframe(COOK_START, 0.0) + ramp.create_keyframe(COOK_END, 100.0) + + flat = anim_chop.create_channel('cook_flat') + flat.create_keyframe(COOK_START, 7.0) + flat.create_keyframe(COOK_END, 7.0) + + +def check_cook_test(anim_chop, result): + """Compare the cooked CHOP output against the evaluated channels.""" + result.begin_suite('cooked output') + print("\n--- Testing cooked CHOP output ---") + try: - # In TouchDesigner, 'me' refers to the current operator - anim_chop = op('Animation1') - print(f"Running tests on node: {anim_chop}") - except NameError: - print("ERROR: This script must be run from within TouchDesigner") - print("Use: op('your_animationchop_name').run_tests()") + result.assert_equal(2, anim_chop.numChans, + "Cooked output has one CHOP channel per animation channel") + result.assert_equal(['cook_ramp', 'cook_flat'], + [c.name for c in anim_chop.chans()], + "Cooked channel names match the animation channels") + # NOTE: range mode computes this as end_time * sample_rate, ignoring the + # range start. With COOK_START at 0 the two agree; with a non-zero start + # the output is longer than the range, which is worth revisiting. + result.assert_equal(COOK_SAMPLES, anim_chop.numSamples, + "Cooked sample count covers the range at the sample rate") + except Exception as e: + result.record_exception("Cooked output shape", e) return - + + try: + ramp_out = anim_chop['cook_ramp'] + ramp_src = anim_chop.get_channel('cook_ramp') + + result.assert_near(0.0, ramp_out[0], 1e-4, + "First cooked sample matches the first keyframe") + result.assert_near(100.0, ramp_out[COOK_SAMPLES - 1], 1e-4, + "Last cooked sample matches the last keyframe") + + # The interesting one: every sample has to agree with evaluate(), which + # is the contract the CHOP output rests on. Tolerance is loose because + # the CHOP stores float32 while evaluate() returns double. + mismatches = 0 + worst = 0.0 + for i in range(COOK_SAMPLES): + t = COOK_START + i * COOK_STEP + delta = abs(ramp_out[i] - ramp_src.evaluate(t)) + worst = max(worst, delta) + if delta > 1e-3: + mismatches += 1 + result.assert_equal(0, mismatches, + f"Every cooked sample matches Channel.evaluate() " + f"(worst delta {worst:.6f})") + + flat_out = anim_chop['cook_flat'] + result.assert_near(7.0, flat_out[COOK_SAMPLES // 2], 1e-4, + "A flat channel cooks to its constant value") + except Exception as e: + result.record_exception("Cooked output values", e) + + +def run_api_tests(anim_chop, cleanup=True): + """Run the binding suites against a real AnimationCHOP operator. + + Returns the TestResult. The operator is passed in rather than looked up, so + this module never needs to know where it lives in the network. + """ + if anim_chop is None: + print("ERROR: run_api_tests() needs an AnimationCHOP operator.") + print("Use: animation_test.run_api_tests(op('animation1'))") + return None + + print(f"Running tests on node: {anim_chop}") + try: anim_chop.clear() # Clear any existing channels print("Cleared existing channels in AnimationCHOP") except Exception as e: print(f"Failed to clear channels: {e}") - return + return None # Initialize test results result = TestResult() - + print("="*60) print("ANIMATIONCHOP PYTHON API TEST SUITE") print("="*60) - - try: - # Run all test suites - test_point_api(anim_chop, result) - test_keyframe_api(anim_chop, result) - test_enum_apis(anim_chop, result) - test_animation_chop_core_api(anim_chop, result) - test_channel_api(anim_chop, result) - test_advanced_features(anim_chop, result) - test_error_handling(anim_chop, result) - test_state_apis(anim_chop, result) - test_state_error_handling(anim_chop, result) - test_state_roundtrip(anim_chop, result) + suites = [ + ('point', test_point_api), + ('keyframe', test_keyframe_api), + ('enums', test_enum_apis), + ('animation core', test_animation_chop_core_api), + ('channel', test_channel_api), + ('advanced', test_advanced_features), + ('error handling', test_error_handling), + ('state', test_state_apis), + ('state errors', test_state_error_handling), + ('state roundtrip', test_state_roundtrip), + ] - except Exception as e: - print(f"\nUNEXPECTED ERROR: {e}") - print(traceback.format_exc()) - result.failed += 1 - result.errors.append(f"Unexpected error: {e}") - - finally: - if cleanup: + for name, suite in suites: + result.begin_suite(name) + # Isolate the suites from each other: one blowing up should not take the + # rest of the run with it, or the first failure hides everything after. + try: + suite(anim_chop, result) + except Exception as e: + print(f"\nUNEXPECTED ERROR in {name}: {e}") + print(traceback.format_exc()) + result.record_exception(f"{name} suite aborted", e) + + if cleanup: + try: anim_chop.clear() - else: - print("\nSkipping cleanup. Test channels will remain in AnimationCHOP.") + except Exception as e: + print(f"Failed to clear channels during cleanup: {e}") + else: + print("\nSkipping cleanup. Test channels will remain in AnimationCHOP.") # Print final results result.print_summary() - - return result -run_tests() + return result diff --git a/tests/td/td_test_runner.py b/tests/td/td_test_runner.py new file mode 100644 index 0000000..37b6edc --- /dev/null +++ b/tests/td/td_test_runner.py @@ -0,0 +1,168 @@ +"""In-TouchDesigner integration test driver for AnimationCHOP. + +Loaded as a module in TouchDesigner (a DAT under /local/modules, synced to this +file) and invoked from a bootstrap Execute DAT's onStart, which passes the +AnimationCHOP operator in -- the only place that needs to know where it lives: + + def onStart(): + import td_test_runner + td_test_runner.start(op('animation1')) + return + +start() runs the binding suite immediately, then schedules the cooked-output +checks a few frames later so the node has actually cooked in between, and writes +the results.json sentinel. TouchDesigner is left running; the host script +(run_td_tests.ps1 / run_td_tests.sh) detects the sentinel and terminates it. + +Environment variables (set by the host script): + ANIMATIONCHOP_RESULTS path to write results.json + (default: /results.json) +""" + +import os +import json +import traceback + +import animation_test + + +# The node has to cook between setup_cook_test() and check_cook_test(). One +# frame is enough in principle; a few gives the parameter changes room to +# propagate on a loaded project without making the run feel slow. +COOK_DELAY_FRAMES = 10 + + +# Carried across the frame gap between start() and _finish(). The deferred call +# comes back in as a fresh `import td_test_runner`, so module state is the +# handoff -- simpler than threading objects through run()'s args, and it does not +# depend on `me` resolving to this DAT. +_pending_op = None +_pending_result = None + + +def _results_path(): + default = os.path.join(project.folder, "results.json") # noqa: F821 (TD global) + return os.environ.get("ANIMATIONCHOP_RESULTS", default) + + +def start(anim_chop=None): + """Run the suites against anim_chop and write the results sentinel. + + Pass the operator in from the bootstrap Execute DAT. If it is omitted, the + name is taken from ANIMATIONCHOP_OP (which the host script sets from + -OpName / --op), falling back to 'animation1'. + + Any failure here still writes results.json -- if the run dies silently the + host script can only report a timeout, which says nothing about why. + """ + global _pending_op, _pending_result + + print("[td-test] start()") + try: + if anim_chop is None: + name = os.environ.get("ANIMATIONCHOP_OP", "animation1") + anim_chop = op(name) # noqa: F821 (TD global) + if anim_chop is None: + _write_failure(f"No operator named '{name}' in the project.") + return + + result = animation_test.run_api_tests(anim_chop, cleanup=True) + if result is None: + _write_failure("run_api_tests returned nothing -- no operator?") + return + + animation_test.setup_cook_test(anim_chop) + _pending_op = anim_chop + _pending_result = result + + # run() is TouchDesigner's builtin frame-delayed scheduler. Waiting on + # frames is the only way to let the node cook; sleeping would block the + # very frames we are waiting for. + run("import td_test_runner; td_test_runner._finish()", # noqa: F821 (TD global) + delayFrames=COOK_DELAY_FRAMES) + except Exception as e: + print(f"[td-test] start() failed: {e}") + print(traceback.format_exc()) + _write_failure(f"{e}\n{traceback.format_exc()}") + + +def _finish(): + """Second half: the node has cooked, so check its output and write out.""" + anim_chop, result = _pending_op, _pending_result + if result is None: + _write_failure("_finish() ran with no pending result") + return + + try: + animation_test.check_cook_test(anim_chop, result) + result.print_summary() + except Exception as e: + print(f"[td-test] cook checks failed: {e}") + print(traceback.format_exc()) + result.record_exception("cook checks aborted", e) + finally: + try: + anim_chop.clear() + except Exception: + pass + _write_results(result) + + +def _summarize(records): + """Collapse per-assertion records into one entry per suite. + + The suite runs several hundred assertions; the host script prints a line per + suite plus every individual failure, which is the useful shape for a gate. + """ + order = [] + suites = {} + for r in records: + name = r.get("suite", "general") + if name not in suites: + suites[name] = {"name": name, "passed": 0, "failed": 0} + order.append(name) + key = "passed" if r["passed"] else "failed" + suites[name][key] += 1 + return [suites[n] for n in order] + + +def _write_results(result): + records = result.records + failures = [r for r in records if not r["passed"]] + summary = { + "results": [ + { + "name": s["name"], + "passed": s["failed"] == 0, + "detail": f"{s['passed']} passed, {s['failed']} failed", + } + for s in _summarize(records) + ], + "failures": [ + {"suite": r["suite"], "name": r["name"], "detail": r["detail"]} + for r in failures + ], + "passed": result.passed, + "failed": result.failed, + "success": result.failed == 0 and result.passed > 0, + } + _dump(summary) + + +def _write_failure(message): + """Write a results file describing a run that never got to the assertions.""" + _dump({ + "results": [{"name": "runner", "passed": False, "detail": message}], + "failures": [{"suite": "runner", "name": "startup", "detail": message}], + "passed": 0, + "failed": 1, + "success": False, + }) + + +def _dump(summary): + path = _results_path() + with open(path, "w") as f: + json.dump(summary, f, indent=2) + print(f"[td-test] wrote {path}: " + f"{summary['passed']} passed, {summary['failed']} failed") From 7c4e3bb36b84b4a3e068c1e41224de3eddefd27a Mon Sep 17 00:00:00 2001 From: keithlostracco Date: Sun, 26 Jul 2026 11:50:12 -0700 Subject: [PATCH 07/18] fix: correct output sample counts and two parameter defaults Range mode sized its output as end_time * sample_rate, which ignored the range start and dropped the inclusive end. With Range = [10, 70] at 60fps that asked for 4200 samples to cover a 60-second span. The values were still right -- execute() fills via evaluate_range(start, end, numSamples), which distributes across whatever length it is given -- so this was a length bug, and one that put the CHOP at odds with Animation.num_samples for the same range. Both range and auto-range now use ceil(length * rate) + 1, matching evaluate_range() and anim's own convention. AnimationViewCHOP had the same off-by-one in the seconds branch of its samples view, one short of the range, while its samples branch already had the +1. Both now clamp to at least one sample so an inverted range cannot ask TouchDesigner for a negative count. Range mode also assigned start then end unconditionally. The setters clamp against the current opposite bound, so a node moving from [0,30] to [50,70] clamped the new start against the stale end and cooked [30,70] for a frame. Widening before narrowing settles it in one cook -- which is what the Python start_time/end_time setters already did, so the two paths now agree. Outputmode defaulted to "fullrange", which is not one of its menu entries (range, autorange, input, sequence); it landed on index 0 by fallback. Samplerate had its slider bounds swapped, min 120 and max 30. Tests: the in-TD cook checks now run over a non-zero range start, which is the case that was broken, and assert the cooked count agrees with Animation.num_samples. Added pytest coverage for the range setters pushing the opposite bound. --- src/animation_chop.cpp | 58 +++++++++++++++++++++++++++------- src/animation_view_chop.cpp | 12 ++++++- tests/python/test_animation.py | 34 ++++++++++++++++++++ tests/td/animation_test.py | 24 +++++++------- 4 files changed, 102 insertions(+), 26 deletions(-) diff --git a/src/animation_chop.cpp b/src/animation_chop.cpp index e910af7..89c25b6 100644 --- a/src/animation_chop.cpp +++ b/src/animation_chop.cpp @@ -36,6 +36,45 @@ #include #endif +namespace { + +// The output length for the range/auto-range modes. +// +// This has to agree with execute(), which fills the output with +// evaluate_range(start, end, numSamples) -- a span inclusive of both ends. That +// makes the count ceil(length * rate) + 1, the same convention as anim's own +// Animation::num_samples(). Computing the span here rather than calling +// length() avoids its throw-on-inverted-range, so this can never raise across +// TouchDesigner's C API. +int32_t rangeSampleCount(const anim::Animation& animation, double sampleRate) +{ + if (sampleRate <= 0.0) + return 1; + + const double length = animation.end_time() - animation.start_time(); + if (length <= 0.0) + return 1; + + const int32_t count = static_cast(std::ceil(length * sampleRate)) + 1; + return count < 1 ? 1 : count; +} + +// Move the animation's range to [start, end]. +// +// set_start_time/set_end_time each clamp against the current opposite bound, so +// assigning in a fixed order clamps against a stale one whenever the whole +// range moves (a [0,30] node told to become [50,70] would land on [30,70] for a +// frame). Widening before narrowing settles it in one cook, and an inverted +// range collapses to a zero-length one rather than throwing. +void setAnimationRange(anim::Animation& animation, double start, double end) +{ + animation.set_end_time(std::max(start, end)); + animation.set_start_time(start); + animation.set_end_time(end); +} + +} // namespace + // static PyObject* py_animationFromDict(PyObject* self, PyObject* args); static PyObject* py_create_channel(PyObject* self, PyObject* args); static PyObject* py_emplace_channel(PyObject* self, PyObject* args); @@ -245,12 +284,8 @@ AnimationCHOP::getOutputInfo(CHOP_OutputInfo* info, const OP_Inputs* inputs, voi } case OutputMode::range: { auto start_time = inputs->getParDouble("Range", 0); auto end_time = inputs->getParDouble("Range", 1); - m_animation->set_start_time(start_time); - m_animation->set_end_time(end_time); - info->numSamples = static_cast(end_time * info->sampleRate); - if (info->numSamples < 1) { - info->numSamples = 1; - } + setAnimationRange(*m_animation, start_time, end_time); + info->numSamples = rangeSampleCount(*m_animation, info->sampleRate); break; } case OutputMode::autoRange: default: { double max_length = 0.0; @@ -258,9 +293,8 @@ AnimationCHOP::getOutputInfo(CHOP_OutputInfo* info, const OP_Inputs* inputs, voi const auto& channel = m_animation->channel(i); max_length = std::max(max_length, channel.length()); } - info->numSamples = static_cast(std::ceil(max_length * info->sampleRate)); - m_animation->set_start_time(0.0); - m_animation->set_end_time(max_length); + setAnimationRange(*m_animation, 0.0, max_length); + info->numSamples = rangeSampleCount(*m_animation, info->sampleRate); break; } } @@ -455,7 +489,7 @@ AnimationCHOP::setupParameters(OP_ParameterManager* manager,void *reserved1) OP_StringParameter sp; sp.name = "Outputmode"; sp.label = "Output Mode"; - sp.defaultValue = "fullrange"; + sp.defaultValue = "range"; const char *names[] = { "range", "autorange", "input", "sequence" }; const char *labels[] = { "Range", "Auto Range", "Input Index (first channel)", "Sequence Index" }; @@ -485,9 +519,9 @@ AnimationCHOP::setupParameters(OP_ParameterManager* manager,void *reserved1) np.name = "Samplerate"; np.label = "Sample Rate"; np.defaultValues[0] = 60.0; - np.minSliders[0] = 120.0; + np.minSliders[0] = 1.0; np.minValues[0] = 1.0; - np.maxSliders[0] = 30.0; + np.maxSliders[0] = 120.0; np.clampMins[0] = true; OP_ParAppendResult res = manager->appendFloat(np); diff --git a/src/animation_view_chop.cpp b/src/animation_view_chop.cpp index f774101..f91833e 100644 --- a/src/animation_view_chop.cpp +++ b/src/animation_view_chop.cpp @@ -3,6 +3,7 @@ #include #include +#include #include @@ -252,10 +253,19 @@ AnimationViewCHOP::getOutputInfo(CHOP_OutputInfo* info, const OP_Inputs* inputs, m_samplesStartTime = rangeStart / info->sampleRate; m_samplesEndTime = rangeEnd / info->sampleRate; } else { // Seconds - info->numSamples = static_cast(range_delta * info->sampleRate); + // + 1 to include the start, matching the samples branch above and + // the evaluate_range() call in execute(), which spans start..end + // inclusive. Without it the output is one sample short of the range. + info->numSamples = + static_cast(std::ceil(range_delta * info->sampleRate)) + 1; m_samplesStartTime = rangeStart; m_samplesEndTime = rangeEnd; } + // An inverted range would otherwise ask TouchDesigner for a negative + // sample count. + if (info->numSamples < 1) { + info->numSamples = 1; + } return true; } case ViewMode::keyframes: { info->numChannels = static_cast(m_keyframes_chan_names.size()); diff --git a/tests/python/test_animation.py b/tests/python/test_animation.py index 3098651..c48a691 100644 --- a/tests/python/test_animation.py +++ b/tests/python/test_animation.py @@ -91,6 +91,40 @@ def test_animation_range_is_settable(op): assert op.length == pytest.approx(60.0) +def test_range_setters_push_the_opposite_bound(op): + """Setting one bound past the other drags that one along, never inverts. + + This matters because the setters are assigned one at a time: if start + merely clamped, moving a whole range forward would silently land on the + wrong start for as long as the old end held it back. + """ + op.start_time = 0.0 + op.end_time = 30.0 + + op.start_time = 40.0 + assert (op.start_time, op.end_time) == pytest.approx((40.0, 40.0)) + + op.end_time = 10.0 + assert (op.start_time, op.end_time) == pytest.approx((10.0, 10.0)) + + +def test_range_can_be_moved_forward_in_either_order(op): + """A consequence of the above: neither assignment order loses the range.""" + op.start_time = 0.0 + op.end_time = 30.0 + + op.start_time = 40.0 + op.end_time = 70.0 + assert (op.start_time, op.end_time) == pytest.approx((40.0, 70.0)) + + op.start_time = 0.0 + op.end_time = 30.0 + + op.end_time = 70.0 + op.start_time = 40.0 + assert (op.start_time, op.end_time) == pytest.approx((40.0, 70.0)) + + def test_clear_leaves_the_range_alone(op): """clear() removes channels but keeps the configured range.""" op.start_time = 10.0 diff --git a/tests/td/animation_test.py b/tests/td/animation_test.py index 1371a15..b3afdb7 100644 --- a/tests/td/animation_test.py +++ b/tests/td/animation_test.py @@ -1168,17 +1168,16 @@ def test_state_roundtrip(anim_chop, result): # evaluate to. They are split in half because the node has to cook between them, # which takes a frame -- td_test_runner supplies the delay. -COOK_START = 0.0 -COOK_END = 2.0 +import math + +# Deliberately a non-zero start: range mode used to size its output as +# end_time * sample_rate, which ignored the start and over-ran the range. The +# count is ceil(length * rate) + 1, spanning start..end inclusive -- the same +# convention as Animation.num_samples. +COOK_START = 1.0 +COOK_END = 3.0 COOK_RATE = 60.0 - -# Range mode sizes its output as end_time * sample_rate. Note it does NOT -# subtract start_time, so this expectation only holds while COOK_START is 0 -- -# see the note in check_cook_test(). -COOK_SAMPLES = int(COOK_END * COOK_RATE) - -# The samples themselves span start..end inclusive (Channel::evaluate_range -# steps by (end - start) / (n - 1)), so the sample spacing is not 1/rate. +COOK_SAMPLES = int(math.ceil((COOK_END - COOK_START) * COOK_RATE)) + 1 COOK_STEP = (COOK_END - COOK_START) / (COOK_SAMPLES - 1) @@ -1212,11 +1211,10 @@ def check_cook_test(anim_chop, result): result.assert_equal(['cook_ramp', 'cook_flat'], [c.name for c in anim_chop.chans()], "Cooked channel names match the animation channels") - # NOTE: range mode computes this as end_time * sample_rate, ignoring the - # range start. With COOK_START at 0 the two agree; with a non-zero start - # the output is longer than the range, which is worth revisiting. result.assert_equal(COOK_SAMPLES, anim_chop.numSamples, "Cooked sample count covers the range at the sample rate") + result.assert_equal(anim_chop.num_samples, anim_chop.numSamples, + "Cooked sample count agrees with Animation.num_samples") except Exception as e: result.record_exception("Cooked output shape", e) return From d193838b65e51c393ae7477b1382a33d88c614c8 Mon Sep 17 00:00:00 2001 From: keithlostracco Date: Sun, 26 Jul 2026 12:17:47 -0700 Subject: [PATCH 08/18] docs: add licensing, README and contributor docs; open up CI Adds the files a public repository needs: MIT LICENSE, NOTICE covering the third-party components, a root README, and CONTRIBUTING. NOTICE matters more than usual here because two dependencies are not MIT and one of them is redistributed. ext/td/ is Derivative's Custom Operator SDK under their Shared Use License, and the Windows build vendors CPython 3.11 headers and import libraries under the PSF License. anim is a submodule, built from source rather than vendored. CI and release no longer mint a GitHub App token to fetch anim -- it is public now, so the default checkout token reaches it. Drops copilot-setup-steps.yml, which existed only to give Copilot that token. CI now runs the tests it was already capable of running: it configures with ANIMATIONCHOP_BUILD_TESTS=ON and runs ctest, so the pytest suite gates every push. It also fixes the macOS artifact paths, which pointed at .dylib files the build has never produced -- the macOS targets are .plugin bundles. release.yml now publishes a bundle that works on unzip rather than two bare libraries: Plugins/ beside the example project, Keyframer.tox, the modules the project loads, and the licence files. It runs the tests before publishing. Also fixes the same swapped Sample Rate slider bounds in AnimationViewCHOP that the previous commit fixed in AnimationCHOP. --- .github/workflows/ci.yml | 90 ++++-------- .github/workflows/copilot-setup-steps.yml | 41 ------ .github/workflows/release.yml | 131 ++++++++---------- CONTRIBUTING.md | 108 +++++++++++++++ LICENSE | 21 +++ NOTICE | 49 +++++++ README.md | 161 ++++++++++++++++++++++ src/animation_view_chop.cpp | 4 +- 8 files changed, 430 insertions(+), 175 deletions(-) delete mode 100644 .github/workflows/copilot-setup-steps.yml create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE create mode 100644 NOTICE create mode 100644 README.md diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7ae2e5a..c811c49 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,92 +9,60 @@ on: jobs: build: strategy: + fail-fast: false matrix: os: [windows-latest, macos-latest] build_type: [Release] - + runs-on: ${{ matrix.os }} - - steps: - - name: Generate a token - id: generate-token - uses: actions/create-github-app-token@v2 - with: - app-id: ${{ vars.AUTH_PULL_REPO_ID }} - private-key: ${{ secrets.AUTH_PULL_REPO_SECRET }} - owner: Actualize-Interactive - repositories: AnimationCHOP,anim + steps: + # anim is a public submodule, so the default checkout token is enough -- + # no app token needed. - name: Checkout code uses: actions/checkout@v4 with: submodules: recursive - token: ${{ steps.generate-token.outputs.token }} - name: Setup CMake uses: jwlawson/actions-setup-cmake@v2 with: cmake-version: '3.25' - - - name: Setup Python (Windows) - if: runner.os == 'Windows' - uses: actions/setup-python@v4 - with: - python-version: '3.11' - - - name: Setup Python (macOS) - if: runner.os == 'macOS' - uses: actions/setup-python@v4 + + # uv provides the Python 3.11 that runs pytest. The operators themselves + # build against the vendored headers (Windows) or TouchDesigner's framework + # (macOS), not this interpreter. + - name: Setup uv + uses: astral-sh/setup-uv@v5 + + - name: Setup Python + uses: actions/setup-python@v5 with: python-version: '3.11' - - - name: Configure CMake (Windows) - if: runner.os == 'Windows' - run: | - mkdir build - cd build - cmake .. -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} - - - name: Configure CMake (macOS) - if: runner.os == 'macOS' - run: | - mkdir build - cd build - # Find Python and set CMAKE variables - python3-config --cflags - python3-config --ldflags - cmake .. -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} - - - name: Build project (Windows) - if: runner.os == 'Windows' - run: | - cd build - cmake --build . --config ${{ matrix.build_type }} - echo "Listing build outputs:" - dir bin - - - name: Build project (macOS) - if: runner.os == 'macOS' - run: | - cd build - cmake --build . --config ${{ matrix.build_type }} - echo "Listing build outputs:" - ls -la bin/ - + + - name: Configure + run: cmake -S . -B build -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DANIMATIONCHOP_BUILD_TESTS=ON + + - name: Build + run: cmake --build build --config ${{ matrix.build_type }} + + - name: Test + run: ctest --test-dir build --build-config ${{ matrix.build_type }} --output-on-failure + - name: Upload build artifacts (Windows) if: runner.os == 'Windows' uses: actions/upload-artifact@v4 with: name: AnimationCHOPs-windows-${{ matrix.build_type }} path: | - build/bin/Release/AnimationCHOP.dll - build/bin/Release/AnimationViewCHOP.dll - + build/bin/${{ matrix.build_type }}/AnimationCHOP.dll + build/bin/${{ matrix.build_type }}/AnimationViewCHOP.dll + - name: Upload build artifacts (macOS) if: runner.os == 'macOS' uses: actions/upload-artifact@v4 with: name: AnimationCHOPs-macos-${{ matrix.build_type }} path: | - build/bin/AnimationCHOP.dylib - build/bin/AnimationViewCHOP.dylib \ No newline at end of file + build/bin/AnimationCHOP.plugin + build/bin/AnimationViewCHOP.plugin diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml deleted file mode 100644 index b8bfcd7..0000000 --- a/.github/workflows/copilot-setup-steps.yml +++ /dev/null @@ -1,41 +0,0 @@ -name: "Copilot Setup Steps" - -# Allow testing of the setup steps from your repository's "Actions" tab. -on: workflow_dispatch - -jobs: - # The job MUST be called `copilot-setup-steps` or it will not be picked up by Copilot. - copilot-setup-steps: - runs-on: ubuntu-latest - - # Set the permissions to the lowest permissions possible needed for your steps. - # Copilot will be given its own token for its operations. - permissions: - # If you want to clone the repository as part of your setup steps, for example - # to install dependencies, you'll need the `contents: read` permission. - # If you don't clone the repository in your setup steps, Copilot will do this - # for you automatically after the steps complete. - contents: read - - # You can define any steps you want, and they will run before the agent starts. - # If you do not check out your code, Copilot will do this for you. - steps: - - - name: Generate a token - id: generate-token - uses: actions/create-github-app-token@v2 - with: - app-id: ${{ vars.AUTH_PULL_REPO_ID }} - private-key: ${{ secrets.AUTH_PULL_REPO_SECRET }} - owner: Actualize-Interactive - repositories: anim, AnimationCHOP - - - name: Checkout code - uses: actions/checkout@v4 - with: - submodules: recursive - token: ${{ steps.generate-token.outputs.token }} - - - - diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3b89af9..154f62c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,100 +4,89 @@ on: release: types: [published] +permissions: + contents: write + jobs: build-and-release: strategy: + fail-fast: false matrix: include: - os: windows-latest - asset_name: AnimationCHOP.dll + platform: windows - os: macos-latest - asset_name: AnimationCHOP.dylib - + platform: macos + runs-on: ${{ matrix.os }} - - steps: - - name: Generate a token - id: generate-token - uses: actions/create-github-app-token@v2 - with: - app-id: ${{ vars.AUTH_PULL_REPO_ID }} - private-key: ${{ secrets.AUTH_PULL_REPO_SECRET }} - owner: Actualize-Interactive - repositories: AnimationCHOP,anim + steps: + # anim is a public submodule, so the default checkout token is enough. - name: Checkout code uses: actions/checkout@v4 with: submodules: recursive - token: ${{ steps.generate-token.outputs.token }} - + - name: Setup CMake uses: jwlawson/actions-setup-cmake@v2 with: cmake-version: '3.25' - - - name: Setup Python (Windows) - if: runner.os == 'Windows' - uses: actions/setup-python@v4 - with: - python-version: '3.11' - - - name: Setup Python (macOS) - if: runner.os == 'macOS' - uses: actions/setup-python@v4 + + - name: Setup uv + uses: astral-sh/setup-uv@v5 + + - name: Setup Python + uses: actions/setup-python@v5 with: python-version: '3.11' - - - name: Configure CMake (Windows) - if: runner.os == 'Windows' - run: | - mkdir build - cd build - cmake .. -DCMAKE_BUILD_TYPE=Release - - - name: Configure CMake (macOS) - if: runner.os == 'macOS' - run: | - mkdir build - cd build - # Find Python and set CMAKE variables - python3-config --cflags - python3-config --ldflags - cmake .. -DCMAKE_BUILD_TYPE=Release - - - name: Build project (Windows) + + - name: Configure + run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DANIMATIONCHOP_BUILD_TESTS=ON + + - name: Build + run: cmake --build build --config Release + + # Don't publish a release that fails its own tests. + - name: Test + run: ctest --test-dir build --build-config Release --output-on-failure + + # Lay the archive out so it works on unzip: the example project sits beside + # the Plugins folder, which is the only place TouchDesigner looks for + # Custom Operators. A user who only wants the operators copies Plugins/ + # into their own project. + - name: Stage release bundle (Windows) if: runner.os == 'Windows' + shell: bash run: | - cd build - cmake --build . --config Release - echo "Listing build outputs:" - dir bin - - - name: Build project (macOS) + stage="AnimationCHOP-${{ github.event.release.tag_name }}-windows" + mkdir -p "$stage/Plugins" "$stage/modules" + cp build/bin/Release/AnimationCHOP.dll "$stage/Plugins/" + cp build/bin/Release/AnimationViewCHOP.dll "$stage/Plugins/" + cp td/AnimationCHOP.toe "$stage/" + cp td/Keyframer.tox "$stage/" + cp td/animcurves.py "$stage/" + cp -r td/modules/. "$stage/modules/" + cp README.md LICENSE NOTICE "$stage/" + 7z a "$stage.zip" "$stage" + + - name: Stage release bundle (macOS) if: runner.os == 'macOS' + shell: bash run: | - cd build - cmake --build . --config Release - echo "Listing build outputs:" - ls -la bin/ - - - name: Upload Release Asset (Windows) - if: runner.os == 'Windows' - uses: softprops/action-gh-release@v1 + stage="AnimationCHOP-${{ github.event.release.tag_name }}-macos" + mkdir -p "$stage/Plugins" "$stage/modules" + cp -R build/bin/AnimationCHOP.plugin "$stage/Plugins/" + cp -R build/bin/AnimationViewCHOP.plugin "$stage/Plugins/" + cp td/AnimationCHOP.toe "$stage/" + cp td/Keyframer.tox "$stage/" + cp td/animcurves.py "$stage/" + cp -R td/modules/. "$stage/modules/" + cp README.md LICENSE NOTICE "$stage/" + zip -r "$stage.zip" "$stage" + + - name: Upload release asset + uses: softprops/action-gh-release@v2 with: - files: | - build/bin/Release/AnimationCHOP.dll - build/bin/Release/AnimationViewCHOP.dll + files: AnimationCHOP-${{ github.event.release.tag_name }}-${{ matrix.platform }}.zip env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Upload Release Asset (macOS) - if: runner.os == 'macOS' - uses: softprops/action-gh-release@v1 - with: - files: | - build/bin/AnimationCHOP.dylib - build/bin/AnimationViewCHOP.dylib - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..b773015 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,108 @@ +# Contributing to AnimationCHOP + +Thanks for your interest. Bug reports and pull requests are welcome. + +## Where things live + +| Path | | +| --- | --- | +| `src/` | The two operators. `animation_chop.cpp` is the scriptable one; `animation_view_chop.cpp` exposes its internals as CHOP data. | +| `src/py_anim_bindings/` | The CPython bindings for anim's types — `Channel`, `Keyframe`, `Point`, and the enums. | +| `ext/anim/` | The animation curve library, as a submodule. Curve maths belongs [there](https://github.com/Actualize-Interactive/anim), not here. | +| `ext/td/` | Derivative's Custom Operator SDK headers. Do not edit; they are vendored verbatim. | +| `ext/Python/` | Vendored CPython 3.11 headers and import libraries for the Windows build. | +| `td/` | The example project, `Keyframer.tox`, and its Python modules and shaders. | +| `tests/python/` | pytest against a compiled test extension. No TouchDesigner needed. | +| `tests/td/` | The in-TouchDesigner integration suite and its runner. | +| `docs/` | The Python API reference. | + +**Curve behaviour changes go to `anim`.** If a fix is about how a curve +interpolates, how handles are solved, or how a channel extrapolates, it belongs +in the library, which has its own Catch2 suite. This repository is the +TouchDesigner binding. + +## Building + +```bash +git clone --recurse-submodules https://github.com/Actualize-Interactive/AnimationCHOP.git +cd AnimationCHOP +``` + +```powershell +.\build.ps1 # Windows +``` + +```bash +./build.sh # macOS +``` + +## Tests + +Run these before opening a pull request: + +```powershell +cmake --workflow --preset dev +``` + +That configures, builds both operators plus the test extension, and runs the +pytest suite. It needs no TouchDesigner install — the extension compiles the +real binding sources against a fake `PY_Context`. + +If your change touches the operators' cooking, output sizing, or parameters, +also run the integration suite against a real TouchDesigner: + +```powershell +.\run_td_tests.ps1 +``` + +See [TESTING.md](TESTING.md), including the one-time project wiring it needs. + +## Adding tests + +New Python API surface needs a test in `tests/python/`. Prefer that suite: it is +fast, runs in CI, and needs no license or GPU. Reach for `tests/td/` only for +behaviour that genuinely requires TouchDesigner — cooking, output channels, +parameters. + +If you find surprising-but-intended behaviour, pin it with a test that says why +in its docstring, rather than leaving it undocumented. There are several already +(keyframes being detached copies, the range being independent of channel +content); they exist so the next person does not read them as bugs. + +## Style + +Match the file you are editing — this codebase predates any linter and is not +uniformly formatted. Tabs and 4-space indentation both appear; follow the +surrounding block. + +Comments should explain *why*, not restate the code. If something looks odd but +is deliberate — an invariant, a TouchDesigner API constraint, an ordering that +matters — say so, since that is what a reader cannot recover from the code. + +## Pull requests + +- Branch from `main`. +- Keep a pull request to one concern. +- Say what you changed and why. If it changes behaviour, say what a user would + notice. +- Note whether you ran the TouchDesigner integration suite, since CI cannot. + +## Reporting bugs + +Include: + +- Your TouchDesigner version and platform. +- The AnimationCHOP version (release tag, or commit if built from source). +- A minimal repro — ideally a few lines of Python against a fresh + AnimationCHOP, or a small `.toe`. +- What you expected and what happened. + +Curve-shape and interpolation issues are usually better filed against +[anim](https://github.com/Actualize-Interactive/anim); if you are not sure, file +here and it can be moved. + +## License + +By contributing you agree that your contributions are licensed under the MIT +License, the same as the rest of this repository. Note that `ext/td/` and +`ext/Python/` carry their own third-party terms — see [NOTICE](NOTICE). diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..3ad595b --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025-2026 Actualize Interactive Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/NOTICE b/NOTICE new file mode 100644 index 0000000..4200711 --- /dev/null +++ b/NOTICE @@ -0,0 +1,49 @@ +AnimationCHOP +Copyright (c) 2025-2026 Actualize Interactive Inc. + +This product bundles or depends on the following third-party components. + +------------------------------------------------------------------------------- +TouchDesigner Custom Operator SDK headers + Files: ext/td/include/CPlusPlus_Common.h + ext/td/include/CHOP_CPlusPlusBase.h + Copyright (c) Derivative Inc. + +These headers are part of the TouchDesigner Custom Operator SDK (the C++ CHOP +API, version 10, with common API version 2) and are redistributed here so the +operators can be built without a separate SDK download. They remain the +property of Derivative Inc. and are governed by Derivative's licensing terms +(the "Shared Use License" reproduced at the top of each file), not the MIT +license that covers the rest of this repository. The matching or newer versions +ship with TouchDesigner under + + Samples/CPlusPlus/CHOP/ + +in your TouchDesigner installation. + +------------------------------------------------------------------------------- +CPython + https://www.python.org/ (Python 3.11.1) + Copyright (c) 2001-2024 Python Software Foundation. All Rights Reserved. + Licensed under the PSF License Agreement. + + Files: ext/Python/Include/**, ext/Python/lib/x64/** + +The Windows build vendors the CPython 3.11 headers and import libraries so the +operators can be compiled without a local Python installation matching +TouchDesigner's. Only headers and import libraries are redistributed — no +CPython runtime binaries. TouchDesigner supplies the Python runtime when the +operators are loaded. The macOS build uses TouchDesigner's own Python framework +instead and vendors nothing. + +The full PSF License Agreement is at + https://docs.python.org/3/license.html + +------------------------------------------------------------------------------- +anim + https://github.com/Actualize-Interactive/anim + Copyright (c) 2025-2026 Actualize Interactive Inc. + Licensed under the MIT License. + +The animation curve library the operators are built on. Included as a git +submodule at ext/anim and built from source; not vendored in this repository. diff --git a/README.md b/README.md new file mode 100644 index 0000000..afc1b4d --- /dev/null +++ b/README.md @@ -0,0 +1,161 @@ +# AnimationCHOP + +[![CI](https://github.com/Actualize-Interactive/AnimationCHOP/actions/workflows/ci.yml/badge.svg)](https://github.com/Actualize-Interactive/AnimationCHOP/actions/workflows/ci.yml) +[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) +![C++20](https://img.shields.io/badge/C%2B%2B-20-blue.svg) + +Keyframe animation curves for TouchDesigner, as native C++ Custom Operators with +a full Python API. + +Build curves from keyframes with cubic Bézier interpolation, drive them from +Python, and get them back as CHOP channels — without rebuilding an animation +system out of Pattern CHOPs and Lookup CHOPs every time. + +The animation maths lives in [anim](https://github.com/Actualize-Interactive/anim), +a standalone C++20 library; these operators are the TouchDesigner binding around +it. + +## What's included + +| | | +| --- | --- | +| **AnimationCHOP** | Holds named animation channels and cooks them to CHOP samples. This is the operator you script against. | +| **AnimationViewCHOP** | Exposes an AnimationCHOP's internals as CHOP data — samples, keyframes, Bézier segments, channels — for building UI on top. | +| **Keyframer.tox** | A ready-made keyframe editor component built on both operators, with a GLSL-rendered curve view. | + +## Features + +- Named channels, each an independent curve; multiple channels per operator +- Per-keyframe interpolation: `Constant` (step), `Linear`, `Bezier` +- Bézier handle modes: `Flat`, `Smooth`, `Aligned`, `Free`, and the aligned + variants `AlignStrict` / `AlignFlex` / `AlignAdjustable` +- Per-channel extrapolation before the first and after the last keyframe: + `Hold`, `Repeat`, `Mirror` +- Output modes: a fixed time range, an auto range fitted to the content, an + index driven by an input CHOP, or a single scrubbed sequence index +- A complete Python API — create and edit channels and keyframes, evaluate the + curve at any time, and save or restore whole-animation state as a plain dict + +## Requirements + +- **TouchDesigner** — a build providing Custom Operator CHOP API version 10 + (common API version 2), i.e. one with node data persistence + (`saveData`/`loadData`). Earlier builds will refuse to load the operators. +- **Windows or macOS.** Windows builds are x64. + +## Installation + +1. Download the archive for your platform from + [Releases](https://github.com/Actualize-Interactive/AnimationCHOP/releases). +2. Copy the operator libraries into a `Plugins` folder beside your `.toe`: + + ``` + MyProject/ + MyProject.toe + Plugins/ + AnimationCHOP.dll (or .plugin on macOS) + AnimationViewCHOP.dll + ``` + + TouchDesigner only loads Custom Operators from a `Plugins` folder next to the + project file, or from the system-wide plugin folder. +3. Open the project. The first load of a new operator build shows a prompt + asking you to trust it — approve it once. +4. Add an **AnimationCHOP** from the operator palette, or drop in the included + `Keyframer.tox` for the full editor. + +The release archive also contains an example project you can open directly. + +## Quick start + +Everything is driven from Python on the operator itself: + +```python +n = op('animation1') + +# Create a channel and key it +tx = n.create_channel('tx') +tx.create_keyframe(0, 0) +tx.create_keyframe(30, 100, n.Function.BEZIER, n.HandleMode.SMOOTH) + +# Evaluate anywhere on the curve +print(tx.evaluate(15)) + +# Edit in place... +tx.set_keyframe_value(1, 250) + +# ...or read, modify, write back +kf = tx[0] +kf.function = n.Function.LINEAR +tx[0] = kf + +# Save and restore the whole animation +state = n.state +n.clear() +n.state = state +``` + +One thing to know before writing much against it: **`Channel` is a live handle, +but `Keyframe` and `Point` are values.** `tx[0]` gives you a detached copy, so +setting a property on it does not reach the channel until you write it back. +[The docs explain why](docs/README.md#keyframes-are-values-channels-are-handles) — +it is a consequence of how keyframes have to be re-solved against their +neighbours. + +## Documentation + +Full Python API reference under [`docs/`](docs/README.md): + +- [AnimationCHOP](docs/AnimationCHOP.md) — the operator: channels, range, state +- [Channel](docs/Channel.md) — a single curve +- [Keyframe](docs/Keyframe.md) · [Point](docs/Point.md) +- [Function](docs/Function.md) · [HandleMode](docs/HandleMode.md) + +## Building from source + +```bash +git clone --recurse-submodules https://github.com/Actualize-Interactive/AnimationCHOP.git +cd AnimationCHOP +``` + +(If you already cloned without submodules: `git submodule update --init --recursive`.) + +```powershell +# Windows +.\build.ps1 +``` + +```bash +# macOS +./build.sh +``` + +Either way the built operators are copied into `td/Plugins/` so the example +project picks them up. + +Windows vendors the CPython 3.11 headers and import libraries it needs, so there +is nothing to install. macOS builds against TouchDesigner's own Python framework +and expects TouchDesigner in `/Applications`. + +## Testing + +```powershell +cmake --workflow --preset dev # configure, build, run the unit tests +``` + +The pytest suite runs the real binding sources against a fake TouchDesigner +context, so it needs no TouchDesigner install. There is also an integration +harness that drives a real project inside TouchDesigner. See +[TESTING.md](TESTING.md). + +## Contributing + +See [CONTRIBUTING.md](CONTRIBUTING.md). + +## License + +MIT — see [LICENSE](LICENSE). + +The TouchDesigner SDK headers under `ext/td/` are Derivative Inc.'s and carry +their own terms; the vendored CPython headers are under the PSF License. See +[NOTICE](NOTICE) for the full third-party attribution. diff --git a/src/animation_view_chop.cpp b/src/animation_view_chop.cpp index f91833e..5bfb8f3 100644 --- a/src/animation_view_chop.cpp +++ b/src/animation_view_chop.cpp @@ -541,9 +541,9 @@ AnimationViewCHOP::setupParameters(OP_ParameterManager* manager, void* reserved1 np.name = "Samplerate"; np.label = "Sample Rate"; np.defaultValues[0] = 60.0; - np.minSliders[0] = 120.0; + np.minSliders[0] = 1.0; np.minValues[0] = 1.0; - np.maxSliders[0] = 30.0; + np.maxSliders[0] = 120.0; np.clampMins[0] = true; OP_ParAppendResult res = manager->appendFloat(np); From 48e62d7806098f5f3105f80238b29d95c2e75c4e Mon Sep 17 00:00:00 2001 From: keithlostracco Date: Sun, 26 Jul 2026 12:39:56 -0700 Subject: [PATCH 09/18] feat: persist animations into the .toe Implements TouchDesigner's saveData()/loadData(), so an operator's channels and keyframes are stored inside the project file and restored on load. This replaces saving state by hand from the component, and needs neither an external file nor a Python interpreter -- loadData() runs during node construction, before one is available. The format is a lean versioned binary blob rather than JSON or the Python state dict: it is written on every project save, a keyframe-heavy animation runs to tens of thousands of doubles, and there is no interpreter to hand. src/animation_codec.cpp is kept free of both TouchDesigner and Python so it can be tested on its own. Those bytes are persisted user data, so decode() validates rather than trusts. A blob that is truncated, foreign, from a newer format version, or carrying an out-of-range enum is rejected outright, and the operator keeps the animation it already had instead of coming back half-built. A failed restore is reported through getErrorString rather than swallowed -- a node that silently returns empty looks like data loss, and the keyframes are still in the file. Restoring replays the explicit handles of Free and Aligned keyframes after every neighbour exists. Insertion re-solves handles incrementally, which is right for the derived modes but otherwise loses what an explicit handle was holding. Adds tests/cpp (Catch2) over the codec: round-trip fidelity including that a decoded curve evaluates identically, and rejection of every truncation of a valid blob. One known gap is pinned rather than left to be discovered -- anim caches a channel's last keyframe's pre-inheritance function and handle mode, that cache is private, so appending a keyframe after a reload no longer reverts it. Also settles versioning: the project moves to 0.4.0, staying on 0.x until the API is stable, and gains a CHANGELOG. release.yml now triggers on tag and takes its release body from the matching CHANGELOG section, as anim does, failing the release if the section is missing. The heading match there is a plain string prefix test rather than the dynamic regex anim uses. Building one as "^## \[" v "\]" reads correctly but is not portable: gawk strips the backslashes when converting a string to a regex, leaving [0.4.0] as a character class matching a single character. It fails by producing empty notes, not an error. Documents the minimum TouchDesigner build (2025.33070) now that the API it requires is known. --- .github/workflows/release.yml | 127 +++++++--- CHANGELOG.md | 85 +++++++ CMakeLists.txt | 13 +- NOTICE | 5 +- README.md | 9 +- TESTING.md | 19 +- src/animation_chop.cpp | 40 +++ src/animation_chop.h | 12 + src/animation_codec.cpp | 268 ++++++++++++++++++++ src/animation_codec.h | 50 ++++ tests/cpp/CMakeLists.txt | 28 +++ tests/cpp/test_animation_codec.cpp | 382 +++++++++++++++++++++++++++++ tests/python/CMakeLists.txt | 1 + 13 files changed, 989 insertions(+), 50 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 src/animation_codec.cpp create mode 100644 src/animation_codec.h create mode 100644 tests/cpp/CMakeLists.txt create mode 100644 tests/cpp/test_animation_codec.cpp diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 154f62c..3b4b484 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,14 +1,18 @@ name: Release on: - release: - types: [published] + push: + tags: + - 'v*.*.*' + workflow_dispatch: permissions: - contents: write + contents: read jobs: - build-and-release: + # Build, test and package on every platform as the release gate. Ubuntu is + # absent on purpose: TouchDesigner ships on Windows and macOS only. + build: strategy: fail-fast: false matrix: @@ -19,11 +23,11 @@ jobs: platform: macos runs-on: ${{ matrix.os }} + timeout-minutes: 30 steps: # anim is a public submodule, so the default checkout token is enough. - - name: Checkout code - uses: actions/checkout@v4 + - uses: actions/checkout@v4 with: submodules: recursive @@ -51,42 +55,97 @@ jobs: run: ctest --test-dir build --build-config Release --output-on-failure # Lay the archive out so it works on unzip: the example project sits beside - # the Plugins folder, which is the only place TouchDesigner looks for - # Custom Operators. A user who only wants the operators copies Plugins/ - # into their own project. - - name: Stage release bundle (Windows) - if: runner.os == 'Windows' + # the Plugins folder, which is the only place TouchDesigner looks for Custom + # Operators. A user who only wants the operators copies Plugins/ into their + # own project. + - name: Package shell: bash run: | - stage="AnimationCHOP-${{ github.event.release.tag_name }}-windows" + version="${GITHUB_REF_NAME:-dev}" + stage="AnimationCHOP-${version}-${{ matrix.platform }}" mkdir -p "$stage/Plugins" "$stage/modules" - cp build/bin/Release/AnimationCHOP.dll "$stage/Plugins/" - cp build/bin/Release/AnimationViewCHOP.dll "$stage/Plugins/" - cp td/AnimationCHOP.toe "$stage/" - cp td/Keyframer.tox "$stage/" - cp td/animcurves.py "$stage/" - cp -r td/modules/. "$stage/modules/" - cp README.md LICENSE NOTICE "$stage/" - 7z a "$stage.zip" "$stage" - - name: Stage release bundle (macOS) - if: runner.os == 'macOS' - shell: bash - run: | - stage="AnimationCHOP-${{ github.event.release.tag_name }}-macos" - mkdir -p "$stage/Plugins" "$stage/modules" - cp -R build/bin/AnimationCHOP.plugin "$stage/Plugins/" - cp -R build/bin/AnimationViewCHOP.plugin "$stage/Plugins/" + if [ "${{ matrix.platform }}" = "windows" ]; then + cp build/bin/Release/AnimationCHOP.dll "$stage/Plugins/" + cp build/bin/Release/AnimationViewCHOP.dll "$stage/Plugins/" + else + cp -R build/bin/AnimationCHOP.plugin "$stage/Plugins/" + cp -R build/bin/AnimationViewCHOP.plugin "$stage/Plugins/" + fi + cp td/AnimationCHOP.toe "$stage/" cp td/Keyframer.tox "$stage/" cp td/animcurves.py "$stage/" cp -R td/modules/. "$stage/modules/" - cp README.md LICENSE NOTICE "$stage/" - zip -r "$stage.zip" "$stage" + cp README.md LICENSE NOTICE CHANGELOG.md "$stage/" + + # zip is present on both runners; 7z is not on macOS. + if [ "${{ matrix.platform }}" = "windows" ]; then + 7z a "$stage.zip" "$stage" > /dev/null + else + zip -qr "$stage.zip" "$stage" + fi + + - name: Upload package + uses: actions/upload-artifact@v4 + with: + name: package-${{ matrix.platform }} + path: AnimationCHOP-*-${{ matrix.platform }}.zip + if-no-files-found: error + + # Publish the release. + # + # Only runs for tag pushes. A release cannot be published from a branch, so on + # workflow_dispatch this job is skipped and the workflow acts as a dry run of + # the cross-platform build, test and packaging gate above. + release: + if: startsWith(github.ref, 'refs/tags/') + needs: build + runs-on: ubuntu-latest + permissions: + contents: write + + steps: + - uses: actions/checkout@v4 + + - name: Download packages + uses: actions/download-artifact@v4 + with: + pattern: package-* + merge-multiple: true + + # Use the CHANGELOG entry for this tag as the release body, so the notes are + # written once and reviewed in the pull request that introduced them. + - name: Extract release notes from CHANGELOG + run: | + version="${GITHUB_REF_NAME#v}" + # Match the heading with a plain string prefix test, not a dynamic + # regex. Building one as "^## \\[" v "\\]" looks right but is not + # portable: gawk strips the backslashes when converting a string to a + # regex, leaving [0.4.0] as a character class that matches a single + # character. The bug is silent -- it yields empty notes, not an error. + awk -v hdr="## [$version]" ' + index($0, hdr) == 1 { found = 1; next } # heading for this version + found && /^## \[/ { exit } # next version heading + found && /^\[.*\]: / { exit } # link reference block + found && /^ diff --git a/CMakeLists.txt b/CMakeLists.txt index 5105a17..d778b29 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,6 @@ cmake_minimum_required(VERSION 3.14) -project(AnimationCHOP VERSION 1.0.0 LANGUAGES CXX) +# Stays on 0.x until the API is stable; see CHANGELOG.md. +project(AnimationCHOP VERSION 0.4.0 LANGUAGES CXX) set(VERBOSE_STATUS ON CACHE BOOL "Verbose status messages" FORCE) set(SUPPRESS_NOT_REFERENCED_WARNINGS ON CACHE BOOL "Suppress not referenced warnings" FORCE) @@ -23,6 +24,7 @@ set(PY_ANIM_BINDINGS_SRCS set(ANIMATION_CHOP_SOURCES ${PY_ANIM_BINDINGS_SRCS} ${SRC}/animation_chop.cpp + ${SRC}/animation_codec.cpp ) # Explicitly define sources for AnimationViewCHOP @@ -245,11 +247,9 @@ install(TARGETS animation_chop animation_view_chop # tests/python runs pytest against an extension that compiles the real operator # sources against a fake PY_Context. No TouchDesigner needed, so it runs in CI. # -# There is deliberately no Catch2 suite yet: anim carries its own (5026 -# assertions), and what remains here is bound to TouchDesigner or to CPython, -# so a C++ suite today would either test nothing meaningful or drag Python in. -# The saveData()/loadData() codec is the natural subject and should bring -# tests/cpp with it. +# tests/cpp is Catch2 over the .toe persistence codec -- the one piece here that +# is pure C++, and the one whose output lands in users' project files. Curve +# behaviour is anim's to test; it carries its own suite. # # The TouchDesigner integration test is separate and local-only (TouchDesigner # needs a licence and a GPU, so it cannot run on a cloud runner); see @@ -289,5 +289,6 @@ if(ANIMATIONCHOP_BUILD_TESTS) set(ANIMATIONCHOP_PYTHON_LIBRARIES Python3::Module) endif() + add_subdirectory(tests/cpp) add_subdirectory(tests/python) endif() diff --git a/NOTICE b/NOTICE index 4200711..4a5f895 100644 --- a/NOTICE +++ b/NOTICE @@ -10,8 +10,9 @@ TouchDesigner Custom Operator SDK headers Copyright (c) Derivative Inc. These headers are part of the TouchDesigner Custom Operator SDK (the C++ CHOP -API, version 10, with common API version 2) and are redistributed here so the -operators can be built without a separate SDK download. They remain the +API, version 10, with common API version 2, as shipped with TouchDesigner +2025.33070) and are redistributed here so the operators can be built without a +separate SDK download. They remain the property of Derivative Inc. and are governed by Derivative's licensing terms (the "Shared Use License" reproduced at the top of each file), not the MIT license that covers the rest of this repository. The matching or newer versions diff --git a/README.md b/README.md index afc1b4d..6964504 100644 --- a/README.md +++ b/README.md @@ -35,12 +35,15 @@ it. index driven by an input CHOP, or a single scrubbed sequence index - A complete Python API — create and edit channels and keyframes, evaluate the curve at any time, and save or restore whole-animation state as a plain dict +- Animations persist inside the `.toe`: channels and keyframes are saved with + the project and restored on load, with no external files ## Requirements -- **TouchDesigner** — a build providing Custom Operator CHOP API version 10 - (common API version 2), i.e. one with node data persistence - (`saveData`/`loadData`). Earlier builds will refuse to load the operators. +- **TouchDesigner 2025.33070 or newer.** Earlier builds will refuse to load the + operators. That build is where the Custom Operator API gained node data + persistence (`saveData`/`loadData`), which is how animations are stored in the + `.toe`; the operators target CHOP API version 10 / common API version 2. - **Windows or macOS.** Windows builds are x64. ## Installation diff --git a/TESTING.md b/TESTING.md index 673ef42..f970289 100644 --- a/TESTING.md +++ b/TESTING.md @@ -2,17 +2,19 @@ Tests live under `tests/`: +- `tests/cpp/` — Catch2 over the `.toe` persistence codec (no TouchDesigner needed). - `tests/python/` — pytest against a compiled test extension (no TouchDesigner needed). - `tests/td/` — the TouchDesigner project, the in-network test modules, and the local integration harness (`run_td_tests.ps1` / `run_td_tests.sh`). > Note: TouchDesigner cannot run in cloud CI (it needs a license and a GPU), so -> CI only builds the operators and runs the `tests/python` suite. The `tests/td` -> integration test runs against a local TouchDesigner install. +> CI only builds the operators and runs the `tests/cpp` and `tests/python` +> suites. The `tests/td` integration test runs against a local TouchDesigner +> install. -There is no `tests/cpp` suite. The animation maths lives in the `anim` -submodule, which has its own Catch2 tests; what remains here is binding and -TouchDesigner glue, which the two suites below cover directly. +Curve behaviour is not tested here — that belongs to the `anim` submodule, which +carries its own Catch2 suite. What these cover is the binding and TouchDesigner +glue built on top of it. ## Unit tests (no TouchDesigner) @@ -29,6 +31,13 @@ through `uv run`, so pytest is fetched automatically — nothing to install firs (Without uv, install `tests/python/requirements.txt` into the interpreter and ctest will call `pytest` there instead.) +`tests/cpp` covers `src/animation_codec.cpp`, the binary format the operator +writes into the `.toe`. Those bytes are persisted user data, so the suite checks +both round-trip fidelity — including that a decoded curve evaluates identically +to the one that was saved — and that a truncated, foreign or out-of-range blob +is rejected outright rather than half-loaded. Catch2 is fetched at configure +time; nothing to install. + `tests/python` builds a small CPython extension (`animationchop`) that compiles the *real* operator sources against a fake `PY_Context`, so the bindings are exercised directly rather than through a copy. Once it is built you can also run diff --git a/src/animation_chop.cpp b/src/animation_chop.cpp index 89c25b6..658cf0b 100644 --- a/src/animation_chop.cpp +++ b/src/animation_chop.cpp @@ -13,6 +13,7 @@ */ #include "animation_chop.h" +#include "animation_codec.h" #include "py_anim_bindings/py_bindings.h" #include "py_anim_bindings/py_extend.h" @@ -468,6 +469,12 @@ AnimationCHOP::getWarningString(OP_String *warning, void* reserved1) void AnimationCHOP::getErrorString(OP_String *error, void* reserved1) { + // A failed restore outranks a cooking error: it explains why the node has + // no channels, which is usually the cause of whatever else is complaining. + if (!m_loadError.empty()) { + error->setString(m_loadError.c_str()); + return; + } error->setString(m_error); } @@ -543,6 +550,39 @@ AnimationCHOP::pulsePressed(const char* name, void* reserved1) { } +void +AnimationCHOP::saveData(OP_NodeSaveState* saver, void* reserved1) +{ + if (!saver || !m_animation) + return; + + const std::vector blob = animation_codec::encode(*m_animation); + saver->saveEntry(animation_codec::kSaveKey, + blob.data(), + static_cast(blob.size())); +} + +void +AnimationCHOP::loadData(const OP_NodeLoadState* loader, void* reserved1) +{ + if (!loader || !m_animation) + return; + + int64_t byteSize = 0; + const void* blob = loader->loadEntry(animation_codec::kSaveKey, &byteSize); + if (!blob || byteSize <= 0) + return; // A project saved before this operator persisted anything. + + std::string error; + if (!animation_codec::decode(blob, static_cast(byteSize), *m_animation, &error)) { + // Surface it rather than silently starting empty: the user's keyframes + // are in that .toe, and a node that comes back blank with no + // explanation looks like data loss. decode() leaves the animation + // untouched on failure, so the node is still usable. + m_loadError = "Could not restore saved animation: " + error; + } +} + // --- Channel creation and insertion --- static PyObject* py_create_channel(PyObject *self, PyObject *args) { diff --git a/src/animation_chop.h b/src/animation_chop.h index df114d6..827653c 100644 --- a/src/animation_chop.h +++ b/src/animation_chop.h @@ -36,6 +36,13 @@ class AnimationCHOP : public CHOP_CPlusPlusBase virtual void setupParameters(OP_ParameterManager* manager, void *reserved1) override; virtual void pulsePressed(const char* name, void* reserved1) override; + // Persist the animation into the .toe. TouchDesigner calls saveData() on + // every project save and whenever the operator is unloaded, and loadData() + // on load or reload -- so the channels a user keyframed survive a restart + // with no external file and no Python. + virtual void saveData(OP_NodeSaveState* saver, void* reserved1) override; + virtual void loadData(const OP_NodeLoadState* loader, void* reserved1) override; + anim::Animation* animation() { return m_animation.get(); } const anim::Animation& animation() const { return *m_animation; } float sampleRate() const { return m_sampleRate; } @@ -51,6 +58,11 @@ class AnimationCHOP : public CHOP_CPlusPlusBase const OP_NodeInfo* m_nodeInfo; const char* m_warning; const char* m_error; + // A failure to restore the saved animation, kept separately because + // execute() clears m_error on every cook and this has to stay visible: the + // user's keyframes were in that .toe. Owns its text, unlike m_error, which + // only ever points at string literals. + std::string m_loadError; float m_sampleRate { 60.0f }; std::unique_ptr m_animation; diff --git a/src/animation_codec.cpp b/src/animation_codec.cpp new file mode 100644 index 0000000..898f44f --- /dev/null +++ b/src/animation_codec.cpp @@ -0,0 +1,268 @@ +#include "animation_codec.h" + +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace animation_codec { +namespace { + +// "ACHP" -- checked before anything else so a blob written by some other +// operator, or a stale entry under the same key, is rejected rather than +// interpreted. +constexpr uint8_t kMagic[4] = { 'A', 'C', 'H', 'P' }; + +// A channel count or name length larger than this means the data is corrupt. +// Without a ceiling a bogus length would have us reserve gigabytes before the +// bounds check on the following read ever ran. +constexpr uint32_t kSaneCountLimit = 100u * 1000u * 1000u; + +// Both platforms TouchDesigner runs on are little-endian IEEE-754, so doubles +// go out raw. If that ever stops being true this is the assumption to revisit; +// a .toe is expected to move between Windows and macOS. +static_assert(sizeof(double) == 8, "Encoding assumes 8-byte doubles"); + +// --- writing --------------------------------------------------------------- + +void put(std::vector& out, const void* bytes, size_t count) +{ + const auto* p = static_cast(bytes); + out.insert(out.end(), p, p + count); +} + +void putU8(std::vector& out, uint8_t value) +{ + out.push_back(value); +} + +void putU32(std::vector& out, uint32_t value) +{ + put(out, &value, sizeof(value)); +} + +void putDouble(std::vector& out, double value) +{ + put(out, &value, sizeof(value)); +} + +void putPoint(std::vector& out, const anim::Point& point) +{ + putDouble(out, point.time); + putDouble(out, point.value); +} + +void putString(std::vector& out, const std::string& value) +{ + putU32(out, static_cast(value.size())); + put(out, value.data(), value.size()); +} + +// --- reading --------------------------------------------------------------- + +// A bounds-checked cursor. Every read goes through take(), so a truncated blob +// fails the read instead of running off the end of the buffer. +class Reader +{ +public: + Reader(const uint8_t* data, size_t size) : m_data(data), m_size(size) {} + + bool take(void* dest, size_t count) + { + if (count > m_size - m_offset) // m_offset <= m_size always, so no overflow + return false; + std::memcpy(dest, m_data + m_offset, count); + m_offset += count; + return true; + } + + bool u8(uint8_t& value) { return take(&value, sizeof(value)); } + bool u32(uint32_t& value) { return take(&value, sizeof(value)); } + bool real(double& value) { return take(&value, sizeof(value)); } + + bool point(anim::Point& value) + { + return real(value.time) && real(value.value); + } + + bool string(std::string& value, uint32_t limit) + { + uint32_t length = 0; + if (!u32(length) || length > limit || length > remaining()) + return false; + value.resize(length); + return length == 0 || take(value.data(), length); + } + + size_t remaining() const { return m_size - m_offset; } + +private: + const uint8_t* m_data; + size_t m_size; + size_t m_offset = 0; +}; + +bool fail(std::string* error, const char* message) +{ + if (error) + *error = message; + return false; +} + +// The enums are stored as their underlying byte, so a blob from a build with +// more modes than this one must not produce an out-of-range enum. +template +bool readEnum(Reader& reader, Enum& out, uint8_t limit) +{ + uint8_t raw = 0; + if (!reader.u8(raw) || raw >= limit) + return false; + out = static_cast(raw); + return true; +} + +constexpr uint8_t kFunctionCount = 3; // Constant, Linear, Bezier +constexpr uint8_t kHandleModeCount = static_cast(anim::HandleMode::Count); +constexpr uint8_t kExtendCount = 3; // Hold, Repeat, Mirror + +} // namespace + +std::vector encode(const anim::Animation& animation) +{ + std::vector out; + + put(out, kMagic, sizeof(kMagic)); + putU32(out, kFormatVersion); + putDouble(out, animation.start_time()); + putDouble(out, animation.end_time()); + + const size_t channelCount = animation.num_channels(); + putU32(out, static_cast(channelCount)); + + for (size_t c = 0; c < channelCount; ++c) { + const anim::Channel& channel = animation.channel(c); + + putString(out, channel.name()); + putU8(out, static_cast(channel.extend_start())); + putU8(out, static_cast(channel.extend_end())); + + const auto& keyframes = channel.keyframes(); + putU32(out, static_cast(keyframes.size())); + + for (const anim::Keyframe& keyframe : keyframes) { + putPoint(out, keyframe.position); + putPoint(out, keyframe.in_handle); + putPoint(out, keyframe.out_handle); + putU8(out, static_cast(keyframe.function)); + putU8(out, static_cast(keyframe.handle_mode)); + } + } + + return out; +} + +bool decode(const void* data, + size_t byteSize, + anim::Animation& animation, + std::string* error) +{ + if (!data || byteSize == 0) + return fail(error, "No data"); + + Reader reader(static_cast(data), byteSize); + + uint8_t magic[4] = {}; + if (!reader.take(magic, sizeof(magic)) || std::memcmp(magic, kMagic, sizeof(magic)) != 0) + return fail(error, "Not an AnimationCHOP blob"); + + uint32_t version = 0; + if (!reader.u32(version)) + return fail(error, "Truncated header"); + if (version != kFormatVersion) + return fail(error, "Unsupported format version"); + + double startTime = 0.0; + double endTime = 0.0; + if (!reader.real(startTime) || !reader.real(endTime)) + return fail(error, "Truncated animation range"); + + uint32_t channelCount = 0; + if (!reader.u32(channelCount) || channelCount > kSaneCountLimit) + return fail(error, "Bad channel count"); + + // Decode into a scratch animation and only commit on success, so a blob + // that fails halfway does not leave the node holding half an animation. + anim::Animation decoded; + + for (uint32_t c = 0; c < channelCount; ++c) { + std::string name; + if (!reader.string(name, kSaneCountLimit)) + return fail(error, "Bad channel name"); + + anim::Extend extendStart = anim::Extend::Hold; + anim::Extend extendEnd = anim::Extend::Hold; + if (!readEnum(reader, extendStart, kExtendCount) || + !readEnum(reader, extendEnd, kExtendCount)) + return fail(error, "Bad extend mode"); + + uint32_t keyframeCount = 0; + if (!reader.u32(keyframeCount) || keyframeCount > kSaneCountLimit) + return fail(error, "Bad keyframe count"); + + // create_channel appends, so channels come back in their saved order. + // A duplicate name in the blob would collide; anim allows it at the + // index level, and lookups by name resolve to the first, which matches + // how the animation behaved when it was saved. + anim::Channel& channel = decoded.create_channel(name); + + std::vector keyframes; + keyframes.reserve(keyframeCount < 4096 ? keyframeCount : 4096); + + for (uint32_t k = 0; k < keyframeCount; ++k) { + anim::Keyframe keyframe; + if (!reader.point(keyframe.position) || + !reader.point(keyframe.in_handle) || + !reader.point(keyframe.out_handle)) + return fail(error, "Truncated keyframe"); + + if (!readEnum(reader, keyframe.function, kFunctionCount) || + !readEnum(reader, keyframe.handle_mode, kHandleModeCount)) + return fail(error, "Bad keyframe mode"); + + keyframes.push_back(keyframe); + channel.emplace_keyframe(std::move(keyframe)); + } + + // Inserting re-solves the neighbouring handles, and it does so + // incrementally -- a keyframe's handles can be adjusted again by the + // ones inserted after it. That is correct for the derived modes + // (Flat, Smooth), which are meant to follow their neighbours, but it + // loses the explicit handles of a Free or Aligned keyframe. Replay + // them now that every neighbour exists. + for (size_t k = 0; k < keyframes.size() && k < channel.num_keyframes(); ++k) { + const anim::Keyframe& saved = keyframes[k]; + if (saved.handle_mode == anim::HandleMode::Flat || + saved.handle_mode == anim::HandleMode::Smooth) + continue; + channel.set_keyframe_in_handle(k, saved.in_handle); + channel.set_keyframe_out_handle(k, saved.out_handle); + } + + channel.set_extend_start(extendStart); + channel.set_extend_end(extendEnd); + } + + decoded.set_end_time(std::max(startTime, endTime)); + decoded.set_start_time(startTime); + decoded.set_end_time(endTime); + + animation = std::move(decoded); + return true; +} + +} // namespace animation_codec diff --git a/src/animation_codec.h b/src/animation_codec.h new file mode 100644 index 0000000..dbcdd9c --- /dev/null +++ b/src/animation_codec.h @@ -0,0 +1,50 @@ +#pragma once + +#include +#include +#include +#include + +#include + +// Binary serialization for an anim::Animation. +// +// This is what the operator hands to TouchDesigner's saveData()/loadData(), so +// the encoded bytes end up inside the .toe file. That means the format is +// persisted user data: anything written by a released build has to keep +// decoding in every later build, which is why the blob is versioned and why +// decode() validates rather than trusting its input. +// +// Deliberately not JSON or the Python state dict. The blob is written on every +// project save and read on every load, a keyframe-heavy animation runs to tens +// of thousands of values, and the codec has to work with no Python interpreter +// available -- loadData() runs during node construction. +// +// Kept free of both TouchDesigner and Python so it can be tested on its own; +// see tests/cpp. +namespace animation_codec { + +// Bumped only when the layout changes incompatibly. decode() refuses anything +// it does not recognize rather than guessing at the bytes. +inline constexpr uint32_t kFormatVersion = 1; + +// The key the operator stores the blob under, via OP_NodeSaveState::saveEntry. +inline constexpr const char* kSaveKey = "animation"; + +// Serializes the animation: range, then every channel with its extend modes and +// keyframes, in order. +std::vector encode(const anim::Animation& animation); + +// Rebuilds `animation` from bytes produced by encode(). +// +// Returns false and leaves `animation` untouched if the data is not a blob this +// build understands -- wrong magic, unknown version, truncated, or internally +// inconsistent. A .toe can carry a blob from a newer build, or a corrupted one; +// neither should take the node down or half-load an animation. On failure, +// `error` (when given) describes what was rejected. +bool decode(const void* data, + size_t byteSize, + anim::Animation& animation, + std::string* error = nullptr); + +} // namespace animation_codec diff --git a/tests/cpp/CMakeLists.txt b/tests/cpp/CMakeLists.txt new file mode 100644 index 0000000..269b049 --- /dev/null +++ b/tests/cpp/CMakeLists.txt @@ -0,0 +1,28 @@ +# C++ unit tests (Catch2). No Python or TouchDesigner required. +# +# The codec is the only part of this repository that is pure C++ -- everything +# else is either anim (which has its own suite) or binding glue that needs a +# Python interpreter. That makes it exactly what a C++ suite is good for, and +# it matters more than most: the bytes it writes go into users' .toe files. +include(FetchContent) +FetchContent_Declare( + Catch2 + GIT_REPOSITORY https://github.com/catchorg/Catch2.git + GIT_TAG v3.5.2 +) +FetchContent_MakeAvailable(Catch2) + +add_executable(animationchop_cpp_tests + test_animation_codec.cpp + ${CMAKE_SOURCE_DIR}/src/animation_codec.cpp +) +target_include_directories(animationchop_cpp_tests PRIVATE ${CMAKE_SOURCE_DIR}/src) +target_link_libraries(animationchop_cpp_tests PRIVATE + Catch2::Catch2WithMain + anim_static +) +target_compile_features(animationchop_cpp_tests PRIVATE cxx_std_20) + +list(APPEND CMAKE_MODULE_PATH ${catch2_SOURCE_DIR}/extras) +include(Catch) +catch_discover_tests(animationchop_cpp_tests) diff --git a/tests/cpp/test_animation_codec.cpp b/tests/cpp/test_animation_codec.cpp new file mode 100644 index 0000000..63ebbd4 --- /dev/null +++ b/tests/cpp/test_animation_codec.cpp @@ -0,0 +1,382 @@ +// Tests for the .toe persistence codec. +// +// The bytes this codec writes end up inside users' project files, so the two +// things that matter are that a round trip is faithful, and that decode() +// refuses anything it does not fully understand rather than producing a +// half-built animation. + +#include +#include + +#include +#include + +#include "animation_codec.h" + +#include +#include + +using Catch::Matchers::WithinAbs; + +namespace { + +constexpr double kEps = 1e-9; + +void requirePointsEqual(const anim::Point& actual, const anim::Point& expected) +{ + REQUIRE_THAT(actual.time, WithinAbs(expected.time, kEps)); + REQUIRE_THAT(actual.value, WithinAbs(expected.value, kEps)); +} + +void requireKeyframesEqual(const anim::Keyframe& actual, const anim::Keyframe& expected) +{ + requirePointsEqual(actual.position, expected.position); + requirePointsEqual(actual.in_handle, expected.in_handle); + requirePointsEqual(actual.out_handle, expected.out_handle); + REQUIRE(actual.function == expected.function); + REQUIRE(actual.handle_mode == expected.handle_mode); +} + +void requireAnimationsEqual(const anim::Animation& actual, const anim::Animation& expected) +{ + REQUIRE_THAT(actual.start_time(), WithinAbs(expected.start_time(), kEps)); + REQUIRE_THAT(actual.end_time(), WithinAbs(expected.end_time(), kEps)); + REQUIRE(actual.num_channels() == expected.num_channels()); + + for (size_t c = 0; c < expected.num_channels(); ++c) { + const anim::Channel& a = actual.channel(c); + const anim::Channel& e = expected.channel(c); + + INFO("channel " << c << " (" << e.name() << ")"); + REQUIRE(a.name() == e.name()); + REQUIRE(a.extend_start() == e.extend_start()); + REQUIRE(a.extend_end() == e.extend_end()); + REQUIRE(a.num_keyframes() == e.num_keyframes()); + + for (size_t k = 0; k < e.num_keyframes(); ++k) { + INFO("keyframe " << k); + requireKeyframesEqual(a.keyframe(k), e.keyframe(k)); + } + } +} + +// Round trip and compare, returning the decoded animation for further checks. +anim::Animation roundTrip(const anim::Animation& source) +{ + const std::vector bytes = animation_codec::encode(source); + REQUIRE_FALSE(bytes.empty()); + + anim::Animation decoded; + std::string error; + REQUIRE(animation_codec::decode(bytes.data(), bytes.size(), decoded, &error)); + REQUIRE(error.empty()); + return decoded; +} + +} // namespace + +TEST_CASE("An empty animation round trips", "[codec]") +{ + anim::Animation source; + source.set_start_time(0.0); + source.set_end_time(30.0); + + const anim::Animation decoded = roundTrip(source); + + REQUIRE(decoded.num_channels() == 0); + requireAnimationsEqual(decoded, source); +} + +TEST_CASE("Channels, keyframes and the range round trip", "[codec]") +{ + anim::Animation source; + source.set_start_time(1.0); + source.set_end_time(11.0); + + anim::Channel& tx = source.create_channel("tx"); + tx.create_keyframe(0.0, 0.0); + tx.create_keyframe(5.0, 100.0); + tx.create_keyframe(10.0, 50.0); + + anim::Channel& ty = source.create_channel("ty"); + ty.create_keyframe(2.5, -1.5); + + const anim::Animation decoded = roundTrip(source); + + REQUIRE(decoded.num_channels() == 2); + REQUIRE(decoded.channel(0).name() == "tx"); + REQUIRE(decoded.channel(1).name() == "ty"); + requireAnimationsEqual(decoded, source); +} + +TEST_CASE("Channel order is preserved", "[codec]") +{ + anim::Animation source; + for (const char* name : { "z", "a", "m", "b" }) + source.create_channel(name).create_keyframe(0.0, 0.0); + + const anim::Animation decoded = roundTrip(source); + + REQUIRE(decoded.channel_names() == std::vector{ "z", "a", "m", "b" }); +} + +TEST_CASE("Every interpolation function round trips", "[codec]") +{ + anim::Animation source; + anim::Channel& channel = source.create_channel("f"); + channel.create_keyframe(0.0, 0.0, anim::Function::Constant, anim::HandleMode::Flat); + channel.create_keyframe(1.0, 1.0, anim::Function::Linear, anim::HandleMode::Flat); + channel.create_keyframe(2.0, 2.0, anim::Function::Bezier, anim::HandleMode::Flat); + + const anim::Animation decoded = roundTrip(source); + + REQUIRE(decoded.channel(0).keyframe(0).function == anim::Function::Constant); + REQUIRE(decoded.channel(0).keyframe(1).function == anim::Function::Linear); + + // Note the third is NOT Bezier as created. A channel's last keyframe + // inherits its predecessor's function and handle mode, because its own + // would govern a segment that does not exist. Assert against the source + // rather than what was asked for -- the codec's job is fidelity to the + // animation as it actually is. + REQUIRE(decoded.channel(0).keyframe(2).function == channel.keyframe(2).function); + requireAnimationsEqual(decoded, source); +} + +TEST_CASE("Restoring loses the last keyframe's pre-inheritance function", "[codec][known-gap]") +{ + // anim caches what the last keyframe's function/handle mode were before + // inheritance overwrote them, and restores them if another keyframe is + // appended after it. That cache is private, so the codec cannot see it and + // cannot persist it. + // + // Consequence: append a keyframe to a channel that has been through a save + // and reload, and the formerly-last keyframe keeps its inherited function + // instead of reverting to the one it was created with. Pinned here so the + // gap is known rather than discovered. + anim::Animation source; + anim::Channel& channel = source.create_channel("f"); + channel.create_keyframe(0.0, 0.0, anim::Function::Constant, anim::HandleMode::Flat); + channel.create_keyframe(1.0, 1.0, anim::Function::Bezier, anim::HandleMode::Smooth); + + // The second keyframe was created Bezier but shows Constant, inherited. + REQUIRE(channel.keyframe(1).function == anim::Function::Constant); + + anim::Animation decoded = roundTrip(source); + + // Append to both, which is what makes the cache observable. + channel.create_keyframe(2.0, 2.0, anim::Function::Linear, anim::HandleMode::Flat); + decoded.channel(0).create_keyframe(2.0, 2.0, anim::Function::Linear, anim::HandleMode::Flat); + + // The original restores the Bezier it had cached; the reloaded one cannot. + REQUIRE(channel.keyframe(1).function == anim::Function::Bezier); + REQUIRE(decoded.channel(0).keyframe(1).function == anim::Function::Constant); +} + +TEST_CASE("Every handle mode round trips", "[codec]") +{ + // Explicit handles are the interesting case: inserting a keyframe re-solves + // its neighbours' handles, so a naive rebuild loses whatever a Free or + // Aligned keyframe was actually holding. + anim::Animation source; + anim::Channel& channel = source.create_channel("h"); + + const auto modes = { + anim::HandleMode::Flat, + anim::HandleMode::Smooth, + anim::HandleMode::Aligned, + anim::HandleMode::Free, + anim::HandleMode::AlignStrict, + anim::HandleMode::AlignFlex, + anim::HandleMode::AlignAdjustable, + }; + + double time = 0.0; + for (anim::HandleMode mode : modes) { + channel.create_keyframe(time, time * 2.0, anim::Function::Bezier, mode); + time += 1.0; + } + + const anim::Animation decoded = roundTrip(source); + + REQUIRE(decoded.channel(0).num_keyframes() == channel.num_keyframes()); + requireAnimationsEqual(decoded, source); +} + +TEST_CASE("Free handles survive the round trip", "[codec]") +{ + anim::Animation source; + anim::Channel& channel = source.create_channel("free"); + channel.create_keyframe(0.0, 0.0, anim::Function::Bezier, anim::HandleMode::Free); + channel.create_keyframe(4.0, 10.0, anim::Function::Bezier, anim::HandleMode::Free); + channel.create_keyframe(8.0, 0.0, anim::Function::Bezier, anim::HandleMode::Free); + + channel.set_keyframe_in_handle(1, anim::Point(3.25, 7.5)); + channel.set_keyframe_out_handle(1, anim::Point(5.75, 12.5)); + + const anim::Animation decoded = roundTrip(source); + + requireAnimationsEqual(decoded, source); + requirePointsEqual(decoded.channel(0).keyframe(1).in_handle, + channel.keyframe(1).in_handle); + requirePointsEqual(decoded.channel(0).keyframe(1).out_handle, + channel.keyframe(1).out_handle); +} + +TEST_CASE("Extend modes round trip", "[codec]") +{ + anim::Animation source; + anim::Channel& channel = source.create_channel("e"); + channel.create_keyframe(0.0, 0.0); + channel.create_keyframe(1.0, 1.0); + channel.set_extend_start(anim::Extend::Repeat); + channel.set_extend_end(anim::Extend::Mirror); + + const anim::Animation decoded = roundTrip(source); + + REQUIRE(decoded.channel(0).extend_start() == anim::Extend::Repeat); + REQUIRE(decoded.channel(0).extend_end() == anim::Extend::Mirror); +} + +TEST_CASE("Channel names with awkward content round trip", "[codec]") +{ + anim::Animation source; + source.create_channel(""); // empty + source.create_channel("a name with spaces"); + source.create_channel("utf8: \xc3\xa9\xc3\xa8"); + source.create_channel(std::string("embedded\0nul", 12)); + + const anim::Animation decoded = roundTrip(source); + + REQUIRE(decoded.num_channels() == 4); + REQUIRE(decoded.channel(0).name().empty()); + REQUIRE(decoded.channel(1).name() == "a name with spaces"); + REQUIRE(decoded.channel(3).name() == std::string("embedded\0nul", 12)); +} + +TEST_CASE("The decoded curve evaluates identically", "[codec]") +{ + // The point of persistence: the curve a user gets back has to be the curve + // they saved, not merely the same keyframe values. + anim::Animation source; + anim::Channel& channel = source.create_channel("curve"); + channel.create_keyframe(0.0, 0.0, anim::Function::Bezier, anim::HandleMode::Smooth); + channel.create_keyframe(3.0, 10.0, anim::Function::Bezier, anim::HandleMode::Aligned); + channel.create_keyframe(6.0, -5.0, anim::Function::Linear, anim::HandleMode::Free); + channel.create_keyframe(9.0, 2.0, anim::Function::Constant, anim::HandleMode::Flat); + + const anim::Animation decoded = roundTrip(source); + + for (double t = -1.0; t <= 10.0; t += 0.125) { + INFO("t = " << t); + REQUIRE_THAT(decoded.channel(0).evaluate(t), + WithinAbs(channel.evaluate(t), 1e-9)); + } +} + +// --- rejecting bad input ---------------------------------------------------- + +TEST_CASE("decode rejects null and empty data", "[codec]") +{ + anim::Animation animation; + std::string error; + + REQUIRE_FALSE(animation_codec::decode(nullptr, 0, animation, &error)); + REQUIRE_FALSE(error.empty()); + + const uint8_t nothing = 0; + REQUIRE_FALSE(animation_codec::decode(¬hing, 0, animation, &error)); +} + +TEST_CASE("decode rejects a foreign blob", "[codec]") +{ + const char foreign[] = "not an animation at all, just some bytes"; + anim::Animation animation; + std::string error; + + REQUIRE_FALSE(animation_codec::decode(foreign, sizeof(foreign), animation, &error)); + REQUIRE(error == "Not an AnimationCHOP blob"); +} + +TEST_CASE("decode rejects an unknown format version", "[codec]") +{ + anim::Animation source; + source.create_channel("tx").create_keyframe(0.0, 1.0); + std::vector bytes = animation_codec::encode(source); + + // Version sits immediately after the 4-byte magic. + bytes[4] = 0xFF; + + anim::Animation animation; + std::string error; + REQUIRE_FALSE(animation_codec::decode(bytes.data(), bytes.size(), animation, &error)); + REQUIRE(error == "Unsupported format version"); +} + +TEST_CASE("decode rejects truncation at every length", "[codec]") +{ + // A .toe can be truncated, and a blob written by a newer build can be + // longer than we expect. No prefix of a valid blob should decode as + // anything but a failure. + anim::Animation source; + source.set_end_time(12.0); + anim::Channel& channel = source.create_channel("tx"); + channel.create_keyframe(0.0, 0.0); + channel.create_keyframe(6.0, 3.0); + source.create_channel("ty").create_keyframe(1.0, 1.0); + + const std::vector bytes = animation_codec::encode(source); + + for (size_t length = 1; length < bytes.size(); ++length) { + INFO("truncated to " << length << " of " << bytes.size() << " bytes"); + anim::Animation animation; + REQUIRE_FALSE(animation_codec::decode(bytes.data(), length, animation)); + } +} + +TEST_CASE("decode rejects an out-of-range enum", "[codec]") +{ + anim::Animation source; + source.create_channel("tx").create_keyframe(0.0, 1.0); + std::vector bytes = animation_codec::encode(source); + + // The keyframe's two mode bytes are the last of the blob. + bytes[bytes.size() - 1] = 0x7F; + + anim::Animation animation; + std::string error; + REQUIRE_FALSE(animation_codec::decode(bytes.data(), bytes.size(), animation, &error)); + REQUIRE(error == "Bad keyframe mode"); +} + +TEST_CASE("A failed decode leaves the target animation untouched", "[codec]") +{ + anim::Animation existing; + existing.create_channel("keep").create_keyframe(0.0, 42.0); + existing.set_end_time(7.0); + + const char garbage[] = "ACHP but then nonsense follows here"; + REQUIRE_FALSE(animation_codec::decode(garbage, sizeof(garbage), existing)); + + REQUIRE(existing.num_channels() == 1); + REQUIRE(existing.channel(0).name() == "keep"); + REQUIRE_THAT(existing.channel(0).keyframe(0).value(), WithinAbs(42.0, kEps)); + REQUIRE_THAT(existing.end_time(), WithinAbs(7.0, kEps)); +} + +TEST_CASE("A large animation round trips", "[codec]") +{ + anim::Animation source; + for (int c = 0; c < 8; ++c) { + anim::Channel& channel = source.create_channel("chan" + std::to_string(c)); + for (int k = 0; k < 500; ++k) + channel.create_keyframe(k * 0.25, static_cast(k % 17)); + } + + const std::vector bytes = animation_codec::encode(source); + anim::Animation decoded; + REQUIRE(animation_codec::decode(bytes.data(), bytes.size(), decoded)); + + REQUIRE(decoded.num_channels() == 8); + REQUIRE(decoded.channel(0).num_keyframes() == 500); + requireAnimationsEqual(decoded, source); +} diff --git a/tests/python/CMakeLists.txt b/tests/python/CMakeLists.txt index 31958aa..c32ff12 100644 --- a/tests/python/CMakeLists.txt +++ b/tests/python/CMakeLists.txt @@ -5,6 +5,7 @@ add_library(animationchop_testext MODULE extension/animationchop_module.cpp ${CMAKE_SOURCE_DIR}/src/animation_chop.cpp + ${CMAKE_SOURCE_DIR}/src/animation_codec.cpp ${CMAKE_SOURCE_DIR}/src/py_anim_bindings/py_channel.cpp ${CMAKE_SOURCE_DIR}/src/py_anim_bindings/py_extend.cpp ${CMAKE_SOURCE_DIR}/src/py_anim_bindings/py_function.cpp From 835de01130c22322244741dad94129aa3951a3de Mon Sep 17 00:00:00 2001 From: keithlostracco Date: Sun, 26 Jul 2026 12:57:46 -0700 Subject: [PATCH 10/18] deps: follow anim to half-open rate sampling Pins anim at fix/half-open-rate-sampling. Sampling by rate now covers a half-open span: 30 seconds at 60 fps is 1800 samples rather than 1801, the range end is not sampled, and the times come from the sample index so they sit exactly one period apart with no drift. Both operators now fill their output with evaluate_range_by_rate() instead of evaluate_range(). This is the part that matters beyond the count. evaluate_range spreads a sample count across a *closed* interval, dividing by n - 1, so it only produces 1/rate spacing for one particular count -- and not at all when the span is not a whole number of periods. A CHOP's samples are implicitly one period apart, since the format stores no per-sample times, so pairing a half-open count with the closed-range fill would have declared 60 Hz while emitting data spaced 1/59.5 apart, skewing the whole channel. evaluate_range keeps its closed meaning for callers who want the end included. rangeSampleCount now delegates to Animation::num_samples rather than recomputing the formula, so the declared length and the generated data share one rounding rule and cannot drift apart. It still guards a non-positive rate, which num_samples throws on, and clamps up because a CHOP cannot have zero samples while an animation with no channels has none. Tests follow the new convention, and add the cases that distinguish it: that the last cooked sample sits one period short of the range end rather than on it, that evaluate_range stays closed while evaluate_range_by_rate is half-open, and that a duration landing a few ulps off a whole number of periods does not gain a sample. Also aligns the changelog-extraction awk with the form anim settled on -- the same fix, arrived at independently. --- .github/workflows/release.yml | 21 ++++++++-------- CHANGELOG.md | 20 +++++++++++---- ext/anim | 2 +- src/animation_chop.cpp | 37 ++++++++++++++++------------ src/animation_view_chop.cpp | 17 +++++++------ tests/python/test_animation.py | 45 +++++++++++++++++++++++++++++++--- tests/td/animation_test.py | 23 +++++++++++------ 7 files changed, 114 insertions(+), 51 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3b4b484..992fcf1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -119,17 +119,16 @@ jobs: - name: Extract release notes from CHANGELOG run: | version="${GITHUB_REF_NAME#v}" - # Match the heading with a plain string prefix test, not a dynamic - # regex. Building one as "^## \\[" v "\\]" looks right but is not - # portable: gawk strips the backslashes when converting a string to a - # regex, leaving [0.4.0] as a character class that matches a single - # character. The bug is silent -- it yields empty notes, not an error. - awk -v hdr="## [$version]" ' - index($0, hdr) == 1 { found = 1; next } # heading for this version - found && /^## \[/ { exit } # next version heading - found && /^\[.*\]: / { exit } # link reference block - found && /^