Cinema 4D Goodies

Here's a couple more scripts maybe someone can use.

Sometimes I have an object with many seperate pieces that need to use a single texture.
By temporarily combining them into a single object with poly selection tags, I can
unwrap, scale and position the UV's into a single UV region for use with a single texture.

After unwrapping I can break the object back into it's original pieces
and then assign a single texture to all.
This can be done manually ofcourse but it can get tedious when there are many parts.

The 2 scripts I created (posted below) are 'connect_with_tags' to create a single object for unwrapping
and then 'disconnect_by_tags' to break that back up into it's original pieces.


Tested with C4DR10.1. It should work for 9.6 to 10.5 also.
For other versions of C4D the values of the constants found at the top of the page will have to be changed.
For example the function 'Connect' has a value of 12144, as defined in the Command Manager, in R9.6 to R10x.
 
connect_with_tags script:

This script combines an object and it's children into a single object and assigns
polygon selection tags representing each original piece.

Code:
////////////////////////////////////////////
//
//   connect_with_tags
//   G.T Dec 11 2010
//
//   Combines an object and it's children into a single object and assigns
//    polygon selection tags representing each original piece.
//    Objects must be polygon objects.
//    
//
//////////////////////////

// Constants for CallCommands, C4D R9.6 & 10:
// These values can be found in the Command Manager.
// Change for other versions of C4D
//...........................
const var CLEAR_CONSOLE = 13957;
const var FUNCTIONS_CONNECT = 12144;

//prototypes:
GetNextObject(obj);
CreatePolyTagAll(obj);
RemoveTagsByType(obj,tag_type);
RemoveTagsByTypeAndName(obj,tag_type,name);

////////////////////
//
//  main
//
////////////////////

main(doc,op)
{
    CallCommand(CLEAR_CONSOLE);//Clear console
    println("-start:enum_with_tags");

    var parent_obj, obj, tagg;


    //Get selected parent object.
    //.........................
    parent_obj = doc->GetActiveObject();
    if(!parent_obj)
    {
        TextDialog("No parent object selected.", DLG_OK);
        return;
    } //

    obj = GetNextObject(parent_obj);

    while(obj)
    {

        if (obj->GetType() != OBJECT_POLYGON)
        {
            obj = GetNextObject(obj);
            continue;
        }//

        CreatePolyTagAll(obj);

        obj->SetBit(BIT_AOBJ); //this makes each object active.

        obj = obj->GetNext();

    }//wh

    CallCommand(FUNCTIONS_CONNECT);//Connect selected objects

    //rename the new object which is now selected.
    //.......................
    obj = doc->GetActiveObject();
    obj->SetName(parent_obj->GetName());

    //phong tags are not copied over so create one.
    //........................
    tagg = AllocTag(Tphong);
    obj->InsertTag(tagg);

    //remove all material tags.
    //........................
    RemoveTagsByType(obj,Ttexture);

    //remove the tags we set on the original objects.
    //....................................
    obj = GetNextObject(parent_obj);

    while(obj)
    {
        if (obj->GetType() != OBJECT_POLYGON)
        {
            obj = GetNextObject(obj);
            continue;
        }//
                    
        RemoveTagsByTypeAndName(obj,Tpolygonselection,obj->GetName());

        obj = obj->GetNext();

    }//wh

    println("-end:enum_with_tags.");
}//

////////////////////////////
//
//    GetNextObject
//
//
////////////////////////////

GetNextObject(obj)
{
    if(!obj) return obj;

    var temp_obj = obj->GetDown();

    if(temp_obj){return temp_obj;}
    
    return obj->GetNext();
                            
}//

////////////////////////////
//
//    CreatePolyTagAll
//
//
////////////////////////////

CreatePolyTagAll(obj)
{
    var bs, poly_count,poly_tag;

    bs = obj->GetPolygonSelection();
    poly_count = obj->GetPolygonCount();
    bs->SelectAll(0,poly_count-1);
    obj->SetPolygonSelection(bs);

    //create a tag, insert it and assign the selection of all polys.
    //......................

    poly_tag = AllocTag(Tpolygonselection);
    obj->InsertTag(poly_tag);
    poly_tag->SetSelection(bs);
    poly_tag->SetName(obj->GetName());


}//

////////////////////////////
//
//    RemoveTagsByType
//
//
////////////////////////////

RemoveTagsByType(obj,tag_type)
{

    var tagg;
     
    tagg = obj->GetFirstTag();
                
    while (tagg)
    {

        if(tagg->GetType() == tag_type)
        {
            tagg->Remove();
            tagg = obj->GetFirstTag(); 
        }
        else
        { tagg = tagg->GetNext();}
    }//wh


}//

