base.openMainWindow() vs pixel2d

I came upon this strange problem:

Since packing many executables is problematic, so I’m trying to make my configuration utility and the main game into one file.

The configuration utility is a fixed-size, undecorated 512x512 window.
The main game can have any resolution, is not fixed sized, has the cursor hidden and can be fullscreen.

To switch between one and the other I’m using base.openMainWindow() - it works, but there’s one small bug.

After cleaning up my “config” class but before initializing my ‘game’ class I want to show a loading screen, because loading the game class takes a while, I do it like this:

config_win_size=ConfigVariableInt('win-size', '640 480') 
wp = WindowProperties.getDefault() 
wp.setFixedSize(False)  
wp.setCursorHidden(True)
wp.setUndecorated(False)  
wp.setTitle("Avolition - v.0.2.1")       
if(config_fulscreen.getValue ()==1): 
    wp.setFullscreen(True)
wp.setSize(config_win_size.getWord(0),config_win_size.getWord(1))

base.openMainWindow(props = wp)

winX = (wp.getXSize()-800)/2
winY = (wp.getYSize()-600)/2
load_screen = DirectFrame(frameSize=(-1024, 0, 0, 1024),
                                    frameColor=(1,1,1,1),
                                    frameTexture='loading.png',
                                    parent=pixel2d)
load_screen.setPos(1024+winX,0,-1024-winY) 

for i in range(2):
    base.graphicsEngine.renderFrame()

I’d expect to have a 800x600 part of ‘loading.png’ centered on the screen, but I get a 512x512 part of ‘loading.png’ stretched to whatever screen resolution I set. At any other point pixel2d works as expected.

Why is this happening?
How can I fix this?

A small test case would be nice.

I’ve made a loader for the carousel sample, drop these two files into the samples\Carousel and it should work (run game_loader.py)

Everything I added to the carousel sample is tagged with #NEW!!!
game_loader.py:

from panda3d.core import loadPrcFileData
loadPrcFileData('','sync-video 1')
from panda3d.core import WindowProperties
wp = WindowProperties.getDefault() 
wp.setFixedSize(True)  
wp.setCursorHidden(False)
wp.setFullscreen(False)
wp.setUndecorated(True)  
wp.setSize(512,512)
WindowProperties.setDefault(wp)
from direct.gui.DirectGui import *
from panda3d.core import *
import direct.directbase.DirectStart
from direct.showbase.DirectObject import DirectObject

class Config(DirectObject):
    def __init__(self):
        base.setBackgroundColor(0, 0, 0)        
        self.run_button=DirectFrame(frameSize=(-220, 0, 0, 32),
                                    frameColor=(1,1,1, 1),
                                    text="CLICK HERE TO START!",
                                    text_scale=20,
                                    text_pos=(-110,13),
                                    text_align=TextNode.ACenter,
                                    state=DGG.NORMAL,
                                    parent=pixel2d)
        self.run_button.setPos(366, 0, -512)        
        self.run_button.bind(DGG.B1PRESS, self.run_game)         
        
    def run_game(self, mouse=None):
        self.run_button.destroy()
        from world import World        
        game=World()        
        
main = Config()    
run()  

world.py:

# Author: Shao Zhang, Phil Saltzman, and Eddie Canaan
# Last Updated: 4/19/2005
#
# This tutorial will demonstrate some uses for intervals in Panda
# to move objects in your panda world.
# Intervals are tools that change a value of something, like position, rotation
# or anything else, linearly, over a set period of time. They can be also be
# combined to work in sequence or in Parallel
# 
# In this lesson, we will simulate a carousel in motion using intervals.
# The carousel will spin using an hprInterval while 4 pandas will represent
# the horses on a traditional carousel. The 4 pandas will rotate with the
# carousel and also move up and down on their poles using a LerpFunc interval.
# Finally there will also be lights on the outer edge of the carousel that
# will turn on and off by switching their texture with intervals in Sequence
# and Parallel

#NEW!!!!
#config
from panda3d.core import loadPrcFileData
from panda3d.core import ConfigVariableInt
from panda3d.core import WindowProperties
loadPrcFileData('','win-size 1024 768') 
config_win_size=ConfigVariableInt('win-size', '640 480') 

