PDA

View Full Version : Difficult question (catching object manager events from c4d tools with python?)


FantaBurky
01-24-2013, 12:13 AM
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.

littledevil
01-24-2013, 08:39 AM
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())

FantaBurky
01-24-2013, 10:19 AM
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).

littledevil
01-24-2013, 10:56 AM
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.

FantaBurky
01-24-2013, 01:40 PM
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 :p

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()
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())

littledevil
01-24-2013, 02:44 PM
your fix is correct :) 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 :)

NiklasR
01-24-2013, 07:49 PM
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() (http://docs.python.org/2/library/functions.html#isinstance) function is usually the better choice. :thumbsup:

Best Regards,
Niklas

FantaBurky
01-24-2013, 08:04 PM
your fix is correct :) 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 :)
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..

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() (http://docs.python.org/2/library/functions.html#isinstance) function is usually the better choice. :thumbsup:

Best Regards,
Niklas
Thank you, more good stuff to look into :D

littledevil
01-24-2013, 09:22 PM
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(...???

FantaBurky
01-24-2013, 10:03 PM
:bowdown: :bowdown:

Thank you :D I'll post it here when I figure out the rest :)

FantaBurky
01-25-2013, 07:33 PM
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())

littledevil
01-25-2013, 09:44 PM
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 :)

FantaBurky
01-25-2013, 10:24 PM
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 :)

littledevil
01-26-2013, 06:45 AM
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?

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.

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

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.

FantaBurky
01-26-2013, 08:52 AM
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 :p

I wasn't sure if Plugin Cafe was as active as CGTalk's c4d forum, but thanks for the tip :) 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

littledevil
01-26-2013, 09:32 AM
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 :) but you shouldn't post them on the interwebs , they are super secret ;)

FantaBurky
01-26-2013, 10:59 AM
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()).
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 :p

you shouldn't post them on the interwebs , they are super secret ;)
ops, heh :scream:

littledevil
01-26-2013, 11:41 AM
you are making things to complictaed :)

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 :)

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

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

FantaBurky
01-26-2013, 09:49 PM
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 "FreeHandToggle" in Console.
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.

littledevil
01-27-2013, 12:06 AM
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 ;) 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.

FantaBurky
01-27-2013, 12:30 AM
Freaking awesome :buttrock: :bowdown: :bowdown:
That works great. And thanks for the commented bits in there as well :)

But it's missing the code to make Freehand spline the active tool once the command plugin is turned on/checked. Where would be the ideal place to add it (doc.SetAction(14055))? And is it possible to return to the tool that was active before the button was pressed, when the command plugin becomes unchecked/turned off again?

Thanks again, can't tell you how much this has helped my learning process :D

littledevil
01-27-2013, 07:00 PM
yeah setaction would be the method. to store the last tool, just write the result of
basedoucment.getaction into a variable of your class before you call setaction. when
the plugin is turned off write this variable/id back into the document with setaction.

FantaBurky
01-27-2013, 08:33 PM
It works, yaaay :wip: :bowdown:
import c4d, string
from c4d import documents, plugins

PID_MESSAGE = 1029730
PID_CMDDATA = 1029729
TOOLID = 14055

class fhFreehandMessageData(plugins.MessageData):

def __init__ (self):
self.Cache = None
self.IsActive = False
self.Counter = 0

def CoreMessage(self, id, bc):
doc = documents.GetActiveDocument()

# set the cache when the plugin instance has been created
# and there is no cache yet
if not self.Cache:
self.Cache = doc.GetFirstObject()

# ---> here it happens
# listen for our commanddata id and togle the state
if id == PID_CMDDATA:
self.IsActive = not self.IsActive
if self.IsActive:
self.PrevMode = doc.GetMode()
self.PrevTool = doc.GetAction()
doc.SetMode(c4d.Mpoints)
doc.SetAction(TOOLID)
print 'Yay, i am back again.'
else:
doc.SetMode(self.PrevMode)
doc.SetAction(self.PrevTool)
print 'I should shut up ? You will be doomed without me !'