////////////////////////////
//
//    RemoveTagsByTypeAndName
//
//
////////////////////////////

RemoveTagsByTypeAndName(obj,tag_type,name)
{

    var tagg;

    tagg = obj->GetFirstTag();
                
    while (tagg)
    {

        if( (tagg->GetType() == tag_type) && (tagg->GetName() == name))
        {
            tagg->Remove();
            tagg = obj->GetFirstTag(); 
        }
        else
        { tagg = tagg->GetNext();}

    }//wh


}//
 
disconnect_by_tags script:

This script splits a single object into seperate objects
as defined by it's polygon selection tags.

Code:
////////////////////
//
// disconnect_by_tags
// G.T Dec 11 2010
//
// Splits a single object into seperate objects
// as defined by it's polygon selection tags.
//
//////////


// Constants for CallCommands, C4D R9.6 & 10:
// These values can be found in the Command Manager.
// Change for other versions of C4D
//...........................
const var CLEAR_CONSOLE = 13957;
const var FUNCTIONS_SPLIT = 14046;

//prototypes:
RemoveTagsByType(obj,tag_type);

////////////////////
//
//  main
//
////////////////////

main(doc,op)
{
    
    var tagg, bs,obj;

    CallCommand(CLEAR_CONSOLE);//Clear console
    println("-start:disconnect_by_tags");

    if(!op)
    {
        TextDialog("No  object selected.", DLG_OK);
        return;
    } //

    tagg = op->GetFirstTag();
    obj = op;
    
    while(tagg)
    {

        if(tagg->GetType() == Tpolygonselection)
        { 
            bs = tagg->GetSelection();
            op->SetPolygonSelection(bs);
            CallCommand(FUNCTIONS_SPLIT); //split funtion
            
            obj = op->GetNext(); //always inserted after the first op.
            obj->SetName(tagg->GetName());
            RemoveTagsByType(obj,Tpolygonselection);
            
    
        }//if
    
        tagg = tagg->GetNext();

    }//wh        

    println("-end:disconnect_by_tags");

}//

////////////////////////////
//
//    RemoveTagsByType
//
//
////////////////////////////

RemoveTagsByType(obj,tag_type)
{

    var tagg;
     
    tagg = obj->GetFirstTag();
                
    while (tagg)
    {

        if(tagg->GetType() == tag_type)
        {
            tagg->Remove();
            tagg = obj->GetFirstTag(); 
        }
        else
        { tagg = tagg->GetNext();}
    }//wh


}//
 
This C4D script checks for overlapping UV's and direction of normals.
If any errors are found it adds a polygon selection tag with the offending polys which can be either caused by reversed normals and/or overlapping UV's.

//It assumes some attempt has been made to unwrap the UV's. It wont detect a bunch of rogue disconnected polys like the default UV for a cube, for example. Maybe sometime I'll check for that but for now it does what I need it too.



Code:
///////////////////////////////////////
//
//  CheckOverlappedUVs
//     G.T- December 15 2010.
//    Last edit: Dec 15(2) 2010. (optimize)

// Checks whether any UV cooridinates are overlapping.
// If so, a polygon selection tag is added with the bad polys.
// Also checks for Normal's direction.
//
// 
//
////////////////////

// Constants for CallCommands, C4D R9.6 & 10:
// These values can be found in the Command Manager.
// Change for other versions of C4D
//...........................
const var CLEAR_CONSOLE = 13957; //same code for 9.6-11.5

// prototypes
//...........
GetUvwTag(obj);
CheckUVTag(UVtag);
CheckFace(p1,p2,p3);

////////////////////
//
//  main
//
////////////////////
main(doc,obj)
{

    var UVW_tag, version, blUVtag;

    CallCommand(CLEAR_CONSOLE);//Clear console
    println("-start:CheckOverlappedUVs");

    if(!obj)
    {
        TextDialog("No object selected.", DLG_OK);
        return;
    } //

    if (obj->GetType() != OBJECT_POLYGON)
    {
        TextDialog("Object must be polygonal.", DLG_OK);
        return;
    }//

    //There may be more than one UV tag. Check each one for this object.
    //........................

    UVW_tag = obj->GetFirstTag();
    blUVtag = false;
    
    while(UVW_tag)
    {
        if(UVW_tag->GetType() == tag_UVW)
        {    blUVtag = true;
            CheckUVTag(UVW_tag);}

        UVW_tag = UVW_tag->GetNext();
    }//wh

    if(!blUVtag){TextDialog("No uv tags on object.", DLG_OK);}
    println("-end:CheckOverlappedUVs");

}//

