Solved Phong Tag with ObjectData

Hello,
I'm trying to get a Phong tag to work with my ObjectData plugin similar to the Phong tags assigned to primitive Polygon Objects such as Sphere & Cube.

Using the py-rounded_tube_r13.pyp as reference, I'm currently calling a function from ObjectData.GetVirtualObjects that sets the Phong with BaseObject.SetPhong. In my ObjectData I'm creating a Phong tag in my overridden NodeData.Init function. The Phong Tag is not having any effect unless I make the Object Editable. I've tried removing the SetPhong call, but it doesn't make a difference.

How can I get the Phong tag to have an effect on my ObjectData object?

Thank you!

Hi @blastframe I think you get the concept wrong, Generator doesn't have a "proper" Phong tag, or at least the Phong tag you apply to it has no real meaning since Phong is not a hierarchical tag(like Texture is), but it affects only the current object and a generator don't have any Polygon Information.
With that's said this Phong tag on the generator is very useful because it let you expose parameter to the user in a meaningful manner, so the Generator can know for each Polygon Object it will generate, it can basically Retrieve a clone of this Phong and apply the same value. But the generator still has the liberty to not do it.

So the proper workflow is

  • Create a Tag in the MSG_MENUPREPARE so you expose to the user a UI to control the Phong shading.
  • Attach a Clone of this Tag to all PolygonObject you generate within your Generator.

So this gives us code like that:

import os
import math
import sys
import c4d
from c4d import utils

PLUGIN_ID = 1234569


class PhongDemoObject(c4d.plugins.ObjectData):
    size = 100

    def __init__(self, *args):
        super(PhongDemoObject, self).__init__(*args)
        self.SetOptimizeCache(True)

    def GetVirtualObjects(self, op, hierarchyhelp):
        ret = self.CreateMyObject(op)
        if ret is None:
            return None

        ret.SetName(op.GetName())
        return ret

    def CreateMyObject(self, node):
        newObj = c4d.PolygonObject(8, 6)

        newObj.SetPoint(0, c4d.Vector(-self.size,-self.size,-self.size))
        newObj.SetPoint(1, c4d.Vector(-self.size,self.size,-self.size))
        newObj.SetPoint(2, c4d.Vector(self.size,-self.size,-self.size))
        newObj.SetPoint(3, c4d.Vector(self.size,self.size,-self.size))
        newObj.SetPoint(4, c4d.Vector(self.size,-self.size,self.size))
        newObj.SetPoint(5, c4d.Vector(self.size,self.size,self.size))
        newObj.SetPoint(6, c4d.Vector(-self.size,-self.size,self.size))
        newObj.SetPoint(7, c4d.Vector(-self.size,self.size,self.size))

        newObj.SetPolygon(0, c4d.CPolygon(3, 1, 7, 5))
        newObj.SetPolygon(1, c4d.CPolygon(3, 5, 4, 2))
        newObj.SetPolygon(2, c4d.CPolygon(4, 6, 0, 2))
        newObj.SetPolygon(3, c4d.CPolygon(6, 7, 1, 0))
        newObj.SetPolygon(4, c4d.CPolygon(0, 1, 3, 2))
        newObj.SetPolygon(5, c4d.CPolygon(4, 5, 7, 6))

        # If the generator already have a PhongTag, jsut clone it otherwise define it a 92° phong shading
        phongTag = node.GetTag(c4d.Tphong)
        if phongTag is not None:
            newTag = phongTag.GetClone(c4d.COPYFLAGS_NONE)
            newObj.InsertTag(newTag)  # Add the cloned tag to the new Polygon Object present only in the cache
        else:
            newObj.SetPhong(True, True, c4d.utils.DegToRad(92.0))

        newObj.Message(c4d.MSG_UPDATE)
        return newObj

    def Message(self, node, type, data):
        # On the creation of the Object, via the Menu create a Phong tag on the generator
        # c4d.BaseList2D(1234569) will not trigger it
        if type == c4d.MSG_MENUPREPARE:
            # Create a phong tag and set the default value to 92° (so we see the phong shading)
            node.SetPhong(True, True, c4d.utils.DegToRad(92.0))
        return True


if __name__ == "__main__":
    c4d.plugins.RegisterObjectPlugin(id=PLUGIN_ID,
                                     str="phongdemoobject",
                                     g=PhongDemoObject,
                                     description="phongdemoobject",
                                     icon=None,
                                     info=c4d.OBJECT_GENERATOR )

Regarding your issue about AttributeError: 'function' object has no attribute 'im_func': As written in the doc of NodeData.Message you are supposed to return a bool. (I agree the code sample is a bit weird because it doesn't show the return statement, I will fix it)

I've opened a BugReport since the error could be more explicit I will look at it but I guess this is a leftover of Python2.7 since im_func is removed in Python3.

If you have any questions, please let me know,
Cheers,
Maxime.

Hi,

could you show us an example for your code not working? On R23 SetPhong seems to work properly, when tested from a Python generator object.

import c4d

def main():
    """
    """
    node = c4d.BaseList2D(c4d.Osphere)
    if op[c4d.ID_USERDATA, 1]:
        node.SetPhong(True, True, 3.14159 * .125)
    return node

smooth_sphere.c4d

Cheers,
zipit

MAXON SDK Specialist
developers.maxon.net

hi @blastframe, usually the BaseObject::SetPhong() is not called in ObjectData::GetVirtualObjects() but rather in NodeData::Message() when the message intercepted is MSG_MENUPREPARE.

Something like

if type == c4d.MSG_MENUPREPARE:
    node.SetPhong(True, True, c4d.utils.DegToRad(40.0))

Cheers, R

Hi @r_gigante & @zipit,
Thank you both for your replies. I am still experiencing the issue, unfortunately. I can set the Phong angle manually, but I want to set (or override) my object's Phong angle with a tag. As can be seen in this image, the Phong tag is at 91⁰.
bc6daa42-e84a-4c99-a033-7414e135dec7-image.png

It should look like this. (side note: when the object is made editable, it creates a Phong tag with the angle set with SetPhong and deletes the previous one):
057fc233-14ec-4270-a677-5596801181af-image.png

@zipit
My issue is not with setting the Phong. My issue is that I cannot surface the Phong tag to the user as is done with the Cinema 4D Primitives. Here is a code example that shows how the Phong tag I'm creating does not associate with the object's Phong tag until it is made editable.

import os
import math
import sys
import c4d
from c4d import utils

PLUGIN_ID = 1234569

class PhongDemoObject(c4d.plugins.ObjectData):
    size = 100

    def __init__(self, *args):
        super(PhongDemoObject, self).__init__(*args)
        self.SetOptimizeCache(True)

    def Init(self, op):
        tag = op.MakeTag(c4d.Tphong)
        tag[c4d.PHONGTAG_PHONG_ANGLELIMIT] = True
        tag[c4d.PHONGTAG_PHONG_ANGLE] = c4d.utils.DegToRad(91.0)
        tag[c4d.PHONGTAG_PHONG_USEEDGES] = False
        c4d.EventAdd()

        return True

    def GetVirtualObjects(self, op, hierarchyhelp):
        ret = self.CreateMyObject()
        if ret is None:
            return None
        ret.SetName(op.GetName())
        return ret

    def CreateMyObject(self):
        node = c4d.PolygonObject(8, 6)

        node.SetPoint(0, c4d.Vector(-self.size,-self.size,-self.size))
        node.SetPoint(1, c4d.Vector(-self.size,self.size,-self.size))
        node.SetPoint(2, c4d.Vector(self.size,-self.size,-self.size))
        node.SetPoint(3, c4d.Vector(self.size,self.size,-self.size))
        node.SetPoint(4, c4d.Vector(self.size,-self.size,self.size))
        node.SetPoint(5, c4d.Vector(self.size,self.size,self.size))
        node.SetPoint(6, c4d.Vector(-self.size,-self.size,self.size))
        node.SetPoint(7, c4d.Vector(-self.size,self.size,self.size))

        node.SetPolygon(0, c4d.CPolygon(3,1,7,5))
        node.SetPolygon(1, c4d.CPolygon(3,5,4,2))
        node.SetPolygon(2, c4d.CPolygon(4,6,0,2))
        node.SetPolygon(3, c4d.CPolygon(6,7,1,0))
        node.SetPolygon(4, c4d.CPolygon(0,1,3,2))
        node.SetPolygon(5, c4d.CPolygon(4,5,7,6))

        node.SetPhong(True, True, c4d.utils.DegToRad(40.0))

        node.Message(c4d.MSG_UPDATE)
        return node

"""
    #this throws an error:
    #AttributeError: 'function' object has no attribute 'im_func'
    def Message(self, node, type, data):
        if type == c4d.MSG_MENUPREPARE:
            node.SetPhong(True, True, c4d.utils.DegToRad(40.0))
            return True
"""

if __name__ == "__main__":
    c4d.plugins.RegisterObjectPlugin(id=PLUGIN_ID,
                                     str="PhongDemoObject",
                                     g=PhongDemoObject,
                                     description="phongdemoobject",
                                     icon=None,
                                     info=c4d.OBJECT_GENERATOR )

I got some errors without including the res folder, so I've uploaded it if you wish to demo it on your machine:
Phong Demo Object.zip

@r_gigante
I am calling BaseObject::SetPhong() as it is done in the example: py-rounded_tube_r13.pyp. In that file, the method that sets the Node's points & then the Phong (GenerateLathe) is called from ObjectData::GetVirtualObjects().

Using the code below threw this error AttributeError: 'function' object has no attribute 'im_func':

    def Message(self, node, type, data):
        if type == c4d.MSG_MENUPREPARE:
            node.SetPhong(True, True, c4d.utils.DegToRad(40.0))

Am I doing this incorrectly?

Thanks again.

Hi @blastframe I think you get the concept wrong, Generator doesn't have a "proper" Phong tag, or at least the Phong tag you apply to it has no real meaning since Phong is not a hierarchical tag(like Texture is), but it affects only the current object and a generator don't have any Polygon Information.
With that's said this Phong tag on the generator is very useful because it let you expose parameter to the user in a meaningful manner, so the Generator can know for each Polygon Object it will generate, it can basically Retrieve a clone of this Phong and apply the same value. But the generator still has the liberty to not do it.

So the proper workflow is

  • Create a Tag in the MSG_MENUPREPARE so you expose to the user a UI to control the Phong shading.
  • Attach a Clone of this Tag to all PolygonObject you generate within your Generator.

So this gives us code like that:

import os
import math
import sys
import c4d
from c4d import utils

PLUGIN_ID = 1234569


class PhongDemoObject(c4d.plugins.ObjectData):
    size = 100

    def __init__(self, *args):
        super(PhongDemoObject, self).__init__(*args)
        self.SetOptimizeCache(True)

    def GetVirtualObjects(self, op, hierarchyhelp):
        ret = self.CreateMyObject(op)
        if ret is None:
            return None

        ret.SetName(op.GetName())
        return ret

    def CreateMyObject(self, node):
        newObj = c4d.PolygonObject(8, 6)

        newObj.SetPoint(0, c4d.Vector(-self.size,-self.size,-self.size))
        newObj.SetPoint(1, c4d.Vector(-self.size,self.size,-self.size))
        newObj.SetPoint(2, c4d.Vector(self.size,-self.size,-self.size))
        newObj.SetPoint(3, c4d.Vector(self.size,self.size,-self.size))
        newObj.SetPoint(4, c4d.Vector(self.size,-self.size,self.size))
        newObj.SetPoint(5, c4d.Vector(self.size,self.size,self.size))
        newObj.SetPoint(6, c4d.Vector(-self.size,-self.size,self.size))
        newObj.SetPoint(7, c4d.Vector(-self.size,self.size,self.size))

        newObj.SetPolygon(0, c4d.CPolygon(3, 1, 7, 5))
        newObj.SetPolygon(1, c4d.CPolygon(3, 5, 4, 2))
        newObj.SetPolygon(2, c4d.CPolygon(4, 6, 0, 2))
        newObj.SetPolygon(3, c4d.CPolygon(6, 7, 1, 0))
        newObj.SetPolygon(4, c4d.CPolygon(0, 1, 3, 2))
        newObj.SetPolygon(5, c4d.CPolygon(4, 5, 7, 6))

        # If the generator already have a PhongTag, jsut clone it otherwise define it a 92° phong shading
        phongTag = node.GetTag(c4d.Tphong)
        if phongTag is not None:
            newTag = phongTag.GetClone(c4d.COPYFLAGS_NONE)
            newObj.InsertTag(newTag)  # Add the cloned tag to the new Polygon Object present only in the cache
        else:
            newObj.SetPhong(True, True, c4d.utils.DegToRad(92.0))

        newObj.Message(c4d.MSG_UPDATE)
        return newObj

    def Message(self, node, type, data):
        # On the creation of the Object, via the Menu create a Phong tag on the generator
        # c4d.BaseList2D(1234569) will not trigger it
        if type == c4d.MSG_MENUPREPARE:
            # Create a phong tag and set the default value to 92° (so we see the phong shading)
            node.SetPhong(True, True, c4d.utils.DegToRad(92.0))
        return True


if __name__ == "__main__":
    c4d.plugins.RegisterObjectPlugin(id=PLUGIN_ID,
                                     str="phongdemoobject",
                                     g=PhongDemoObject,
                                     description="phongdemoobject",
                                     icon=None,
                                     info=c4d.OBJECT_GENERATOR )

Regarding your issue about AttributeError: 'function' object has no attribute 'im_func': As written in the doc of NodeData.Message you are supposed to return a bool. (I agree the code sample is a bit weird because it doesn't show the return statement, I will fix it)

I've opened a BugReport since the error could be more explicit I will look at it but I guess this is a leftover of Python2.7 since im_func is removed in Python3.

If you have any questions, please let me know,
Cheers,
Maxime.

It's working! :smile: Thank you @zipit , @r_gigante , & @m_adam ! Have an excellent weekend.

A note for future readers
One issue with my demo code is I wasn't passing the op from GetVirtualObjects to the CreateMyObject method. @m_adam created a new object (newObj) and only calls GetTag on the op (variable name: node).

phongTag = node.GetTag(c4d.Tphong)