Programaticaly creating function graphs

Return to Code Snippets

Programaticaly creating function graphs

Postby zzarko » Thu Jan 22, 2009 11:49 am

Here is some code I was working on past few days. It should be a visualisation of numerical calculations (i.e. bridge stability) (curently, there is some repeated random data). I followed the Panda manual about generating objects programaticaly, and used dinoint's code to embed Panda window into PyQt. Because of some problem with initialisation (commented self.ResetPos() call), you must press zero after starting to set the scene in default position (I know I colud do it with a timer, but don't like that solution). After that, you can manipulate graph with +,-,*,/,5,zero and enter. Quit with escape. Any comments and/or improvements are welcome.

Code: Select all
# -*- coding: UTF-8 -*-

#fullscreen
from pandac.PandaModules import loadPrcFileData
#loadPrcFileData("", "fullscreen 1")
loadPrcFileData("", "window-title Bridge")
loadPrcFileData("", "window-type none")

import direct.directbase.DirectStart
#from direct.showbase.DirectObject import DirectObject
from pandac.PandaModules import WindowProperties

from PyQt4.QtCore import *
from PyQt4.QtGui import *

P3D_WIN_WIDTH = 800
P3D_WIN_HEIGHT = 500

import direct.directbase.DirectStart
from direct.showbase.DirectObject import *
from pandac.PandaModules import *
from direct.interval.IntervalGlobal import *

from direct.task import Task
from direct.interval.IntervalGlobal import *
from math import *
import random
import codecs
import sys


#----------------------------------------------------------------------
# definitions for dynamic object creation
#----------------------------------------------------------------------

graphVertexFormat = GeomVertexFormat.getV3n3c4t2()

#put here any font you like
labelFont = loader.loadFont('arial.ttf')
labelFont.setPointSize(10)

#----------------------------------------------------------------------
# PyQt GUI (taken from dinoint's 'post)
#----------------------------------------------------------------------

class QTTest(QDialog):
   def __init__(self, pandaCallback, parent=None):
      super(QDialog, self).__init__(parent)
      self.setWindowTitle("Test")
      self.setGeometry(30,30,800,600)
      
      self.pandaContainer = QWidget(self)
      self.pandaContainer.setGeometry(0,0,P3D_WIN_WIDTH,P3D_WIN_HEIGHT)

      self.lineedit = QLineEdit("Proba ugnjezdenog prikaza...")
      
      layout = QVBoxLayout()
      layout.addWidget(self.pandaContainer)
      layout.addWidget(self.lineedit)
      
      self.setLayout(layout)
      
      # this basically creates an idle task
      timer =  QTimer(self)
      self.connect( timer, SIGNAL("timeout()"), pandaCallback )
      timer.start(0)

#----------------------------------------------------------------------
# graph classes
#----------------------------------------------------------------------

#3D point
class GraphCoordinate3D(object):
   def __init__(self,cx=0,cy=0,cz=0):
      self.x = cx
      self.y = cy
      self.z = cz

#2D point
class GraphCoordinate2D(object):
   def __init__(self,cx=0,cy=0):
      self.x = cx
      self.y = cy