////////////////////
//
//  CheckUVTag
//
////////////////////

CheckUVTag(UVtag)
{
    var obj, nPolys, face_index, poly;
    var p1,p2,p3,p4,UVW_tagdata,uvi;
    var fpe = 4; //fields per element
    var bs,poly_tag,poly_count;


    obj = UVtag->GetObject();
    nPolys  = obj->GetPolygonCount();
    UVW_tagdata = UVtag->GetData();
    bs = obj->GetPolygonSelection();
    bs->DeselectAll();
    obj->SetPolygonSelection(bs);


    uvi = 0;
    for(face_index = 0; face_index < nPolys; face_index++,uvi+=fpe) 
    {
        poly = obj->GetPolygon(face_index);

        p1 = UVW_tagdata[uvi + 0];
        p2 = UVW_tagdata[uvi + 1];
        p3 = UVW_tagdata[uvi + 2];

        //the 3 or 4 texture UV's for the 3-4 points of this face. These are not 'face' normals, they're texture coords.
        // there are nPoly entries listed here but there are only nVertices vt's, so many repeat.
        //......
        if( !CheckFace(p1,p2,p3))
        { bs->Select(face_index);} 
         
        if(poly->c != poly->d)//quad
        {
            p4 = UVW_tagdata[uvi + 3];    //if a quad  

            if( !CheckFace(p3,p4,p1) )
            {  bs->Select(face_index);}
        }//if

    }//for.............

    if(bs->GetCount())
    {        
        poly_tag = AllocTag(Tpolygonselection);
        poly_tag->SetName("bad uv's");
        obj->InsertTag(poly_tag);
        poly_tag->SetSelection(bs);
        TextDialog( tostring(bs->GetCount()) + " polys in error.", DLG_OK);
    }//if
    else
    {TextDialog("No UV errors.", DLG_OK);}

}//

////////////////////
//
//  CheckFace
//
////////////////////
CheckFace(p1,p2,p3)
{
    
    //C4D has x,y,z coords. The z coord is always 0, for 2d uv maps.
    //.....................

    if( (p1 == p2) || (p2 == p3) || (p3 == p1))
    {return 0;}

    var dx1,dy1,dx2,dy2;
    
    dx1 = p2.x - p1.x;
    dy1 = p2.y - p1.y;
    
    dx2 = p3.x - p1.x;
    dy2 = p3.y - p1.y;
   
    if( (dx1 * dy2 - dy1 * dx2) < 0)
    { return 0;}
    

    return 1;

}//
 
Last edited:
Oh, just a funny note. I was just running the 'CheckOverlappedUVs' script on one of my models and one of the pieces had errors on every polygon. At first I thought the normals were all reversed but they were fine. It seems the UV's were mirrored. I think because I was using the symmetry tool to get a mirrored copy or I used the Interactive Command with the wrong angle (180 deg). At any rate, I mirrored the UV's again in the UV editor, rechecked them and they're fine now.

So I'm glad I caught that before I sent the model off. That wouldn't have been good. Cool.
 
Hello Gord.T.
Thanks for your work.
Can i ask you, can you port your tpwriter for c4d r12? I'm interested in baking tp to binary or plain, like cashing of core particles.
 
Unfortunatley at this time I'm unable to run r12 on my current PC.

If you are familiar with programming at all I'd be happy to discuss the methods of how you can approach it. I'm familiar with RF4's binary format so it's just a matter of accessing the C4D's particle code and rewriting it into a form that RF4 understands.

But like I say, without R12 accessiblilty, my hands are tied at the moment. I'm sorry. I'd be glad to help in any other ways that I can.

Thanks for your interest.
 
Last edited:
Hello Gord.T. -- yes when you get up and running on R12, I'd also love an update for your "C4D animations to object file sequences (.obj)." I use it all of the time and it works great on R11.5.

Many many thanks for sharing so freely your knowledge and scripting skills.
 
Hey thanks Bleb. The first chance I get on R12 I'll write up a new script for you. My nephew has an year old laptop that's smashed to hell and is going to buy a new one some maybe I can ask him if I can have it. :)
 
Hello Gord.T. -- yes when you get up and running on R12, I'd also love an update for your "C4D animations to object file sequences (.obj)." I use it all of the time and it works great on R11.5.

Many many thanks for sharing so freely your knowledge and scripting skills.


For R12, just scroll to line 156 and change: if(UVW_tag->GetType() == tag_UVW) to if(UVW_tag->GetType() == Tuvw)
 
