Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ set(SRC ${CMAKE_CURRENT_SOURCE_DIR}/src)

set(PY_ANIM_BINDINGS_SRCS
${SRC}/py_anim_bindings/py_channel.cpp
${SRC}/py_anim_bindings/py_extend.cpp
${SRC}/py_anim_bindings/py_function.cpp
${SRC}/py_anim_bindings/py_handle_mode.cpp
${SRC}/py_anim_bindings/py_keyframe.cpp
Expand Down
7 changes: 5 additions & 2 deletions src/animation_chop.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

#include "animation_chop.h"
#include "py_anim_bindings/py_bindings.h"
#include "py_anim_bindings/py_extend.h"


#include <stdio.h>
Expand Down Expand Up @@ -79,6 +80,7 @@ static PyGetSetDef getSets[] =
{"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},
{"Function", get_function_enum, nullptr, "Function enum for keyframe interpolation type.", nullptr},
{"Extend", get_extend_enum, nullptr, "Extend enum for channel extrapolation behavior.", nullptr},
{"Keyframe", get_keyframe_type, nullptr, "Keyframe type for animation curves.", nullptr},
{"channels", py_get_channels, nullptr, "Get all channels.", nullptr},
{"channel_names", py_get_channel_names, nullptr, "Get all channel names.", nullptr},
Expand Down Expand Up @@ -179,6 +181,7 @@ AnimationCHOP::~AnimationCHOP()
{
cleanup_handle_mode_enum();
cleanup_function_enum();
cleanup_extend_enum();
}

void
Expand Down Expand Up @@ -463,8 +466,8 @@ AnimationCHOP::setupParameters(OP_ParameterManager* manager,void *reserved1)
assert(res == OP_ParAppendResult::Success);
} {
OP_NumericParameter np;
np.name = "Sequence";
np.label = "Sequence Index";
np.name = "Sequence";
np.label = "Sequence Index";
np.defaultValues[0] = 0.0;
np.minSliders[0] = 0.0;
np.maxSliders[0] = 30.0;
Expand Down
3 changes: 2 additions & 1 deletion src/py_anim_bindings/py_bindings.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#include "py_point.h"
#include "py_keyframe.h"
#include "py_channel.h"
#include "py_handle_mode.h"
#include "py_function.h"
#include "py_channel.h"
#include "py_extend.h"
80 changes: 80 additions & 0 deletions src/py_anim_bindings/py_channel.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include "py_channel.h"
#include "py_keyframe.h" // For KeyframeToPY_Object, PY_KeyframeType
#include "utils.h" // For AnimationCHOP, AnimationToPY_Object
#include "py_extend.h" // For Extend enum support
#include <vector>
#include <optional>

Expand Down Expand Up @@ -706,6 +707,68 @@ static PyObject* PY_Channel_num_samples(PY_Channel *self, PyObject* args) {
}
}

static PyObject* PY_Channel_get_extend_start(PY_Channel *self, void*) {
auto channelData = getChannelData(self, false);
if (!channelData.channel) {
PyErr_SetString(PyExc_RuntimeError, "Channel is not valid");
return NULL;
}
return PyLong_FromLong(static_cast<long>(channelData.channel->extend_start()));
}

static int PY_Channel_set_extend_start(PY_Channel *self, PyObject* value, void*) {
auto channelData = getChannelData(self, false);
if (!channelData.channel) {
PyErr_SetString(PyExc_RuntimeError, "Channel is not valid");
return -1;
}
if (!PyLong_Check(value)) {
PyErr_SetString(PyExc_TypeError, "extend_start must be an integer");
return -1;
}
long extend_val = PyLong_AsLong(value);
if (extend_val < 0 || extend_val > 2) {
PyErr_SetString(PyExc_ValueError, "extend_start must be a valid Extend value (0-2)");
return -1;
}
channelData.channel->set_extend_start(static_cast<anim::Extend>(extend_val));
if (channelData.node_struct) {
channelData.node_struct->context->makeNodeDirty();
}
return 0;
}

static PyObject* PY_Channel_get_extend_end(PY_Channel *self, void*) {
auto channelData = getChannelData(self, false);
if (!channelData.channel) {
PyErr_SetString(PyExc_RuntimeError, "Channel is not valid");
return NULL;
}
return PyLong_FromLong(static_cast<long>(channelData.channel->extend_end()));
}

static int PY_Channel_set_extend_end(PY_Channel *self, PyObject* value, void*) {
auto channelData = getChannelData(self, false);
if (!channelData.channel) {
PyErr_SetString(PyExc_RuntimeError, "Channel is not valid");
return -1;
}
if (!PyLong_Check(value)) {
PyErr_SetString(PyExc_TypeError, "extend_end must be an integer");
return -1;
}
long extend_val = PyLong_AsLong(value);
if (extend_val < 0 || extend_val > 2) {
PyErr_SetString(PyExc_ValueError, "extend_end must be a valid Extend value (0-2)");
return -1;
}
channelData.channel->set_extend_end(static_cast<anim::Extend>(extend_val));
if (channelData.node_struct) {
channelData.node_struct->context->makeNodeDirty();
}
return 0;
}

// --- State methods ---
PyObject* PY_Channel_get_state(PY_Channel *self, void *closure) {
auto channelData = getChannelData(self, false);
Expand All @@ -725,6 +788,8 @@ PyObject* PY_Channel_get_state(PY_Channel *self, void *closure) {
PyDict_SetItemString(state_dict, "length", PyFloat_FromDouble(channelData.channel->length()));
PyDict_SetItemString(state_dict, "num_keyframes", PyLong_FromSize_t(channelData.channel->num_keyframes()));
PyDict_SetItemString(state_dict, "empty", PyBool_FromLong(channelData.channel->empty() ? 1 : 0));
PyDict_SetItemString(state_dict, "extend_start", PyUnicode_FromString(extend_to_string(channelData.channel->extend_start())));
PyDict_SetItemString(state_dict, "extend_end", PyUnicode_FromString(extend_to_string(channelData.channel->extend_end())));

// Create keyframes list using keyframe state
PyObject* keyframes_list = PyList_New(channelData.channel->num_keyframes());
Expand Down Expand Up @@ -796,6 +861,19 @@ int PY_Channel_set_state(PY_Channel *self, PyObject *value, void *closure) {
channelData.channel->set_name(PyUnicode_AsUTF8(name_obj));
}

// Set extend modes if provided
PyObject* extend_start_obj = PyDict_GetItemString(value, "extend_start");
if (extend_start_obj && PyUnicode_Check(extend_start_obj)) {
const char* extend_start_str = PyUnicode_AsUTF8(extend_start_obj);
channelData.channel->set_extend_start(string_to_extend(extend_start_str));
}

PyObject* extend_end_obj = PyDict_GetItemString(value, "extend_end");
if (extend_end_obj && PyUnicode_Check(extend_end_obj)) {
const char* extend_end_str = PyUnicode_AsUTF8(extend_end_obj);
channelData.channel->set_extend_end(string_to_extend(extend_end_str));
}

// Process keyframes
PyObject* keyframes_obj = PyDict_GetItemString(value, "keyframes");
if (keyframes_obj) {
Expand Down Expand Up @@ -974,6 +1052,8 @@ static PyGetSetDef PY_Channel_getset[] = {
{"start_time", (getter)PY_Channel_start_time, NULL, "Start time", NULL},
{"end_time", (getter)PY_Channel_end_time, NULL, "End time", NULL},
{"length", (getter)PY_Channel_length, NULL, "Length", NULL},
{"extend_start", (getter)PY_Channel_get_extend_start, (setter)PY_Channel_set_extend_start, "Start extend mode", NULL},
{"extend_end", (getter)PY_Channel_get_extend_end, (setter)PY_Channel_set_extend_end, "End extend mode", NULL},
{"state", (getter)PY_Channel_get_state, (setter)PY_Channel_set_state, "Channel state as dictionary", NULL},
{NULL}
};
Expand Down
133 changes: 133 additions & 0 deletions src/py_anim_bindings/py_extend.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
#include "py_extend.h"
#include <anim/extend.hpp>

static PyObject* extend_enum_singleton = NULL;

static PyObject* create_extend_enum() {
// If we already have a singleton instance, return that
if (extend_enum_singleton) {
Py_INCREF(extend_enum_singleton);
return extend_enum_singleton;
}

PyObject* enum_module = NULL;
PyObject* int_enum_class = NULL;
PyObject* members_dict = NULL;
PyObject* extend_enum_name = NULL;
PyObject* extend_enum_type = NULL; // The created enum type

enum_module = PyImport_ImportModule("enum");
if (!enum_module) {
// PyImport_ImportModule sets an error
return NULL;
}

int_enum_class = PyObject_GetAttrString(enum_module, "IntEnum");
if (!int_enum_class) {
Py_DECREF(enum_module);
return NULL;
}

members_dict = PyDict_New();
if (!members_dict) {
Py_DECREF(int_enum_class);
Py_DECREF(enum_module);
return NULL;
}

// Helper lambda to add members and check for errors
auto add_member = [&](const char* name, anim::Extend val) -> bool {
PyObject* py_val = PyLong_FromLong(static_cast<long>(val));
if (!py_val) {
return false;
}
int result = PyDict_SetItemString(members_dict, name, py_val);
Py_DECREF(py_val);
if (result < 0) {
return false;
}
return true;
};

if (!add_member("HOLD", anim::Extend::Hold) ||
!add_member("REPEAT", anim::Extend::Repeat) ||
!add_member("MIRROR", anim::Extend::Mirror)) {
Py_DECREF(members_dict);
Py_DECREF(int_enum_class);
Py_DECREF(enum_module);
return NULL;
}

extend_enum_name = PyUnicode_FromString("Extend");
if (!extend_enum_name) {
Py_DECREF(members_dict);
Py_DECREF(int_enum_class);
Py_DECREF(enum_module);
return NULL;
}

// Create the IntEnum type: IntEnum("Extend", {"HOLD": 0, ...})
extend_enum_type = PyObject_CallFunctionObjArgs(int_enum_class, extend_enum_name, members_dict, NULL);
if (!extend_enum_type) {
Py_DECREF(extend_enum_name);
Py_DECREF(members_dict);
Py_DECREF(int_enum_class);
Py_DECREF(enum_module);
return NULL;
}

// Success path: Clean up intermediate objects
Py_DECREF(extend_enum_name);
Py_DECREF(members_dict);
Py_DECREF(int_enum_class);
Py_DECREF(enum_module);

// Store the created enum as our singleton
extend_enum_singleton = extend_enum_type;
Py_INCREF(extend_enum_singleton); // Add extra reference to keep it alive
return extend_enum_type; // Return new reference to the created enum type
}

// Getter function for the Extend enum
PyObject* get_extend_enum(PyObject* self, void* closure) {
return create_extend_enum();
}

// Cleanup function for module shutdown
void cleanup_extend_enum() {
if (extend_enum_singleton) {
Py_DECREF(extend_enum_singleton);
extend_enum_singleton = NULL;
}
}

// Extend enum string representations
static const char* extend_names[] = {
"HOLD",
"REPEAT",
"MIRROR"
};

static const char* extend_full_names[] = {
"Extend.HOLD",
"Extend.REPEAT",
"Extend.MIRROR"
};

// Helper functions for state serialization
const char* extend_to_string(anim::Extend extend) {
int idx = static_cast<int>(extend);
if (idx >= 0 && idx < 3) {
return extend_full_names[idx];
}
return "Extend.HOLD";
}

anim::Extend string_to_extend(const char* str) {
for (int i = 0; i < 3; ++i) {
if (strcmp(str, extend_full_names[i]) == 0 || strcmp(str, extend_names[i]) == 0) {
return static_cast<anim::Extend>(i);
}
}
return anim::Extend::Hold; // Default fallback
}
17 changes: 17 additions & 0 deletions src/py_anim_bindings/py_extend.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#ifndef PY_EXTEND_H
#define PY_EXTEND_H

#include <Python.h>
#include <anim/extend.hpp>

// Getter function for the Extend enum
PyObject* get_extend_enum(PyObject* self, void* closure);

// Cleanup function for module shutdown
void cleanup_extend_enum();

// Helper functions for state serialization
const char* extend_to_string(anim::Extend extend);
anim::Extend string_to_extend(const char* str);

#endif // PY_EXTEND_H
Binary file modified td/Plugins/AnimationCHOP.dll
Binary file not shown.
20 changes: 0 additions & 20 deletions td/Plugins/AnimationCHOP.plugin/Contents/Info.plist

This file was deleted.

Binary file not shown.
20 changes: 0 additions & 20 deletions td/Plugins/AnimationViewCHOP.plugin/Contents/Info.plist

This file was deleted.

Binary file not shown.
Loading
Loading