Jump to content

Need help for a material helper and (Octane) converter PlugIn


McDev

Recommended Posts

Hello, I have this situation that we mainly do offline rendering using Octane but often I am assigned to make Realtime apps based on these models. It is absolute horror sometimes to convert these scenes, with mixer materials inside mixer materials and multiple assigned material stacks (although only the last one is rendered). Currently we only run R20 btw.

 

Here are reocurring tasks that I do:

  • Strip away all the material tags except the last one.
  • Reassign materials (if a material is assigned to a null-parent, I want it to be assigned to the mesh below unless it has a texture-tag already)
  • Normalize UV coordinates (sometimes offset is set to 100% but I need it to be 0%, otherwise the engine importer/fbx exporter will generate another material instance... Same is for scaling.
    • Bonus: Apply Texture-Tag offset and scale to the actual UVW tag and normalize the texture-tag this way.
  • Converting Octane materials to C4D materials (Diffuse color and texture would be enough).

 

So can someone give me any hints on how to approach this, if things can't be done (especially regarding octane) and if I should stick to C++ or python? I messed around a little bit with c4d plugin development but I simply have no starting point currently.
I always feel so lost when I see a new API and I don't want to do mistakes, especially as a C# dev C++ is a bit new.

Maybe I simply post my progress and code here and get some feedback or maybe someone can help me out to maybe get started with the first point in the list? It basically should be iterating all the nodes and removing all Material tags except the right hand side one. Shouldn't be that complicated...

Any other wishes that come to your mind?

 

Well I already bookmarked this one, I should start with this for now 🙂

 

 

 

Link to comment

At first look this all can be done via simple scripting with Python.

Usually you iterate over the tags of an object. With inbuilt tags this is not a problem, but I guess the Octane materials have a specific pluginID.

Right now I am in a hurry, but could help you later, if needed.

 

Link to comment

Here is already a start: remove all texture tags except the last one.

 

 

import c4d

gTagType = c4d.Ttexture

def GetTextureTagCount(obj):
    count = 0
    if obj:
        while (obj.GetTag(gTagType, count)):
            count += 1
    return count

def RemoveTextureTagsButLast(obj):
    if not obj:
        print ('No object selected')
        return
    count = GetTextureTagCount(obj)
    if count == 0:
        print ('No texture tags')
        return

    doc.StartUndo()

    for i in range(0, count - 1):
        tag = obj.GetTag(gTagType)
        doc.AddUndo(c4d.UNDOTYPE_DELETE, tag)
        obj.KillTag(gTagType)

    doc.EndUndo()
    return

def main():
    if not op:
        print ('No object selected')
        return

    RemoveTextureTagsButLast(op)

    c4d.EventAdd()

if __name__=='__main__':
    main()

 

It only works on a single selected object, but you get the point.

Now this script works with default Cinema4D materials and Cinema4D texture tags. Not sure how this looks like when Octane is used.

Is only the Octane material different from the native one, or does an object also carry specific Octane texture tags? Honestly, I wouldn't know.

If specific Octane texture tags are used, you can replace "c4d.Ttexture" with the pluginID of such Octane texture tag.

 

EDIT: modified the script to perform the undo on the tag instead of the host object, for a correct redo.

 

Link to comment

Thanks, I got this working for C++ as I rather hate python. Still I need to learn more C++ fundamentals.

 

For now I miss

  • how to get selected or all objects
  • how to debug (start debug fails, no valid Win32 Application) or write a console log

 

And yes the Octane Materials work with the regular TextureTag.

 

class RemoveTextureTagsButLast : public CommandData {
public:
	virtual Bool Execute(BaseDocument* doc)
	{
		doc->StartUndo();

		BaseObject* const object = doc->SearchObject("Cube"_s);
		if (object == nullptr)
			return false;

		int count = GetTagCount(object);
		RemoveTagsFromLeft(object, count);
		doc->AddUndo(UNDOTYPE::DELETEOBJ, object);

		doc->EndUndo();

		//Does a refresh of the editor windows
		EventAdd();
		return true;
	}


	int GetTagCount(BaseObject* object) {
		int count = 0;
		while (object->GetTag(Ttexture, count)) {
			count++;
		}
		return count;
	}

	void RemoveTagsFromLeft(BaseObject* object, int count) {
		for (int i = 0; i < count - 1; i++)
		{
			object->KillTag(Ttexture, 0);
		}
	}
};

 

Link to comment

I am with you on preferring C++ over Python, but I cannot deny that Python is much easier to prototype a plugin.

However, for debugging purposes or finding a problem in code I much prefer having a C++ plugin at hand.

 

If I may I would like to point out some issues in your code.

1. In your CommandData::Execute you call "doc->StartUndo()" before obtaining the object you will be working with.

This means that -potentially- the obtained object is nullptr and you exit the method. But without closing the started undo.

Better to start the undo after having obtained the object.

In your case you could even move all the undo stuff into your "RemoveTagsFromLeft" method, encapsulating the for loop.

2. AddUndo always need to be called BEFORE you actually perform the action, except when adding new objects. See the AddUndo documentation

 

To get selected object(s) see BaseDocument::GetActiveObject and BaseDocument::GetActiveObjects

 

To debug: right mouse click your project in Visual Studio, and select "properties" at the bottom of the popup-menu:

Enter the location of the cinema4d executable. Optionally you can provide a "g_alloc=Debug" argument, which will provide allocation issues like memory leaks, etc.

image.thumb.png.ae187434770d2d488cbe35e26090bc41.png

 

You can use "ApplicationOutput" to write to the CInema4D console window.

 

Link to comment

I have adjusted the Python script, as I noticed I performed an undo on the host object, instead of the tag itself.

You will need to do the same in your C++ code.

Link to comment

I wasn't sure if you should do that per Tag or rather per Operation. Because say you delete 200 Tags of 100 objects, do you add 200 Undo entries? Wouldn't you want just one for the whole operation? Like say Remove unused materials.

 

Thanks for the extra tips, makes total sense. I still had trouble with the Debugging, that is something else but I don't have the code until Monday. It is I guess still trying to find some lib file within the Project-SDK folder.

Link to comment
4 hours ago, McDev said:

I wasn't sure if you should do that per Tag or rather per Operation. Because say you delete 200 Tags of 100 objects, do you add 200 Undo entries?

Everything between a StartUndo and EndUndo is a single undo (to the user).

You can do as many AddUndo as you want, on a same or different object. To the user it is still a single undo operation.

However, if you do a StartUndo-AddUndo-EndUndo for every of your 200 tags, then yes: the user will get 200 undo entries.

But StartUndo-AddUndo-AddUndo-AddUndo-...-EndUndo is just a single undo operation.

 

Link to comment

Alright this is getting better! I hate the documentation but I was able to find out how GetActiveObjects can be used.

 

I also managed to get Debugging working, apparently I was not having the correct project set to be run in VS...

 

This code now deletes all TextureTags except the right hand side one of all selected objects.
 

Spoiler

 



class RemoveTextureTagsButLast : public CommandData {
public:
	virtual Bool Execute(BaseDocument* doc)
	{
		AutoAlloc<AtomArray>opList;
		if (!opList)
			return false;

		doc->GetActiveObjects(*opList, GETACTIVEOBJECTFLAGS::NONE);
		if (opList->GetCount() <= 0)
			return true;

		BaseObject*		op = nullptr;
		int tagCount;

		doc->StartUndo();
		for (Int32 i = 0; i < opList->GetCount(); ++i)
		{
			op = (BaseObject*)opList->GetIndex(i);
			if (op)
			{
				RemoveTagsFromLeft(doc, op, GetTagCount(op));
			}
		}
		doc->EndUndo();

		EventAdd();
		return true;
	}

	int GetTagCount(BaseObject* object) {
		int count = 0;
		while (object->GetTag(Ttexture, count)) {
			count++;
		}
		return count;
	}

	void RemoveTagsFromLeft(BaseDocument* doc, BaseObject* object, int count) {
		for (int i = 1; i < count; i++)
		{
			BaseTag* tag = object->GetTag(Ttexture);
			doc->AddUndo(UNDOTYPE::DELETEOBJ, tag);
			object->KillTag(Ttexture);
		}
	}
};

 

 

 

 

Link to comment

Well I have to learn a lot, makes me question if I should switch to python 🙂

 

First I had to change the GETACTIVEOBJECTFLAGS to ::CHILDREN as ::NONE would only give the root objects. I though that this would select all the children of selected objects as well.

 

I tried to do that but obviously this is not working. Can someone point out to a noob how this can be done?
Or should this even work as expected? I am only getting "Tags found: " not "Tags found: 0"

int tagcount = tagList->GetCount();
ApplicationOutput("Tags found: " + tagcount);

 

EDIT: I don't want UVW Tags, I want TextureTags of course... So this is semantically wrong.

Also I tried getting a list of tags but I failed. As I don't even know yet how to use dynamic arrays (but I will soon) I choose the AtomArray, but it doesn't seem to take the tags that I append (very bottom and yes it is called). I wonder why? Also what kind of List would be ideal to store tags or other objects? Is AtomArray common or rather some specific Tag list? Do I make a fundamental mistake here?

 

Spoiler

 





class NormalizeUVWTags : public CommandData {
public:
	virtual Bool Execute(BaseDocument* doc)
	{
		AutoAlloc<AtomArray>opList;
		if (!opList)
			return false;

		//Get all selected objects
		doc->GetActiveObjects(*opList, GETACTIVEOBJECTFLAGS::CHILDREN);
		if (opList->GetCount() <= 0)
			return true;

		AutoAlloc<AtomArray> tagList;
		if (!tagList)
			return false;

		//Try getting all the UVW Tags of them
		BaseObject*		op = nullptr;
		doc->StartUndo();
		for (Int32 i = 0; i < opList->GetCount(); ++i)
		{
			op = (BaseObject*)opList->GetIndex(i);
			if (op)
			{
				AddAllUVWTags(op, *tagList);
			}
		}

		int tagcount = tagList->GetCount();
		//How to properly convert int to strings/char[] and combine them?
		ApplicationOutput("Tags found: " + tagcount);
		doc->EndUndo();

		EventAdd();
		return true;
	}

	BaseList2D* FilterUnnormalizedTags() {

	}

	int GetTagCount(BaseObject* object) {
		int count = 0;
		while (object->GetTag(Tuvw, count)) {
			count++;
		}
		return count;
	}

	void AddAllUVWTags(BaseObject* object, AtomArray& tags) {
		int count = 0;
		BaseTag* tag = nullptr;
		while (count < WHILE_ESCAPE_LOOP_COUNT) {
			tag = object->GetTag(Tuvw, count++);
			if (!tag) {
				break;
			}

			tags.Append((AtomArray*)tag);
			int tagcount = tags.GetCount();
		}
	}
};

 

 

 

 

Well my current goal is:

1. Get all UWV Tags of selected objects.

2. Filter these tags by some rules (if offset and scale are not normalized)

3. Select these tags in C4D (deselect everything else)
4. Optionally change some of their values.

 

I just ask dummy questions, if someone helps that is great, if not I keep digging 🙂

Link to comment
ApplicationOutput("Tags found: @", tagcount);

Yeah, the documentation isn't quite helpful as it should be for newbies, but after a while and with the help of some SDK examples you get there in the end.

 

As for storing tags it depends what you want to do with it, but next to AtomArray you also can use

maxon::BaseArray<BaseTag*>

maxon::HashSet<BaseTag*>

maxon::HashMap<...

 

it all depends what manipulations you want to do with the list.

To begin with, I mostly use BaseArray for simple lists.

 

Additionally, prefer to use maxon's types (Int32, UInt, ...) over the standard types (int)

 

Link to comment
  • 6 months later...

I was able to continue with this and making quite some progress. Also I switched over to Python now, this really is easier!

EDIT: Found the soultion: shader.CheckType(1029508)

 

Currently I try to convert an Octane Material. The issue is now that I need to check the textures. I ask for the BaseShader object and want to know if it is an (Octane) Texture Shader. The Property says this:

 

print  mat[c4d.OCT_MATERIAL_DIFFUSE_LINK]
<c4d.BaseShader object called 'Name/ImageTexture' with ID 1029508 at 0x0000020CAEDABE10>

 

Now how do I check if the property is this object, how can I access the ID "1029508"?

Link to comment
×
×
  • Create New...

Copyright Core 4D © 2023 Powered by Invision Community