Does this COFFEE script work in Mac C4D R12 or R13?

Is it possible to do this in Python (which seems like the new hotness)?
 
I believe COFFEE is cross platform compatible so yes it should work on the Mac. I haven't look into python for C4D yet although I have dome a bit of it with RealFlow4 and it's a powerfull scripting language.
I'll be reviewing the scripts and updating them as needed in the next few months or so.
 
COFFEE scripting still works up to C4D R17 at least but C4D eventually went with Python exclusively.
I'm on R20 now and while I can run my old COFFEE scripts on my other PC that has R17 on it I decided, out of need, to finally delve into Python this week so here is my first script.

I'd be happy to answer any general questions on it but keep in mind I'm a total noob at python...and any code recommendations I'd be very interested in.

I primarily wrote this code to fix some specific mesh errors on a custom Daz Genesis 8 character. Specifically the distorted eyes generated from a FacegenArtist Pro export and a distortion
in the hidden teeth/tongue area that I accidentally messed up while sculpting the face in Zbrush (not it's the first time I've done that). So with this script I can quickly 'repair' those mesh areas in C4D without losing
my current sculpt progress.
Let me know if you get any use out of it or have any issues ect.

Code:
#########################################################################################
#
#                 gTransfer Points to a Morphable Polygon Object
#                 Python script for C4D. Tested on R20 only. April 21 2021. -Gord.T
#
#                 Tranfers a point selection from one polygom model to another.
#                 Both objects must have the same number of points and point indicies.
#                 In other words one must be morphable to the other.
#
#                 Usage:
#                      Select both the model that has the selected points already selected that you want to tranfer over to
#                 and the model you want to transfer it to.
#                 Make sure the 'recieving' model has no points selected.
#                 Then run the script.
#
#
#
#########################################################################################


import c4d
from c4d import gui

#======================================
def gGetNumberOfSelectedPoints(obj):

    if not obj:
        gui.MessageDialog("Selected obj is not a valid object. Exiting")
        return-2

    bs = obj.GetPointS()# bs means it's a Base Select object.
    if not bs: return -3
    sel = bs.GetAll(obj.GetPointCount())  # Returns a selection of ALL points.

    nSelected = 0
    for index, selected in enumerate(sel):
        if not selected: continue
        nSelected = nSelected + 1

    return nSelected

#======================================

# Main function
def main():

    print "Start script: gTransfer Points to a Morphable Polygon Object"


#---------------------------------------------------------------
    # flags fro GetActiveObjects are:
        # 0 None
        # 1 Children
        # 2 Selection order. The selection array is sorted in the selection order, e.g the first selected object is the first element in the array.
    objs = doc.GetActiveObjects(0) #Look at a list of selected objects.
    if not objs:
        gui.MessageDialog("You must select 2 Polygon objects! One with it's points selected that are to be transferred to the other, and the other without it's points selected. Exiting")
        return


    nObjs = len(objs)

    if nObjs < 2:
        gui.MessageDialog("You must select 2 Polygon objects! One with it's points selected that are to be transferred to the other, and the other without it's points selected. Exiting")
        return
    elif nObjs > 2:
        gui.MessageDialog("You must select only 2 Polygon objects! One with it's points selected that are to be transferred to the other, and the other without it's points selected. Exiting")
        return


#---------------------------------------------------------------
#    We now have 2 objects. One of the two objects must have points selected that are to be tranferred to the second object.
#    Identify and assign each appropriatly to objSrc and objDest.
#    Lets assume for now that they are polygon objects because this code my also work on other objects with points.

    nPointsSelected_0 = gGetNumberOfSelectedPoints(objs[0])
    nPointsSelected_1 = gGetNumberOfSelectedPoints(objs[1])

    if nPointsSelected_0 > 0 and nPointsSelected_1 == 0:
        objSrc = objs[0]
        objDest = objs[1]
    elif nPointsSelected_1 > 0 and nPointsSelected_0 == 0:
        objSrc = objs[1]
        objDest = objs[0]
    elif nPointsSelected_1 > 0 and nPointsSelected_0 > 0:
        gui.MessageDialog('Only one of the two objects must have points selected that are to be transferred to the other. Exiting')
        return
    elif nPointsSelected_1 == 0 and nPointsSelected_0 == 0:
        gui.MessageDialog('One of the 2 objects must have points selected that are to be transferred to the other. Exiting')
        return
    elif nPointsSelected_1 < 0 or nPointsSelected_0 < 0:
        print "gGetNumberOfSelectedPoints return error code: Aborting."
    else:
         gui.MessageDialog('Unknown condition. Exiting')
         return

