loadModel() issue

Hello. I’m new to Panda3D and was experimenting with the tutorials (here) and came across something that I got stuck on. Hopefully you guys can help me figure it out.
Thanks in advance.

Here is the issue:
I’m wanting to make a class to load the environment (to later be expanded upon) in the tutorial.
I was able to make a class for the walking panda that I can instantiate into the application without any issue by subclassing the Actor class, but understand that one should use a different class for static models.
After looking around, I didn’t see something to subclass, so I thought perhaps I can just call the model into a class and instantiate my class after that. So, here is the code that I have:

from direct.showbase.Loader import Loader
class Environment:
    def __init__(self, Node):
        filename = r'models/environment'
        self.node = Node
        self.base = Loader.loadModel(filename)
        self.base.setScale(.25,.25,.25)
        self.base.setPos(-8, 42, 0)
        self.base.reparentTo(self.node)

I can then later call this into the application class I have with this:

self.environ = Environment(self.render)

I am getting this error returned to me:

TypeError: unbound method loadModel() must be called with Loader instance as first argument (got str instance instead)

I’m not sure how to rectify this error.


In case it is needed, here is all of the code in my file (think the pertinent information is above though.)

import math
from direct.showbase.ShowBase import ShowBase
from direct.actor.Actor import Actor
from direct.showbase.Loader import Loader
from direct.task import Task
from direct.interval.IntervalGlobal import Sequence
from panda3d.core import Point3

class PandaActor(Actor):
    def __init__(self, Node, x=0, y=0, z=0):
        self.node = Node
        animations = {'walk': 'models/panda-walk4'}
        self.base = Actor(r'models/panda-model', animations)
        self.base.setScale(.005, .005, .005)
        self.base.setPos(x, y, z)
        self.base.loop('walk')
        self.base.reparentTo(self.node)
        
    def pace(self):
        pandaPosInterval1 = self.node.posInterval(13, Point3(  0, -10, 0), startPos=Point3(  0,  10, 0))
        pandaPosInterval2 = self.node.posInterval(13, Point3(  0,  10, 0), startPos=Point3(  0, -10, 0))
        pandaHprInterval1 = self.node.hprInterval( 3, Point3(180,   0, 0), startHpr=Point3(  0,   0, 0))
        pandaHprInterval2 = self.node.hprInterval( 3, Point3(  0,   0, 0), startHpr=Point3(180,   0, 0))
        self.pandaPace = Sequence(pandaPosInterval1,
                                  pandaHprInterval1,
                                  pandaPosInterval2,
                                  pandaHprInterval2,
                                  name = 'pandaPace')
        self.pandaPace.loop()

class Environment:
    def __init__(self, Node):
        filename = r'models/environment'
        self.node = Node
        self.base = Loader.loadModel(filename)
        self.base.setScale(.25,.25,.25)
        self.base.setPos(-8, 42, 0)
        self.base.reparentTo(self.node)



class MyApp(ShowBase):
    def __init__(self):
        ShowBase.__init__(self)
        
        self.disableMouse()
        self.taskMgr.add(self.spinCameraTask, 'SpinCameraTask')

        self.environ = Environment(self.render)
        
        self.PandaNode = self.render.attachNewNode('ForPandaActor')
        self.pandaActor = PandaActor(Node=self.PandaNode)
        self.pandaActor.pace()
        
    def spinCameraTask(self, task):
        angleDegrees = task.time * 6
        angleRadians = angleDegrees * (math.pi / 180)
        self.camera.setPos(20 * math.sin(angleRadians), -20.0 * math.cos(angleRadians), 3)
        self.camera.setHpr(angleDegrees, 0, 0)
        return Task.cont
    
    


if __name__ == '__main__':
    print 'Basics_02_panda_and_environ_classed.py'
    app = MyApp()
    app.run()

You’re not instantiating the Loader object, but calling the class directly, try changing:

# from
self.base = Loader.loadModel(filename)

# to
self.base = Loader().loadModel(filename)

And you don’t need to sub-class anything to use static models, the approach you’re taking is fairly standard.

Cslos is correct about the cause of the error, I believe, but I disagree on the solution: I’m inclined to suggest just using the extant Loader object, “loader” (note that the initial “l” is in lower-case), like so:

self.base = loader.loadModel(fileName)

If I may, I’d like to comment on a few other issues that I notice:

First, your “PandaActor” class: you’re subclassing Actor, but then making the actual Actor that you use a variable within your class, rather than the instance itself. While this likely functions, I suspect that it’s not acting as you think that it is: right now you could replace “PandaActor(Actor)” in the class definition with “PandaActor()” (that is, inheriting from nothing) and it should function just as well.

Second, be careful with this line, as it may encourage misunderstanding:

self.PandaNode = self.render.attachNewNode('ForPandaActor')

If I have it correct, “PandaNode” is the name of the class used to represent an individual node in the scene-graph; “NodePath”, however, is the class that is more usually use to manipulate nodes–and is the class returned by “attachNewNode”, I believe. See this manual page for a more detailed discussion, I believe.

Third, be careful in using variable names that start with capitals, especially when they share their name with a class–you could find yourself withsome difficult-to-diagnose errors.

Finally, to answer something that you mentioned:

The class in question would probably be PandaNode–but I’m not sure that doing so is a good idea. It’s probably safer to simply store the relevant NodePath or Actor in a class of your own creation (as you’re doing with the “Environment” class), and keep a reference to that yourself.

There’s some more information on such subclassing here, I believe.

So, the suggestion of saying:

self.base = Loader().loadModel(filename)

throws a different error:

self.base = Loader().loadModel(filename)
TypeError: __init__() takes exactly 2 arguments (1 given)

So, Loader() needs some additional object to instantiate it.

If instead I write:

self.base = loader.loadModel(filename)

it works, so thank you.

In regards to Thaumaturge’s other comments:
Your quite right about not needing to subclass Actor.
Apparently PandaNode is a panda3D class, so thank you for bringing this to my attention. Unfortunate pick of variables on my end; and thank you for the reminder about the capitalization, this is something I still need to break the habit of, and would have avoided the potential issue rising later.
Also, thank you for the links.

I’m glad that you managed to get it working, and glad to have been of aid. :slight_smile: