Difficult question (catching object manager events from c4d tools with python?)


#1

I have a more difficult question this time =P Maybe someone here knows.

I’m trying to get the console to print “Hello World!” every time a spline object has been created with the Freehand spline tool. Is this only possible by making a plugin, or can it be accomplished through a Python script?

I’m thinking the script (if possible) would be something really complicated, like: start the script with making Freehand spline the active tool (doc.SetAction(14055)???). Then check when a spline object becomes available in the Object Manager while using that tool. And then repeat for everytime the mouse is released (EditorWindow.MouseDragEnd()???). But this would require the script to somehow check all the time if a spline object becomes available in the Object Manager. And also check for when we stop using the Freehand spline tool, and if so stop the script.

Maybe someone can shed some light on this, cause I’m really lost right now. If only to point me in the right direction. I’d really appreciate any help. I’ve lost two nights sleep over this ha ha =D. Scripting, while very difficult is also very addictive I must say.


#2

i do not think that this is possible with a script, but you might get it to work with some
dirty threading tricks, but scripts are officially not meant to use threads. you cannot run
the script and the freehand tool at the same time. the common way would be message or
tool plugin and overwriting the message method.

this plugin prints hello world every time a new object has been inserted into the document
while the freehand tool is active. the plugin assumes that new objects are being inserted
at the top of the object list/document.

import c4d
from c4d import documents, plugins

PID_MESSAGE = 1000000
TOOLID = 14055

class fhFreehandMessageData(plugins.MessageData):
	def __init__ (self):
		self.Cache = None

	def CoreMessage(self, id, bc):
		doc = documents.GetActiveDocument()
		if not self.Cache:
			self.Cache = doc.GetFirstObject()
		if id == c4d.EVMSG_CHANGE and doc.GetAction() == TOOLID:
			if self.Cache and self.Cache is not doc.GetFirstObject():
				self.Cache = doc.GetFirstObject()
				print "Hello World."
		return True
		
if __name__ == "__main__":
    plugins.RegisterMessagePlugin(id  = PID_MESSAGE, 
                                 str  = "fhFreeHandMessage",
                                 info = c4d.PLUGINFLAG_SMALLNODE,
                                 dat  = fhFreehandMessageData())

#3

Wow, I didn’t expect anyone to be so generous as to write a complete plugin =O. Thank you for taking the time to do this. It works great. Only I wish I had said it was supposed to run as a seperate tool that references to a built-in tool in c4d.

Is it possible to convert it into a ToolData plugin? So that it doesn’t affect using the standard built-in Freehand spline tool, but instead acts as a tool of it’s own (even though it references to the Freehand spline tool).


#4

well,

it would be basically the same. you’ll just have to add some on/off filtering. the easiest
way would be to write a second plugin, a commanddata plugin which does nothing more
than providing a button and a toggle feature. this plugin then would have to send a core
message to the messagedata plugin telling the messagedata plugin if it is meant to be on
or off. try to write the commanddata plugin with the functinality of toggling the on off
state and i’ll help you with sending the core message if needed.

here a little snippet on how you write the visual toggle state for commanddata or tooldata
plugin button. you will have to make sure that your commandData class has a variable to
store the state, like self.isActive in the example. then you will have to overwrite Execute
for your command data plugin to toggle this variable on user clicks.

# toggle the plugin button state, pressed / active / disabled (unsaved document)
# --------------------------------------------------------------------------------------
    def GetState(self, doc):
        if (doc.GetDocumentPath() != ""):
            if (self.isActive):
                return c4d.CMD_VALUE + c4d.CMD_ENABLED
            else:
                return c4d.CMD_ENABLED
        else: 
            return 0

ps :

there is also still a ‘bug’ in my message plugin example, when you have the freehandspline
tool active and insert an object into the document, like for example a parametric sphere,
the plugin prints ‘Hello World’. this is actually not intended. try to fix this. hint - you will
find the solution in c4d.C4DAtom or you could also use a python keyword to solve this.


#5