#---------------------------------------------------------------

    bs = objSrc.GetPointS()# bs means it's a Base Select object.
    if not bs: return
    sel = bs.GetAll(objSrc.GetPointCount())  # Returns a selection of ALL points.

    for index, selected in enumerate(sel):
        if not selected: continue
        ptSrc = objSrc.GetPoint(index) #returns a vector, the position of a point

        objDest.SetPoint(index,ptSrc)

    objDest.Message (c4d.MSG_UPDATE) # MUST call this after the object to update it's polygons!!!

#---------------------------------------------------------------


    c4d.EventAdd() # Update Cinema 4D
    print "Script End: gTransfer Points to a Morphable Polygon Object"



# Execute main()
if __name__=='__main__':
    main()

##############################END###############################

//April 22

I know that parts of a Daz mesh could be modified via ZB as well so I did a quick video of that for demonstration purposes only.
It's not a tutorial and there is no audio.

gRestoreDazMeshPartsWithGOZ
https://vimeo.com/539963289

That method is fine for something that doesn't require a high degree of accuracy. Doing it in C4D with my python script gives me precise control
over what I'm modifying. I wanted to point out that difference.
I don't have a video demonstration of using my script method at the moment.
-------------------
 
Last edited:
Can i ask you, can you port your tpwriter for c4d r12? I'm interested in baking tp to binary or plain, like cashing of core particles. IMP is abbreviated as GNU Interface Manipulation Program which is a tool editor for retouching of images and re-editing it for much for better image interface. Download GIMP 2.6
 
Last edited:
Can i ask you, can you port your tpwriter for c4d r12? I'm interested in baking tp to binary or plain, like cashing of core particles. IMP is abbreviated as GNU Interface Manipulation Program which is a tool editor for retouching of images and re-editing it for much for better image interface. Download GIMP 2.6

So it didn't work with R12? (you did mean R12 and not R21) And I assume your on a P.C and not a Mac. I'll have to check my archived disks to see if I still have the original project files (2009). Barring that, the other options would include running it on R10.1 or lastly I could try re-writing the entire plugin for R12. Interesting request though. Give me a week or 2 to look into it. I'll send you a Notification either way as well if I have any luck or not.

------------------
Good News:
I found the original project files with all of my notes. I had noted at one point I was having issues in R11 along with a code change fix for it. I don't think R12 was ever tested.
I'll need to explore the project files more and what issues there were and so on...and that missing demo video. :)

I had 3 demo versions released TpWriter_V1.4_DemoRelease_X86 was the last in this archive dated 08/07/2009.
More later.
 
Last edited:
chadrickt2007: Here is the latest version I had (it was the newest one and not the one I originally posted in post #6). It was tested in R11 so maybe it will work for R12 for you. Can I ask you to try it and let me know either way and if it still doesn't work then I'll have to reinstall R11 and R12 and RF4 and go from there. I also have the original c++ code to compile the plugin if your into c++ Visual Studio programming at all that you can have.

So this should be TpWriter_V1.4_DemoRelease_X86.zip ->View attachment TpWriter_V1.4_DemoRelease_X86.zip

Oh no, they're thinking. lolll - TpWriter icon):
tpwriter.png
 
Last edited:
Okay, I'll keep the rest of the updates in this thread and update accordingly. RE: TpWriter.

* May 28 2021:
-I've reinstalled R10.1 to begin the plugin tests from there then progressing to R12.
-I'm not able to find my C4D R11 right now although there are many disks I'd have to check still.
-I do have R12 but it's not installed yet as I just finished testing in R10.1

--TpWriter-TpWriter_V1.4_DemoRelease_X86 IS working in R10.1.
I'll make a demo vid of how to use it because I frankly forgot how to myself and I can't find the original demo vid (previous hard drive crash). So I'll try and have that for viewing tomorrow.

* I'm still setting up my Visual Studio SDK's for recompiling, and that's being a bugger so far, and then I'll be looking at testing R12, or maybe I'll test V1.4 in it first. i.d.k.
I'll be back tonight or tomorrow with a demo vid.
------
Quick usage vid for V1.4 using R10.1 https://vimeo.com/556506429
 
Last edited:
Here's the c++ source code for TpWriter_V1.4 if anyone else wants to play around with it.



If and when I get my SDK's setup I'll have another look at porting the code.
I think X-Particles can export particles as well although that's for R13 and above.

Good luck.
 

Attachments

  • TpWriter v1p4 source code.zip
    5.7 KB · Views: 0
Last edited:
Back
Top