#one subgraph
class SubGraph(object):
   def __init__(self, gData, stripNo, parentNode, colGraph = VBase4(0,1,0,1), colAxes = VBase4(1,0,0,1), colStrip = VBase4(0,0,0,1), scale = 1, thickness = 2):
      self.axesPoints = []      #axes (GraphCoordinate2D)
      self.rotation = 0         #graph rotation (not needed?)

      st = gData.strips[stripNo]   #get strip data
      self.stri = st

      #graph dimensions
      xmin = st.data[0].x
      xmax = xmin
      ymin = st.data[0].y
      ymax = ymin
      for i in st.data:
         if xmin > i.x : xmin = i.x
         if xmax < i.x : xmax = i.x
         if ymin > i.y : ymin = i.y
         if ymax < i.y : ymax = i.y
      label1Y = ymin
      label2Y = ymax
      if ymin > 0 : ymin = 0
      if ymax < 0 : ymax = 1

      arH = 0.2            #arrow height
      arW = 0.1            #arrow width
      yscale = 1.4         #axes scale (relative to graph size)
      
      ymax = ymax * yscale
      ymin = ymin * yscale
      
      #axes definition
      self.axesPoints.append(GraphCoordinate2D(0,0))            #[0](0,0) for Y
      self.axesPoints.append(GraphCoordinate2D(0,ymax))         #[1]end of Y
      self.axesPoints.append(GraphCoordinate2D(-arW,ymax-arH))   #[2]arrow 1
      self.axesPoints.append(GraphCoordinate2D(arW,ymax-arH))      #[3]arrow 2
      self.axesPoints.append(GraphCoordinate2D(0,0))            #[4](0,0) for X (so it can be different color)
      self.axesPoints.append(GraphCoordinate2D(xmax,0))         #[5]end of X
      self.axesPoints.append(GraphCoordinate2D(0,ymin))         #[6](eventually needed) more Y
      self.axesPoints.append(GraphCoordinate2D(xmin,0))         #[7](eventually needed) more X
      
      self.vertexData = GeomVertexData('GraphData', graphVertexFormat, Geom.UHDynamic)
      
      graphWriterVertex = GeomVertexWriter(self.vertexData, 'vertex')
      graphWriterNormal = GeomVertexWriter(self.vertexData, 'normal')
      graphWriterColor = GeomVertexWriter(self.vertexData, 'color')
      graphWriterTexcoord = GeomVertexWriter(self.vertexData, 'texcoord')

      graphPrimitive = GeomLines(Geom.UHStatic)
      graphPrimitiveStrip = GeomLines(Geom.UHStatic)

      #axes points
      firstPoint = 0
      for i in self.axesPoints:
         graphWriterVertex.addData3f(i.x*scale,0,i.y*scale)   #XYZ
         graphWriterNormal.addData3f(0,0,1)   #XYZ
         if firstPoint in [4,5,7]:
            graphWriterColor.addData4f(colStrip)   #RGBA
         else:
            graphWriterColor.addData4f(colAxes)   #RGBA
         graphWriterTexcoord.addData2f(0,0)   #UV
         firstPoint = firstPoint + 1
   
      #graph points
      lastPoint = firstPoint
      for i in st.data:
         graphWriterVertex.addData3f(i.x*scale,0,i.y*scale)   #XYZ
         graphWriterNormal.addData3f(0,0,1)   #XYZ
         graphWriterColor.addData4f(colGraph)   #RGBA
         graphWriterTexcoord.addData2f(0,0)   #UV
         lastPoint = lastPoint + 1

      #axes lines
      graphPrimitive.addVertices(0,1)
      graphPrimitive.closePrimitive()
      graphPrimitive.addVertices(1,2)
      graphPrimitive.closePrimitive()
      graphPrimitive.addVertices(1,3)
      graphPrimitive.closePrimitive()
      if self.axesPoints[6].x <> 0 or self.axesPoints[6].y <> 0:
         graphPrimitive.addVertices(0,6)
         graphPrimitive.closePrimitive()
      graphPrimitiveStrip.addVertices(4,5)
      graphPrimitiveStrip.closePrimitive()
      if self.axesPoints[7].x <> 0 or self.axesPoints[7].y <> 0:
         graphPrimitiveStrip.addVertices(4,7)
         graphPrimitiveStrip.closePrimitive()
      
      #graph lines
      for i in range(firstPoint,lastPoint-1):
         graphPrimitive.addVertices(i,i+1)
         graphPrimitive.closePrimitive()

      #node generation
      graphGeom = Geom(self.vertexData)
      graphGeom.addPrimitive(graphPrimitive)
      self.node = GeomNode('GraphNode')
      self.node.addGeom(graphGeom)
      self.nodePathAll = parentNode.attachNewNode("Dummy")
      self.nodePathGraphLab = self.nodePathAll.attachNewNode("Dummy")
      self.nodePathGraph = self.nodePathGraphLab.attachNewNode(self.node)
      self.nodePathGraph.setRenderModeThickness(thickness)
      self.nodePathGraphLab.setY(-0.05)
      graphGeomStrip = Geom(self.vertexData)
      graphGeomStrip.addPrimitive(graphPrimitiveStrip)
      self.nodeStrip = GeomNode('GraphNode')
      self.nodeStrip.addGeom(graphGeomStrip)
      self.nodePathStrip = self.nodePathAll.attachNewNode(self.nodeStrip)
      self.nodePathStrip.setRenderModeThickness(thickness*2)
      
      #labels
      self.label1 = TextNode('label1')
      self.label1.setText(str(round(label1Y,2)))
      self.label1.setFont(labelFont)
      self.label1NodePath = self.nodePathGraphLab.attachNewNode(self.label1)
      self.label1NodePath.setTwoSided(True)
      self.label1NodePath.setColor(colGraph)
      self.label1NodePath.setPos(-0.5,0,label1Y)
      self.label1NodePath.setScale(0.2)
      self.label2 = TextNode('label2')
      self.label2.setText(str(round(label2Y,2)))
      self.label2.setFont(labelFont)
      self.label2NodePath = self.nodePathGraphLab.attachNewNode(self.label2)
      self.label2NodePath.setTwoSided(True)
      self.label2NodePath.setColor(colGraph)
      self.label2NodePath.setPos(-0.5,0,label2Y)
      self.label2NodePath.setScale(0.2)

      #rotating and positioning of graph
      a = gData.lines[st.p2].x - gData.lines[st.p1].x
      b = gData.lines[st.p2].y - gData.lines[st.p1].y
      angle = degrees(-atan2(b,a))
      self.nodePathAll.setR(angle)
      self.nodePathAll.setPos(gData.lines[st.p1].x-4,0,gData.lines[st.p1].y)
      
