Why is my PyObjc Cocoa view class forgetting its fields?
Question
I was trying to hack up a tool to visualize shaders for my game and I figured I would try using python and cocoa. I have ran into a brick wall of sorts though. Maybe its my somewhat poor understand of objective c but I can not seem to get this code for a view I was trying to write working:
from objc import YES, NO, IBAction, IBOutlet
from Foundation import *
from AppKit import *
import gv
class SceneView(NSOpenGLView):
def __init__(self):
NSOpenGLView.__init__(self)
self.renderer = None
def doinit(self):
self.renderer = gv.CoreRenderer()
def initWithFrame_(self, frame):
self = super(SceneView, self).initWithFrame_(frame)
if self:
self.doinit()
print self.__dict__
return self
def drawRect_(self, rect):
clearColor = [0.0,0.0,0.0,0.0]
print self.__dict__
self.renderer.clear(CF_Target|CF_ZBuffer,clearColor)
It outputs this when executed:
{'renderer': <gv.CoreRenderer; proxy of <Swig Object of type 'GV::CoreRenderer *' at 0x202c7d0> >}
{}
2009-04-03 19:13:30.941 geom-view-edit[50154:10b] An exception has occured:
Traceback (most recent call last):
File "/System/Library/Frameworks/Python.framework/Versions/2.5/Extras/lib/python/PyObjCTools/AppHelper.py", line 235, in runEventLoop
File "/mnt/gilead/amcharg/projects/geom-view-edit/build/Debug/geom-view-edit.app/Contents/Resources/SceneView.py", line 37, in drawRect_
self.renderer.clear(CF_Target|CF_ZBuffer,clearColor)
AttributeError: 'SceneView' object has no attribute 'renderer'
It seems to be losing my renderer variable which is not that surprising considering how funky the initWithFrame_ code is but this was something xcode seemed to write which I suppose makes sense since objective C has the init separate from alloc idiom. It is still strange seeing it python however.
Is there anyways to salvage this or should I take it out behind the code shed shoot it and use QT or wxPython? I considered using objective-c but I want to test out these nifty swig bindings I just compiled =)
Solution
Depending on what's happening elsewhere in your app, your instance might actually be getting copied.
In this case, implement the copyWithZone
method to ensure that the new copy gets the renderer as well. (Caveat, while I am a Python developer, and an Objective-C cocoa developer, I haven't used PyObjC myself, so I can't say for certain if you should be implementing copyWithZone
or __copy__
).
In fact, shoving a copyWithZone
method into the class with a print will allow you to tell if the method is being called and if that's the reason renderer appears to have vanished.
Edit: Base on your feedback, I've pasted your code into a blank xcode python project (just substituting something else for gv.CoreRenderer, since I don't have that), and it works fine with some minor modifications. How are you instantiating your SceneView?
In my case I:
- Created a blank xcode project using the Cocoa-Python template
- Created a new file called
SceneView.py
. I pasted in your code. - Opened the
MainMenu.xib
file, and dragged an NSOpenGLView box onto the window. - With the NSOpenGLView box selected, I went to the attributes inspector and changed the class of the box to
SceneView
- Back in xcode, I added
import SceneView
in the imports inmain.py
so that the class would be available when the xib file is loaded - I implemented an
awakeFromNib
method inSceneView.py
to handle setting upself.renderer
. Note that__init__
, andinitWithFrame
are not called for nib objects during your program execution... they are considered "serialized" into the nib file, and therefore already instantiated. I'm glossing over some details, but this is why awakeFromNib exists. - Everything worked on run. The
__dict__
had appropriate values in thedrawRect_
call, and such.
Here's the awakeFromNib function:
def awakeFromNib(self):
print "Awake from nib"
self.renderer = gv.CoreRenderer()
So, I'm guessing there are just some crossed wires somewhere in how your object is being instantiated and/or added to the view. Are you using Interface Builder for your object, or are you manually creating it and adding it to a view later? I'm curious to see that you are getting loggin outputs from initWithFrame, which is why I'm asking how you are creating the SceneView.
OTHER TIPS
Even if they weren't serialized, the __init__-constructor of python isn't supported by the ObjectiveC-bridge. So one needs to overload e.g. initWithFrame: for self-created Views.