# listen if c4d send EventAdd(EVMSG_CHANGE) and check if the
# currently topmost object is the same to our stored / cached
# object, if not there has been inserted a new object.
elif id == c4d.EVMSG_CHANGE and doc.GetAction() == TOOLID:
# add 'and self.IsActive, which is the short form for and self.IsActive == True'
if self.Cache and self.Cache is not doc.GetFirstObject() and self.IsActive:
self.Cache = doc.GetFirstObject()
if self.Cache.GetType() == c4d.Ospline:
print "Spline No.{0}, named {1} - this boring , can we do somethingg else please ?".format(self.Counter, self.Cache.GetName())
self.Counter += 1
# object is inserted but the plugin is not 'active'
elif self.Cache and self.Cache is not doc.GetFirstObject() and not self.IsActive:
print 'I am not allowed to say something... Ooops.'
return True


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

def Execute(self, doc):
self.isActive = not self.isActive
# ---> send the message
c4d.SpecialEventAdd(PID_CMDDATA, 0,0)
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.RegisterMessagePlugin(id = PID_MESSAGE,
str = "fhFreeHandMessage",
info = c4d.PLUGINFLAG_SMALLNODE,
dat = fhFreehandMessageData())
plugins.RegisterCommandPlugin(id = PID_CMDDATA,
str = "FreehandPlusToggle",
info = 0,
icon = None,
help = "This is a Tooltip for FreehandPlusToggle.",
dat = FreehandPlusToggle())
I also used SetMode to enter Points Mode when Freehand Spline Tool is selected, cause it was showing the bounding box and axis otherwise (which is not what happens with the standard Freehand spline tool). I tried inserting the variables elsewhere, but either the plugin wouldn't load or it wouldn't see the variables in the else statement.

The only issue I've encountered is if you leave the CommandData plugin enabled/toggled ON, and then delete all the objects in the Object Manager: It prints this in console:
Traceback (most recent call last):
File "'FreeHandToggle.pyp'", line 46, in CoreMessage
AttributeError: 'NoneType' object has no attribute 'GetType'
Not a big problem though I guess. Same thing happens if you leave the plugin enabled/toggled on, and close the document (and c4d creates a new one). It will keep the ON state and the Freehand spline tool, and if you toggle it OFF and then ON it prints the same message again :surprised...

Anyway, I really have to thank you!! I've learned so much from this :bowdown: :bowdown: And I really did not expect anyone to give so much of their time to help teach others. I really, really appreciate it :beer:

littledevil
01-27-2013, 08:51 PM
well check line 46:

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

it is an error in my code due to sloppy coding. think of what happens when you call
self.Cache = doc.GetFirstObject() for an empty document. in other lines i have done it
properly, but here i got lazzy. i have also described the problem earlier in the thread
here.

rather than coping similar lines you should look up the c4d.documents.basedocument.
GetFirstObject() return value in sdk and try to understand why this raises a runtime
error (the code is trying to call a method which does not exist).

won't spoil all the fun for you :)

edit : ps, there will be people who tell you otherwise, but i would define self.PrevMode etc.
in the constructor / the __init__ method of your class with a None value, it does make it
easier to read your code later on. it would make it easier to understand that this class
stores a variable called self.PrevMode which is of some importance.

FantaBurky
01-28-2013, 12:31 AM
Doh, ok. I'm a dumbass :blush: I was somewhat aware of what was causing the error, but the problem was, I became really confused when it didn't help when I added the green highlighted stuff :argh::
elif id == c4d.EVMSG_CHANGE and doc.GetAction() == TOOLID:
# add 'and self.IsActive, which is the short form for and self.IsActive == True'
if self.Cache and self.Cache is not doc.GetFirstObject() and self.IsActive:
self.Cache = doc.GetFirstObject()
if not self.Cache:
self.Cache = doc.GetFirstObject()
elif self.Cache.GetType() == c4d.Ospline:
print "Spline No.{0}, named {1} - this boring , can we do somethingg else please ?".format(self.Counter, self.Cache.GetName())
self.Counter += 1
# object is inserted but the plugin is not 'active'
elif self.Cache and self.Cache is not doc.GetFirstObject() and not self.IsActive:
print 'I am not allowed to say something... Ooops.'
return TrueTurns out I was editing the wrong file LOL. I was writing in a backup copy of the .pyp file that I had somewhere else on my system, so nothing was actually being modified :banghead:.. Anyway, all the error messages are gone now :D And I defined the PrevTool/PrevMode in the constructor like you said. Thanks for the tip :)

But one last thing remains. How to make sure the toggle is OFF everytime the BaseDocument changes (closes or a new one is created/opened) OR a tool other than (!=) TOOLID (Freehand spline) is selected, while it's toggled ON.

I thought since the doc.GetAction() changes usually when the BaseDocument changes, it might be good to check if doc.GetAction() != TOOLID. So I tried inserting that into the def GetState(), and it works great for changing the visual toggle to OFF (c4d.CMD_ENABLED).

But it only toggles the visual toggle, not the actual button toggle. Cause if I click on the toggle again after it's been visually toggled OFF, it toggles the button OFF (I assume, cause it prints 'I should shut up ? You will be doomed without me !'), and then I have to click it a second time to get the button to toggle ON:
def GetState(self, doc):
if self.isActive and doc.GetAction() != TOOLID:
return c4d.CMD_ENABLED
if (self.isActive):
return c4d.CMD_VALUE + c4d.CMD_ENABLED
else:
return c4d.CMD_ENABLEDAny ideas? :shrug:

littledevil
01-28-2013, 01:06 AM
for the BaseDocment.GetFirstObject() thing :

the method returns None when there are no objects in the document. This is is a fact i
do not have thought of when i wrote the quick message plugin example on my first
posting. your code fixes the runtime error, but it would be a better approach to:

1. do not check in MessageData.CoreMessage if self.Cache is none as an own
if statement.

2. do not overwrite the self.Cache if it is none, as the result could also be none
again, which will cost runtime.

3.on each occasion you have to use self.Cache check if the cache is none:

if self.Cache and ... :

for the document thing. i have asked this question not long ago myself in the
pcafe forum, how this is done properly. there is no message being sent when
the active document has changed. the way maxon wants us to do this is either
a SceneHook plugin (which is not possible in python) or with EVMSG_CHANGE
and check if the current document is the same as the stored reference .
EVMSG_CHANGE is the same ID you are already using in the message pluin and
will result in a lot of checking :)

if currentDoucment is self.myDoucmentReference:

FantaBurky
01-29-2013, 02:06 PM
I couldn't seem to get the self.Cache thingy to work :shrug:

So I'm looking into SceneHook now instead. Something that I probably shouldn't be trying, but I found an example posted on plugincafe. Thought I'd try compiling it and see what happens:
http://www.plugincafe.com/forum/forum_posts.asp?TID=3286

But is SceneHook really needed to make the Tool change work. Like if you have the plugin toggled ON with TOOLID selected (Freehand spline), and you choose a tool different from the TOOLID, it doesn't toggle OFF automatically.

I found a recently added method to the SDK which looks like it might be able to help with just tool change.
BaseDocument.SendInfo(type, format, fn, bl, hooks_only)

littledevil
02-03-2013, 10:36 PM
The code in the posted link is C++, this won't work with python :) Please also note that SceneHookData is not wrapped for python. Not sure what you mean with SceneHook in the current context. You can get hold of existing SceeneHooks with BaseDocument.FindSceneHook().

You could implement your desired features just with messages :)

FantaBurky
02-04-2013, 08:45 AM
Oh, I missunderstood :surprised

I thought SceneHook was a specific c++ plugin, with the sole purpose of sending messages that couldn't be sent by other plugins (in this case Python). Like when one BaseDocument closes and another opens. And that the Python plugin could then intercept the message and act on it.

Anyway, keeping it all Python is way better for me, so I'll take another look at messages :)

I have to thank you again, for having patience with me. I know I must be the worst/slowest learner. So I really appreciate it :bowdown:

CGTalk Moderation
02-04-2013, 08:45 AM
This thread has been automatically closed as it remained inactive for 12 months. If you wish to continue the discussion, please create a new thread in the appropriate forum.