#data for one strip
class StripData(object):
   def __init__(self):
      self.p1 = 0         #prva linija
      self.p2 = 0         #druga linija
      self.data = []      #podaci za pod-grafik (graphCoordinate2D)

#data for all subgraphs
class GraphData(object):
   def __init__(self):
      self.lines = []         #koordinate linija u preseku (GraphCoordinate2D?)
      self.strips = []      #podaci o trakama (StripData)

      self.graphs = []      #podaci o pod-graficima (SubGraph)
      self.scale = 1         #faktor skaliranja za ceo grafik
      
      self.node = 0         #nodePath za ceo grafik
      self.nodePath = 0      #nodePath za ceo grafik

#----------------------------------------------------------------------
# main program
#----------------------------------------------------------------------

class World(DirectObject):
   def __init__(self):
      
      base.disableMouse()

      #test data
      self.data = GraphData()
      
      #dummy node
      self.data.nodePath = render.attachNewNode("Graph")

      #self.fnt.setSpaceAdvance(1)

        #below is some just-for-testing data generation
       
      #lines definition (strip is defined by two lines)
      self.data.lines.append(GraphCoordinate2D(0,0))   #0
      self.data.lines.append(GraphCoordinate2D(2,0))   #1
      self.data.lines.append(GraphCoordinate2D(6,0))   #2
      self.data.lines.append(GraphCoordinate2D(8,0))   #3
      self.data.lines.append(GraphCoordinate2D(3,-2))   #4
      self.data.lines.append(GraphCoordinate2D(5,-2))   #5
      
      #strip definition
      st = StripData()
      st.p1 = 0
      st.p2 = 1
      a = self.data.lines[st.p2].x - self.data.lines[st.p1].x
      b = self.data.lines[st.p2].y - self.data.lines[st.p1].y
      l = sqrt(a*a + b*b)
      st.data.append(GraphCoordinate2D(0.0*l,1))
      st.data.append(GraphCoordinate2D(0.3*l,0.5))
      st.data.append(GraphCoordinate2D(0.7*l,0.3))
      st.data.append(GraphCoordinate2D(1.0*l,0.7))
      self.data.strips.append(st)
      print len(self.data.strips), self.data.strips[0].p1, self.data.strips[0].p2

      st = StripData()
      st.p1 = 1
      st.p2 = 2
      a = self.data.lines[st.p2].x - self.data.lines[st.p1].x
      b = self.data.lines[st.p2].y - self.data.lines[st.p1].y
      l = sqrt(a*a + b*b)
      st.data.append(GraphCoordinate2D(0.0*l,1))
      st.data.append(GraphCoordinate2D(0.3*l,0.5))
      st.data.append(GraphCoordinate2D(0.7*l,0.3))
      st.data.append(GraphCoordinate2D(1.0*l,0.7))
      self.data.strips.append(st)
       
      st = StripData()
      st.p1 = 2
      st.p2 = 3
      a = self.data.lines[st.p2].x - self.data.lines[st.p1].x
      b = self.data.lines[st.p2].y - self.data.lines[st.p1].y
      l = sqrt(a*a + b*b)
      st.data.append(GraphCoordinate2D(0.0*l,1))
      st.data.append(GraphCoordinate2D(0.3*l,0.5))
      st.data.append(GraphCoordinate2D(0.7*l,0.3))
      st.data.append(GraphCoordinate2D(1.0*l,0.7))
      self.data.strips.append(st)
       
      st = StripData()
      st.p1 = 1
      st.p2 = 4
      a = self.data.lines[st.p2].x - self.data.lines[st.p1].x
      b = self.data.lines[st.p2].y - self.data.lines[st.p1].y
      l = sqrt(a*a + b*b)
      st.data.append(GraphCoordinate2D(0.0*l,1))
      st.data.append(GraphCoordinate2D(0.3*l,0.5))
      st.data.append(GraphCoordinate2D(0.7*l,0.3))
      st.data.append(GraphCoordinate2D(1.0*l,0.7))
      self.data.strips.append(st)
       
      st = StripData()
      st.p1 = 4
      st.p2 = 5
      a = self.data.lines[st.p2].x - self.data.lines[st.p1].x
      b = self.data.lines[st.p2].y - self.data.lines[st.p1].y
      l = sqrt(a*a + b*b)
      st.data.append(GraphCoordinate2D(0.0*l,1))
      st.data.append(GraphCoordinate2D(0.3*l,0.5))
      st.data.append(GraphCoordinate2D(0.7*l,0.3))
      st.data.append(GraphCoordinate2D(1.0*l,0.7))
      self.data.strips.append(st)
       
      st = StripData()
      st.p1 = 5
      st.p2 = 2
      a = self.data.lines[st.p2].x - self.data.lines[st.p1].x
      b = self.data.lines[st.p2].y - self.data.lines[st.p1].y
      l = sqrt(a*a + b*b)
      st.data.append(GraphCoordinate2D(0.0*l,1))
      st.data.append(GraphCoordinate2D(0.3*l,0.5))
      st.data.append(GraphCoordinate2D(0.7*l,0.3))
      st.data.append(GraphCoordinate2D(1.0*l,0.7))
      self.data.strips.append(st)
   
      colors = [VBase4(0,1,0,1),VBase4(0,0,1,1),VBase4(1,1,0,1),VBase4(1,0,1,1),VBase4(0,1,1,1),VBase4(1,1,1,1)]
      col = 0
      for i in range(len(self.data.strips)):
         self.data.graphs.append(SubGraph(self.data, i, self.data.nodePath, scale = self.data.scale, colGraph = colors[col], colAxes = colors[col]*0.6))
         col = col + 1
      #self.data.nodePath.setPos(-4,0,0)

      #events
      self.accept('escape', sys.exit)
      self.accept('+', self.ZoomIn)
      self.accept('+-repeat', self.ZoomIn)
      self.accept('-', self.ZoomOut)
      self.accept('--repeat', self.ZoomOut)
      self.accept('*', self.RotateCW)
      self.accept('*-repeat', self.RotateCW)
      self.accept('/', self.RotateCCW)
      self.accept('/-repeat', self.RotateCCW)
      self.accept('enter', self.Cycle)
      self.accept('0', self.ResetPos)
      self.accept('5', self.RotateZ)
      
      self.seq = 0
      
      #this doesn't work after embedding panda into pyqt, so it's commented
      #self.ResetPos()
      
   def ResetPos(self):
      if self.seq <> 0 and self.seq.isPlaying(): return
      base.disableMouse()
      base.camera.setPos(0,-15,0)
      base.camera.setHpr(0,0,0)
      self.cycle = -2
      self.Cycle()

   def ZoomIn(self):
      if self.seq <> 0 and self.seq.isPlaying(): return
      posLerp = LerpPosInterval(base.camera,0.3,VBase3(0,base.camera.getY()+1,0),blendType='easeInOut')
      self.seq = Sequence(posLerp)
      self.seq.start()
      
   def ZoomOut(self):
      if self.seq <> 0 and self.seq.isPlaying(): return
      posLerp = LerpPosInterval(base.camera,0.3,VBase3(0,base.camera.getY()-1,0),blendType='easeInOut')
      self.seq = Sequence(posLerp)
      self.seq.start()
      
   def RotateCW(self):
      if self.seq <> 0 and self.seq.isPlaying(): return
      hprLerp = LerpHprInterval(base.camera,0.6,VBase3(0,0,base.camera.getR()-45),blendType='easeInOut')
      self.seq = Sequence(hprLerp)
      self.seq.start()
      
   def RotateZ(self):
      if self.seq <> 0 and self.seq.isPlaying(): return
      hprLerp = LerpHprInterval(self.data.nodePath,2,VBase3(self.data.nodePath.getH()+360,0,0),blendType='easeInOut')
      self.seq = Sequence(hprLerp)
      self.seq.start()
      
   def RotateCCW(self):
      if self.seq <> 0 and self.seq.isPlaying(): return
      hprLerp = LerpHprInterval(base.camera,0.6,VBase3(0,0,base.camera.getR()+45),blendType='easeInOut')
      self.seq = Sequence(hprLerp)
      self.seq.start()

   def Cycle(self):
      self.cycle = self.cycle + 1
      if self.cycle == len(self.data.graphs): self.cycle = -1
      cnt = 0
      for i in self.data.graphs:
         if self.cycle == -1 or self.cycle == cnt:
            i.nodePathGraphLab.show()
         else:
            i.nodePathGraphLab.hide()
         cnt = cnt + 1
         
   def step(self):
      taskMgr.step()
   
   def bindToWindow(self, windowHandle):
      wp = WindowProperties().getDefault()
      wp.setOrigin(0,0)
      wp.setSize(P3D_WIN_WIDTH, P3D_WIN_HEIGHT)
      wp.setParentWindow(windowHandle)
      base.openDefaultWindow(props=wp)
      self.wp = wp

