Exit
  • Global community
    • Language:
      • Deutsch
      • English
      • Español
      • Français
      • Português
  • 日本語コミュニティ
  • 한국 커뮤니티
1

Creating Layers/ Altering pixels from raw data in an AEGP.

Explorer ,
Nov 22, 2023 Nov 22, 2023

Having a hard time coming up with some solutions for this one...

I'd like to be able to pass raw image data into an AEGP, and use that to either alter existing pixels in items, or create new footage/layers with the image/video/whatever data. 

 

I have an ImageData struct like so;

struct ImageData {
    std::shared_ptr<std::vector<uint8_t>> data;
    int width;
    int height;
    int channels;

    // Default constructor
    ImageData() : width(0), height(0), channels(0), data(std::make_shared<std::vector<uint8_t>>()) {}

    // Constructor with shared_ptr and dimensions
    ImageData(std::shared_ptr<std::vector<uint8_t>> d, int w, int h, int c)
        : data(std::move(d)), width(w), height(h), channels(c) {}

    // Constructor with initialization
    ImageData(int w, int h, int c) : width(w), height(h), channels(c) {
        data = std::make_shared<std::vector<uint8_t>>(w * h * c);
    }
};

 

From what I'm seeing I need to use a file path for actually adding new layers and items. 

 

I've considered possibly using a null or solid layer, then replacing that, or possibly even trying to embed an effect plugin to be added to layers and called by the AEGP when necessary?

From some initial tests, it is super slow getting AEGP_WorldH across all frames in a comp, but it is definitely doable. Same with altering the AEGP_WorldH with data passed in. 

Perhaps AEGP_IterateGeneric could help?

 

At this point I'm kind of leaning towards the "embedded plugin" idea, since that could utilize multithreading, and the only communication between it and the AEGP would be done from a single threaded call, thus eliminating potential race conditions and issues with multiple AEGP instances?

 

TLDR; I'm exposing the SDK to python and I want to give some options for adding and manipulating custom image/video/audio data ( both gathered and passed in) so that people can utilize the massive amount of python libs to create some really cool stuff, and I need some assistance figuring out some alternative approaches to doing so.


Any suggestions would be welcome.

 

Thank you! 🙂

TOPICS
Audio , How to , Import and export , Performance , Resources , SDK
795
Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Nov 24, 2023 Nov 24, 2023

well... all that very much depends on what user experience or workflow you're aiming for.

if it's an "overall" python script that governs the entire project, a "once off" script that does a task when asked to, or a filter that chages the layer image like an effect.

 

let's talk technical concepts.

an AEGP can request an item's buffer and read it. can can the AEGP *change* that buffer? the buffer passed on to the AEGP may be chaced by AE for use, it may be a COPY of the cached buffer, and it may be a temp buffer entirely uncached. so changin the passed buffer is highly likely to not show up anywhere, and even so the changed buffer may be discarded and regenerated without any detectable indication.

an AEGP can create a new image file on disk. that image file can be imported back into AE and placed in a comp, but as i said, that's a different user experience and workflow.

 

effects are passed the buffer of either the layer or the preceding masks/effects, and are also handed an output buffer to write the output to. that buffer can be passed to the AEGP for it to write into. however, that has to be synchronous, as once the render call has exited there's not telling what AE will do with that buffer, so it must be filled before returning.

also, the fetching of the input buffer to the effects is much more optimized than requesting said inputs directly from an AEGP. it's not that AE renders faster for effects, it's that AE can predict what effects would need and therefore cache required inputs, while it can't predict what AEGP would need and therefore usually renders inputs on request.

 

so, perhaps if you described the workflow from what you'd like the user's point of view to be, then maybe i could help you strategize technically.

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Explorer ,
Nov 25, 2023 Nov 25, 2023
Project Overview and Workflow:

My project aims to enable users to write scripts in Python to create and manipulate custom image, video, and audio data within After Effects. The current functionality allows users to select and run scripts through a menu command, with plans to evolve this into a more interactive and GUI-based approach. It could be a global script, or it could be a simple task based script. Best way I can put it is it works nearly the same as running extendscript from file->scripts->run, and end-goal is exposing that functionality to "CEP" ("PYE") extensions as well, effectively offering an alternative to extendscript. 

Technical Approach and Error Handling:

I'm working with pybind11 for Python integration and wrapping SDK functions in a Result<T, A_Err> struct for error handling. If an SDK function fails, the error is propagated up to Python, allowing for appropriate handling. Result structs are thread safe, and exposed python classes use std::shared_ptrs for memory management. I'm wrapping the entire SDK in single-concern functions like this;

```c++
Result<AEGP_ItemH> getActiveItem()
{
    AEGP_SuiteHandler& suites = SuiteManager::GetInstance().GetSuiteHandler();
    A_Err err = A_Err_NONE;
    AEGP_ItemH itemH = nullptr; // Initialize to nullptr in case AEGP_GetActiveItem fails
    ERR(suites.ItemSuite9()->AEGP_GetActiveItem(&itemH));

    Result<AEGP_ItemH> result;
    if (err != A_Err_NONE) {
        result.value = nullptr;
        throw std::runtime_error("Error getting active item. Error code: " + std::to_string(err));
    }
    else {
        result.value = itemH;
    }
    result.error = err;   // Set the error

    return result;
}
```

Regarding the wrappers-- anything that stands out that I could be doing a bit better here for error handling?

Performance Concerns:

A primary focus is on optimizing performance, particularly regarding UI blocking, processing speed, and memory management.
I've solved the UI-blocking issue by putting python on a separate thread, and calling into the idleHook with 1:1 command calls. (Each exposed python method sends one call into the idleHook queue, which even with multiple items in the queue, will return with each call to prevent blocking.)

Python-AE Communication:

The plugin uses a thread-safe queue for communication between Python and AE, ensuring a non-blocking UI.

Frame Handling in Effect Plugins:
Here's how I envision the frame handling working out: In the effect plugin, frames are gathered and placed in a struct that includes the frame number for accurate handling in Python. They are then processed by Python and sent back. Another approach is for users to write a custom callback function, used by the effect to implement the desired effect. Here's an example in Python demonstrating this concept:

```python
from PyShiftCore import *

def some_callback_function(**args) # create a callback function
    # processing pixels here
    return ImageData

comp = app.project.activeItem  # check for the activeItem

if isinstance(comp, CompItem):  # if comp is actually a composition

    layer1 = comp.layer[0] # get the first layer

    layer1.customEffect(some_callback_function(**args)) # pass a custom callback function to pass into the effect, which will then be used to call into python to perform processing.

    layer1.requestFrames(list[int] frameNum) #request a frame at a given time
    layer1.requestAllFrames() #request all frames from the layer
    layer1.replaceFrame(int frameNum, ImageData frame) #replace frame at given time
    layer1.replaceAllFrames() #replace all frames in layer.

else:
    app.reportInfo("Select a Composition first!") # if comp is not a CompItem, the user needs to select a comp instead.

```

Regarding your mention of AEGPs writing into the output buffer, this synchronous approach seems promising, especially considering the challenges with the Python GIL. My main objective is to streamline the transfer of pixel data into numpy arrays for extensive processing capabilities.

Future Plans and Challenges:
The long-term goal is to enable users to create custom effects, transitions, and standard manipulation through Python, eliminating the need for extensive C++ plugin development. I have established a solid foundation with numerous working classes, attributes, and methods. In the near term, I aim to finish wrapping the SDK functions, ensuring proper error and memory handling, and then flesh out PyShiftCore to match most attributes of extendscript.

Hopefully this paints a bit clearer of a picture. If there's anything that needs further clarification or if you have any specific suggestions or feedback, especially regarding workflows for the custom processing and object lifetime, please let me know!!

Thank you for your time and consideration!
Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Nov 25, 2023 Nov 25, 2023

first off, wow! love it!

now, the basis for project manipulation and "once off" operations seems solid. processing python on a separate thread while synching both sides during idle hook is the optimal way to go. (IMHO)

 

as for image processing, i see a bottleneck in the current scheme. the latest AE versions employ multiple render threads. if all said threads send their images processing to the one python thread while waiting synchronously for it to return the result, then parallel processes will not enjoy the benefit of MFR (multiple frame rendering).

my knowledge of python is very limited, so i don't know if it's possible or not, but consider the following:

if the processing of each "python filter" instance is independent of other instances, then is it possible to run multiple separate threads of python? this way each render thread gets to process without waiting in queue with the others.

if it's not possible because of threading issues, or if the processing is co-dependent and has to be done by some "central brain", then at least the communication between python filer render request and the python thread should be immediate and not via idle hook. yes, the idle hook calls are pretty frequent, but a while 0.05 second  delay is ok, longer lags that may occur due to queue accumulation make the user experience feel sluggish instead of interactive. it's not a "usage killer" but it's worth considering.

 

that being said, AE also has it's "mutex" crossroads on it's rendering pipline. not all plug-ins are thread-safe, and therefore hold for sequential processing instead of parallel. same goes for GPU effects that are rendered one at a time. so take that whole bit about multi-threading the python rendering with a grain of salt...

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Explorer ,
Nov 26, 2023 Nov 26, 2023
Shachar,
Thank you for all of the feedback!
 
It is totally feasible to give each thread its own python instance. I'll have to play around with threads vs processes, but its certainly possible.
This means I could have the user write a python callback function, pass it in, and have the (potentially multi-threaded) effect plugin process them!

 

That gives a promising avenue for one point. Now, what about times where the user would need to process their frames outside of the effect?
I'm thinking it would still interface with the AEGP, but instead of providing a callback function for altering frames, a built in callback to retrieve frames is provided. Perhaps passing the indata/outdata pointers to the AEGP could help with this part, and I could lazily load the frames from outData as python needs them?

 

Your points about AE's own rendering pipeline and its limitations with thread-safe plugins and GPU effects are well-taken. I think in any case, this won't cause too much trouble, provided any communication is set up via IPC or something similar, rather than AEGP calls directly. Cutting out the middle man, like you had suggested earlier.

 

I'm thinking I can send a command requesting the proper pointers, receive it in the AEGP, then pass what it needs from that into python, and back in.
 
I'm guessing passing the pointer between effect and AEGP is pretty straightforward? (cast to refcon, pass the proper direction, access what I need from it, etc).
 
If you have any extra thoughts about that, I would love to hear them!

Thanks again!

-Trenton
Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Nov 26, 2023 Nov 26, 2023

a couple of thoughts:

 

in regards to frame checkout, passing the in_data/out_data structures to the AEGP is only valid for the duration of the effect's call. say you pass them during the effect's render call, once the effect returns, all the info in the in/out data structs are now garbage. even copying the won't solve the issue in most cases as the inportant bits data itself are mostly refernces to some opaque AE handles that are now invalidated.

so the AEGP *can* use the in_data to lazily chekcout frames, but only if it's synchronous and stalls the render call until it returns.

in terms of optimizing the whole of the performance, it's important to declare the checked out frames during the effet's pre-render call, so AE would know in advance and try to cache these frames.

 

 as for passing a pointer to an effect instance, that a bit tricky. why? because what IS an instance? it looks obvious from the user's point of view, but behind the scenes there are no actual instances. an effect is a stateless skeleton with data being passed through on a call to call basis for all instances. even tagging an instance in sequence data is not a deterministic way to tell instances apart, as effects can have copies made, that would carry the same info. (it would require detecting copies and changing the new one's UID. not impossible, but definitely some hassle...)

so, now that you've recognized a specific instance, what would be a pointer to it? a pointer to some handle inside it's sequence data is the closest i could think of...

you could instead pass a location description (comp itemH + layer ID + index of effect on layer), and then talk to that effect directly using AEGP_EffectCallGeneric(), which allows you to pass custom data and handle it by a specific instance. that is a very good way of feeding an instance some data from an AEGP. (note, using this method to talk between instances has quite a few problems... try to use only from AEGP to effect. i'm serious. it won't work if the call stack is currently effect->AEGP->effect. works well only when AE calls an AEGP directly and it makes the call)

even after that's done, there's a matter of synchronizing the data between the UI thread (where idle calls happen), and the render thread. changing the sequence data doesn't necessarily mean the data got synced to the render threads.

that's a lot of info to chew through... and the implementation varies vastly according what data you wish to pass and when...

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Explorer ,
Dec 02, 2023 Dec 02, 2023

Okay! So, I think I came up with a solution, and Id be keen to hear your thoughts--

End Goal:
From my AEGP plugin, allow the user to script the creation of a "custom" effect. 


How I plan to do it:

On the AEGP & Effect Plugin Sides:

Embed a python intepreter. 

Expose classes like so;

