Ah. I see. Here’s the whole program. Everything worked with Tkinter user inputs outside the main class. I thought the other parts should be fine, but perhaps not.
# Dependancies for math
from math import pi, sin, cos, sqrt
from random import randint
import sys
# Dependancies for Panda 3D
from direct.showbase.ShowBase import ShowBase
from direct.task import Task
from panda3d.core import AmbientLight, DirectionalLight, Material
from panda3d.core import LVector3, Mat4, TextNode
from pandac.PandaModules import VBase4
from direct.gui.DirectGui import *
from direct.gui.OnscreenText import OnscreenText
from direct.interval.IntervalGlobal import *
# globals
SPHERE_SIZE = 5
DIST_BTW = 35
COLOR = VBase4(0.4, 0.4, 0.4, 1)
v1 = [1]
v3 = [True]
v4 = [1]
params = [SPHERE_SIZE, 1, DIST_BTW, True, 1]
Next = False
"""
Output:
params[0] - sphere size
params[1] - One point = 1, Two points = 2, Cerebellum simulation = 3
params[2] - how far apart two points will be if you choose two (2 for params[1])
params[3] - How many points for the cerebellar simulation, 4 = True, 5 = False
params[4] - What color scheme. Gray = 1, random = 2, RGBY(W) = 3
"""
# Useful function to make the color of the sphere random.
# No input, just outputs a random VBase4 color.
def random_color():
random_color = VBase4(float(randint(0, 100))/100,
float(randint(0, 100))/100,
float(randint(0, 100))/100,
1)
return random_color
# Useful function for finding the Euclidean distance in 3D.
def euclidean(vector1, vector2):
def square(x): return float(x)*float(x)
return sqrt(square(vector1[0] - vector2[0])
+ square(vector1[1] - vector2[1])
+ square(vector1[2] - vector2[2]))
# Main class.
class main(ShowBase):
def __init__(self):
ShowBase.__init__(self)
# set background color
base.setBackgroundColor(1,1,1)
# set ambient and directional light on the sphere
# for scoping reasons, make them class variables
ambientLight = AmbientLight("ambientLight")
ambientLight.setColor((0.3 , 0.4, 0.5, 1))
directionalLight = DirectionalLight("directionalLight")
directionalLight.setDirection(LVector3(0, 8, -2.5))
directionalLight.setColor((0.9, 0.9, 0.9, 1))
self.ambientLight = ambientLight
self.directionalLight = directionalLight
self.disableMouse()
self.camera.setPos(0, -300, 0) # Sets the camera back.
# Stock code from the Panda3D manual that reactivates the mouse standard.
# Left click moves in the x and z planes, right click in the y plane.
# Both together rotates around (0, 0, 0).
mat=Mat4(camera.getMat())
mat.invertInPlace()
base.mouseInterfaceNode.setMat(mat)
base.enableMouse()
#call methods
self.input_variables()
self.calculate()
self.choose_and_run()
def input_variables(self):
# Calls to the text and button widgets.
# Q0 sphere size
button0_text = OnscreenText(text = "Sphere Size",
scale = 0.1, pos = (0, 0.9),
align=TextNode.ACenter)
button0 = DirectEntry(text = "",
command = self.item0, initialText = "5",
scale = 0.1, width = 10,
numLines = 1, pos = (-0.4, 0, 0.75))
# Q1 one point, two points, cerebellum simulation
button1_text = OnscreenText(text = "What type of simulation?",
scale = 0.1, pos = (0, 0.6),
align=TextNode.ACenter)
button11 = DirectRadioButton(text = "One Point",
command = self.item1, variable = v1,
value = [1], scale = 0.1,
pos = (-0.56, 0, 0.45),
pressEffect = 1, indicatorValue = 0)
button12 = DirectRadioButton(text = "Two Point",
command = self.item1, variable = v1,
value = [2], scale = 0.1,
pos = (0, 0, 0.45),
pressEffect = 1, indicatorValue = 0)
button13 = DirectRadioButton(text = "Cerebellum",
command = self.item1, variable = v1,
value = [3], scale = 0.1,
pos = (0.6, 0, 0.45),
pressEffect = 1, indicatorValue = 0)
buttons1 = [button11, button12, button13]
for button in buttons1:
button.setOthers(buttons1)
# Q2 How far apart will the two points be if you use two.
button2_text = OnscreenText(text = "How far apart (if two points)",
scale = 0.1, pos = (0, 0.3),
align=TextNode.ACenter)
button2 = DirectEntry(text = "",
command = self.item2, initialText = "30",
scale = 0.1, width = 10,
numLines = 1, pos = (-0.4, 0, 0.15))
# Q3 How many points in the cerebellar simulation?
button3_text = OnscreenText(text = "What type of simulation?",
scale = 0.1, pos = (0, 0),
align=TextNode.ACenter)
button31 = DirectRadioButton(text = "Four",
command = self.item3, variable = v3,
value = [True], scale = 0.1,
pos = (-0.2, 0, -0.15),
pressEffect = 1, indicatorValue = 0)
button32 = DirectRadioButton(text = "Five",
command = self.item3, variable = v3,
value = [False], scale = 0.1,
pos = (0.2, 0, -0.15),
pressEffect = 1, indicatorValue = 0)
buttons3 = [button31, button32]
for button in buttons3:
button.setOthers(buttons3)
# Q4 What color scheme. Gray, random, RGBY(W)?
button4_text = OnscreenText(text = "What color scheme?",
scale = 0.1, pos = (0, -0.3),
align=TextNode.ACenter)
button41 = DirectRadioButton(text = "Gray",
command = self.item4, variable = v4,
value = [1], scale = 0.1,
pos = (-0.5, 0, -0.45),
pressEffect = 1, indicatorValue = 0)
button42 = DirectRadioButton(text = "Random",
command = self.item4, variable = v4,
value = [2], scale = 0.1,
pos = (-0.05, 0, -0.45),
pressEffect = 1, indicatorValue = 0)
button43 = DirectRadioButton(text = "RGBY(W)",
command = self.item4, variable = v4,
value = [3], scale = 0.1,
pos = (0.5, 0, -0.45),
pressEffect = 1, indicatorValue = 0)
buttons4 = [button41, button42, button43]
for button in buttons4:
button.setOthers(buttons4)
# exit
exit_button = DirectButton(text = "Make it so!",
command = self.exit_button,
scale = 0.1,
pos = (0, 0, -0.75),
pressEffect = 1)
self.button0_text = button0_text
self.button0 = button0
self.button1_text = button1_text
self.button11 = button11
self.button12 = button12
self.button13 = button13
self.button2_text = button2_text
self.button2 = button2
self.button3_text = button3_text
self.button31 = button31
self.button32 = button32
self.button4_text = button4_text
self.button41 = button41
self.button42 = button42
self.button43 = button43
self.exit_button = exit_button
# sphere size
def item0(self, param0):
try:
param0 = int(param0)
except ValueError:
param0 = 5
params[0] = param0
# One point, two points, cerebellum simulation
def item1(self):
params[1] = v1[0]
# How far apart will the two points be if you use two.
def item2(self, param2):
try:
param2 = int(param2)
except ValueError:
param2 = 30
params[2] = param2
# How many points in the cerebellar simulation, 4 or 5
def item3(self):
params[3] = v3[0]
# What color scheme. Gray, random, RGBY(W)?
def item4(self):
params[4] = v4[0]
def exit_button(self):
self.button0_text.cleanup()
self.button0.destroy()
self.button1_text.cleanup()
self.button11.destroy()
self.button12.destroy()
self.button13.destroy()
self.button2_text.cleanup()
self.button2.destroy()
self.button3_text.cleanup()
self.button31.destroy()
self.button32.destroy()
self.button4_text.cleanup()
self.button41.destroy()
self.button42.destroy()
self.button43.destroy()
self.exit_button.destroy()
print(params)
def calculate(self):
# As outputs, 3D_tree_input tells you whether the user wanted
# one or two points or the cerebellum simulation
SPHERE_SIZE = params[0]
DIST_BTW = params[1]
radius = SPHERE_SIZE/1.5
# This is the extra distance between the spheres not immediately adjacent.
# Basically, it specifies how much crowding is allowed.
extra_distance = int(round(1.8*radius))
# Allowable vectors from a point.
# Calculates number of possible vectors of a certain distance
# That distance is proportional to radius.
possible_vectors = []
# turns degrees into radians 1 degree through 360 degrees.
thetas = [(float(i)*pi)/180 for i in range(360)]
phis = thetas
for theta in thetas:
for phi in phis:
x = radius * sin(theta) * cos(phi)
y = radius * sin(theta) * sin(phi)
z = radius * cos(theta)
x = int(round(x))
y = int(round(y))
z = int(round(z))
possible_vectors.append((x, y, z))
possible_vectors = list(set(possible_vectors))
def choose_and_run(self):
# one sphere at (0, 0, 0)
if params[2] == 1:
sphere_list = [(0, 0, 0)]
outside_sphere_list0 = []
outside_sphere_list0.append(sphere_list[0])
counter_list0 = [0]
# choose random color
if params[4] == 2:
COLOR = random_color()
# Two spheres DIST_BTW apart if user selects "Two Spheres"
# Sets cerebellum to True if user selects "Cerebellum?"
elif params[2] == 2:
sphere_list = [(-DIST_BTW/2, 0, 0), (DIST_BTW/2, 0, 0)]
if params[4] == 1:
COLOR = [VBase4(0.4, 0.4, 0.4, 1), VBase4(0.6, 0.6, 0.6, 1)]
elif params[4] == 2:
COLOR = []
COLOR.append(random_color())
COLOR.append(random_color())
elif params[4] == 3:
COLOR = []
for i in range(2):
g = randint(0, 4)
COLOR.append(
[VBase4(1, 0, 0, 1),
VBase4(0, 0.5, 0, 1),
VBase4(0, 0, 1, 1),
VBase4(1, 1, 0, 1),
VBase4(1, 1, 1, 1)][g]
)
# Will need these local variable lists. They define which
# nodes are chosen each iteration to 'grow'
# these lists all depend on the 'master' sphere_list.
# will need one list for each time we want a new node (coudn't find a way around it)
outside_sphere_list0 = []
outside_sphere_list0.append(sphere_list[0])
counter_list0 = [0]
outside_sphere_list1 = []
outside_sphere_list1.append(sphere_list[1])
counter_list1 = [0]
elif params[2] == 3:
sphere_list = [(-25, 0, 0), (25, 0, 0), (0, 0, 13), (0, 0, -17), (-25, 0, -17)]
# choose colors based on user input.
if params[4] == 1:
COLOR = [VBase4(float(i)/10, float(i)/10, float(i)/10, 1) for i in range(3, 8)]
elif params[4] == 2:
COLOR = [random_color() for i in range(5)]
elif params[4] == 3:
COLOR = [VBase4(1, 0, 0, 1),
VBase4(0, 0.5, 0, 1),
VBase4(0, 0, 1, 1),
VBase4(1, 1, 0, 1),
VBase4(1, 1, 1, 1)]
# Similar to if the user chooses two points, but more points obviously
outside_sphere_list0 = []
outside_sphere_list0.append(sphere_list[0])
counter_list0 = [0]
outside_sphere_list1 = []
outside_sphere_list1.append(sphere_list[1])
counter_list1 = [0]
outside_sphere_list2 = []
outside_sphere_list2.append(sphere_list[2])
counter_list2 = [0]
outside_sphere_list3 = []
outside_sphere_list3.append(sphere_list[3])
counter_list3 = [0]
if params[3] == False:
outside_sphere_list4 = []
outside_sphere_list4.append(sphere_list[4])
counter_list4 = [0]
# task manager for drawing the spheres with add_sphere below
if params[2] == 1:
self.load_sphere(sphere_list[0], directionalLight, ambientLight, COLOR)
self.taskMgr.add(self.add_sphere, "add_sphere",
extraArgs = [COLOR, sphere_list[0], outside_sphere_list0, counter_list0],
appendTask = True)
if params[2] == 2:
self.load_sphere(sphere_list[0], directionalLight, ambientLight, COLOR[0])
self.load_sphere(sphere_list[1], directionalLight, ambientLight, COLOR[1])
self.taskMgr.add(self.add_sphere, "add_sphere",
extraArgs = [COLOR[0], sphere_list[0], outside_sphere_list0, counter_list0],
appendTask = True)
self.taskMgr.add(self.add_sphere, "add_sphere",
extraArgs = [COLOR[1], sphere_list[1], outside_sphere_list1, counter_list1],
appendTask = True)
# This is for all the spheres in the cerebellum simulation.
if params[2] == 3:
if params[3] == True:
four_or_five = 4
elif params[3] == False:
four_or_five = 5
for i in range(four_or_five):
self.load_sphere(sphere_list[i], directionalLight, ambientLight, COLOR[i])
self.taskMgr.add(self.add_sphere, "add_sphere",
extraArgs = [COLOR[0], sphere_list[0], outside_sphere_list0, counter_list0],
appendTask = True)
self.taskMgr.add(self.add_sphere, "add_sphere",
extraArgs = [COLOR[1], sphere_list[1], outside_sphere_list1, counter_list1],
appendTask = True)
self.taskMgr.add(self.add_sphere, "add_sphere",
extraArgs = [COLOR[2], sphere_list[2], outside_sphere_list2, counter_list2],
appendTask = True)
self.taskMgr.add(self.add_sphere, "add_sphere",
extraArgs = [COLOR[3], sphere_list[3], outside_sphere_list3, counter_list3],
appendTask = True)
if params[3] == False:
self.taskMgr.add(self.add_sphere, "add_sphere",
extraArgs = [COLOR[4], sphere_list[4], outside_sphere_list4, counter_list4],
appendTask = True)
# Loads a sphere. The inputs are self explanatory.
# I set the directional and ambient light to class globals,
# so it wouldn't creat a new light per sphere, which is possible
# but apparently expensive.
def load_sphere(self,
position,
directionalLight,
ambientLight,
color):
# Load a sphere.
pos = position
self.sphere = self.loader.loadModel("models/misc/ball")
self.sphere.setScale(SPHERE_SIZE,
SPHERE_SIZE,
SPHERE_SIZE)
self.sphere.setPos(pos)
# sets the sphere color
self.sphere.setColor(color)
self.sphere.reparentTo(render)
# make the sphere a material (to behave with the light)
material = Material()
material.setShininess(10.0)
self.sphere.setMaterial(material)
# Set lighting on the sphere
self.sphere.setLight(self.sphere.attachNewNode(directionalLight))
self.sphere.setLight(self.sphere.attachNewNode(ambientLight))
# adds spheres to the scene
def add_sphere(self, color, starting_node, outside_sphere_list, counter_list, task):
dLight = self.directionalLight
aLight = self.ambientLight
# select randomly from the list of spheres
n = randint(0, len(outside_sphere_list) - 1)
chosen_sphere = outside_sphere_list[n]
# select randomly from the list of possible vectors
n = randint(0, len(possible_vectors) - 1)
chosen_vector = possible_vectors[n]
# add the vectors together to get a new location
# for a sphere
x = chosen_sphere[0] + chosen_vector[0]
y = chosen_sphere[1] + chosen_vector[1]
z = chosen_sphere[2] + chosen_vector[2]
# calculates the distance from where the new sphere will randomly be
# drawn to the other spheres, removes reduntant numbers
d = []
f = []
distances = []
pop = list(sphere_list)
if len(pop) > 1:
pop.remove(chosen_sphere)
d = [(x, y, z) for i in range(len(pop))]
distances = [int(euclidean(d[i], pop[i])) \
for i in range(len(pop))]
distances = list(set(distances))
# Recalculate distances for the chosen_sphere, so that in later code, we can remove
# sheres that are too crowded.
d = []
chosen_distances = []
d = [chosen_sphere for i in range(len(pop))]
chosen_distances = [int(euclidean(d[i], pop[i])) for i in range(len(pop))]
chosen_distances = list(set(chosen_distances))
# Sorts the chosen_distances lowest to highest.
chosen_distances.sort()
# temp will be used to index the counter of chosen_sphere.
temp = outside_sphere_list.index(chosen_sphere)
# make the new sphere list and render the sphere
# first if statement only allows the render if the sphere
# is going to be a certain distance away from all the other spheres,
# in this case, a 'radius' distance away
if min(distances) >= int(radius):
if counter_list[temp] <= 1:
if len(chosen_distances) <= 5:
outside_sphere_list.append((x, y, z))
sphere_list.append((x, y, z))
counter_list.append(0)
# The chosen_sphere counter goes up by one.
counter_list[temp] += 1
# draw sphere
self.load_sphere((x, y, z),
dLight,
aLight,
color)
if len(chosen_distances) > 5 \
and chosen_distances[5] >= int(radius) + extra_distance:
outside_sphere_list.append((x, y, z))
sphere_list.append((x, y, z))
counter_list.append(0)
# The chosen_sphere counter goes up by one.
counter_list[temp] += 1
# draw sphere
self.load_sphere((x, y, z),
dLight,
aLight,
color)
# The only available spheres for selection are the outside
# ones in sphere_list. They 'burn out' after the number chosen
# in the if statement.
if counter_list[temp] > 1:
outside_sphere_list.remove(chosen_sphere)
del counter_list[temp]
return Task.cont
Cerebellum = main()
Cerebellum.run()