if __name__ == '__main__':
   world = World()

   app = QApplication(sys.argv)
   form = QTTest(world.step)
   x = int(form.winId())
   world.bindToWindow(int(form.winId()))
   
   form.show()
   app.exec_()

#w = World()

#run()
Last edited by zzarko on Thu Jan 22, 2009 3:27 pm, edited 1 time in total.
zzarko
 
Posts: 14
Joined: Mon Jun 30, 2008 11:25 am

Postby rdb » Thu Jan 22, 2009 12:44 pm

Sure looks interesting. Could you maybe post it using UNIX newlines instead of windows newlines?

Also, I'm getting this error with 1.5.4:
Code: Select all
    labelFont.setPointSize(10)
AttributeError: 'libpanda.StaticTextFont' object has no attribute 'setPointSize'


On 1.6.0, I have no problems, however. You might wanna consider changing this so it works with 1.5.4, too.
I rarely respond to PMs
rdb
 
Posts: 8565
Joined: Mon Dec 04, 2006 5:58 am
Location: Netherlands

Postby drwr » Thu Jan 22, 2009 1:57 pm

Try:
Code: Select all
labelFont = loader.loadFont('arial.ttf', pointSize = 10)

David
drwr
 
Posts: 11253
Joined: Fri Feb 13, 2004 12:42 pm
Location: Glendale, CA

