Loading a model by creating a seperate py file

Hello, I am creating a .py file which takes the name of model as input and returns a variable with the model stored in it.
The code I have written is as follows:

from direct.showbase.ShowBase import ShowBase

class loadMyModel(ShowBase):
    def __init__(self, model):
        super().__init__()

        self.dire = r'Assets\Models\hi_res_tex\a_'
        self.modelName = model
        self.ComDir = self.dire + self.modelname + ".jpg"

        self.ModelLoad = self.loader.loadModel("Assets/Models/planet_sphere")
        self.ModelLoad = self.loader.loadTexture(self.ComDir)

and from the other file i am calling it as:

from direct.showbase.ShowBase import ShowBase
import ModelLoader as ml

class trial(ShowBase):
    def __init__(self):
        super().__init__()

        self.Earth = ml.loadMyModel("earth_daymap")
        self.Earth.reparentTo(render)

trialrun = trial()
trial.run()

And I am getting the following error:

    self.ComDir = self.dire + self.model + ".jpg"
    AttributeError: 'loadMyModel' object has no attribute 'model'
    raise Exception("Attempt to spawn multiple ShowBase instances!")
Exception: Attempt to spawn multiple ShowBase instances!


How do I correct it?
Is there a better way?

Well, why does your model-loader inherit from ShowBase? What is the purpose behind that?

That said, I see another potential problem coming up: you’re using the __init__ method–the constructor–of your “loadMyModel” class for your model-loading, and based on the rest of your code, you intend to use its return-value to access the loaded model.

However, __init__ methods don’t return anything (or aren’t supposed to; I forget whether it’s possible or not). What you’d actually get is an instance of the class “loadMyModel”, rather than the model that was loaded.

Hmm… But if I may ask, why is “loadMyModel” a class? It does only one thing, after all–why not just make it a single method? Do you intend to add further functionality to it at some point, perhaps?

for the loader.loadModel(I am getting an error if i run without inheriting ShowBase)

yea you are right!! we can use the variable in the same class but not outside it!! i will correct it.

i was intending to use the same class to load the model and the actor, so i thought i might use a class for that.

What error are you getting? You shouldn’t need a given class to inherit from ShowBase in order to access loader, I believe.

[edit]
Looking at your code, it might be that your other error is coming from the fact that you have “self.” in front of loader. That does make sense when called from within a class that inherits from ShowBase (because “self” would generally refer to the current class, and ShowBase provides the “loader” object). But it then won’t work when called from a class that doesn’t inherit from ShowBase.

However! There are other ways to gain access to something: You could pass in a reference to your ShowBase-derived class, or create a common-script that stores a reference to your ShowBase-derived class and import that, for two options.
[/edit]

Would a method not do the same? Or even just a pair of methods, if the internals are that different?

AttributeError: 'loadMyModel' object has no attribute 'loader'

it would do the same, i will try

Indeed, I think that this goes back to what I described in my edit above: “loader” is a variable contained within the ShowBase class. However, there are I believe ways of accessing your primary class (“trial”), and through that gaining access to loader!

Could you please link to any resources where i could find the appropriate solution for it?

Let me instead simply show you two ways in which it can be done:


Approach One: A Common File


Here, we create a “common” script-file that holds a reference to our game-class, and which can be imported to provide access to that class:

The common-file

class Common():
    # Being placed outside of any method, but inside 
    # the class-definition, this variable belongs to the class,
    # and not to a specific instance.
    game = None

Some arbitrary Python-file, such as your model-loader

# Import the Common class, so that we have access to its contents
from Common import Common

# Note that this class doesn't inherit from ShowBase!
class SomeOtherClass()
    def __init__(self):
        # Load a model.
        # To do this, we access the "game"-class as stored in
        # the "Common" class, and from there access the
        # "loader" held by that "game"-class
        self.modelObject = Common.game.loader.loadModel("someModelFile")

The main “game” class

from direct.showbase.ShowBase import ShowBase

# Import the "Common" class
from Common import Common

from SomeOtherClass import SomeOtherClass

class MyGame(ShowBase):
    def __init__(self):
        ShowBase.__init__(self)
        
        # Assign this object to the variable in the Common-class
        Common.game = self

        # Use "SomeOtherClass"
        someObject = SomeOtherClass("aModelFile")

Approach Two: Passing Parameters


Here we simply pass a reference to our “game” class into the other class, thus giving us access to that class.

Some arbitrary Python-file, such as your model-loader


# Note that this class doesn't inherit from ShowBase!
class SomeOtherClass()
    def __init__(self, modelFile, gameReference):
        # Load a model.
        # To do this, we access "loader" from the "gameReference" object,
        # which is presumed to hold a reference to an object of class "MyGame"
        self.modelObject = gameReference.loader.loadModel(modelFile)

The main “game” class

from direct.showbase.ShowBase import ShowBase

from SomeOtherClass import SomeOtherClass

class MyGame(ShowBase):
    def __init__(self):
        ShowBase.__init__(self)

        # Use "SomeOtherClass"
        # Here we pass in not only the model-file, but
        # this "MyGame"-object itself!
        someObject = SomeOtherClass("aModelFile", self)

Thoughts


The “Common”-file approach is arguably a little more complicated. However, conversely, the “parameter” approach can become a little clunky as you start passing parameters here there and everywhere.

Personally, I recommend the “Common”-file approach: it seems to work fairly well, and to do so elegantly and conveniently, I feel.

The problem is that the use of the loader from ShowBase itself is fundamentally wrong. Also note that the logic that can be executed many times in the __init __ area is also fundamentally wrong.

Your new bootloader might look like this.

file: ModelLoader.py

from panda3d.core import Loader, NodePath

class loadMyModel():
    def __init__(self):
        self.loader = Loader.get_global_ptr()

    def load(self, model):
        ob = self.loader.load_sync(model)

        return NodePath(ob)

file: main.py

from direct.showbase.ShowBase import ShowBase
from ModelLoader import loadMyModel

class MyApp(ShowBase):

    def __init__(self):
        ShowBase.__init__(self)

        load_my_model = loadMyModel()

        self.panda = load_my_model.load("panda")
        self.panda.setPos(0, 5, 0)
        self.panda.reparentTo(render)

        self.box = load_my_model.load("box")
        self.box.setPos(0, -5, 0)
        self.box.reparentTo(render)

app = MyApp()
app.run()

I don’t think that there’s anything inherently wrong with using the “loader” object provided by ShowBase.

You are right that there’s an issue with placing such logic in an __init__ method (as I pointed out above). It can be done, but it’s iffy at least.

(I suggested a method rather than a class, myself.)

The whole point is that an object how ShowBase should be written by you for your project. But we are used to using a built-in solution, which is most likely intended to demonstrate the engine and is in no way a universal template for your game.

What makes you say that it’s intended to demonstrate the engine, and not to be used as-is?

And even if it is so… I see no reason to not use it. It’s a useful tool; why not employ it?

I am still getting an error as:

self.Earth = loadMyModel("earth_daymap")
TypeError: __init__() takes 1 positional argument but 2 were given

Which version of “loadMyModel” are you using? The error suggests that you’re using one of the versions that implements it as a class–but that you’re not using one of the versions that takes a model-name parameter.

Probably because it instills this style. Have a shared file, or transfer it self. If these and other methods are separated from the “ShowBase” class into separate classes. Then this problem would not exist. This whole problem is related to the integrity of this class.

I’m not sure that I agree. It seems to me that the problem here is a misunderstanding of certain elements of Python programming, not the presence of ShowBase.

Specifically, in providing “loader”, ShowBase doesn’t to anything that some other class might not do. As a result, it seems likely enough to me that a similar issue would come up eventually.

The updated version

class loadMyModel():
    def __init__(self):
        self.dire = r'Assets\Models\hi_res_tex\a_'
        self.modelName = model
        self.ComDir = self.dire + self.modelName + ".jpg"

        self.modelObject = Common.game.loader.loadModel("Assets/Models/planet_sphere")
        self.ModelLoad = Common.game.loader.loadTexture(self.ComDir)

Note that your __init__ method there doesn’t take a parameter other than the default “self” parameter. Thus, when you pass in a parameter while constructing the “loadMyModelObject” to create “self.Earth”, Python doesn’t know what to do with it.

Also, let me note again, if I may: In the following line of code:
self.Earth = loadMyModel("earth_daymap")
Presuming that it were working, it would result in “self.Earth” containing an object of type “loadMyModel”, not a NodePath.

(Of course, that object would contain the model in the variable “modelObject”, and thus the model could be accessed from there.)

@iamlrk

The most comfortable option if you hold on to the ‘ShowBase’ class.

file: ModelLoader.py

class loadMyModel():
    def __init__(self):
        pass

    def load(self, base, model):
        ob = base.loader.loadModel(model)
        return ob

file: main.py

from direct.showbase.ShowBase import ShowBase
from ModelLoader import loadMyModel

class MyApp(ShowBase):

    def __init__(self):
        ShowBase.__init__(self)

        load_my_model = loadMyModel()

        self.panda = load_my_model.load(base, "panda")
        self.panda.setPos(0, 5, 0)
        self.panda.reparentTo(render)

        self.box = load_my_model.load(base, "box")
        self.box.setPos(0, -5, 0)
        self.box.reparentTo(render)

app = MyApp()
app.run()
1 Like

earth_daymap is the texture i am applying on a common model, here is the complete code I have written

from direct.showbase.ShowBase import ShowBase

from common import Common

class loadMyModel():
    def __init__(self):
        self.dire = r'Assets\Models\hi_res_tex\a_'
        self.ComDir = self.dire + "earth_daymap" + ".jpg"

        self.modelObject = Common.game.loader.loadModel("Assets/Models/planet_sphere")
        self.modelObject_tex = Common.game.loader.loadTexture(self.ComDir)
        self.modelObject.setTexture(self.modelObject_tex,1)

I tried it normally in a single file, it worked.

I tried the same code with the panda model, it is still showing the same error