// Slider class equivalent
class Slider {
public:
    Slider(const std::string& name, int minS, int maxS, int increment, int default_val)
        : name(name), minimum(minS), maximum(maxS), increment(increment), default_val(default_val) {}

    std::string name;
    int minimum, maximum, increment, default_val;
};

// Params class equivalent
class Params {
public:
    std::vector<Slider> sliders;
};

// CustomEffect class equivalent
class CustomEffect {
public:
    CustomEffect() : name(""), elements(), callback_function() {}

    std::string name;
    Params elements;
    py::function callback_function;
};

This way, I can provide a simple interface to dynamically change the UI elements, and what "effect" is being applied. 

 

From the Effect Plugin Side:

Create a "Pool" of UI elements, hidden when first applied. AFAIK I can't dynamically add/remove elements, but I can hide/show existing ones. 

Initialize a 'CustomEffect' instance within inData (This can be done through globalData, correct?)
Embed the python interpreter. To test things out initially I'm only working single-threaded, but my idea for multit-threading is as follows:

 

Determine the # of threads for the effect, and initialize that many python processes in a pool. 

When the render command is called, it will use the pool, adding and removing things from the queue as it finishes them. 

 

The Effect will receive PF_Cmd_COMPLETELY_GENERAL, which will contain the CustomEffect data sent from the AEGP.

It will then take this, modify the InData->GlobalData refcon we stored earlier.

The RespondtoAEGP function will update the UI with the required elements and arguments (keeping all others hidden). It will also cast the callback function to the refcon.

 

In the render function, it will conditionally check for a function there. If it exists, it will use it. (The assumption is that the order of params in the func matches the order and # of params in the UI, which will be documented and required in the API). If it doesn't exist, it'll just push input to output. 

From the AEGP Side:
After passing the Information over, the AEGP simply receives a response indicating success or failure, so that it may continue executing, if it needs to. From this point, we now have an entirely custom effect applied! (Though I'd like to figure out how to maintain state for the project somwhow)

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Dec 06, 2023 Dec 06, 2023

i think there's some confusion here between inData and sequence_data.

in_data is the structure passed by AE to an effect plug-in on every call. it contain's information relevant to that call, and is valid for the durtion of that call.

sequence data is the "custom storage" handle for each effect isntance. the that handle is passed to the effect via the in_data struct on every call. that data can be "flat" (Serialzied) or "unflat" (deserialzed).

 

i also didn't quite understand the use of global data here. yes, global data is a good place to store some "brains" for all instances together, but we're also talking about an AEGP which is (usually) a separate plugin. so i think there might be some confusion between the two here as well... or i just didn't understant the workflow you described.

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Explorer ,
Dec 06, 2023 Dec 06, 2023

I'll try to give a bit better context--

I have this struct, which I'm using to:
A) Create the class/struct from python
B) Pass the created instance into my AEGP plugin,

and

C) Use the instance in an effect plugin to do things.

// Slider class equivalent
class Slider {
public:
    Slider(const std::string& name, float minS, float maxS, float increment, float default_val)
        : name(name), minimum(minS), maximum(maxS), increment(increment), default_val(default_val) {}

    std::string name;
    float minimum, maximum, increment, default_val;
};

// Params class equivalent
class Params {
public:
    std::vector<Slider> sliders;
};

// CustomEffect class equivalent
class CustomEffect {
public:
    CustomEffect(){}

    std::string name;
    std::shared_ptr<Params> elements;
    py::function callback_function;
};

I can confirm that my data is received properly, but outside of the name, it isn't altered properly. 
I first set up a default instance in my global setup;

static PF_Err GlobalSetup(PF_InData* in_data, PF_OutData* out_data, PF_ParamDef* params[], PF_LayerDef* output) {
	out_data->my_version = PF_VERSION(MAJOR_VERSION, MINOR_VERSION, BUG_VERSION, STAGE_VERSION, BUILD_VERSION);
	PF_Err err = PF_Err_NONE;
	// Allocate memory for CustomEffect using After Effects' memory allocation functions
	AEGP_SuiteHandler suites(in_data->pica_basicP);
	//initialize_python_module();
	CustomEffect customEffect = CustomEffect();
	customEffect.name = "Skeleton";

	PF_Handle handle = suites.HandleSuite1()->host_new_handle(sizeof(CustomEffect));

	if (handle) {

			void* ptr = suites.HandleSuite1()->host_lock_handle(handle);
			if (ptr) {
				// Copy the CustomEffect object to the handle
				memcpy(ptr, &customEffect, sizeof(CustomEffect));
			}
			else {
				// Handle lock failure
				err = PF_Err_INTERNAL_STRUCT_DAMAGED;
			}
	}
	else {
		// Handle allocation failure
		err = PF_Err_OUT_OF_MEMORY;
	}
	out_data->global_data = handle;
	out_data->out_flags = PF_OutFlag_DEEP_COLOR_AWARE;  // Just 16bpc, not 32bpc
	suites.HandleSuite1()->host_unlock_handle(handle);
	return err;
}

In my PF_Cmd_COMPLETELY_GENERAL, I receive a custom struct from my AEGP plugin, and try to copy it over;


static PF_Err RespondtoAEGP(
	PF_InData* in_data,
	PF_OutData* out_data,
	PF_ParamDef* params[],
	PF_LayerDef* output,
	void* extra)
{
	PF_Err err = PF_Err_NONE;

	AEGP_SuiteHandler suites(in_data->pica_basicP);
	PF_Handle handle;
	if (extra) {
		// Extra contains CustomEffect object, use it to update the global_data.
		CustomEffect* newCustomEffect = reinterpret_cast<CustomEffect*>(extra);
		
		if (newCustomEffect != NULL) {
			CustomEffect* globalCustomEffect = reinterpret_cast<CustomEffect*>(suites.HandleSuite1()->host_lock_handle(out_data->global_data));

			if (globalCustomEffect) {
				// Copy the content of newCustomEffect to globalCustomEffect
				memcpy(globalCustomEffect, newCustomEffect, sizeof(CustomEffect));
				suites.HandleSuite1()->host_unlock_handle(out_data->global_data);
			}
			else {
				// Handle the case where lock_handle fails
				err = PF_Err_INTERNAL_STRUCT_DAMAGED;
			}
		}
		else {
			// Handle the case where extra CustomEffect is NULL
			err = PF_Err_BAD_CALLBACK_PARAM;
		}

	}
	else {
		// Handle the case where extra is NULL
		err = PF_Err_BAD_CALLBACK_PARAM;
	}
	suites.HandleSuite1()->host_unlock_handle(out_data->global_data);

	return err;
}

I'm then taking the new data, and trying to 

a) Adjust the UI, using PF_Cmd_UPDATE_PARAMS_UI:

static PF_Err
UpdateParameterUI(
	PF_InData* in_data,
	PF_OutData* out_data,
	PF_ParamDef* params[],
	PF_LayerDef* outputP)
{
	PF_Err err = PF_Err_NONE;
	AEGP_SuiteHandler suites(in_data->pica_basicP);

	// Lock the handle to get a pointer to the CustomEffect object in global_data
	CustomEffect* customEffect = reinterpret_cast<CustomEffect*>(suites.HandleSuite1()->host_lock_handle(out_data->global_data));
	if (customEffect) {
		if (customEffect->name == "Skeleton") {
			suites.HandleSuite1()->host_unlock_handle(out_data->global_data);
			return err;
		}
		std::shared_ptr<Params> paramsP = customEffect->elements;
		Params* paramsZ = paramsP.get();



		std::vector<Slider> sliders = paramsZ->sliders;
		// Create a copy of parameters
		PF_ParamDef param_copy[SKELETON_NUM_PARAMS];
		ERR(MakeParamCopy(params, param_copy));

		// Loop through CustomEffect sliders and update AE sliders
		for (int i = 0; i < sliders.size() && (i + SKELETON_SLIDER_1) < SKELETON_NUM_PARAMS; i++) {
			Slider slider = sliders[i];

			// Update AE slider name and value
			strncpy(param_copy[i + SKELETON_SLIDER_1].name, slider.name.c_str(), PF_MAX_EFFECT_PARAM_NAME_LEN - 1);
			param_copy[i + SKELETON_SLIDER_1].name[PF_MAX_EFFECT_PARAM_NAME_LEN - 1] = '\0';  // Ensure null-termination
			param_copy[i + SKELETON_SLIDER_1].u.fs_d.value = slider.default_val;
			param_copy[i + SKELETON_SLIDER_1].u.fs_d.valid_min = slider.minimum;
			param_copy[i + SKELETON_SLIDER_1].u.fs_d.valid_max = slider.maximum;
			param_copy[i + SKELETON_SLIDER_1].u.fs_d.slider_min = slider.minimum;
			param_copy[i + SKELETON_SLIDER_1].u.fs_d.slider_max = slider.maximum;

			// Update UI for each parameter
			ERR(suites.ParamUtilsSuite3()->PF_UpdateParamUI(in_data->effect_ref, i + SKELETON_SLIDER_1, &param_copy[i + SKELETON_SLIDER_1]));
		}

		// Hide any unused sliders
		for (int i = sliders.size(); i < SKELETON_NUM_PARAMS - 1; i++) { // Adjusted to start from the first unused slider
			param_copy[i + SKELETON_SLIDER_1].ui_flags |= PF_PUI_INVISIBLE;
			ERR(suites.ParamUtilsSuite3()->PF_UpdateParamUI(in_data->effect_ref, i + SKELETON_SLIDER_1, &param_copy[i + SKELETON_SLIDER_1]));
		}
	}
	else {
		// Handle the case where lock_handle fails
		err = PF_Err_INTERNAL_STRUCT_DAMAGED;
	}

	suites.HandleSuite1()->host_unlock_handle(out_data->global_data);

	// Set flags to force re-render and refresh UI
	if (!err) {
		out_data->out_flags |= PF_OutFlag_REFRESH_UI | PF_OutFlag_FORCE_RERENDER;
	}

	return err;
}

 