Postby zzarko » Thu Jan 22, 2009 3:31 pm

pro-rsoft wrote:Sure looks interesting. Could you maybe post it using UNIX newlines instead of windows newlines?

I didn't check that, sorry, it's OK now.
Also, I'm getting this error with 1.5.4:
Code: Select all
    labelFont.setPointSize(10)
AttributeError: 'libpanda.StaticTextFont' object has no attribute 'setPointSize'


On 1.6.0, I have no problems, however. You might wanna consider changing this so it works with 1.5.4, too.


I was doing all this with Panda3D 1.5.4 both on Windows and Ubuntu 8.04 (and I had that font at hand at the moment). I needed good looking text even with large zoom. Just put some other font in labelFont and you'll be OK... (i guess)
zzarko
 
Posts: 14
Joined: Mon Jun 30, 2008 11:25 am

Postby drwr » Thu Jan 22, 2009 4:28 pm

You can explicitly set the point size on "dynamic" fonts, which are .ttf files and what not. You can't set it on "static" fonts, which are .egg files or .bam files. So if you call:
Code: Select all
font.setPointSize(10)

it might fail, according to what kind of font file you've got. This is why it's not a good idea to make this call directly; instead, use the pointSize = xx parameter to loader.loadFont(), which will properly ignore the setting if it can't be set.

Of course, the default point size is 10 anyway, so this is not a particularly useful call. All it's doing is making your code less portable.

David
drwr
 
Posts: 11253
Joined: Fri Feb 13, 2004 12:42 pm
Location: Glendale, CA


Return to Code Snippets

Who is online

Users browsing this forum: No registered users and 0 guests