wp = WindowProperties.getDefault() 
wp.setFixedSize(False)  
wp.setUndecorated(False)  
wp.setTitle("Load-Screen Test")  
wp.setSize(config_win_size.getWord(0),config_win_size.getWord(1))
# END of NEW!!!!


#import direct.directbase.DirectStart
from panda3d.core import AmbientLight, DirectionalLight, LightAttrib
from panda3d.core import NodePath
from panda3d.core import Vec3, Vec4
from direct.interval.IntervalGlobal import *   #Needed to use Intervals
from direct.gui.DirectGui import *

#Importing math constants and functions
from math import pi, sin

#NEW!!!!
#load the loadscreen
base.openMainWindow(props = wp)
winX = (wp.getXSize()-808)/2
winY = (wp.getYSize()-626)/2
load_screen = DirectFrame(frameSize=(-808, 0, 0, 626), 
                                    frameColor=(1,1,1,1),
                                    frameTexture='screenshots/carousel.jpg',
                                    parent=pixel2d)
load_screen.setPos(808+winX,0,-626-winY) 
#to make the loading screen visible for longer, I'll render some frames
for i in range(60):
    base.graphicsEngine.renderFrame()
# END of NEW!!!!

class World:
  def __init__(self):
    #This creates the on screen title that is in every tutorial
    self.title = OnscreenText(text="Panda3D: Tutorial 2 - Carousel",
                              style=1, fg=(1,1,1,1),
                              pos=(0.87,-0.95), scale = .07)

    base.setBackgroundColor(.6, .6, 1) #Set the background color
    base.disableMouse()                #Allow manual positioning of the camera
    camera.setPosHpr( 0, -8, 2.5, 0, -9, 0 )  #Set the cameras' position
                                              #and orientation

    self.loadModels()                  #Load and position our models
    self.setupLights()                 #Add some basic lighting
    self.startCarousel()               #Create the needed intervals and put the
                                       #carousel into motion
                                       
    #NEW!!!!                                   
    taskMgr.add(self.hideLoadscreen, 'hideLoadscreenTask')
    # END of NEW!!!!
    
  def loadModels(self):
    #Load the carousel base
    self.carousel = loader.loadModel("models/carousel_base")
    self.carousel.reparentTo(render)   #Attach it to render

    #Load the modeled lights that are on the outer rim of the carousel
    #(not Panda lights)
    #There are 2 groups of lights. At any given time, one group will have the
    #"on" texture and the other will have the "off" texture.
    self.lights1 = loader.loadModel("models/carousel_lights")
    self.lights1.reparentTo(self.carousel)
    
    #Load the 2nd set of lights
    self.lights2 = loader.loadModel("models/carousel_lights")
    #We need to rotate the 2nd so it doesn't overlap with the 1st set.
    self.lights2.setH(36)
    self.lights2.reparentTo(self.carousel)

    #Load the textures for the lights. One texture is for the "on" state,
    #the other is for the "off" state.
    self.lightOffTex = loader.loadTexture("models/carousel_lights_off.jpg")
    self.lightOnTex = loader.loadTexture("models/carousel_lights_on.jpg")

    #Create an list (self.pandas) with filled with 4 dummy nodes attached to
    #the carousel.
    #This uses a python concept called "Array Comprehensions." Check the Python
    #manual for more information on how they work
    self.pandas = [self.carousel.attachNewNode("panda"+str(i))
                   for i in range(4)]
    self.models = [loader.loadModel("models/carousel_panda")
                   for i in range(4)]
    self.moves = [0 for i in range(4)]

    for i in range(4):
      #set the position and orientation of the ith panda node we just created
      #The Z value of the position will be the base height of the pandas.
      #The headings are multiplied by i to put each panda in its own position
      #around the carousel
      self.pandas[i].setPosHpr(0, 0, 1.3, i*90, 0, 0)

      #Load the actual panda model, and parent it to its dummy node
      self.models[i].reparentTo(self.pandas[i])
      #Set the distance from the center. This distance is based on the way the
      #carousel was modeled in Maya
      self.models[i].setY(.85)       

    #Load the environment (Sky sphere and ground plane)
    self.env = loader.loadModel("models/env")
    self.env.reparentTo(render)
    self.env.setScale(7)

  #Panda Lighting
  def setupLights(self):
    #Create some lights and add them to the scene. By setting the lights on
    #render they affect the entire scene
    #Check out the lighting tutorial for more information on lights
    ambientLight = AmbientLight("ambientLight")
    ambientLight.setColor(Vec4(.4, .4, .35, 1))
    directionalLight = DirectionalLight("directionalLight")
    directionalLight.setDirection(Vec3(0, 8, -2.5))
    directionalLight.setColor(Vec4(0.9, 0.8, 0.9, 1))
    render.setLight(render.attachNewNode(directionalLight))
    render.setLight(render.attachNewNode(ambientLight))

    #Explicitly set the environment to not be lit
    self.env.setLightOff()

  def startCarousel(self):
    #Here's where we actually create the intervals to move the carousel
    #The first type of interval we use is one created directly from a NodePath
    #This interval tells the NodePath to vary its orientation (hpr) from its
    #current value (0,0,0) to (360,0,0) over 20 seconds. Intervals created from
    #NodePaths also exist for position, scale, color, and shear
  
    self.carouselSpin = self.carousel.hprInterval(20, Vec3(360, 0, 0))
    #Once an interval is created, we need to tell it to actually move.
    #start() will cause an interval to play once. loop() will tell an interval
    #to repeat once it finished. To keep the carousel turning, we use loop()
    self.carouselSpin.loop()

    #The next type of interval we use is called a LerpFunc interval. It is
    #called that becuase it linearly interpolates (aka Lerp) values passed to
    #a function over a given amount of time.

    #In this specific case, horses on a carousel don't move contantly up,
    #suddenly stop, and then contantly move down again. Instead, they start
    #slowly, get fast in the middle, and slow down at the top. This motion is
    #close to a sine wave. This LerpFunc calls the function oscilatePanda
    #(which we will create below), which changes the hieght of the panda based
    #on the sin of the value passed in. In this way we achieve non-linear
    #motion by linearly changing the input to a function
    for i in range(4):
      self.moves[i] = LerpFunc(
                      self.oscilatePanda,  #function to call
                      duration = 3,  #3 second duration
                      fromData = 0,  #starting value (in radians)
                      toData = 2*pi, #ending value (2pi radians = 360 degrees)
                                     #Additional information to pass to
                                     #self.oscialtePanda
                      extraArgs=[self.models[i], pi*(i%2)]
                      )
      #again, we want these to play continuously so we start them with loop()
      self.moves[i].loop()

    #Finally, we combine Sequence, Parallel, Func, and Wait intervals,
    #to schedule texture swapping on the lights to simulate the lights turning
    #on and off.
    #Sequence intervals play other intervals in a sequence. In other words,
    #it waits for the current interval to finish before playing the next
    #one.
    #Parallel intervals play a group of intervals at the same time
    #Wait intervals simply do nothing for a given amount of time
    #Func intervals simply make a single function call. This is helpful because
    #it allows us to schedule functions to be called in a larger sequence. They
    #take virtually no time so they don't cause a Sequence to wait.

    self.lightBlink = Sequence(
      #For the first step in our sequence we will set the on texture on one
      #light and set the off texture on the other light at the same time
      Parallel(
        Func(self.lights1.setTexture, self.lightOnTex, 1),
        Func(self.lights2.setTexture, self.lightOffTex, 1)), 
      Wait(1), #Then we will wait 1 second
      #Then we will switch the textures at the same time
      Parallel(
        Func(self.lights1.setTexture, self.lightOffTex, 1),
        Func(self.lights2.setTexture, self.lightOnTex, 1)), 
      Wait(1)  #Then we will wait another second
    )

    self.lightBlink.loop() #Loop this sequence continuously


  def oscilatePanda(self, rad, panda, offset):
    #This is the oscillation function mentioned earlier. It takes in a degree
    #value, a NodePath to set the height on, and an offset. The offset is there
    #so that the different pandas can move opposite to each other.
    #The .2 is the amplitude, so the height of the panda will vary from -.2 to
    #.2
    panda.setZ(sin(rad + offset) * .2)
    
  #NEW!!!!  
  def hideLoadscreen(self, task):
    load_screen.hide()        
    return task.done
  # END of NEW!!!!  
#w = World()
#run()