And finally in my render function, I also use the struct to call into python;

static PF_Err
Render(
	PF_InData* in_data,
	PF_OutData* out_data,
	PF_ParamDef* params[],
	PF_LayerDef* output)
{
	PF_Err              err = PF_Err_NONE;
	AEGP_SuiteHandler   suites(in_data->pica_basicP);
	// Get the image data and convert it to a NumPy array
	PF_EffectWorld* input = &params[SKELETON_INPUT]->u.ld;
	if (!input) {
		return err;
	}
	CustomEffect* customEffect = reinterpret_cast<CustomEffect*>(suites.HandleSuite1()->host_lock_handle(out_data->global_data));

	if (customEffect) {
		// Get the number of params
		if (customEffect->name == "Skeleton") {
			suites.HandleSuite1()->host_unlock_handle(out_data->global_data);
			return err;
		}
		int num_params = customEffect->elements->sliders.size();
		// Collect slider values
		std::vector<float> slider_values = {};
		for (int i = 0; i < num_params - 1; i++) {
			int result = params[i + SKELETON_SLIDER_1]->u.fs_d.value;
			slider_values.push_back(result);
		}

		py::gil_scoped_acquire acquire;
		py::array_t<unsigned char> input_array = effectworld_to_numpy(input);  // GIL problems.

		// Call the callback function with unpacked arguments
		py::function func = customEffect->callback_function;
		py::args args = py::make_tuple(input_array, slider_values);
		py::array_t<unsigned char> output_array = func(*args).cast<py::array_t<unsigned char>>();

		// Copy the output array to the output
		numpy_to_effectworld(output_array, output);
	}
	else {
		// Copy the input to the output
		PF_COPY(input, output, NULL, NULL);
	}
	suites.HandleSuite1()->host_unlock_handle(out_data->global_data);
	return err;
}

 

For some reason data outside of "name" isn't copied over properly, nor is the UI actually updating. 

The render function has its own issues, but thats related to python and the GIL-- my main point of conflict is the global data updating and UI update issues..

 

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Dec 08, 2023 Dec 08, 2023
LATEST

ok... i'm beginning to get the full picture.

1. by storing the "instance" in global data, you allow for only one "instance" at a time to be handled. is that your intent?

2. take note to sanitize RespondtoAEGP... i've used PF_Cmd_COMPLETELY_GENERAL on a plugin, and after we shipped we got reports of crashes. turns out some other plug-in was sending PF_Cmd_COMPLETELY_GENERAL calls to all effects on the same layer, assuming only the those who were designed to interact with it would respond to that call. well... ours wasn't. it was solved when we checked if the data sent with the PF_Cmd_COMPLETELY_GENERAL was indeed ours, before casting it blindly trusting the caller must be who we think it is...

3. as for the params not updating, i don't see you setting param_copy.uu.change_flags = PF_ChangeFlag_CHANGED_VALUE; look it up in the sdk guide for more detail.

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines