[SOLVED] How to override a texture supplied by a bam file

Hi,

I have a model in .bam format that I created in Blender. I assigned it UV coordinates and a texture using Blender. It looks fine in Panda3D, until I try switching out the texture for another image with a different color scheme. With the following code, the new texture is multiplied by the old one, even though it should completely replace the old texture:

buoyModel = loader.loadModel('buoy.bam')
buoyModel.reparentTo(render)
tex = loader.loadTexture('buoy_yellow.png')
ts = TextureStage('ts')
ts.setMode(TextureStage.MReplace)
buoyModel.setTexture(ts, tex, 0)

Anyone know what I’m doing wrong? Thanks!

I’ve found that I can replace the texture by modifying the RenderState of the model’s Geom:

buoyModel = loader.loadModel('buoy.egg')
buoyModel.reparentTo(render)
tex = loader.loadTexture('buoy_green.png')
texAttrib = TextureAttrib.make(tex)
geomNode = buoyModel.find('**/Sphere').node()
newRenderState = geomNode.getGeomState(0).addAttrib(texAttrib, 1)
geomNode.setGeomState(0, newRenderState)

As expected, this completely replaces the original texture. After I do this, however, calling buoyModel.setTexture(anotherTex, 2) does nothing, even if I set the Geom’s TextureAttrib to a blank one (TextureAttrib.make() or TextureAttrib.makeDefault()). Is this a bug? I’d like to be able to change the texture without digging in to the Geoms.

In order to completely replace an existing texture on a model, you have to have a pointer to the TextureStage that was used to apply the texture to the model, and use that same TextureStage.

Since your example code creates a new TextureStage, it will of course add to the texture stack, rather than replacing the existing texture. Think of each TextureStage as a slot to hold a texture. If you want to replace a particular texture, you have to store it in the same slot. Each time you apply a new TextureStage, you are stacking a new texture slot into the box.

Normally, if your model doesn’t apply any custom-named UV’s or any other fancy texture properties, then the egg loader will apply its textures to the default TextureStage, which is why you can usually override a texture on a simple model with simply model.setTexture(newTex, 1), which also uses the default TextureStage.

Since your model uses custom-named UV’s, it will have a custom TextureStage implicitly created to apply this texture. So you can find that TextureStage in the model and reuse it with something like:

ts = model.findTextureStage('*')
model.setTexture(ts, tex, 1)

Note that the above also works for the case that the model uses the default TextureStage.

David

Hey David, thanks for help. I didn’t realize that my exported model had a special TextureStage.

For anyone else having this problem, you might find the following code handy. It removes textures from every node in a model, so setTexture(tex) will work.

from panda3d.core import GeomNode, TextureAttrib

def stripTextures(model):
    ''' Remove the textures from every node at and under the given NodePath. '''
    node = model.node()
    if isinstance(node, GeomNode):
        for i in xrange(node.getNumGeoms()):
            renderState = node.getGeomState(i)
            if renderState.hasAttrib(TextureAttrib):
                newRenderState = renderState.removeAttrib(TextureAttrib)
                node.setGeomState(i, newRenderState)
    for child in model.getChildren():
        stripTextures(child)

You can also simply do:

model.setTextureOff(1)
model.setTexture(myTex, 1)

The first line disables all textures at this node and below; the second line sets only myTex. The same override parameter, 1 in this case, must be supplied to both functions; the override value must be higher than 0 (or whatever is already in the model) in order to make your new texture operation supercede the textures already on the model.

David

Hmm, when I try using

model.setTextureOff(1) 
model.setTexture(myTex, 1)

I just get an all-white model. It seems like the second call doesn’t take.

Try this:

from direct.directbase.DirectStart import *

smiley = loader.loadModel('smiley.egg')
smiley.reparentTo(render)

grid = loader.loadTexture('maps/color-grid.rgb')
smiley.setTextureOff(1)
smiley.setTexture(grid, 1)

run()

Do you see a white sphere, or one with a grid applied to it? I see a grid.

David

I see a grid, and this works for my model, too. I realized that I’m actually turning the texture off on the model, and then setting the new texture on a nodepath a couple levels above the model. Of course, the “off” setting lower in the tree was overriding the texture I was setting above it.

Thanks for the help!