Thanks for all the help! I really appreciate it. I don’t know how many days or weeks/months/months I’d spend trying to figure out certain things without some help.

I’ll look into commanddata plugins in the sdk+google. But I should mention I haven’t even scratched the surface on that. I’ve dreaded coding plugins thinking it would be like a completely different language than regular python scripting. Which I’ve also dreaded :D. Just recently decided to start learning. It helps a lot to have a code to look at and dissect, so thank you for taking time to write them! I’ll post when I’ve figured it out, could take a while though :stuck_out_tongue:

About the ‘bug’, c4datom helped, thanks. Don’t know if I’ve used it as optimised as possible but it seems to be working:

import c4d
from c4d import documents, plugins

PID_MESSAGE = 1000000
TOOLID = 14055

class fhFreehandMessageData(plugins.MessageData):
	def __init__ (self):
		self.Cache = None

	def CoreMessage(self, id, bc):
		doc = documents.GetActiveDocument()
		if not self.Cache:
			self.Cache = doc.GetFirstObject()
		if id == c4d.EVMSG_CHANGE and doc.GetAction() == TOOLID:
			if self.Cache and self.Cache is not doc.GetFirstObject():
				self.Cache = doc.GetFirstObject()
[B]				if self.Cache.GetType() == 5101:[/B]
					print "Hello World."
		return True
		
if __name__ == "__main__":
    plugins.RegisterMessagePlugin(id  = PID_MESSAGE, 
                                 str  = "fhFreeHandMessage",
                                 info = c4d.PLUGINFLAG_SMALLNODE,
                                 dat  = fhFreehandMessageData())

#6

your fix is correct :slight_smile: i haven’t checked the ID, but i guess it is the splineobject id.
as a sidenote. it is better to use the string version of the ids, because it does make
your code more readable. to get hold of the string version of an ID there are two
ways. 1. use a textfile search software on your cinema4d folder. 2. the basic IDs are
also listed in python sdk with the init method of the respective object.

http://www.thirdpartyplugins.com/python_r14/modules/c4d/C4DAtom/GeListNode/BaseList2D/BaseObject/index.html#BaseObject.init
http://www.thirdpartyplugins.com/python_r14/types/objects.html

if self.Cache.GetType() == c4d.Ospline :

as editable splines are an actual class you could also use the python type keyword.
this is important when you are not sure if the test instance is derived of c4d.C4dAtom.
or in other words if the testobject provides a GetType() method. this will be never
the case for selecteable objects, but nevertheless:

if type(self.Cache) == c4d.SplineObject:

happy coding :slight_smile:


#7

Instead of if self.Cache.GetType() == c4d.Ospline, you could also write if self.Cache.CheckType(c4d.Ospline). And for the type-checking, the isinstance() function is usually the better choice. :thumbsup:

Best Regards,
Niklas


#8

Cool. Those are very useful, thanks :bowdown:

I’ve been searching/reading like crazy for a few hours now on the commanddata. I will probably not figure it out today, but I’ll post it tomorrow if I do. Good thing is I’m learning more about Python scripting, which is what I’d like to focus on.

I’ll also give tooldata a chance as well, cause I would prefer if it would run similar to the Freehand spline tool. Without clicking on a button everytime to initialize. Sadly there’s little information about on-off filtering on google :sad: The only thing I can find in the sdk is:
http://www.thirdpartyplugins.com/python_r14/modules/c4d.plugins/index.html?highlight=filterpluginlist#c4d.plugins.FilterPluginList

Which doesn’t sound right…

Thank you, more good stuff to look into :smiley:


#9

on/off filtering isn’t an actual term i just used it to describe the functionailty of the plugin.
you are a bit off the track, ill give you a little help, you will have to fill in the rest.

import c4d
from c4d import documents, plugins

# make sure to ge a valid pluin id, these are only testing ids
PLUGINID = 1000001

class myCommandDataPluin(plugins.CommandData):
	# the constructor, this would be a good place to define some variables you 
	# want to use in all methods
	def __init__ (self):
		???

	# the method c4d calls when the command is called / the button has been clicked
	def Execute(self, doc):
		???

	# visually enable or disable the plugin button
	def GetState(self, doc):
		???

if __name__ == "__main__":
    # register the plugin
    plugins.RegisterCommandPlugin(...???

#10

:bowdown: :bowdown:

Thank you :smiley: I’ll post it here when I figure out the rest :slight_smile:


#11

Man, that went terrible. Got a nasty headache and not feeling much love for Python anymore ha ha. A complete mess sadly :blush:, to the point that I’m almost forgetting what I’ve learnt in regular Python scripting.

I got it (def execute) to stick, but not toggle. I’ll give it another go tomorrow :shrug:

import c4d
from c4d import documents, plugins

# make sure to ge a valid plugin id, these are only testing ids
PLUGIN_ID = 1000009

class FreehandPlusToggle(c4d.plugins.CommandData):
    def __init__ (self):
        self.isActive = 1
        self.isActive = 0

    def Execute(self, doc):
        if (doc.GetDocumentPath() != ""):
            if (self.GetState == c4d.CMD_VALUE + c4d.CMD_ENABLED):
                self.isActive = 0
            else:
                self.isActive = 1
        else:
            return 0

    def GetState(self, doc):
        if (doc.GetDocumentPath() != ""):
            if (self.isActive == 1):
                return c4d.CMD_VALUE + c4d.CMD_ENABLED
            else:
                return c4d.CMD_ENABLED
        else:
            return 0


if __name__ == "__main__":
    plugins.RegisterCommandPlugin(PLUGIN_ID, "FreehandPlusToggle", 0, None, "This is a Tooltip for FreehandPlusToggle.", FreehandPlusToggle())

#12

hi, here is a corrected version, i haven’t tested in c4d, so i might have overlooked other
errors.

class FreehandPlusToggle(c4d.plugins.CommandData):
   # this does not make much sense, you are defining the same variable twice
   # it also would be  more convenient to use a BOOL type rather then a INTEGER type
    def __init__ (self):
    	self.isActive = 1
    	self.isActive = 0
        self.isActive = False


   # here is you main error. you are comparing self.isActive against CMD_VALUE +
   # CMD_ENABLED. both are just integer constants which could be translated to 1
   # and 2 (i think, could also be 0 and 1). so you are doing this -
   #
   # if (self.GetState == 3) :
   #
   # which will never be True. i think you wanted to do an logic or here :
   #
   # if self.isActive == c4d.CMD_VALUE or self.isActive == c4d.CMD_ENABLED:
   #
   # however this would still not be correct, as both values are c4d constants to 
   # return the button display state and have no relation to our class isActive 
   # variable. you also check for an unsaved document here, which will
   # cause you button only to work for saved documents. an empty string 
   # document path means that the document has not been saved yet.
    def Execute(self, doc):
        if (doc.GetDocumentPath() != ""):
            if (self.GetState == c4d.CMD_VALUE + c4d.CMD_ENABLED):
                self.isActive = 0
            else:
                self.isActive = 1
        else:
            return 0

            # toggle the boolean state. True becomes False and the other way arround.
            self.isActive = not self.isActive
            # we have alaway return a boolean for execute so that c4d knows if the
            # the code has been executed successfully or not.
            return True
    

    # you do not need to check if the doucment has been saved, your button will
    #  become otheriwse disabled (grey out - unclickable) for unsaved documents.
    def GetState(self, doc):
        if (doc.GetDocumentPath() != ""):
            if (self.isActive):
                return c4d.CMD_VALUE + c4d.CMD_ENABLED
            else:
                return c4d.CMD_ENABLED
        else:
            return 0


if __name__ == "__main__":
    plugins.RegisterCommandPlugin(PLUGIN_ID, "FreehandPlusToggle", 0, None, "This is a Tooltip for FreehandPlusToggle.", FreehandPlusToggle())

edit : lol, after posting this, it looks terrible, but you general direction was the correct one :slight_smile:


#13

Really? I just tried your corrected version, and it works great. And the commented bits are golden for me :bowdown: :

import c4d
from c4d import documents, plugins

PLUGIN_ID = 1000009

class FreehandPlusToggle(c4d.plugins.CommandData):
    def __init__ (self):
        self.isActive = False

    def Execute(self, doc):
        self.isActive = not self.isActive
        return True

    def GetState(self, doc):
            if (self.isActive):
                return c4d.CMD_VALUE + c4d.CMD_ENABLED
            else:
                return c4d.CMD_ENABLED


if __name__ == "__main__":
    plugins.RegisterCommandPlugin(PLUGIN_ID, "FreehandPlusToggle", 0, None, "This is a Tooltip for FreehandPlusToggle.", FreehandPlusToggle())

One thing though, if I add print “activated” and print “deactivated” before each of the GetState returns and toggle the button it prints both messages twice in console. I assume it’s not not a problem?

    def GetState(self, doc):
            if (self.isActive):
                print "activated"   #<----here
                return c4d.CMD_VALUE + c4d.CMD_ENABLED
            else:
                print "deactivated"   #<----here
                return c4d.CMD_ENABLED

Console:

activated
activated
deactivated
deactivated

I’ll try to get the SendCoreMessage/CoreMessage working, hopefully it’s not as difficult as this was.

Thanks again :slight_smile:


#14

nah, for instantiated objects this happens quite often. c4d creates copies of your
objects internally for multiple reasons. these objects also call your methods.
a command data plugin is not instantiated, but the gui of the plugin is. or in
other words - you can have multiple buttons for your plugin the gui. for each
of them getstate seems to be called.

SendCoreMessage is the correct method. but for this you will have to register at
plugin cafe and get some proper ids, because i do not know if the tesing ids are
also unused as message ids. you can register a plugin id from the forum top menu
(next to logut). you can register two ids, one for your plugin, one for the message,
but in your case one id for both would be enough (you would be sending your plugin
id as the message id).

in the message plugin you will have to listen in the coremessage method for your plugin
id when you recieve the id you will have to toggle an internal variable like you did in the
command data plugin. based on this variable the message plugin enables/disables its
output.

http://www.plugincafe.com/forum/default.asp

registering there will you also give access to the help of people being by far more experienced than me and the maxon dev support / the maxon devs.


#15

Sounds pretty straightforward. Just as long as I can find a way to enable/disable the message output. I’ll probably try disabling the message plugin first without core messages, and then with. - edit: ok, I forgot coremessage is the reason the message plugin does what it does. That makes it slightly more difficult :stuck_out_tongue:

I wasn’t sure if Plugin Cafe was as active as CGTalk’s c4d forum, but thanks for the tip :slight_smile: I’ve registered and generated this one. Hope they don’t mind generating ID’s for test plugins (maybe I can use it in the future when I’ve learned more and can create something of my own from scratch):

1029729 	FreeHandToggle

*whips out the SDK


#16

if you are unsure about the sending part you could use c4d.EVMSG_CHANGE first
to implement the enabling and disabling of the Message plugin. but you should be
aware of the fact, that it is fired ALOT (it is the message for EventAdd()).

about the ids, you do not have to care, as you cann see there already have been
generated almost 30000 ids, i do not think that maxon cares if you get one more
or less :slight_smile: but you shouldn’t post them on the interwebs , they are super secret :wink:


#17

Hmm, I haven’t managed to get SendCoreMessage working yet. But I had some success with using c4d.IsCommandChecked(). If the FreeHandToggle is ON while using Freehand spline tool it prints “Hello World.”. If it’s OFF nothing happens:

import c4d
from c4d import documents, plugins

PID_MESSAGE = 1000000
TOOLID = 14055

class fhFreehandMessageData(plugins.MessageData):
    def __init__ (self):
        self.Cache = None

    def CoreMessage(self, id, bc):
        doc = documents.GetActiveDocument()
        doc.StartUndo()
        CmdEnabled = c4d.IsCommandChecked(1029729)
        if not self.Cache:
            self.Cache = doc.GetFirstObject()
        if CmdEnabled == True:
            if id == c4d.EVMSG_CHANGE and doc.GetAction() == TOOLID:
                if self.Cache and self.Cache is not doc.GetFirstObject():
                    self.Cache = doc.GetFirstObject()
                    if self.Cache.GetType() == 5101:
                        print "Hello World."
        return True
        
if __name__ == "__main__":
    plugins.RegisterMessagePlugin(id  = PID_MESSAGE, 
                                 str  = "fhFreeHandMessage",
                                 info = c4d.PLUGINFLAG_SMALLNODE,
                                 dat  = fhFreehandMessageData())

Only problem is I’d have to make sure the Freehand spline tool becomes active when the FreeHandToggle plugin is checked. And deactivated when the plugin is not checked. But that’s gonna require SendCoreMessage I guess, so I’ll start over now :stuck_out_tongue:

ops, heh :scream:


#18

you are making things to complictaed :slight_smile:

  1. in both plugin classes create a variable which stores the state like self.IsEnabled.
  2. make sure both are initalized with the same state, currently it is false, as you
    do not want the tool active per default.
  3. when your commanddata plugin is called simply send a plugin message with the id
  4. in the core message plugin , every time you recive the id simply toggle your internal
    variable too. the parameter id passed to your CoreMessage method will be the
    id send by your commanddata plugin.
  5. your approach with reading the plugin state is also possible, but it does not
    make so much sense for message plugin :slight_smile:

so your commanddata plugin shouts 1029729 and messagedata thinks id == 1029729,
ok commanddata buddy thinks something important has happened :slight_smile:

the chance of your both plugins gettings out of sync this way is REALLY small, but i am
pretty sure some nitpicker will complain about it, so here you can read how to unpack
core message data (you will have to send your boolean state as a coremessage parameter):

http://www.plugincafe.com/forum/forum_posts.asp?TID=7564&PID=31266#31266


#19

Sorry :blush:

I can’t seem to get the ID out from the commanddata plugin. It outputs the name of the plugin instead (FreeHandToggle).

import c4d
from c4d import documents, plugins

PLUGIN_ID = 1029729

class FreeHandToggle(c4d.plugins.CommandData):
    def __init__ (self):
        self.isActive = False
        self.isEnabled = False

    def Execute(self, doc):
        msg = c4d.BaseContainer(c4d.COREMSG_CINEMA_GETCOMMANDNAME)
        msg.SetLong(c4d.COREMSG_CINEMA_GETCOMMANDNAME, 1029729)
        name = c4d.SendCoreMessage(c4d.COREMSG_CINEMA, msg, 0)
        print name # prints out "[COLOR=Cyan]FreeHandToggle" in Console.[/COLOR]
        self.isActive = not self.isActive
        self.isEnabled = not self.isEnabled
        return True

    def GetState(self, doc):
            if (self.isActive):
                return c4d.CMD_VALUE + c4d.CMD_ENABLED
            else:
                return c4d.CMD_ENABLED


if __name__ == "__main__":
    plugins.RegisterCommandPlugin(PLUGIN_ID, "FreeHandToggle", 0, None, "FreeHandToggle Tooltip.", FreeHandToggle())

edit: I realise that making the c4d.SendCoreMessage a variable won’t actually send the message. I just did that to test what the output was.


#20

hm,
this was kind of my fault, CoreMessage is not the correct method for this, but
c4d.SpecialEventAdd() is . i have merged both plugins into one file. here is a fully
working example, i used a ‘wrong’ plugin ID for the messagedata plugin, as currently
all test ids are in use here, so you might want to change this.

example with a naughty message plugin :wink: the cgtalk code formating is just awfull,
so the code as a link:

http://codepad.org/DeVmgrMB

edit: fixed two things which were really bad, there are still some things which could
be done better, but i want to keep the code small, so i think this will be ok.