Copy material with links to another doc

On 28/07/2017 at 15:29, xxxxxxxx wrote:

User Information:
Cinema 4D Version:   R17 
Platform:   Windows  ;   
Language(s) :


I am trying to copy a multi-material which contains the links to other materials from one doc to another one. And it fails when we cntl+c and cntrl+v the mult-material, but sub-materials are not copied. 
How to workaround this?

I have tried to modify the function with an idea to use GetLink/ForceGetLink to resolve this

Bool multiMaterial::CopyTo(NodeData * dest, GeListNode * srcNode, GeListNode * destNode, COPYFLAGS flags, AliasTrans * aliasTrans)

But the destNode->GetDocument() returns 0.
How to solve that? When copy a multi-material then all sub-materials must be moved to the destination too. In the destination I have to make GetClone, dest_doc->InsertMaterial, etc, etc.

On 01/08/2017 at 07:35, xxxxxxxx wrote:


I'm note quite sure what the referenced muliMaterial is and how the sub-materials are organized/handled. Are the sub-materials normal materials inside the Material Editor or are they handled differently?

In any case this situation needs some special handling and a few corner cases will have to be considered.
You will also need to take care of situations, where the original scene, where stuff is being copied from, got already closed, when the paste operation is happening. Fortunately there's always only one Copy/Paste operation on the fly at any given time.

And here are your tools:
You will need to implement a SceneHook in order to receive the following two messages (well, actually same type, but different sub-type) : MSG_DOCUMENTINFO with the following sub-types: MSG_DOCUMENTINFO_TYPE_COPY and MSG_DOCUMENTINFO_TYPE_PASTE, the received data is of type DocumentInfoClipData.

On MSG_DOCUMENTINFO_TYPE_COPY you will receive an AtomArray with the entities being copied. There's also a BaseContainer, which you can use to transfer data to the pasting side. I recommend to store your own BaseContainer with your plugin ID inside that container. I have doubts the provided AliasTrans will help in your situation, but I might be wrong. So upon receiving this message you can determine if any of your multiMaterials is involved in the copy operation and you can prepare whatever data you need for the paste and to perhaps fix links on paste. As said before therefore you can use the provided BaseContainer or, as there's only one Copy/Paste at a time, also global variables come to mind. Just be careful to do proper cleanup.

And then on MSG_DOCUMENTINFO_TYPE_PASTE you can do all the needed stuff, as inserting materials, repairing links and so on and so forth.

Sorry, I have no ready made solution, but i think, with these messages you should be able to achieve what you want.

On 01/08/2017 at 12:33, xxxxxxxx wrote:

Hi Andreas, thanks for reply! It sounds like you explained everything, I will try to do it later after other features are done.

Basically, our multi-material is a structure of shaders and 10 links to other leaf materials (only shaders). It's basically a fat mixer. All these materials participate in Material Editor too.

While waiting for your reply I did a trick (based on info from other threads) :

Bool cntlMaterial::CopyTo(NodeData * dest, GeListNode * srcNode, GeListNode * destNode, COPYFLAGS flags, AliasTrans * aliasTrans)
	if (!dest) return FALSE;
	if (!srcNode || !destNode) return FALSE;
	Bool res = NodeData::CopyTo(dest, srcNode, destNode, flags, aliasTrans);  
	// copy linked children
	BaseDocument * docD = destNode->GetDocument();
	BaseDocument * docS = srcNode->GetDocument();
	BaseMaterial * mat = (BaseMaterial * )srcNode;
	BaseContainer * data = mat->GetDataInstance();
	if (docS != 0 && data != 0)
		bool anychange = false;
		for (int param_link = IDD_PARAMS_LINK_BEGIN + 1; param_link < IDD_PARAMS_LINK_END; param_link++)
			BaseMaterial * child = (BaseMaterial * )getC4DLink(mat, param_link, PID_MY_STDMAT);
			BaseMaterial * childF = (BaseMaterial * )getC4DForcedLink(*mat, param_link, PID_MY_STDMAT);
			if (childF != 0 && child == 0)
				BaseMaterial * new_child = (BaseMaterial * )childF->GetClone(COPYFLAGS_0, NULL);
				data->SetLink(param_link, new_child);
				anychange = true;
		if (anychange) {
			mat->Update(true, true);
	return res;

As you know from experience this code resolves the copy of mixer with linked children only if source and destination documents are opent at the time of "paste" action. If user closes the source document and then paste data from buffer to new doc then only mix material is created there without it's children.
I thought about SceneHook in the past. But will there be problems related to it's threaded execution? We just need to insert into doc. It's better to work with it from the main thread.

On 01/08/2017 at 13:52, xxxxxxxx wrote:

Wait an official reply but I guess you could make a SceneHook, intercept MSG_DOCUMENTINFO_TYPE_COPY then build a tempory document with material needed, don't insert it into the list.
Save this document into a variable like that you can check, if this variable is already set, that mean the previous COPY from this document was a copy from your material, so you can free this document and create a new one.
Save this tempory document into the BaseContainer of DocumentInfoClipData.

In MSG_DOCUMENTINFO_TYPE_PASTE get the document from the container of DocumentInfoClipData, and it should work , don't delete document here elsewhere you will not be able to copy twice ! :)

EDIT: The only "problem" is when to free old document if the document where the copy has been done is closed. Because if it's closed, there is no more scenehook, and we don't want to delete the tempo doc when the sceneHook it's free because that will be similar to free this tempo doc when we free the doc.

Then for avoid that, in the MSG_DOCUMENTINFO_TYPE_COPY instead of hold tempo document into a variable, you can hold the tempo document in a Container of the document.
Then instead of just checking if the actual member variable is set, you check all Container of all documents. If in one document you find a tempory doc, you free this tempory doc and create new one.
Then in MSG_DOCUMENTINFO_TYPE_PASTE, you also copy this document into a BaseContainer inside the document where you copy.

And it should work, not sure it's the cleaner way but in the paper it should work.

On 02/08/2017 at 03:09, xxxxxxxx wrote:

Will save this description and do it later. It seems a good trial&error cycle is required for this.

On 03/08/2017 at 01:52, xxxxxxxx wrote:

I just thinked in the case where someone press copy -> close the document and never press paste. So that will lead to a memory leak of the tempo document.
And this time I dont have a solution for it :/

Edit: maybe you could mark the tempo document (adding a container with your plugin id or mark as you prefer) then in the scenehook in the copy hook you just have to list all documents and if one got this mark just delete it.
And make sure to do the same thing when you close c4d. For that use C4DPL_ENDACTIVITY in the PluginMessage function of your scenehook

And now everthing must be fine :)

On 03/08/2017 at 02:33, xxxxxxxx wrote:

That shouldn't be a leak since it is saving to custom exchange buffer. If you copy something else then the same  memory slot should be used.

On 04/08/2017 at 09:17, xxxxxxxx wrote:

I'm not sure I understood every notion of the latest posts.

In general you can not add anything to the AtomArray provided in the COPY message. So you will have your own global storage for anything needed. On COPY message you will free everything stored in there and store everything needed for the new Copy&Paste. Yes, if no further copy happens, this memory will be used forever. But that's I think normal. In PluginMessage(C4DPL_ENDACTIVITY) you need to make sure, you free any resource held by your plugin. That's the place to free those resources held by your copy/paste storage. PluginMessage() is not something dedicated to just the SceneHook, but global to all plugins registered in your cdl64/dylib.

See also the manual on Plugin Functions.

On 05/08/2017 at 01:27, xxxxxxxx wrote:

Btw after this message (I mean PluginMessage(C4DPL_ENDACTIVITY) ) the whole app and corresponding process are closing. Memory will be released automatically, so do opened file handles and etc. etc. At least on Windows it works this way.

On 07/08/2017 at 03:01, xxxxxxxx wrote:

Of course you are right, in the end after C4D has quit, all memory is free'd anyway. Still we recommend to properly free all resources allocated by your plugin. For example it makes testing and debugging easier, as Cinema 4D won't report leaks on these allocations if started with "g_alloc=debug", so you can focus on real leaks.

On 07/08/2017 at 13:30, xxxxxxxx wrote:

You are right! Btw, I was not 100% correct. Just yesterday detected a situation like this:
I have a static singleton class to store some global vars, tmp buffers and other stupid things. And inside it there was a member of type BaseContainer (not a pointer). It was usefull to buffer C4D links because pointers can be lost.
When I quit Cinema 4D it successfully went out of PluginEnd() function. But later (out of developer control) there was some crash (between PluginEnd and final switch off). Furtunatelly it reported in VS debugger a bug with garbage collection for that my thing. And it was solved with a pointer to BaseContainer and manual new/delete at start and end of the plugin session.