mirror of
https://github.com/panda3d/panda3d.git
synced 2025-10-04 19:08:55 -04:00
3215 lines
127 KiB
Python
3215 lines
127 KiB
Python
from PandaObject import *
|
|
from PieMenu import *
|
|
from OnscreenText import *
|
|
from Tkinter import *
|
|
from DirectGeometry import *
|
|
from SceneGraphExplorer import *
|
|
from tkSimpleDialog import askstring
|
|
from tkMessageBox import showinfo
|
|
from tkFileDialog import *
|
|
from whrandom import *
|
|
import Pmw
|
|
import EntryScale
|
|
import VectorWidgets
|
|
import string
|
|
import os
|
|
import __builtin__
|
|
|
|
# Colors used by all color menus
|
|
DEFAULT_COLORS = [
|
|
Vec4(1,1,1,1),
|
|
Vec4(0.75, 0.75, 0.75, 1.0 ),
|
|
Vec4(0.5, 0.5, 0.5, 1.0),
|
|
Vec4(0.25, 0.25, 0.25, 1.0)]
|
|
# The list of items with color attributes
|
|
COLOR_TYPES = ['wall_color', 'window_color',
|
|
'window_awning_color', 'door_color',
|
|
'door_awning_color', 'cornice_color',
|
|
'prop_color']
|
|
# The list of dna components maintained in the style attribute dictionary
|
|
DNA_TYPES = ['wall', 'window', 'door', 'cornice', 'toon_landmark',
|
|
'prop', 'street']
|
|
BUILDING_TYPES = ['10_10', '20', '10_20', '20_10', '10_10_10']
|
|
# The list of neighborhoods to edit
|
|
NEIGHBORHOODS = ['toontown_central', 'donalds_dock',
|
|
'minnies_melody_land', 'the_burrrgh']
|
|
NEIGHBORHOODS = ['toontown_central', 'donalds_dock',]
|
|
NEIGHBORHOOD_CODES = {'toontown_central': 'TT', 'donalds_dock': 'DD',
|
|
'minnies_melody_land': 'MM', 'the_burrrgh': 'BR'}
|
|
OBJECT_SNAP_POINTS = {
|
|
'street_5x20': [(Vec3(5.0,0,0), Vec3(0)),
|
|
(Vec3(0), Vec3(0))],
|
|
'street_10x20': [(Vec3(10.0,0,0), Vec3(0)),
|
|
(Vec3(0), Vec3(0))],
|
|
'street_20x20': [(Vec3(20.0,0,0), Vec3(0)),
|
|
(Vec3(0), Vec3(0))],
|
|
'street_40x20': [(Vec3(40.0,0,0), Vec3(0)),
|
|
(Vec3(0), Vec3(0))],
|
|
'street_80x20': [(Vec3(80.0,0,0), Vec3(0)),
|
|
(Vec3(0), Vec3(0))],
|
|
'street_5x40': [(Vec3(5.0,0,0), Vec3(0)),
|
|
(Vec3(0), Vec3(0))],
|
|
'street_10x40': [(Vec3(10.0,0,0), Vec3(0)),
|
|
(Vec3(0), Vec3(0))],
|
|
'street_20x40': [(Vec3(20.0,0,0), Vec3(0)),
|
|
(Vec3(0), Vec3(0))],
|
|
'street_30x40': [(Vec3(30.0,0,0), Vec3(0)),
|
|
(Vec3(0), Vec3(0))],
|
|
'street_40x40': [(Vec3(40.0,0,0), Vec3(0)),
|
|
(Vec3(0), Vec3(0))],
|
|
'street_80x40': [(Vec3(80.0,0,0), Vec3(0)),
|
|
(Vec3(0), Vec3(0))],
|
|
'street_angle_30': [(Vec3(0), Vec3(-30,0,0)),
|
|
(Vec3(0), Vec3(0))],
|
|
'street_angle_45': [(Vec3(0), Vec3(-45,0,0)),
|
|
(Vec3(0), Vec3(0))],
|
|
'street_angle_60': [(Vec3(0), Vec3(-60,0,0)),
|
|
(Vec3(0), Vec3(0))],
|
|
'street_inner_corner': [(Vec3(20.0,0,0), Vec3(0)),
|
|
(Vec3(0), Vec3(0))],
|
|
'street_outer_corner': [(Vec3(20.0,0,0), Vec3(0)),
|
|
(Vec3(0), Vec3(0))],
|
|
'street_full_corner': [(Vec3(40.0,0,0), Vec3(0)),
|
|
(Vec3(0), Vec3(0))],
|
|
'street_t_intersection': [(Vec3(40.0,0,0), Vec3(0)),
|
|
(Vec3(0), Vec3(0))],
|
|
'street_y_intersection': [(Vec3(30.0,0,0), Vec3(0)),
|
|
(Vec3(0), Vec3(0))],
|
|
'street_street_20x20': [(Vec3(20.0,0,0), Vec3(0)),
|
|
(Vec3(0), Vec3(0))],
|
|
'street_street_40x40': [(Vec3(40.0,0,0), Vec3(0)),
|
|
(Vec3(0), Vec3(0))],
|
|
'street_sidewalk_20x20': [(Vec3(20.0,0,0), Vec3(0)),
|
|
(Vec3(0), Vec3(0))],
|
|
'street_sidewalk_40x40': [(Vec3(40.0,0,0), Vec3(0)),
|
|
(Vec3(0), Vec3(0))],
|
|
'street_divided_transition': [(Vec3(40.0,0,0), Vec3(0)),
|
|
(Vec3(0), Vec3(0))],
|
|
'street_divided_40x70': [(Vec3(40.0,0,0), Vec3(0)),
|
|
(Vec3(0), Vec3(0))],
|
|
'street_stairs_40x10x5': [(Vec3(40.0,0,0), Vec3(0)),
|
|
(Vec3(0), Vec3(0))],
|
|
'street_4way_intersection': [(Vec3(40.0,0,0), Vec3(0)),
|
|
(Vec3(0), Vec3(0))],
|
|
'street_incline_40x40x5': [(Vec3(40.0,0,0), Vec3(0)),
|
|
(Vec3(0), Vec3(0))],
|
|
'street_courtyard_70': [(Vec3(0.0,0,0), Vec3(45,0,0)),
|
|
(Vec3(0), Vec3(0))],
|
|
'street_courtyard_70_exit': [(Vec3(0.0,0,0), Vec3(45,0,0)),
|
|
(Vec3(0), Vec3(0))],
|
|
'street_courtyard_90': [(Vec3(0.0,0,0), Vec3(45,0,0)),
|
|
(Vec3(0), Vec3(0))],
|
|
'street_courtyard_90_exit': [(Vec3(0.0,0,0), Vec3(45,0,0)),
|
|
(Vec3(0), Vec3(0))],
|
|
'street_50_transition': [(Vec3(10.0,0,0), Vec3(0)),
|
|
(Vec3(0), Vec3(0))],
|
|
'street_20x50': [(Vec3(20.0,0,0), Vec3(0)),
|
|
(Vec3(0), Vec3(0))],
|
|
'street_40x50': [(Vec3(40.0,0,0), Vec3(0)),
|
|
(Vec3(0), Vec3(0))],
|
|
'street_keyboard_10x40': [(Vec3(10.0,0,0), Vec3(0)),
|
|
(Vec3(0), Vec3(0))],
|
|
'street_keyboard_20x40': [(Vec3(20.0,0,0), Vec3(0)),
|
|
(Vec3(0), Vec3(0))],
|
|
'street_keyboard_40x40': [(Vec3(40.0,0,0), Vec3(0)),
|
|
(Vec3(0), Vec3(0))],
|
|
}
|
|
SNAP_ANGLE = 15.0
|
|
|
|
try:
|
|
if dnaLoaded:
|
|
pass
|
|
except NameError:
|
|
# DNAStorage instance for storing level DNA info
|
|
__builtin__.DNASTORE = DNASTORE = DNAStorage()
|
|
# Load the generic storage file
|
|
loadDNAFile(DNASTORE, 'phase_4/dna/storage.dna', CSDefault, 1)
|
|
# Load all the neighborhood specific storage files
|
|
loadDNAFile(DNASTORE, 'phase_4/dna/storage_TT.dna', CSDefault, 1)
|
|
loadDNAFile(DNASTORE, 'phase_6/dna/storage_DD.dna', CSDefault, 1)
|
|
loadDNAFile(DNASTORE, 'phase_6/dna/storage_MM.dna', CSDefault, 1)
|
|
loadDNAFile(DNASTORE, 'phase_6/dna/storage_BR.dna', CSDefault, 1)
|
|
__builtin__.dnaLoaded = 1
|
|
|
|
# Precompute class types for type comparisons
|
|
DNA_CORNICE = DNACornice.getClassType()
|
|
DNA_DOOR = DNADoor.getClassType()
|
|
DNA_FLAT_BUILDING = DNAFlatBuilding.getClassType()
|
|
DNA_NODE = DNANode.getClassType()
|
|
DNA_GROUP = DNAGroup.getClassType()
|
|
DNA_VIS_GROUP = DNAVisGroup.getClassType()
|
|
DNA_LANDMARK_BUILDING = DNALandmarkBuilding.getClassType()
|
|
DNA_NODE = DNANode.getClassType()
|
|
DNA_PROP = DNAProp.getClassType()
|
|
DNA_STREET = DNAStreet.getClassType()
|
|
DNA_WALL = DNAWall.getClassType()
|
|
DNA_WINDOWS = DNAWindows.getClassType()
|
|
|
|
# DNA Utility functions (possible class extensions?)
|
|
def DNARemoveChildren(dnaObject):
|
|
""" Utility function to delete all the children of a DNANode """
|
|
children = []
|
|
for i in range(dnaObject.getNumChildren()):
|
|
children.append(dnaObject.at(i))
|
|
for child in children:
|
|
dnaObject.remove(child)
|
|
DNASTORE.removeDNAGroup(child)
|
|
|
|
def DNARemoveChildOfClass(dnaNode, classType):
|
|
""" Remove the first object of that type you come across """
|
|
for i in range(dnaNode.getNumChildren()):
|
|
child = dnaNode.at(i)
|
|
if DNAClassEqual(child, classType):
|
|
dnaNode.remove(child)
|
|
DNASTORE.removeDNAGroup(child)
|
|
return 1
|
|
# None found
|
|
return 0
|
|
|
|
def DNAGetChild(dnaObject, type = DNA_NODE, childNum = 0):
|
|
childCount = 0
|
|
for i in range(dnaObject.getNumChildren()):
|
|
child = dnaObject.at(i)
|
|
if DNAClassEqual(child, type):
|
|
if childCount == childNum:
|
|
return child
|
|
childCount = childCount + 1
|
|
# Not found
|
|
return None
|
|
|
|
def DNAGetChildOfClass(dnaNode, classType):
|
|
for i in range(dnaNode.getNumChildren()):
|
|
child = dnaNode.at(i)
|
|
if DNAClassEqual(child, classType):
|
|
return child
|
|
# Not found
|
|
return None
|
|
|
|
def DNAGetClassType(dnaObject):
|
|
return dnaObject.__class__.getClassType()
|
|
|
|
def DNAClassEqual(dnaObject, classType):
|
|
return DNAGetClassType(dnaObject).eq(classType)
|
|
|
|
def DNAIsDerivedFrom(dnaObject, classType):
|
|
return DNAGetClassType(dnaObject).isDerivedFrom(classType)
|
|
|
|
def DNAGetWallHeights(aDNAFlatBuilding):
|
|
""" Get a list of wall heights for a given flat building """
|
|
# Init variables
|
|
heightList = []
|
|
offsetList = []
|
|
offset = 0.0
|
|
# Compute wall heights
|
|
for i in range(aDNAFlatBuilding.getNumChildren()):
|
|
child = aDNAFlatBuilding.at(i)
|
|
if DNAClassEqual(child, DNA_WALL):
|
|
height = child.getHeight()
|
|
heightList.append(height)
|
|
offsetList.append(offset)
|
|
offset = offset + height
|
|
return heightList, offsetList
|
|
|
|
class LevelEditor(NodePath, PandaObject):
|
|
"""Class used to create a Toontown LevelEditor object"""
|
|
# Primary variables:
|
|
# DNAData: DNA object holding DNA info about level
|
|
# DNAToplevel: Top level DNA Node, all DNA objects descend from this node
|
|
# NPTopLevel: Corresponding Node Path
|
|
# DNAParent: Current DNA Node that new objects get added to
|
|
# NPParent: Corresponding Node Path
|
|
# selectedDNARoot: DNA Node of currently selected object
|
|
# selectedNPRoot: Corresponding Node Path
|
|
# DNATarget: Subcomponent being modified by Pie Menu
|
|
def __init__(self):
|
|
# Make the level editor a node path so that you can show/hide
|
|
# The level editor separately from loading/saving the top level node
|
|
# Initialize superclass
|
|
NodePath.__init__(self)
|
|
# Become the new node path
|
|
self.assign(hidden.attachNewNode('LevelEditor'))
|
|
|
|
# Create ancillary objects
|
|
# Style manager for keeping track of styles/colors
|
|
self.styleManager = LevelStyleManager()
|
|
# Load neighborhood maps
|
|
self.createLevelMaps()
|
|
# Marker for showing next insertion point
|
|
self.createInsertionMarker()
|
|
# Create level Editor Panel
|
|
self.panel = LevelEditorPanel(self)
|
|
|
|
# Initialize LevelEditor variables DNAData, DNAToplevel, NPToplevel
|
|
# DNAParent, NPParent, groupNum, lastAngle
|
|
# Pass in the new toplevel group and don't clear out the old
|
|
# toplevel group (since it doesn't exist yet)
|
|
self.reset(fDeleteToplevel = 0, fCreateToplevel = 1)
|
|
|
|
# The list of events the level editor responds to
|
|
self.actionEvents = [
|
|
# Actions in response to DIRECT operations
|
|
('selectedNodePath', self.selectedNodePathHook),
|
|
('deselectedNodePath', self.deselectedNodePathHook),
|
|
('manipulateObjectCleanup', self.updateSelectedPose),
|
|
|
|
# Actions in response to Level Editor Panel operations
|
|
('SGENodePath_Set Name', self.getAndSetName),
|
|
('SGENodePath_Set Parent', self.setActiveParent),
|
|
('SGENodePath_Reparent', self.reparent),
|
|
('SGENodePath_Add Group', self.addGroup),
|
|
('SGENodePath_Add Vis Group', self.addVisGroup),
|
|
('SGENodePath_Set Color', self.setNPColor),
|
|
# Actions in response to Pie Menu interaction
|
|
('select_building_style', self.setBuildingStyle),
|
|
('select_building_type', self.setBuildingType),
|
|
('select_building_width', self.setBuildingWidth),
|
|
('select_cornice_color', self.setDNATargetColor),
|
|
('select_cornice_orientation', self.setDNATargetOrientation),
|
|
('select_cornice_texture', self.setDNATargetCode, ['cornice']),
|
|
('select_door_color', self.setDNATargetColor),
|
|
('select_door_orientation', self.setDNATargetOrientation),
|
|
('select_door_texture', self.setDNATargetCode, ['door']),
|
|
('select_door_awning_texture', self.setDNATargetCode,
|
|
['door_awning']),
|
|
('select_door_awning_color', self.setDNATargetColor),
|
|
('select_window_color', self.setDNATargetColor),
|
|
('select_window_count', self.setWindowCount),
|
|
('select_window_orientation', self.setDNATargetOrientation),
|
|
('select_window_texture', self.setDNATargetCode, ['windows']),
|
|
('select_window_awning_texture', self.setDNATargetCode,
|
|
['window_awning']),
|
|
('select_window_awning_color', self.setDNATargetColor),
|
|
('select_wall_style', self.setWallStyle),
|
|
('select_wall_color', self.setDNATargetColor),
|
|
('select_wall_orientation', self.setDNATargetOrientation),
|
|
('select_wall_texture', self.setDNATargetCode, ['wall']),
|
|
('select_toon_landmark_texture', self.setDNATargetCode,
|
|
['landmark']),
|
|
('select_toon_landmark_door_color', self.setDNATargetColor),
|
|
('select_toon_landmark_door_orientation',
|
|
self.setDNATargetOrientation),
|
|
('select_landmark_door_texture', self.setDNATargetCode,
|
|
['landmark_door']),
|
|
('select_street_texture', self.setDNATargetCode, ['street']),
|
|
('select_prop_texture', self.setDNATargetCode, ['prop']),
|
|
('select_prop_color', self.setDNATargetColor),
|
|
# Hot key actions
|
|
('a', self.autoPositionGrid),
|
|
('.', self.jumpToInsertionPoint),
|
|
('p', self.plantSelectedNodePath),
|
|
('left', self.keyboardXformSelected, ['left']),
|
|
('right', self.keyboardXformSelected, ['right']),
|
|
('up', self.keyboardXformSelected, ['up']),
|
|
('down', self.keyboardXformSelected, ['down']),
|
|
]
|
|
|
|
# Initialize state
|
|
# Make sure direct is running
|
|
direct.enable()
|
|
# And only the appropriate handles are showing
|
|
direct.widget.disableHandles(['x-ring', 'x-disc',
|
|
'y-ring', 'y-disc',
|
|
'z-post'])
|
|
# Initialize camera
|
|
base.cam.node().setNear(5.0)
|
|
base.cam.node().setFar(3000)
|
|
direct.camera.setPos(0,-10,10)
|
|
# Hide (disable) grid initially
|
|
self.showGrid(0)
|
|
# Create variable for vis groups panel
|
|
self.vgpanel = None
|
|
# Start off enabled
|
|
self.enable()
|
|
# Editing toontown_central
|
|
self.setEditMode('toontown_central')
|
|
|
|
# ENABLE/DISABLE
|
|
def enable(self):
|
|
""" Enable level editing and show level """
|
|
# Make sure level is visible
|
|
self.reparentTo(direct.group)
|
|
self.show()
|
|
# Add all the action events
|
|
for event in self.actionEvents:
|
|
if len(event) == 3:
|
|
self.accept(event[0], event[1], event[2])
|
|
else:
|
|
self.accept(event[0], event[1])
|
|
# Enable mouse interaction (pie menus)
|
|
self.enableMouse()
|
|
# Spawn task to keep insertion marker aligned with grid
|
|
self.spawnInsertionMarkerTask()
|
|
|
|
def disable(self):
|
|
""" Disable level editing and hide level """
|
|
# Deselect everything as a precaution
|
|
direct.deselectAll()
|
|
# Hide the level
|
|
self.reparentTo(hidden)
|
|
# Ignore the hooks
|
|
for eventPair in self.actionEvents:
|
|
self.ignore(eventPair[0])
|
|
# These are added outside of actionEvents list
|
|
self.ignore('insert')
|
|
self.ignore('space')
|
|
# Disable Pie Menu mouse interaction
|
|
self.disableMouse()
|
|
# Remove insertion marker task
|
|
taskMgr.removeTasksNamed('insertionMarkerTask')
|
|
|
|
def reset(self, fDeleteToplevel = 1, fCreateToplevel = 1):
|
|
"""
|
|
Reset level and re-initialize main class variables
|
|
Pass in the new top level group
|
|
"""
|
|
if fDeleteToplevel:
|
|
# First destroy existing scene-graph/DNA hierarchy
|
|
self.deleteToplevel()
|
|
|
|
# Clear DNASTORE
|
|
DNASTORE.resetDNAGroups()
|
|
# Reset DNA VIS Groups
|
|
DNASTORE.resetDNAVisGroups()
|
|
|
|
# Create fresh DNA DATA
|
|
self.DNAData = DNAData('level_data')
|
|
|
|
# Create new toplevel node path and DNA
|
|
if fCreateToplevel:
|
|
self.createToplevel(DNANode('level'))
|
|
|
|
# Initialize variables
|
|
# Reset grid
|
|
direct.grid.setPosHprScale(0,0,0,0,0,0,1,1,1)
|
|
# The selected DNA Object/NodePath
|
|
self.selectedDNARoot = None
|
|
self.selectedNPRoot = None
|
|
# Set active target (the subcomponent being modified)
|
|
self.DNATarget = None
|
|
self.DNATargetParent = None
|
|
# Set count of groups added to level
|
|
self.setGroupNum(0)
|
|
# Heading angle of last object added to level
|
|
self.setLastAngle(0.0)
|
|
# Last wall and building modified using pie menus
|
|
self.lastWall = None
|
|
self.lastBuilding = None
|
|
# Code of last selected object (for autopositionGrid)
|
|
self.snapList = []
|
|
|
|
def deleteToplevel(self):
|
|
# Destory old toplevel node path and DNA
|
|
# First the toplevel DNA
|
|
self.DNAData.remove(self.DNAToplevel)
|
|
# Then the toplevel Node Path
|
|
self.NPToplevel.reparentTo(hidden)
|
|
self.NPToplevel.remove()
|
|
|
|
def createToplevel(self, dnaNode, nodePath = None):
|
|
# When you create a new level, data is added to this node
|
|
# When you load a DNA file, you replace this node with the new data
|
|
self.DNAToplevel = dnaNode
|
|
self.DNAData.add(self.DNAToplevel)
|
|
if nodePath:
|
|
# Node path given, use it
|
|
self.NPToplevel = nodePath
|
|
self.NPToplevel.reparentTo(self)
|
|
else:
|
|
# No node path given, traverse
|
|
self.NPToplevel = self.DNAToplevel.traverse(self, DNASTORE, 1)
|
|
# Update parent pointers
|
|
self.DNAParent = self.DNAToplevel
|
|
self.NPParent = self.NPToplevel
|
|
# Update scene graph explorer
|
|
# self.panel.sceneGraphExplorer.update()
|
|
|
|
def destroy(self):
|
|
""" Disable level editor and destroy node path """
|
|
self.disable()
|
|
self.removeNode()
|
|
self.panel.destroy()
|
|
if self.vgpanel:
|
|
self.vgpanel.destroy()
|
|
|
|
def useDirectFly(self):
|
|
""" Disable player camera controls/enable direct camera control """
|
|
self.enableMouse()
|
|
direct.enable()
|
|
|
|
def useDriveMode(self):
|
|
""" Lerp down to eye level then switch to Drive mode """
|
|
pos = direct.camera.getPos()
|
|
pos.setZ(4.0)
|
|
hpr = direct.camera.getHpr()
|
|
hpr.set(hpr[0], 0.0, 0.0)
|
|
t = direct.camera.lerpPosHpr(pos, hpr, 1.0, blendType = 'easeInOut',
|
|
task = 'manipulateCamera')
|
|
# Note, if this dies an unatural death, this could screw things up
|
|
t.uponDeath = self.switchToDriveMode
|
|
|
|
def switchToDriveMode(self,state):
|
|
""" Disable direct camera manipulation and enable player drive mode """
|
|
direct.minimumConfiguration()
|
|
direct.manipulationControl.disableManipulation()
|
|
base.useDrive()
|
|
# Make sure we're where we want to be
|
|
pos = direct.camera.getPos()
|
|
pos.setZ(4.0)
|
|
hpr = direct.camera.getHpr()
|
|
hpr.set(hpr[0], 0.0, 0.0)
|
|
# Fine tune the drive mode
|
|
base.mouseInterface.getBottomNode().setPos(pos)
|
|
base.mouseInterface.getBottomNode().setHpr(hpr)
|
|
base.mouseInterface.getBottomNode().setForwardSpeed(20.0)
|
|
base.mouseInterface.getBottomNode().setReverseSpeed(20.0)
|
|
|
|
def enableMouse(self):
|
|
""" Enable Pie Menu interaction (and disable player camera control) """
|
|
# Turn off player camera control
|
|
base.disableMouse()
|
|
# Handle mouse events for pie menus
|
|
self.accept('handleMouse3',self.levelHandleMouse3)
|
|
self.accept('handleMouse3Up',self.levelHandleMouse3Up)
|
|
|
|
def disableMouse(self):
|
|
""" Disable Pie Menu interaction """
|
|
# Disable handling of mouse events
|
|
self.ignore('handleMouse3')
|
|
self.ignore('handleMouse3Up')
|
|
|
|
# LEVEL OBJECT MANAGEMENT FUNCTIONS
|
|
def findDNANode(self, nodePath):
|
|
""" Find node path's DNA Object in DNAStorage (if any) """
|
|
return DNASTORE.findDNAGroup(nodePath.id())
|
|
|
|
def replaceSelected(self):
|
|
# Update visible geometry using new DNA
|
|
newRoot = self.replace(self.selectedNPRoot, self.selectedDNARoot)
|
|
# Reselect node path and respawn followSelectedNodePathTask
|
|
direct.select(newRoot)
|
|
|
|
def replace(self, nodePath, dnaNode):
|
|
""" Replace a node path with the results of a DNANode traversal """
|
|
# Find node path's parent
|
|
parent = nodePath.getParent()
|
|
dnaParent = dnaNode.getParent()
|
|
# Get rid of the old node path and remove its DNA and
|
|
# node relations from the DNA Store
|
|
self.remove(dnaNode, nodePath)
|
|
# Traverse the old (modified) dna to create the new node path
|
|
newNodePath = dnaNode.traverse(parent, DNASTORE, 1)
|
|
# Add it back to the dna parent
|
|
dnaParent.add(dnaNode)
|
|
# Update scene graph explorer
|
|
# self.panel.sceneGraphExplorer.update()
|
|
# Return new node path
|
|
return newNodePath
|
|
|
|
def remove(self, dnaNode, nodePath):
|
|
"""
|
|
Delete DNA and Node relation from DNA Store and remove the node
|
|
path from the scene graph.
|
|
"""
|
|
# First delete DNA and node relation from the DNA Store
|
|
if dnaNode:
|
|
# Get DNANode's parent
|
|
parentDNANode = dnaNode.getParent()
|
|
if parentDNANode:
|
|
# Remove DNANode from its parent
|
|
parentDNANode.remove(dnaNode)
|
|
# Delete DNA and associated Node Relations from DNA Store
|
|
DNASTORE.removeDNAGroup(dnaNode)
|
|
if nodePath:
|
|
# Next deselect nodePath to avoid having bad node paths in the dict
|
|
direct.deselect(nodePath)
|
|
# Now you can get rid of the node path
|
|
nodePath.removeNode()
|
|
|
|
def reparent(self, nodePath):
|
|
""" Move node path (and its DNA) to active parent """
|
|
# Do we have a node path?
|
|
if nodePath:
|
|
dnaNode = self.findDNANode(nodePath)
|
|
# Does the node path correspond to a DNA Object
|
|
if dnaNode:
|
|
# Yes, get its parent and see if it has a DNA Object
|
|
parent = nodePath.getParent()
|
|
parentDNAObject = self.findDNANode(parent)
|
|
if parentDNAObject:
|
|
# Yes it does, move node path (and DNA)
|
|
# to new parent (if active parent set)
|
|
if ((self.NPParent != None) &
|
|
(self.DNAParent != None)):
|
|
nodePath.reparentTo(self.NPParent)
|
|
parentDNAObject.remove(dnaNode)
|
|
self.DNAParent.add(dnaNode)
|
|
# Update scene graph explorer to reflect change
|
|
# self.panel.sceneGraphExplorer.update()
|
|
|
|
def reparentSelected(self):
|
|
for nodePath in direct.selected:
|
|
self.reparent(nodePath)
|
|
|
|
def setActiveParent(self, nodePath = None):
|
|
""" Set NPParent and DNAParent to node path and its DNA """
|
|
# If nothing passed in, try currently selected node path
|
|
if not nodePath:
|
|
nodePath = direct.selected.last
|
|
# If we've got a valid node path
|
|
if nodePath:
|
|
# See if this is in the DNA database
|
|
newDNAParent = self.findDNANode(nodePath)
|
|
if newDNAParent:
|
|
self.DNAParent = newDNAParent
|
|
self.NPParent = nodePath
|
|
else:
|
|
print 'LevelEditor.setActiveParent: nodePath not found'
|
|
else:
|
|
print 'LevelEditor.setActiveParent: nodePath == None'
|
|
|
|
def getAndSetName(self, nodePath):
|
|
""" Prompt user for new node path name """
|
|
newName = askstring('Node Path', 'Enter new name:')
|
|
if newName:
|
|
self.setName(nodePath, newName)
|
|
|
|
def setName(self, nodePath, newName):
|
|
""" Set name of a node path and its DNA (if it exists) """
|
|
# First, set name of the node path
|
|
nodePath.setName(newName)
|
|
# Now find the DNA that corresponds to this node path
|
|
dnaNode = self.findDNANode(nodePath)
|
|
if dnaNode:
|
|
# If it exists, set the name of the DNA Node
|
|
dnaNode.setName(newName)
|
|
|
|
def updateSelectedPose(self):
|
|
"""
|
|
Update the DNA database to reflect selected objects current positions
|
|
"""
|
|
for selectedNode in direct.selected:
|
|
# Is this a DNA Object in the DNASTORE database?
|
|
dnaObject = self.findDNANode(selectedNode)
|
|
if dnaObject:
|
|
# It is, is it a DNA_NODE (i.e. does it have pos/hpr/scale)?
|
|
if DNAIsDerivedFrom(dnaObject, DNA_NODE):
|
|
# First snap selected node path to grid
|
|
pos = selectedNode.getPos(direct.grid)
|
|
snapPos = direct.grid.computeSnapPoint(pos)
|
|
if self.panel.fPlaneSnap.get():
|
|
zheight = 0
|
|
else:
|
|
zheight = snapPos[2]
|
|
selectedNode.setPos(direct.grid,
|
|
snapPos[0], snapPos[1], zheight)
|
|
# Angle snap
|
|
h = direct.grid.computeSnapAngle(selectedNode.getH())
|
|
selectedNode.setH(h)
|
|
if selectedNode == direct.selected.last:
|
|
self.setLastAngle(h)
|
|
# Update DNA
|
|
self.updatePose(dnaObject, selectedNode)
|
|
|
|
def updatePose(self, dnaObject, nodePath):
|
|
"""
|
|
Update a DNA Object's pos, hpr, and scale based upon
|
|
node path's current pose
|
|
"""
|
|
# Set DNA's pos, hpr, and scale
|
|
dnaObject.setPos(nodePath.getPos())
|
|
dnaObject.setHpr(nodePath.getHpr())
|
|
dnaObject.setScale(nodePath.getScale())
|
|
|
|
# LEVEL OBJECT CREATION FUNCTIONS
|
|
def initDNANode(self, dnaNode):
|
|
"""
|
|
This method adds a new DNA object to the scene and adds hooks that
|
|
allow duplicate copies of this DNA node to be added using the
|
|
space bar. For DNAFlatBuildings, a new copy with random style is
|
|
generated by hitting the insert key.
|
|
"""
|
|
# First create the visible geometry for this DNA Node
|
|
self.initNodePath(dnaNode)
|
|
# And add hooks to insert copies of dnaNode
|
|
self.addReplicationHooks(dnaNode)
|
|
|
|
def addReplicationHooks(self, dnaNode):
|
|
# Now add hook to allow placement of a new dna Node of this type
|
|
# by simply hitting the space bar or insert key. Note, extra paramter
|
|
# indicates new dnaNode should be a copy
|
|
self.accept('space', self.initNodePath, [dnaNode, 'space'])
|
|
self.accept('insert', self.initNodePath, [dnaNode, 'insert'])
|
|
|
|
def setRandomBuildingStyle(self, dnaNode, name = 'building'):
|
|
""" Initialize a new DNA Flat building to a random building style """
|
|
buildingType = self.getCurrent('building_type')
|
|
# Select a list of wall heights
|
|
if buildingType == 'random20':
|
|
chance = randint(1,100)
|
|
if chance <= 65:
|
|
buildingType = '10_10'
|
|
else:
|
|
buildingType = '20'
|
|
elif buildingType == 'random30':
|
|
chance = randint(1,100)
|
|
if chance <= 40:
|
|
buildingType = '10_20'
|
|
elif (chance > 80):
|
|
buildingType = '10_10_10'
|
|
else:
|
|
buildingType = '20_10'
|
|
|
|
# The building_style attribute dictionary for this number of stories
|
|
dict = self.getAttribute('building_style_' + buildingType).getDict()
|
|
style = self.getRandomDictionaryEntry(dict)
|
|
self.styleManager.setDNAFlatBuildingStyle(
|
|
dnaNode, style, width = self.getRandomWallWidth(), name = name)
|
|
|
|
def getRandomWallWidth(self):
|
|
chance = randint(0,100)
|
|
if chance <= 15:
|
|
return 5.0
|
|
elif (chance > 15) & (chance <= 30):
|
|
return 10.0
|
|
elif (chance > 30) & (chance <= 65):
|
|
return 15.0
|
|
elif (chance > 65) & (chance <= 85):
|
|
return 20.0
|
|
elif (chance > 85):
|
|
return 25.0
|
|
|
|
def initNodePath(self, dnaNode, hotKey = None):
|
|
"""
|
|
Update DNA to reflect latest style choices and then generate
|
|
new node path and add it to the scene graph
|
|
"""
|
|
# Determine dnaNode Class Type
|
|
nodeClass = DNAGetClassType(dnaNode)
|
|
# Did the user hit insert or space?
|
|
if hotKey:
|
|
# Yes, make a new copy of the dnaNode
|
|
dnaNode = dnaNode.__class__(dnaNode)
|
|
# And determine dnaNode type and perform any type specific updates
|
|
if nodeClass.eq(DNA_PROP):
|
|
dnaNode.setCode(self.getCurrent('prop_texture'))
|
|
elif nodeClass.eq(DNA_STREET):
|
|
dnaNode.setCode(self.getCurrent('street_texture'))
|
|
elif nodeClass.eq(DNA_FLAT_BUILDING):
|
|
# If insert, pick a new random style
|
|
if hotKey == 'insert':
|
|
self.setRandomBuildingStyle(dnaNode, dnaNode.getName())
|
|
# Get a new building width
|
|
self.setCurrent('building_width', self.getRandomWallWidth())
|
|
dnaNode.setWidth(self.getCurrent('building_width'))
|
|
|
|
# Add the DNA to the active parent
|
|
self.DNAParent.add(dnaNode)
|
|
# And create the geometry
|
|
newNodePath = dnaNode.traverse(self.NPParent, DNASTORE, 1)
|
|
# Update scene graph explorer
|
|
# self.panel.sceneGraphExplorer.update()
|
|
|
|
# Position it
|
|
if (hotKey is not None) & nodeClass.eq(DNA_PROP):
|
|
# If its a prop and a copy, place it based upon current
|
|
# mouse position
|
|
hitPt = self.getGridIntersectionPoint()
|
|
newNodePath.setPos(direct.grid, hitPt)
|
|
else:
|
|
# Place the new node path at the current grid origin
|
|
newNodePath.setPos(direct.grid,0,0,0)
|
|
# Initialize angle to match last object
|
|
newNodePath.setHpr(self.getLastAngle(), 0, 0)
|
|
# Now update DNA pos and hpr to reflect final pose
|
|
dnaNode.setPos(newNodePath.getPos())
|
|
dnaNode.setHpr(newNodePath.getHpr())
|
|
|
|
# Reset last Code (for autoPositionGrid)
|
|
if DNAClassEqual(dnaNode, DNA_STREET):
|
|
self.snapList = OBJECT_SNAP_POINTS[dnaNode.getCode()]
|
|
# Select the instance
|
|
direct.select(newNodePath)
|
|
# Update grid to get ready for the next object
|
|
self.autoPositionGrid()
|
|
|
|
def addGroup(self, nodePath):
|
|
""" Add a new DNA Node Group to the specified Node Path """
|
|
# Set the node path as the current parent
|
|
self.setActiveParent(nodePath)
|
|
# Add a new group to the selected parent
|
|
self.createNewGroup()
|
|
|
|
def addVisGroup(self, nodePath):
|
|
""" Add a new DNA Group to the specified Node Path """
|
|
# Set the node path as the current parent
|
|
self.setActiveParent(nodePath)
|
|
# Add a new group to the selected parent
|
|
self.createNewGroup(type = 'vis')
|
|
|
|
def createNewGroup(self, type = 'dna'):
|
|
""" Create a new DNA Node group under the active parent """
|
|
# Create a new DNA Node group
|
|
if type == 'dna':
|
|
newDNANode = DNANode('group_' + `self.getGroupNum()`)
|
|
else:
|
|
newDNANode = DNAVisGroup('VisGroup_' + `self.getGroupNum()`)
|
|
# Increment group counter
|
|
self.setGroupNum(self.getGroupNum() + 1)
|
|
# Add new DNA Node group to the current parent DNA Object
|
|
self.DNAParent.add(newDNANode)
|
|
# The new Node group becomes the active parent
|
|
self.DNAParent = newDNANode
|
|
# Traverse it to generate the new node path as a child of NPParent
|
|
newNodePath = self.DNAParent.traverse(self.NPParent, DNASTORE, 1)
|
|
# Update NPParent to point to the new node path
|
|
self.NPParent = newNodePath
|
|
# Update scene graph explorer
|
|
# self.panel.sceneGraphExplorer.update()
|
|
|
|
def addFlatBuilding(self, buildingType):
|
|
# Create new building
|
|
newDNAFlatBuilding = DNAFlatBuilding()
|
|
self.setRandomBuildingStyle(newDNAFlatBuilding,
|
|
name = buildingType + '_DNARoot')
|
|
# Initialize its position and hpr
|
|
newDNAFlatBuilding.setPos(VBase3(0))
|
|
newDNAFlatBuilding.setHpr(VBase3(0))
|
|
# Now place new building in the world
|
|
self.initDNANode(newDNAFlatBuilding)
|
|
|
|
def addLandmark(self, landmarkType):
|
|
# Record new landmark type
|
|
self.setCurrent('toon_landmark_texture', landmarkType)
|
|
# And create new landmark building
|
|
newDNALandmarkBuilding = DNALandmarkBuilding(landmarkType + '_DNARoot')
|
|
newDNALandmarkBuilding.setCode(landmarkType)
|
|
newDNALandmarkBuilding.setPos(VBase3(0))
|
|
newDNALandmarkBuilding.setHpr(VBase3(0))
|
|
newDNADoor = self.createDoor()
|
|
newDNALandmarkBuilding.add(newDNADoor)
|
|
# Now place new landmark building in the world
|
|
self.initDNANode(newDNALandmarkBuilding)
|
|
|
|
def addProp(self, propType):
|
|
# Record new prop type
|
|
self.setCurrent('prop_texture', propType)
|
|
# And create new prop
|
|
newDNAProp = DNAProp(propType + '_DNARoot')
|
|
newDNAProp.setCode(propType)
|
|
newDNAProp.setPos(VBase3(0))
|
|
newDNAProp.setHpr(VBase3(0))
|
|
# Now place new prop in the world
|
|
self.initDNANode(newDNAProp)
|
|
|
|
def addStreet(self, streetType):
|
|
# Record new street type
|
|
self.setCurrent('street_texture', streetType)
|
|
# And create new street
|
|
newDNAStreet = DNAStreet(streetType + '_DNARoot')
|
|
newDNAStreet.setCode(streetType)
|
|
newDNAStreet.setPos(VBase3(0))
|
|
newDNAStreet.setHpr(VBase3(0))
|
|
# Set street texture to neighborhood dependant texture
|
|
newDNAStreet.setStreetTexture(
|
|
'street_street_' + self.neighborhoodCode + '_tex')
|
|
newDNAStreet.setSidewalkTexture(
|
|
'street_sidewalk_' + self.neighborhoodCode + '_tex')
|
|
# Now place new street in the world
|
|
self.initDNANode(newDNAStreet)
|
|
|
|
def createCornice(self):
|
|
newDNACornice = DNACornice('cornice')
|
|
newDNACornice.setCode(self.getCurrent('cornice_texture'))
|
|
newDNACornice.setColor(self.getCurrent('cornice_color'))
|
|
return newDNACornice
|
|
|
|
def createDoor(self):
|
|
newDNADoor = DNADoor('door')
|
|
newDNADoor.setCode(self.getCurrent('door_texture'))
|
|
newDNADoor.setColor(self.getCurrent('door_color'))
|
|
return newDNADoor
|
|
|
|
def createWindows(self):
|
|
newDNAWindows = DNAWindows()
|
|
newDNAWindows.setCode(self.getCurrent('window_texture'))
|
|
newDNAWindows.setWindowCount(self.getCurrent('window_count'))
|
|
newDNAWindows.setColor(self.getCurrent('window_color'))
|
|
return newDNAWindows
|
|
|
|
def removeCornice(self, cornice, parent):
|
|
self.setCurrent('cornice_color', cornice.getColor())
|
|
DNARemoveChildOfClass(parent, DNA_CORNICE)
|
|
|
|
def removeLandmarkDoor(self, door, parent):
|
|
self.setCurrent('door_color', door.getColor())
|
|
DNARemoveChildOfClass(parent, DNA_DOOR)
|
|
|
|
def removeDoor(self, door, parent):
|
|
self.setCurrent('door_color', door.getColor())
|
|
DNARemoveChildOfClass(parent, DNA_DOOR)
|
|
|
|
def removeWindows(self, windows, parent):
|
|
# And record number of windows
|
|
self.setCurrent('window_color', windows.getColor())
|
|
self.setCurrent('window_count', windows.getWindowCount())
|
|
DNARemoveChildOfClass(parent, DNA_WINDOWS)
|
|
|
|
# LEVEL-OBJECT MODIFICATION FUNCTIONS
|
|
def levelHandleMouse3(self):
|
|
# Initialize dna target
|
|
self.DNATarget = None
|
|
|
|
# If nothing selected, just return
|
|
if not self.selectedNPRoot:
|
|
return
|
|
|
|
# Next check to see if the selected object is a DNA object
|
|
dnaObject = self.findDNANode(self.selectedNPRoot)
|
|
# Nope, not a DNA object, just return
|
|
if not dnaObject:
|
|
return
|
|
|
|
# Pick a menu based upon object type
|
|
if DNAClassEqual(dnaObject, DNA_FLAT_BUILDING):
|
|
# FLAT BUILDING OPERATIONS
|
|
menuMode, wallNum = self.getFlatBuildingMode(dnaObject)
|
|
# Find appropriate target
|
|
wall = self.getWall(dnaObject, wallNum)
|
|
# Record bldg/wall
|
|
self.lastBuilding = dnaObject
|
|
self.lastWall = wall
|
|
if string.find(menuMode,'wall') >= 0:
|
|
self.DNATarget = wall
|
|
self.DNATargetParent = dnaObject
|
|
elif string.find(menuMode,'door') >= 0:
|
|
self.DNATarget = DNAGetChildOfClass(wall, DNA_DOOR)
|
|
self.DNATargetParent = wall
|
|
elif string.find(menuMode, 'window') >= 0:
|
|
self.DNATarget = DNAGetChildOfClass(wall, DNA_WINDOWS)
|
|
self.DNATargetParent = wall
|
|
elif string.find(menuMode,'cornice') >= 0:
|
|
self.DNATarget = DNAGetChildOfClass(wall, DNA_CORNICE)
|
|
self.DNATargetParent = wall
|
|
else:
|
|
self.DNATarget = dnaObject
|
|
elif DNAClassEqual(dnaObject, DNA_PROP):
|
|
# PROP OPERATIONS
|
|
self.DNATarget = dnaObject
|
|
if direct.fControl:
|
|
menuMode = 'prop_color'
|
|
else:
|
|
menuMode = 'prop_texture'
|
|
elif DNAClassEqual(dnaObject, DNA_LANDMARK_BUILDING):
|
|
# LANDMARK BUILDING OPERATIONS
|
|
self.DNATarget = DNAGetChildOfClass(dnaObject, DNA_DOOR)
|
|
self.DNATargetParent = dnaObject
|
|
if direct.fAlt:
|
|
menuMode = 'door_orientation'
|
|
elif direct.fControl:
|
|
menuMode = 'door_color'
|
|
else:
|
|
menuMode = 'door_texture'
|
|
elif DNAClassEqual(dnaObject, DNA_STREET):
|
|
# STREET OPERATIONS
|
|
self.DNATarget = dnaObject
|
|
menuMode = 'street_texture'
|
|
|
|
# Now spawn apropriate menu task if menu selected
|
|
self.activeMenu = self.getMenu(menuMode)
|
|
# Set initial state
|
|
state = None
|
|
if self.DNATarget:
|
|
if string.find(menuMode,'texture') >= 0:
|
|
state = self.DNATarget.getCode()
|
|
elif string.find(menuMode, 'color') >= 0:
|
|
state = self.DNATarget.getColor()
|
|
self.panel.setCurrentColor(state)
|
|
self.panel.setResetColor(state)
|
|
elif string.find(menuMode, 'orientation') >= 0:
|
|
state = self.DNATarget.getCode()[-3:]
|
|
elif menuMode == 'building_width' >= 0:
|
|
state = self.DNATarget.getWidth()
|
|
elif menuMode == 'window_count' >= 0:
|
|
state = self.DNATarget.getWindowCount()
|
|
elif menuMode == 'building_style' >= 0:
|
|
# Extract the building style from the current building
|
|
state = DNAFlatBuildingStyle(building = self.DNATarget)
|
|
elif menuMode == 'wall_style' >= 0:
|
|
# Extract the wall style from the current wall
|
|
state = DNAWallStyle(wall = self.DNATarget)
|
|
self.activeMenu.setInitialState(state)
|
|
|
|
# Spawn active menu's tatsk
|
|
self.activeMenu.spawnPieMenuTask()
|
|
|
|
def getFlatBuildingMode(self, dnaObject):
|
|
# Where are we hitting the building?
|
|
hitPt = self.getWallIntersectionPoint()
|
|
wallNum = self.computeWallNum(dnaObject, hitPt)
|
|
if wallNum < 0:
|
|
# Do building related operations
|
|
if direct.fShift:
|
|
menuMode = 'building_type'
|
|
elif direct.fAlt:
|
|
menuMode = 'building_width'
|
|
else:
|
|
menuMode = 'building_style'
|
|
else:
|
|
# Otherwise, do wall specific operations
|
|
# Figure out where you are hitting on the wall
|
|
wallHeights, offsetList = DNAGetWallHeights(dnaObject)
|
|
# Find a normalized X and Z coordinate
|
|
xPt = hitPt[0]/dnaObject.getWidth()
|
|
# Adjust zPt depending on what wall you are pointing at
|
|
wallHeight = wallHeights[wallNum]
|
|
zPt = (hitPt[2] - offsetList[wallNum])/wallHeight
|
|
# Record current wall height
|
|
self.setCurrent('wall_height', wallHeight)
|
|
# Determine which zone you are pointing at
|
|
if (zPt > 0.8):
|
|
# Do cornice operations
|
|
if direct.fControl:
|
|
menuMode = 'cornice_color'
|
|
elif direct.fAlt:
|
|
menuMode = 'cornice_orientation'
|
|
else:
|
|
menuMode = 'cornice_texture'
|
|
elif ((xPt < 0.3) | (xPt > 0.7)):
|
|
# Do wall operations
|
|
if direct.fControl:
|
|
menuMode = 'wall_color'
|
|
elif direct.fAlt:
|
|
menuMode = 'wall_orientation'
|
|
elif direct.fShift:
|
|
menuMode = 'wall_texture'
|
|
else:
|
|
menuMode = 'wall_style'
|
|
elif (zPt < 0.4):
|
|
# Do door operations
|
|
if direct.fControl:
|
|
menuMode = 'door_color'
|
|
elif direct.fAlt:
|
|
menuMode = 'door_orientation'
|
|
else:
|
|
menuMode = 'door_texture'
|
|
else:
|
|
# Do window operations
|
|
if direct.fControl:
|
|
menuMode = 'window_color'
|
|
elif direct.fAlt:
|
|
menuMode = 'window_orientation'
|
|
elif direct.fShift:
|
|
menuMode = 'window_count'
|
|
else:
|
|
menuMode = 'window_texture'
|
|
return menuMode, wallNum
|
|
|
|
def levelHandleMouse3Up(self):
|
|
if self.activeMenu:
|
|
self.activeMenu.removePieMenuTask()
|
|
# Update panel color if appropriate
|
|
if self.DNATarget:
|
|
objClass = DNAGetClassType(self.DNATarget)
|
|
if ((objClass.eq(DNA_WALL)) |
|
|
(objClass.eq(DNA_WINDOWS)) |
|
|
(objClass.eq(DNA_DOOR)) |
|
|
(objClass.eq(DNA_CORNICE)) |
|
|
(objClass.eq(DNA_PROP))
|
|
):
|
|
self.panel.setCurrentColor(self.DNATarget.getColor())
|
|
|
|
def setDNATargetColor(self, color):
|
|
if self.DNATarget:
|
|
self.DNATarget.setColor(color)
|
|
self.replaceSelected()
|
|
|
|
def setDNATargetCode(self, type, code):
|
|
if (self.DNATarget != None) & (code != None):
|
|
# Update code
|
|
self.DNATarget.setCode(code)
|
|
elif (self.DNATarget != None) & (code == None):
|
|
# Delete object, record pertinant properties before
|
|
# you delete the object so you can restore them later
|
|
# Remove object
|
|
if (type == 'cornice'):
|
|
self.removeCornice(self.DNATarget, self.DNATargetParent)
|
|
elif (type == 'landmark_door'):
|
|
self.removeLandmarkDoor(self.DNATarget, self.DNATargetParent)
|
|
elif (type == 'doorToBeImplemented'):
|
|
self.removeDoor(self.DNATarget, self.DNATargetParent)
|
|
elif (type == 'windows'):
|
|
self.removeWindows(self.DNATarget, self.DNATargetParent)
|
|
# Clear out DNATarget
|
|
self.DNATarget = None
|
|
elif (self.DNATarget == None) & (code != None):
|
|
# Add new object
|
|
if (type == 'cornice'):
|
|
self.DNATarget = self.createCornice()
|
|
elif (type == 'landmark_door'):
|
|
self.DNATarget = self.createDoor()
|
|
elif (type == 'doorToBeImplemented'):
|
|
self.DNATarget = self.createDoor()
|
|
elif (type == 'windows'):
|
|
# Make sure window_count n.e. 0
|
|
if self.getCurrent('window_count') == 0:
|
|
self.setCurrent(
|
|
'window_count',
|
|
self.getRandomWindowCount())
|
|
# Now create the windows
|
|
self.DNATarget = self.createWindows()
|
|
self.DNATargetParent.add(self.DNATarget)
|
|
# Update visible representation
|
|
self.replaceSelected()
|
|
|
|
def setDNATargetOrientation(self, orientation):
|
|
if (self.DNATarget != None) & (orientation != None):
|
|
oldCode = self.DNATarget.getCode()[:-3]
|
|
self.DNATarget.setCode(oldCode + '_' + orientation)
|
|
self.replaceSelected()
|
|
|
|
def setBuildingStyle(self, style):
|
|
if (self.DNATarget != None) & (style != None):
|
|
self.styleManager.setDNAFlatBuildingStyle(
|
|
self.DNATarget, style,
|
|
width = self.DNATarget.getWidth(),
|
|
name = self.DNATarget.getName())
|
|
# MRM: Need to disable dna store warning
|
|
self.replaceSelected()
|
|
# Re-add replication hooks so we get right kind of copy
|
|
self.addReplicationHooks(self.DNATarget)
|
|
|
|
def setBuildingType(self, type):
|
|
print 'setBuildingType: ', `type`
|
|
|
|
def setBuildingWidth(self, width):
|
|
if self.DNATarget:
|
|
self.DNATarget.setWidth(width)
|
|
self.replaceSelected()
|
|
|
|
def setWindowCount(self, count):
|
|
if (self.DNATarget != None) & (count != 0):
|
|
self.DNATarget.setWindowCount(count)
|
|
elif (self.DNATarget != None) & (count == 0):
|
|
# Remove windows and clear out DNATarget
|
|
self.removeWindows(self.DNATarget, self.DNATargetParent)
|
|
self.DNATarget = None
|
|
elif (self.DNATarget == None) & (count != 0):
|
|
self.DNATarget = self.createWindows()
|
|
self.DNATargetParent.add(self.DNATarget)
|
|
self.replaceSelected()
|
|
|
|
def setWallStyle(self, style):
|
|
if (self.DNATarget != None) & (style != None):
|
|
self.styleManager.setDNAWallStyle(
|
|
self.DNATarget, style,
|
|
self.DNATarget.getHeight())
|
|
self.replaceSelected()
|
|
|
|
def setNPColor(self, nodePath):
|
|
""" This is used to set color of dnaNode subparts """
|
|
def updateDNANodeColor(color, s = self, np = nodePath):
|
|
# Update node color in DNASTORE here
|
|
# dnaNode = s.findDNANode(np)
|
|
pass
|
|
esg = EntryScale.setColor(nodePath, updateDNANodeColor)
|
|
|
|
# SELECTION FUNCTIONS
|
|
def selectedNodePathHook(self, nodePath):
|
|
"""
|
|
Hook called upon selection of a node path used to restrict
|
|
selection to DNA Objects. Press control to select any type of
|
|
DNA Object, with no control key pressed, hook selects only
|
|
DNA Root objects
|
|
"""
|
|
# Clear out old root variables
|
|
self.selectedDNARoot = None
|
|
self.selectedNPRoot = None
|
|
# Now process newly selected node path
|
|
dnaParent = None
|
|
dnaNode = self.findDNANode(nodePath)
|
|
if direct.fControl:
|
|
# Is the current node a DNA Object?
|
|
if not dnaNode:
|
|
# No it isn't, look for a parent DNA object
|
|
dnaParent = self.findDNAParent(nodePath.getParent())
|
|
else:
|
|
# Is the current node a DNA Root object?
|
|
if nodePath.getName()[-8:] != '_DNARoot':
|
|
# No it isn't, look for a parent DNA Root object
|
|
dnaParent = self.findDNARoot(nodePath.getParent())
|
|
# Do we need to switch selection to a parent object?
|
|
if dnaParent:
|
|
# Yes, deselect currently selected node path
|
|
direct.deselect(nodePath)
|
|
# And select parent
|
|
direct.select(dnaParent, direct.fShift)
|
|
else:
|
|
# We got a valid node path/DNA object, continue
|
|
self.selectedNPRoot = nodePath
|
|
self.selectedDNARoot = dnaNode
|
|
# Reset last Code (for autoPositionGrid)
|
|
if DNAClassEqual(dnaNode, DNA_STREET):
|
|
self.snapList = OBJECT_SNAP_POINTS[dnaNode.getCode()]
|
|
|
|
def deselectedNodePathHook(self, nodePath):
|
|
# Clear out old root variables
|
|
self.selectedDNARoot = None
|
|
self.selectedNPRoot = None
|
|
|
|
def findDNAParent(self, nodePath):
|
|
""" Walk up a node path's ancestry looking for its DNA Root """
|
|
# Check to see if current node is a dna object
|
|
if self.findDNANode(nodePath):
|
|
# Its a root!
|
|
return nodePath
|
|
else:
|
|
# If reached the top: fail
|
|
if not nodePath.hasParent():
|
|
return 0
|
|
else:
|
|
# Try parent
|
|
return self.findDNAParent(nodePath.getParent())
|
|
|
|
def findDNARoot(self, nodePath):
|
|
""" Walk up a node path's ancestry looking for its DNA Root """
|
|
# Check current node's name for root marker
|
|
if (nodePath.getName()[-8:] == '_DNARoot'):
|
|
# Its a root!
|
|
return nodePath
|
|
else:
|
|
# If reached the top: fail
|
|
if not nodePath.hasParent():
|
|
return None
|
|
else:
|
|
# Try parent
|
|
return self.findDNARoot(nodePath.getParent())
|
|
|
|
# MANIPULATION FUNCTIONS
|
|
def keyboardRotateSelected(self, arrowDirection):
|
|
""" Rotate selected objects using arrow keys """
|
|
if ((arrowDirection == 'left') | (arrowDirection == 'up')):
|
|
self.setLastAngle(self.getLastAngle() + SNAP_ANGLE)
|
|
else:
|
|
self.setLastAngle(self.getLastAngle() - SNAP_ANGLE)
|
|
|
|
if (self.getLastAngle() < -180.0):
|
|
self.setLastAngle(self.getLastAngle() + 360.0)
|
|
elif (self.getLastAngle() > 180.0):
|
|
self.setLastAngle(self.getLastAngle() - 360.0)
|
|
# Move selected objects
|
|
for selectedNode in direct.selected:
|
|
selectedNode.setHpr(self.getLastAngle(), 0, 0)
|
|
# Snap objects to grid and update DNA if necessary
|
|
self.updateSelectedPose()
|
|
|
|
def keyboardTranslateSelected(self, arrowDirection):
|
|
gridToCamera = direct.grid.getMat(direct.camera)
|
|
camXAxis = gridToCamera.xformVec(X_AXIS)
|
|
xxDot = camXAxis.dot(X_AXIS)
|
|
xzDot = camXAxis.dot(Z_AXIS)
|
|
|
|
# what is the current grid spacing?
|
|
deltaMove = direct.grid.gridSpacing
|
|
|
|
# Compute the specified delta
|
|
deltaPos = Vec3(0)
|
|
if (abs(xxDot) > abs(xzDot)):
|
|
if (xxDot < 0.0):
|
|
deltaMove = -deltaMove
|
|
# Compute delta
|
|
if (arrowDirection == 'right'):
|
|
deltaPos.setX(deltaPos[0] + deltaMove)
|
|
elif (arrowDirection == 'left'):
|
|
deltaPos.setX(deltaPos[0] - deltaMove)
|
|
elif (arrowDirection == 'up'):
|
|
deltaPos.setY(deltaPos[1] + deltaMove)
|
|
elif (arrowDirection == 'down'):
|
|
deltaPos.setY(deltaPos[1] - deltaMove)
|
|
else:
|
|
if (xzDot < 0.0):
|
|
deltaMove = -deltaMove
|
|
# Compute delta
|
|
if (arrowDirection == 'right'):
|
|
deltaPos.setY(deltaPos[1] - deltaMove)
|
|
elif (arrowDirection == 'left'):
|
|
deltaPos.setY(deltaPos[1] + deltaMove)
|
|
elif (arrowDirection == 'up'):
|
|
deltaPos.setX(deltaPos[0] + deltaMove)
|
|
elif (arrowDirection == 'down'):
|
|
deltaPos.setX(deltaPos[0] - deltaMove)
|
|
|
|
# Move selected objects
|
|
for selectedNode in direct.selected:
|
|
# Move it
|
|
selectedNode.setPos(direct.grid,
|
|
selectedNode.getPos(direct.grid) + deltaPos)
|
|
# Snap objects to grid and update DNA if necessary
|
|
self.updateSelectedPose()
|
|
|
|
def keyboardXformSelected(self, arrowDirection):
|
|
if direct.fControl:
|
|
self.keyboardRotateSelected(arrowDirection)
|
|
else:
|
|
self.keyboardTranslateSelected(arrowDirection)
|
|
|
|
# VISIBILITY FUNCTIONS
|
|
def editDNAVisGroups(self):
|
|
visGroups = self.getDNAVisGroups(self.NPToplevel)
|
|
if visGroups:
|
|
self.vgpanel = VisGroupsEditor(self, visGroups)
|
|
else:
|
|
showinfo('Vis Groups Editor','No DNA Vis Groups Found!')
|
|
|
|
def getDNAVisGroups(self, nodePath):
|
|
""" Find the highest level vis groups in the scene graph """
|
|
dnaNode = self.findDNANode(nodePath)
|
|
if dnaNode:
|
|
if DNAClassEqual(dnaNode, DNA_VIS_GROUP):
|
|
return [[nodePath, dnaNode]]
|
|
childVisGroups = []
|
|
children = nodePath.getChildrenAsList()
|
|
for child in children:
|
|
childVisGroups = (childVisGroups + self.getDNAVisGroups(child))
|
|
return childVisGroups
|
|
|
|
def showGrid(self,flag):
|
|
""" toggle direct grid """
|
|
if flag:
|
|
direct.grid.enable()
|
|
else:
|
|
direct.grid.disable()
|
|
|
|
# LEVEL MAP/MARKER FUNCTIONS
|
|
def createLevelMaps(self):
|
|
"""
|
|
Load up the various neighborhood maps
|
|
"""
|
|
# For neighborhood maps
|
|
self.levelMap = hidden.attachNewNode('level-map')
|
|
self.activeMap = None
|
|
self.mapDictionary = {}
|
|
for neighborhood in NEIGHBORHOODS:
|
|
self.createMap(neighborhood)
|
|
|
|
def createMap(self, neighborhood):
|
|
map = loader.loadModel('models/level_editor/' + neighborhood +
|
|
'_layout')
|
|
map.getBottomArc().setTransition(TransparencyTransition(1))
|
|
map.setColor(Vec4(1,1,1,.4))
|
|
self.mapDictionary[neighborhood] = map
|
|
# Make sure this item isn't pickable
|
|
direct.manipulationControl.addUnpickable(neighborhood + '_layout')
|
|
|
|
def selectMap(self, neighborhood):
|
|
if self.activeMap:
|
|
self.activeMap.reparentTo(hidden)
|
|
self.activeMap = self.mapDictionary[neighborhood]
|
|
self.activeMap.reparentTo(self.levelMap)
|
|
|
|
def toggleMapVis(self, flag):
|
|
if flag:
|
|
self.levelMap.reparentTo(direct.group)
|
|
else:
|
|
self.levelMap.reparentTo(hidden)
|
|
|
|
def createInsertionMarker(self):
|
|
self.insertionMarker = LineNodePath(self)
|
|
self.insertionMarker.lineNode.setName('insertionMarker')
|
|
self.insertionMarker.setColor(VBase4(0.785, 0.785, 0.5,1))
|
|
self.insertionMarker.setThickness(1)
|
|
self.insertionMarker.reset()
|
|
self.insertionMarker.moveTo(-75,0,0)
|
|
self.insertionMarker.drawTo(75,0,0)
|
|
self.insertionMarker.moveTo(0,-75,0)
|
|
self.insertionMarker.drawTo(0,75,0)
|
|
self.insertionMarker.moveTo(0,0,-75)
|
|
self.insertionMarker.drawTo(0,0,75)
|
|
self.insertionMarker.create()
|
|
|
|
def spawnInsertionMarkerTask(self):
|
|
t = Task.Task(self.insertionMarkerTask)
|
|
taskMgr.spawnTaskNamed(t, 'insertionMarkerTask')
|
|
|
|
def insertionMarkerTask(self, state):
|
|
self.insertionMarker.setPosHpr(direct.grid, 0,0,0, 0,0,0)
|
|
# MRM: Why is this necessary?
|
|
self.insertionMarker.setScale(1,1,1)
|
|
return Task.cont
|
|
|
|
# UTILITY FUNCTIONS
|
|
def getRandomDictionaryEntry(self,dict):
|
|
numKeys = len(dict)
|
|
if numKeys > 0:
|
|
keys = dict.keys()
|
|
key = keys[randint(0,numKeys - 1)]
|
|
return dict[key]
|
|
else:
|
|
return None
|
|
|
|
def getRandomWindowCount(self):
|
|
if ((self.lastWall != None) & (self.lastBuilding != None)):
|
|
h = ROUND_INT(self.lastWall.getHeight())
|
|
w = ROUND_INT(self.lastBuilding.getWidth())
|
|
# Otherwise....
|
|
if w == 5:
|
|
# 5 ft walls can have 1 window
|
|
return 1
|
|
elif h == 10:
|
|
# All other 10 ft high bldgs can have 1 or 2
|
|
return randint(1,2)
|
|
else:
|
|
# All others can have 1 - 4
|
|
return randint(1,4)
|
|
else:
|
|
return 1
|
|
|
|
def autoPositionGrid(self):
|
|
# Move grid to prepare for placement of next object
|
|
selectedNode = direct.selected.last
|
|
if selectedNode:
|
|
dnaNode = self.findDNANode(selectedNode)
|
|
if dnaNode == None:
|
|
return
|
|
nodeClass = DNAGetClassType(dnaNode)
|
|
deltaPos = Point3(20,0,0)
|
|
deltaHpr = VBase3(0)
|
|
if nodeClass.eq(DNA_FLAT_BUILDING):
|
|
deltaPos.setX(dnaNode.getWidth())
|
|
elif nodeClass.eq(DNA_STREET):
|
|
objectCode = dnaNode.getCode()
|
|
deltas = self.getNextSnapPoint()
|
|
deltaPos.assign(deltas[0])
|
|
deltaHpr.assign(deltas[1])
|
|
elif nodeClass.eq(DNA_LANDMARK_BUILDING):
|
|
objectCode = dnaNode.getCode()
|
|
if objectCode[-2:-1] == 'A':
|
|
deltaPos.setX(25.0)
|
|
elif objectCode[-2:-1] == 'B':
|
|
deltaPos.setX(15.0)
|
|
elif objectCode[-2:-1] == 'C':
|
|
deltaPos.setX(20.0)
|
|
|
|
# Position grid for placing next object
|
|
# Eventually we need to setHpr too
|
|
taskMgr.removeTasksNamed('autoPositionGrid')
|
|
t = direct.grid.lerpPosHpr(
|
|
deltaPos, deltaHpr, 0.25,
|
|
other = selectedNode,
|
|
blendType = 'easeInOut',
|
|
task = 'autoPositionGrid')
|
|
t.deltaPos = deltaPos
|
|
t.deltaHpr = deltaHpr
|
|
t.selectedNode = selectedNode
|
|
t.uponDeath = self.autoPositionCleanup
|
|
|
|
# Also move the camera
|
|
taskMgr.removeTasksNamed('autoMoveDelay')
|
|
handlesToCam = direct.widget.getPos(direct.camera)
|
|
handlesToCam = handlesToCam * ( direct.dr.near/handlesToCam[1])
|
|
if ((abs(handlesToCam[0]) > (direct.dr.nearWidth * 0.4)) |
|
|
(abs(handlesToCam[2]) > (direct.dr.nearHeight * 0.4))):
|
|
taskMgr.removeTasksNamed('manipulateCamera')
|
|
direct.cameraControl.centerCamIn(direct.dr, 0.5)
|
|
|
|
def autoPositionCleanup(self,state):
|
|
direct.grid.setPosHpr(state.selectedNode, state.deltaPos,
|
|
state.deltaHpr)
|
|
if direct.grid.getHprSnap():
|
|
# Clean up grid angle
|
|
direct.grid.setH(ROUND_TO(direct.grid.getH(), SNAP_ANGLE))
|
|
|
|
def getNextSnapPoint(self):
|
|
""" Pull next pos hpr deltas off of snap list then rotate list """
|
|
if self.snapList:
|
|
deltas = self.snapList[0]
|
|
# Rotate list by one
|
|
self.snapList = self.snapList[1:] + self.snapList[:1]
|
|
return deltas
|
|
else:
|
|
return (ZERO_VEC, ZERO_VEC)
|
|
|
|
def getWallIntersectionPoint(self):
|
|
"""
|
|
Return point of intersection between building's wall and line from cam
|
|
through mouse. Return false, if nothing selected
|
|
"""
|
|
selectedNode = direct.selected.last
|
|
if not selectedNode:
|
|
return 0
|
|
# Find mouse point on near plane
|
|
mouseX = direct.dr.mouseX
|
|
mouseY = direct.dr.mouseY
|
|
nearX = (math.tan(deg2Rad(direct.dr.fovH)/2.0) *
|
|
mouseX * direct.dr.near)
|
|
nearZ = (math.tan(deg2Rad(direct.dr.fovV)/2.0) *
|
|
mouseY * direct.dr.near)
|
|
# Initialize points
|
|
mCam2Wall = direct.camera.getMat(selectedNode)
|
|
mouseOrigin = Point3(0)
|
|
mouseOrigin.assign(mCam2Wall.getRow3(3))
|
|
mouseDir = Vec3(0)
|
|
mouseDir.set(nearX, direct.dr.near, nearZ)
|
|
mouseDir.assign(mCam2Wall.xformVec(mouseDir))
|
|
# Calc intersection point
|
|
return planeIntersect(mouseOrigin, mouseDir, ZERO_POINT, NEG_Y_AXIS)
|
|
|
|
def getGridIntersectionPoint(self):
|
|
"""
|
|
Return point of intersection between ground plane and line from cam
|
|
through mouse. Return false, if nothing selected
|
|
"""
|
|
# Find mouse point on near plane
|
|
mouseX = direct.dr.mouseX
|
|
mouseY = direct.dr.mouseY
|
|
nearX = (math.tan(deg2Rad(direct.dr.fovH)/2.0) *
|
|
mouseX * direct.dr.near)
|
|
nearZ = (math.tan(deg2Rad(direct.dr.fovV)/2.0) *
|
|
mouseY * direct.dr.near)
|
|
# Initialize points
|
|
mCam2Grid = direct.camera.getMat(direct.grid)
|
|
mouseOrigin = Point3(0)
|
|
mouseOrigin.assign(mCam2Grid.getRow3(3))
|
|
mouseDir = Vec3(0)
|
|
mouseDir.set(nearX, direct.dr.near, nearZ)
|
|
mouseDir.assign(mCam2Grid.xformVec(mouseDir))
|
|
# Calc intersection point
|
|
return planeIntersect(mouseOrigin, mouseDir, ZERO_POINT, Z_AXIS)
|
|
|
|
def plantSelectedNodePath(self):
|
|
""" Move selected object to intersection point of cursor on grid """
|
|
selectedNode = direct.selected.last
|
|
if selectedNode:
|
|
# Where is the mouse relative to the grid?
|
|
hitPt = self.getGridIntersectionPoint()
|
|
selectedNode.setPos(direct.grid, hitPt)
|
|
dnaNode = self.findDNANode(selectedNode)
|
|
if dnaNode:
|
|
# Update props placement to reflect current mouse position
|
|
dnaNode.setPos(direct.selected.last.getPos())
|
|
|
|
def jumpToInsertionPoint(self):
|
|
""" Move selected object to insertion point """
|
|
selectedNode = direct.selected.last
|
|
if selectedNode:
|
|
# Check if its a dna node
|
|
dnaNode = self.findDNANode(selectedNode)
|
|
if dnaNode:
|
|
# Place the new node path at the current grid origin
|
|
selectedNode.setPos(direct.grid,0,0,0)
|
|
# Initialize angle to match last object
|
|
selectedNode.setHpr(self.getLastAngle(), 0, 0)
|
|
# Now update DNA pos and hpr to reflect final pose
|
|
dnaNode.setPos(selectedNode.getPos())
|
|
dnaNode.setHpr(selectedNode.getHpr())
|
|
# Update grid to get ready for the next object
|
|
self.autoPositionGrid()
|
|
|
|
# STYLE/DNA FILE FUNCTIONS
|
|
def loadSpecifiedDNAFile(self):
|
|
f = Filename(self.styleManager.stylePathPrefix +
|
|
'/alpha/DIRECT/LevelEditor/DNAFiles')
|
|
path = os.path.join(f.toOsSpecific(), self.outputDir)
|
|
if not os.path.isdir(path):
|
|
print 'LevelEditor Warning: Invalid default DNA directory!'
|
|
print 'Using: C:\\'
|
|
path = 'C:\\'
|
|
dnaFilename = askopenfilename(
|
|
defaultextension = '.dna',
|
|
filetypes = (('DNA Files', '*.dna'),('All files', '*')),
|
|
initialdir = path,
|
|
title = 'Load DNA File',
|
|
parent = self.panel.component('hull'))
|
|
if dnaFilename:
|
|
self.loadDNAFromFile(dnaFilename)
|
|
|
|
def saveToSpecifiedDNAFile(self):
|
|
f = Filename(self.styleManager.stylePathPrefix +
|
|
'/alpha/DIRECT/LevelEditor/DNAFiles')
|
|
path = os.path.join(f.toOsSpecific(), self.outputDir)
|
|
if not os.path.isdir(path):
|
|
print 'LevelEditor Warning: Invalid DNA save directory!'
|
|
print 'Using: C:\\'
|
|
path = 'C:\\'
|
|
dnaFilename = asksaveasfilename(
|
|
defaultextension = '.dna',
|
|
filetypes = (('DNA Files', '*.dna'),('All files', '*')),
|
|
initialdir = path,
|
|
title = 'Save DNA File as',
|
|
parent = self.panel.component('hull'))
|
|
if dnaFilename:
|
|
self.outputDNA(dnaFilename)
|
|
|
|
def loadDNAFromFile(self, filename):
|
|
# Reset level, destroying existing scene/DNA hierarcy
|
|
self.reset(fDeleteToplevel = 1, fCreateToplevel = 0)
|
|
# Now load in new file
|
|
newNPToplevel = loadDNAFile(DNASTORE, filename, CSDefault, 1)
|
|
# Make sure the topmost file DNA object gets put under DNARoot
|
|
newDNAToplevel = self.findDNANode(newNPToplevel)
|
|
# Update toplevel variables
|
|
self.createToplevel(newDNAToplevel, newNPToplevel)
|
|
|
|
def outputDNADefaultFile(self):
|
|
f = Filename(self.styleManager.stylePathPrefix +
|
|
'/alpha/DIRECT/LevelEditor/DNAFiles')
|
|
file = os.path.join(f.toOsSpecific(), self.outputDir, self.outputFile)
|
|
self.outputDNA(file)
|
|
|
|
def outputDNA(self,filename):
|
|
print 'Saving DNA to: ', filename
|
|
self.DNAData.writeDna(Filename(filename),
|
|
Notify.out(),DNASTORE)
|
|
|
|
def saveColor(self):
|
|
self.appendColorToColorPaletteFile(self.panel.colorEntry.get())
|
|
|
|
def appendColorToColorPaletteFile(self, color):
|
|
obj = self.DNATarget
|
|
if obj:
|
|
classType = DNAGetClassType(obj)
|
|
if classType.eq(DNA_WALL):
|
|
tag = 'wall_color:'
|
|
elif classType.eq(DNA_WINDOWS):
|
|
tag = 'window_color:'
|
|
elif classType.eq(DNA_DOOR):
|
|
tag = 'door_color:'
|
|
elif classType.eq(DNA_CORNICE):
|
|
tag = 'cornice_color:'
|
|
elif classType.eq(DNA_PROP):
|
|
tag = 'prop_color:'
|
|
else:
|
|
return
|
|
# Valid type, add color to file
|
|
filename = self.neighborhood + '_colors.txt'
|
|
fname = Filename(self.styleManager.stylePathPrefix +
|
|
'/alpha/DIRECT/LevelEditor/StyleFiles/' +
|
|
filename)
|
|
f = open(fname.toOsSpecific(), 'a')
|
|
f.write('%s Vec4(%.2f, %.2f, %.2f, 1.0)\n' %
|
|
(tag,
|
|
color[0]/255.0,
|
|
color[1]/255.0,
|
|
color[2]/255.0))
|
|
f.close()
|
|
|
|
def saveWallStyle(self):
|
|
if self.lastWall:
|
|
# Valid wall, add style to file
|
|
filename = self.neighborhood + '_wall_styles.txt'
|
|
fname = Filename(self.styleManager.stylePathPrefix +
|
|
'/alpha/DIRECT/LevelEditor/StyleFiles/' +
|
|
filename)
|
|
f = open(fname.toOsSpecific(), 'a')
|
|
# Add a blank line
|
|
f.write('\n')
|
|
# Now output style details to file
|
|
style = DNAWallStyle(wall = self.lastWall)
|
|
style.output(f)
|
|
# Close the file
|
|
f.close()
|
|
|
|
def saveBuildingStyle(self):
|
|
if self.lastBuilding:
|
|
# Valid wall, add style to file
|
|
filename = self.neighborhood + '_building_styles.txt'
|
|
fname = Filename(self.styleManager.stylePathPrefix +
|
|
'/alpha/DIRECT/LevelEditor/StyleFiles/' +
|
|
filename)
|
|
f = open(fname.toOsSpecific(), 'a')
|
|
# Add a blank line
|
|
f.write('\n')
|
|
# Now output style details to file
|
|
style = DNAFlatBuildingStyle(building = self.lastBuilding)
|
|
style.output(f)
|
|
# Close the file
|
|
f.close()
|
|
|
|
# GET/SET
|
|
# DNA Object elements
|
|
def getWall(self, dnaFlatBuilding, wallNum):
|
|
wallCount = 0
|
|
for i in range(dnaFlatBuilding.getNumChildren()):
|
|
child = dnaFlatBuilding.at(i)
|
|
if DNAClassEqual(child, DNA_WALL):
|
|
if wallCount == wallNum:
|
|
return child
|
|
wallCount = wallCount + 1
|
|
# Not found
|
|
return None
|
|
|
|
def computeWallNum(self, aDNAFlatBuilding, hitPt):
|
|
"""
|
|
Given a hitPt, return wall number if cursor is over building
|
|
Return -1 if cursor is outside of building
|
|
"""
|
|
xPt = hitPt[0]
|
|
zPt = hitPt[2]
|
|
# Left or right of building
|
|
if ((xPt < 0) | (xPt > aDNAFlatBuilding.getWidth())):
|
|
return -1
|
|
# Below the building
|
|
if zPt < 0:
|
|
return -1
|
|
# Above z = 0 and within wall width, check height of walls
|
|
heightList, offsetList = DNAGetWallHeights(aDNAFlatBuilding)
|
|
wallNum = 0
|
|
for i in range(len(heightList)):
|
|
# Compute top of wall segment
|
|
top = offsetList[i] + heightList[i]
|
|
if zPt < top:
|
|
return wallNum
|
|
wallNum = wallNum + 1
|
|
return -1
|
|
|
|
def getWindowCount(self, dnaWall):
|
|
windowCount = 0
|
|
for i in range(dnaWall.getNumChildren()):
|
|
child = dnaWall.at(i)
|
|
if DNAClassEqual(child, DNA_WINDOWS):
|
|
windowCount = windowCount + 1
|
|
# Not found
|
|
return windowCount
|
|
|
|
# Style manager edit mode
|
|
def setEditMode(self, neighborhood):
|
|
self.neighborhood = neighborhood
|
|
self.neighborhoodCode = NEIGHBORHOOD_CODES[self.neighborhood]
|
|
self.outputFile = neighborhood + '_working.dna'
|
|
if neighborhood == 'toontown_central':
|
|
self.outputDir = 'ToontownCentral'
|
|
elif neighborhood == 'donalds_dock':
|
|
self.outputDir = 'DonaldsDock'
|
|
elif neighborhood == 'minnies_melody_land':
|
|
self.outputDir = 'MinniesMelodyLand'
|
|
elif neighborhood == 'the_burrgh':
|
|
self.outputDir = 'TheBurrrgh'
|
|
self.panel.editMenu.selectitem(neighborhood)
|
|
self.styleManager.setEditMode(neighborhood)
|
|
self.selectMap(neighborhood)
|
|
|
|
def getEditMode(self):
|
|
return self.styleManager.getEditMode()
|
|
|
|
# Level Style Attributes
|
|
def __getitem__(self,attribute):
|
|
""" Return top level entry in attribute dictionary """
|
|
return self.styleManager.attributeDictionary[attribute]
|
|
|
|
def getAttribute(self, attribute):
|
|
""" Return specified attribute for current neighborhood """
|
|
return self.styleManager.getAttribute(attribute)
|
|
|
|
def getCurrent(self, attribute):
|
|
""" Return neighborhood's current selection for specified attribute """
|
|
return self.getAttribute(attribute).getCurrent()
|
|
|
|
def setCurrent(self, attribute, newCurrent):
|
|
""" Set neighborhood's current selection for specified attribute """
|
|
self.getAttribute(attribute).setCurrent(newCurrent, fEvent = 0)
|
|
|
|
def getMenu(self, attribute):
|
|
""" Return neighborhood's Pie Menu object for specified attribute """
|
|
return self.getAttribute(attribute).getMenu()
|
|
|
|
def getDict(self, attribute):
|
|
""" Return neighborhood's Dictionary for specified attribute """
|
|
return self.getAttribute(attribute).getDict()
|
|
|
|
def getList(self, attribute):
|
|
""" Return neighborhood's List for specified attribute """
|
|
return self.getAttribute(attribute).getList()
|
|
|
|
# DNA variables
|
|
def getDNAData(self):
|
|
return self.DNAData
|
|
|
|
def getDNAToplevel(self):
|
|
return self.DNAToplevel
|
|
|
|
def getDNAParent(self):
|
|
return self.DNAParent
|
|
|
|
def getDNATarget(self):
|
|
return self.DNATarget
|
|
|
|
# Node Path variables
|
|
def getNPToplevel(self):
|
|
return self.NPToplevel
|
|
|
|
def getNPParent(self):
|
|
return self.NPParent
|
|
|
|
# Count of groups added to level
|
|
def setGroupNum(self,num):
|
|
self.groupNum = num
|
|
|
|
def getGroupNum(self):
|
|
return self.groupNum
|
|
|
|
# Angle of last object added to level
|
|
def setLastAngle(self, angle):
|
|
self.lastAngle = angle
|
|
|
|
def getLastAngle(self):
|
|
return self.lastAngle
|
|
|
|
class LevelStyleManager:
|
|
"""Class which reads in style files and manages class variables"""
|
|
def __init__(self):
|
|
# Used to locate the alpha mount on windows (i.e. on what drive)
|
|
self.stylePathPrefix = base.config.GetString('style-path-prefix', '')
|
|
# The main dictionary holding all attribute objects
|
|
self.attributeDictionary = {}
|
|
# Create the style samples
|
|
self.createWallStyleAttributes()
|
|
self.createBuildingStyleAttributes()
|
|
self.createColorAttributes()
|
|
self.createDNAAttributes()
|
|
self.createMiscAttributes()
|
|
|
|
# WALL STYLE FUNCTIONS
|
|
def createWallStyleAttributes(self):
|
|
"""
|
|
Create a wallStyle entry in the attribute dictionary
|
|
This will be a dictionary of style attributes, one per neighborhood
|
|
"""
|
|
# First create an empty dictionary
|
|
dict = self.attributeDictionary['wall_style'] = {}
|
|
# Create a attribute object for each neighborhood
|
|
for neighborhood in NEIGHBORHOODS:
|
|
attribute = LevelAttribute('wall_style')
|
|
attribute.setDict(
|
|
# Create a wall style dictionary for each neighborhood
|
|
self.createWallStyleDictionary(neighborhood))
|
|
# Using this dictionary, create color pie menus
|
|
attribute.setMenu(
|
|
self.createWallStyleMenu(neighborhood, attribute.getDict()))
|
|
dict[neighborhood] = attribute
|
|
|
|
def createWallStyleDictionary(self, neighborhood):
|
|
"""
|
|
Create a dictionary of wall styles for a neighborhood
|
|
"""
|
|
filename = neighborhood + '_wall_styles.txt'
|
|
print 'Loading wall styles from: ' + filename
|
|
styleData = self.getStyleFileData(filename)
|
|
return self.initializeWallStyleDictionary(styleData, neighborhood)
|
|
|
|
def initializeWallStyleDictionary(self, styleData, neighborhood):
|
|
"""
|
|
Fill in the wall style dictionary with data from the style file
|
|
"""
|
|
styleDictionary = {}
|
|
styleCount = 0
|
|
code = NEIGHBORHOOD_CODES[neighborhood]
|
|
while styleData:
|
|
l = styleData[0]
|
|
if l == 'wallStyle':
|
|
# Start of new style, strip off first line then extract style
|
|
style, styleData = self.extractWallStyle(styleData)
|
|
style.name = code + '_wall_style_' + `styleCount`
|
|
# Store style in dictionary
|
|
styleDictionary[style.name] = style
|
|
styleCount = styleCount + 1
|
|
# Move to next line
|
|
styleData = styleData[1:]
|
|
return styleDictionary
|
|
|
|
def extractWallStyle(self, styleData):
|
|
"""
|
|
Pull out one style from a list of style data. Will keep
|
|
processing data until endWallStyle of end of data is reached.
|
|
Returns a wall style and remaining styleData.
|
|
"""
|
|
# Create default style
|
|
style = DNAWallStyle()
|
|
# Strip off first line
|
|
styleData = styleData[1:]
|
|
while styleData:
|
|
l = styleData[0]
|
|
if l == 'endWallStyle':
|
|
# End of style found, break out of while loop
|
|
# Note, endWallStyle line is *not* stripped off
|
|
return style, styleData
|
|
else:
|
|
pair = map(string.strip, l.split(':'))
|
|
if style.__dict__.has_key(pair[0]):
|
|
# Convert colors and count strings to numerical values
|
|
if ((string.find(pair[0],'_color') >= 0) |
|
|
(string.find(pair[0],'_count') >= 0)):
|
|
style[pair[0]] = eval(pair[1])
|
|
else:
|
|
style[pair[0]] = pair[1]
|
|
else:
|
|
print 'getStyleDictionaryFromStyleData: Invalid Key'
|
|
print pair[0]
|
|
styleData = styleData[1:]
|
|
# No end of style found, return style data as is
|
|
return style, None
|
|
|
|
def createWallStyleMenu(self, neighborhood, dictionary):
|
|
"""
|
|
Create a wall style pie menu
|
|
"""
|
|
numItems = len(dictionary)
|
|
newStyleMenu = hidden.attachNewNode(neighborhood + '_style_menu')
|
|
radius = 0.7
|
|
angle = deg2Rad(360.0/numItems)
|
|
keys = dictionary.keys()
|
|
keys.sort()
|
|
styles = map(lambda x, d = dictionary: d[x], keys)
|
|
sf = 0.03
|
|
aspectRatio = (direct.dr.width/float(direct.dr.height))
|
|
for i in range(numItems):
|
|
# Get the node
|
|
node = self.createWallStyleSample(styles[i])
|
|
bounds = node.getBounds()
|
|
center = bounds.getCenter()
|
|
center = center * sf
|
|
# Reposition it
|
|
node.setPos((radius * math.cos(i * angle)) - center[0],
|
|
0.0,
|
|
((radius * aspectRatio * math.sin(i * angle)) -
|
|
center[2]))
|
|
# Scale it
|
|
node.setScale(sf)
|
|
# Add it to the styleMenu
|
|
node.reparentTo(newStyleMenu)
|
|
# Scale the whole shebang down by 0.5
|
|
newStyleMenu.setScale(0.5)
|
|
# Create and return a pie menu
|
|
return PieMenu(newStyleMenu, styles)
|
|
|
|
def createWallStyleSample(self, wallStyle):
|
|
"""
|
|
Create a style sample using the DNA info in the style
|
|
"""
|
|
bldg = DNAFlatBuilding()
|
|
bldgStyle = DNAFlatBuildingStyle(styleList = [(wallStyle, 10.0)])
|
|
self.setDNAFlatBuildingStyle(bldg, bldgStyle, width = 12.0,
|
|
name = 'wall_style_sample')
|
|
return bldg.traverse(hidden, DNASTORE, 1)
|
|
|
|
# BUILDING STYLE FUNCTIONS
|
|
def createBuildingStyleAttributes(self):
|
|
"""
|
|
Create a buildingStyle entry in the attribute dictionary
|
|
This will be a dictionary of style attributes, one per neighborhood
|
|
"""
|
|
# First create an empty dictionary
|
|
styleDict = self.attributeDictionary['building_style'] = {}
|
|
# Create an attribute object of all styles for each neighborhood
|
|
for neighborhood in NEIGHBORHOODS:
|
|
attribute = LevelAttribute('building_style')
|
|
attribute.setDict(
|
|
# Create a wall style dictionary for each neighborhood
|
|
self.createBuildingStyleDictionary(neighborhood))
|
|
# Using this dictionary, create color pie menus
|
|
attribute.setMenu(
|
|
self.createBuildingStyleMenu(neighborhood,
|
|
attribute.getDict()))
|
|
styleDict[neighborhood] = attribute
|
|
|
|
# Now create attribute entries sorted according to building
|
|
# height styles
|
|
attrDict = {}
|
|
# Create an attribute dictionary entry for each building height type
|
|
for type in BUILDING_TYPES:
|
|
key = 'building_style_' + type
|
|
attrDict[type] = self.attributeDictionary[key] = {}
|
|
# For each neighborhood create attribute for each height type
|
|
for neighborhood in NEIGHBORHOODS:
|
|
# Temp lists to accumulate neighborhood styles
|
|
# sorted by height type
|
|
styleLists = {}
|
|
for type in BUILDING_TYPES:
|
|
styleLists[type] = []
|
|
|
|
# Sort through the styles and store in separate lists
|
|
for style in styleDict[neighborhood].getList():
|
|
heightType = string.strip(string.split(style.name, ':')[1])
|
|
styleLists[heightType].append(style)
|
|
|
|
# Now put these lists in appropriate neighborhood attribute
|
|
for type in BUILDING_TYPES:
|
|
attribute = LevelAttribute('building_style_' + type)
|
|
attribute.setList(styleLists[type])
|
|
# Store them according to neighborhood
|
|
attrDict[type][neighborhood] = attribute
|
|
|
|
def createBuildingStyleDictionary(self, neighborhood):
|
|
"""
|
|
Create a dictionary of wall styles for a neighborhood
|
|
"""
|
|
filename = neighborhood + '_building_styles.txt'
|
|
print 'Loading building styles from: ' + filename
|
|
styleData = self.getStyleFileData(filename)
|
|
return self.initializeBuildingStyleDictionary(styleData, neighborhood)
|
|
|
|
def initializeBuildingStyleDictionary(self, styleData, neighborhood):
|
|
"""
|
|
Fill in the building style dictionary with data from the style file
|
|
"""
|
|
# Create a dictionary of all building styles, this will later be
|
|
# split out into a separate dictionary for each building type
|
|
# e.g. 20, 10-10, 10-20....
|
|
bldgStyleDictionary = {}
|
|
styleCount = 0
|
|
code = NEIGHBORHOOD_CODES[neighborhood]
|
|
while styleData:
|
|
# Pull out first line
|
|
l = styleData[0]
|
|
if l[:13] == 'buildingStyle':
|
|
# Start with empty style list
|
|
bldgStyle = DNAFlatBuildingStyle(styleList = [])
|
|
# Extract height information found at end of line
|
|
heightCode = string.strip(string.split(l, ':')[1])
|
|
heightList = map(string.atof, string.split(heightCode, '_'))
|
|
# Construct name for building style. Tack on height code
|
|
# to be used later to split styles by heightCode
|
|
bldgStyle.name = (
|
|
code + '_building_style_' + `styleCount` +
|
|
':' + heightCode)
|
|
# Increment counter
|
|
styleCount = styleCount + 1
|
|
# Reset wall counter to zero
|
|
wallCount = 0
|
|
|
|
elif l == 'endBuildingStyle':
|
|
# Done, add new style to dictionary
|
|
bldgStyleDictionary[bldgStyle.name] = bldgStyle
|
|
|
|
elif l[:9] == 'wallStyle':
|
|
# Beginning of next wall style
|
|
wallStyle, styleData = self.extractWallStyle(styleData)
|
|
wallStyle.name = bldgStyle.name + '_wall_' + `wallCount`
|
|
try:
|
|
height = heightList[wallCount]
|
|
except IndexError:
|
|
height = 10.0
|
|
# Add wall style to building style
|
|
bldgStyle.add(wallStyle, height)
|
|
# Increment wall counter
|
|
wallCount = wallCount + 1
|
|
# Move to next line
|
|
styleData = styleData[1:]
|
|
return bldgStyleDictionary
|
|
|
|
def createBuildingStyleMenu(self, neighborhood, dictionary):
|
|
"""
|
|
Create a wall style pie menu
|
|
"""
|
|
numItems = len(dictionary)
|
|
newStyleMenu = hidden.attachNewNode(neighborhood + '_style_menu')
|
|
radius = 0.7
|
|
angle = deg2Rad(360.0/numItems)
|
|
keys = dictionary.keys()
|
|
keys.sort()
|
|
styles = map(lambda x, d = dictionary: d[x], keys)
|
|
sf = 0.02
|
|
aspectRatio = (direct.dr.width/float(direct.dr.height))
|
|
for i in range(numItems):
|
|
# Get the node
|
|
node = self.createBuildingStyleSample(styles[i])
|
|
bounds = node.getBounds()
|
|
center = bounds.getCenter()
|
|
center = center * sf
|
|
# Reposition it
|
|
node.setPos((radius * math.cos(i * angle)) - center[0],
|
|
0.0,
|
|
((radius * aspectRatio * math.sin(i * angle)) -
|
|
center[2]))
|
|
# Scale it
|
|
node.setScale(sf)
|
|
# Add it to the styleMenu
|
|
node.reparentTo(newStyleMenu)
|
|
# Scale the whole shebang down by 0.5
|
|
newStyleMenu.setScale(0.5)
|
|
# Create and return a pie menu
|
|
return PieMenu(newStyleMenu, styles)
|
|
|
|
def createBuildingStyleSample(self, bldgStyle):
|
|
"""
|
|
Create a style sample using the DNA info in the style
|
|
"""
|
|
bldg = DNAFlatBuilding()
|
|
self.setDNAFlatBuildingStyle(bldg, bldgStyle, width = 10.0,
|
|
name = 'building_style_sample')
|
|
return bldg.traverse(hidden, DNASTORE, 1)
|
|
|
|
def setDNAFlatBuildingStyle(self, fb, bldgStyle, width = 10.0,
|
|
name = 'building'):
|
|
""" Set DNAFlatBuilding style. """
|
|
# Remove flat building's children
|
|
DNARemoveChildren(fb)
|
|
# Update the name
|
|
fb.setName(name)
|
|
# Create the walls
|
|
styleList = bldgStyle.styleList
|
|
heightList = bldgStyle.heightList
|
|
for i in range(len(styleList)):
|
|
wallStyle = styleList[i]
|
|
# Get Style
|
|
if not wallStyle:
|
|
# If set to None use default style
|
|
wallStyle = DNAWallStyle()
|
|
# Try to get height
|
|
try:
|
|
wallHeight = heightList[i]
|
|
except IndexError:
|
|
wallHeight = 10.0
|
|
# Create wall accordingly
|
|
wall = DNAWall()
|
|
self.setDNAWallStyle(wall, wallStyle, wallHeight)
|
|
# Add it to building DNA
|
|
fb.add(wall)
|
|
# Set the buildings width
|
|
fb.setWidth(width)
|
|
|
|
def setDNAWallStyle(self, wall, style, height = 10.0):
|
|
""" Set DNAWall to input style. """
|
|
# Remove wall's children
|
|
DNARemoveChildren(wall)
|
|
# Update wall attributes
|
|
wall.setCode(style['wall_texture'])
|
|
wall.setColor(style['wall_color'])
|
|
wall.setHeight(height)
|
|
# Add windows if necessary
|
|
if style['window_texture']:
|
|
windows = DNAWindows()
|
|
windows.setWindowCount(style['window_count'])
|
|
# Set window's attributes
|
|
windows.setCode(style['window_texture'])
|
|
windows.setColor(style['window_color'])
|
|
# Add windows to the wall
|
|
wall.add(windows)
|
|
# Add a window awning if necessary
|
|
if style['window_awning_texture']:
|
|
awning = DNAProp()
|
|
# Update awning's attributes
|
|
awning.setCode(style['window_awning_texture'])
|
|
awning.setColor(style['window_awning_color'])
|
|
# Add awning to window
|
|
windows.add(awning)
|
|
# Add a door if necessary
|
|
if style['door_texture']:
|
|
door = DNADoor()
|
|
# Set the door's attributes
|
|
door.setCode(style['door_texture'])
|
|
door.setColor(style['door_color'])
|
|
# Add door to wall
|
|
wall.add(door)
|
|
# Add a door awning if necessary
|
|
if style['door_awning_texture']:
|
|
awning = DNAProp()
|
|
awning.setCode(style['door_awning_texture'])
|
|
awning.setColor(style['door_awning_color'])
|
|
door.add(awning)
|
|
# And a cornice if necessary
|
|
if style['cornice_texture']:
|
|
cornice = DNACornice()
|
|
# Set the cornice's attributes
|
|
cornice.setCode(style['cornice_texture'])
|
|
cornice.setColor(style['cornice_color'])
|
|
# Add cornice to wall
|
|
wall.add(cornice)
|
|
|
|
def printFlatBuildingStyle(self, building):
|
|
for i in range(building.getNumChildren()):
|
|
child = building.at(i)
|
|
if DNAClassEqual(child, DNA_WALL):
|
|
self.printWallStyle(child)
|
|
|
|
def printWallStyle(self, wall):
|
|
print 'wall_texture: ' + wall.getCode()
|
|
color = wall.getColor()
|
|
print ('wall_color: Vec4(%.3f, %.3f, %.3f, 1.0)' %
|
|
(color[0], color[1], color[2]))
|
|
for i in range(wall.getNumChildren()):
|
|
child = wall.at(i)
|
|
if DNAClassEqual(child, DNA_WINDOWS):
|
|
print 'window_texture: ' + child.getCode()
|
|
color = child.getColor()
|
|
print ('window_color: Vec4(%.3f, %.3f, %.3f, 1.0)' %
|
|
(color[0], color[1], color[2]))
|
|
# MRM: Check for awnings here
|
|
elif DNAClassEqual(child, DNA_DOOR):
|
|
print 'door_texture: ' + child.getCode()
|
|
color = child.getColor()
|
|
print ('door_color: Vec4(%.3f, %.3f, %.3f, 1.0)' %
|
|
(color[0], color[1], color[2]))
|
|
# MRM: Check for awnings here
|
|
elif DNAClassEqual(child, DNA_CORNICE):
|
|
print 'cornice_texture: ' + child.getCode()
|
|
color = child.getColor()
|
|
print ('cornice_color: Vec4(%.3f, %.3f, %.3f, 1.0)' %
|
|
(color[0], color[1], color[2]))
|
|
|
|
# COLOR PALETTE FUNCTIONS
|
|
def createColorAttributes(self):
|
|
# First compile color information for each neighborhood
|
|
colorDictionary = {}
|
|
colorMenuDictionary = {}
|
|
for neighborhood in NEIGHBORHOODS:
|
|
colorDictionary[neighborhood] = (
|
|
# Create a wall style dictionary for each neighborhood
|
|
self.createColorDictionary(neighborhood))
|
|
# Using this dictionary, create color pie menus
|
|
colorMenuDictionary[neighborhood] = (
|
|
self.createColorMenus(
|
|
neighborhood, colorDictionary[neighborhood]))
|
|
# Now store this info in the appropriate place in the attribute dict
|
|
for colorType in COLOR_TYPES:
|
|
neighborhoodDict = self.attributeDictionary[colorType] = {}
|
|
for neighborhood in NEIGHBORHOODS:
|
|
attribute = LevelAttribute(colorType)
|
|
dict = {}
|
|
# Add colors to attribute dictionary
|
|
colorList = colorDictionary[neighborhood][colorType]
|
|
for i in range(len(colorList)):
|
|
dict[i] = colorList[i]
|
|
attribute.setDict(dict)
|
|
attribute.setMenu(
|
|
colorMenuDictionary[neighborhood][colorType])
|
|
neighborhoodDict[neighborhood] = attribute
|
|
|
|
def createColorDictionary(self, neighborhood):
|
|
filename = neighborhood + '_colors.txt'
|
|
print 'Loading Color Palettes from: ' + filename
|
|
colorData = self.getStyleFileData(filename)
|
|
return self.getColorDictionary(colorData)
|
|
|
|
def getColorDictionary(self, colorData):
|
|
# Initialze neighborhod color dictionary
|
|
dict = {}
|
|
for colorType in COLOR_TYPES:
|
|
dict[colorType] = DEFAULT_COLORS[:]
|
|
# Add color information to appropriate sub-list
|
|
for line in colorData:
|
|
pair = map(string.strip, line.split(':'))
|
|
key = pair[0]
|
|
if dict.has_key(key):
|
|
dict[key].append(eval(pair[1]))
|
|
else:
|
|
print 'LevelStyleManager.getColorDictionary key not found'
|
|
return dict
|
|
|
|
def createColorMenus(self, neighborhood, dictionary):
|
|
menuDict = {}
|
|
keys = dictionary.keys()
|
|
for key in keys:
|
|
menuDict[key] = (
|
|
self.createColorMenu(neighborhood + key, dictionary[key]))
|
|
return menuDict
|
|
|
|
def createColorMenu(self, menuName, colorList, radius = 0.7, sf = 2.0):
|
|
# Create color chips for each color
|
|
numItems = len(colorList)
|
|
# Attach it to hidden for now
|
|
newColorMenu = hidden.attachNewNode(menuName + 'Menu')
|
|
# Compute the angle per item
|
|
angle = deg2Rad(360.0/float(numItems))
|
|
aspectRatio = (direct.dr.width / float(direct.dr.height))
|
|
# Attach the color chips to the new menu and adjust sizes
|
|
for i in range (numItems):
|
|
# Create the node and set its color
|
|
node = OnscreenText(' ', 0.0, 0.0)
|
|
node.setColor(colorList[i])
|
|
bounds = node.getBounds()
|
|
center = bounds.getCenter()
|
|
center = center * (sf * node.getScale()[0])
|
|
# Reposition it
|
|
node.setXY((radius * math.cos(i * angle)) - center[0],
|
|
(radius * aspectRatio * math.sin(i * angle)) -
|
|
center[2])
|
|
node.setScale(node.getScale() * sf)
|
|
# Add it to the wallColorMenu
|
|
node.reparentTo(newColorMenu)
|
|
# Scale the whole shebang down by 0.5
|
|
newColorMenu.setScale(0.5)
|
|
# Create and return resulting pie menu
|
|
return PieMenu(newColorMenu, colorList)
|
|
|
|
# DNA ATTRIBUTES
|
|
def createDNAAttributes(self):
|
|
# Create the DNA Attribute entries
|
|
# Most objects are oriented with graphical menu items
|
|
# Street and props aren't oiented and use text menus
|
|
for dnaType in DNA_TYPES:
|
|
# Create a dictionary of dna types
|
|
dict = {}
|
|
if ((dnaType == 'street') | (dnaType == 'prop') |
|
|
(dnaType == 'toon_landmark')):
|
|
dnaList = self.getCatalogCodes(dnaType)
|
|
else:
|
|
dnaList = [None] + self.getCatalogCodesSuffix(dnaType, '_ur')
|
|
# Add dnaCodes to attribute dictionary
|
|
for i in range(len(dnaList)):
|
|
dict[i] = dnaList[i]
|
|
# Create a LevelAttribute
|
|
attribute = LevelAttribute(dnaType + '_texture')
|
|
attribute.setDict(dict)
|
|
# Prepend None to allow option of no item
|
|
if ((dnaType == 'street') | (dnaType == 'prop') |
|
|
(dnaType == 'toon_landmark')):
|
|
attribute.setMenu(self.createTextPieMenu(dnaType, dnaList))
|
|
elif (dnaType == 'wall'):
|
|
attribute.setMenu(self.createDNAPieMenu(dnaType, dnaList,
|
|
sf = 0.25))
|
|
elif (dnaType == 'door'):
|
|
attribute.setMenu(self.createDNAPieMenu(dnaType, dnaList,
|
|
sf = 0.035))
|
|
elif (dnaType == 'cornice'):
|
|
attribute.setMenu(self.createDNAPieMenu(dnaType, dnaList,
|
|
sf = 0.5))
|
|
elif (dnaType == 'window'):
|
|
attribute.setMenu(self.createDNAPieMenu(dnaType, dnaList,
|
|
sf = 0.125))
|
|
else:
|
|
print 'unknown attribute'
|
|
# Add it to the attributeDictionary
|
|
self.attributeDictionary[dnaType + '_texture'] = attribute
|
|
|
|
def createDNAPieMenu(self, dnaType, dnaList, radius = 0.7, sf = 1.0):
|
|
# Get the currently available window options
|
|
numItems = len(dnaList)
|
|
# Create a top level node to hold the menu
|
|
newMenu = hidden.attachNewNode(dnaType + 'Menu')
|
|
# Compute angle increment per item
|
|
angle = deg2Rad(360.0/numItems)
|
|
aspectRatio = direct.dr.width /float(direct.dr.height)
|
|
# Add items
|
|
for i in range(0, numItems):
|
|
if dnaList[i]:
|
|
# Get the node
|
|
node = DNASTORE.findNode(dnaList[i])
|
|
else:
|
|
node = None
|
|
if node:
|
|
# Add it to the window menu
|
|
path = node.instanceTo(newMenu)
|
|
# Place menu nodes in a circle, offset each in X and Z
|
|
# by half node width/height
|
|
bounds = path.getBounds()
|
|
center = bounds.getCenter()
|
|
center = center * sf
|
|
path.setPos((radius * math.cos(i * angle)) - center[0],
|
|
0.0,
|
|
((radius * aspectRatio * math.sin(i * angle)) -
|
|
center[2]))
|
|
path.setScale(sf)
|
|
# Scale the whole shebang down by 0.5
|
|
newMenu.setScale(0.5)
|
|
# Create and return a pie menu
|
|
return PieMenu(newMenu, dnaList)
|
|
|
|
def createTextPieMenu(self, dnaType, textList, radius = 0.7, sf = 1.0):
|
|
numItems = len(textList)
|
|
# Create top level node for new menu
|
|
newMenu = hidden.attachNewNode(dnaType + 'Menu')
|
|
# Compute angle per item
|
|
angle = deg2Rad(360.0/numItems)
|
|
aspectRatio = direct.dr.width/float(direct.dr.height)
|
|
# Add items
|
|
for i in range (numItems):
|
|
# Create onscreen text node for each item
|
|
if (textList[i] != None):
|
|
node = OnscreenText(str(textList[i]),0,0)
|
|
else:
|
|
node = None
|
|
if node:
|
|
# Reposition it
|
|
bounds = node.getBounds()
|
|
center = bounds.getCenter()
|
|
center = center * (sf * node.getScale()[0])
|
|
node.setXY(radius * math.cos(i * angle) - center[0],
|
|
((radius * aspectRatio * math.sin(i * angle)) -
|
|
center[2]))
|
|
node.setScale(node.getScale() * sf)
|
|
# Add it to the newMenu
|
|
node.reparentTo(newMenu)
|
|
# Scale the whole shebang down by 0.5
|
|
newMenu.setScale(0.5)
|
|
# Create and return a pie menu
|
|
return PieMenu(newMenu, textList)
|
|
|
|
# MISCELLANEOUS MENUS
|
|
def createMiscAttributes(self):
|
|
# Num windows menu
|
|
self.createMiscAttribute('window_count',[0,1,2,3,4])
|
|
# Building width menu
|
|
self.createMiscAttribute('building_width',[5,10,15,15.6,20,20.7,25])
|
|
# Building types
|
|
self.createMiscAttribute('building_type', BUILDING_TYPES)
|
|
# MRM: Need offset on these menus
|
|
# Wall orientation menu
|
|
self.createMiscAttribute('wall_orientation', ['ur','ul','dl','dr'])
|
|
# Wall height menu
|
|
self.createMiscAttribute('wall_height', [10, 20])
|
|
# Window orientation menu
|
|
self.createMiscAttribute('window_orientation', ['ur','ul',None,None])
|
|
# Door orientation menu
|
|
self.createMiscAttribute('door_orientation', ['ur','ul',None,None])
|
|
# Cornice orientation menu
|
|
self.createMiscAttribute('cornice_orientation', ['ur','ul',None,None])
|
|
|
|
def createMiscAttribute(self, miscType, miscList, sf = 3.0):
|
|
# Create a dictionary from miscList
|
|
dict = {}
|
|
# Add items to attribute dictionary
|
|
for i in range(len(miscList)):
|
|
dict[i] = miscList[i]
|
|
# Create the miscellaneous Attribute entries
|
|
attribute = LevelAttribute(miscType)
|
|
attribute.setDict(dict)
|
|
# Now create a pie menu
|
|
attribute.setMenu(self.createTextPieMenu(miscType, miscList,
|
|
sf = sf))
|
|
# Add it to the attributeDictionary
|
|
self.attributeDictionary[miscType] = attribute
|
|
|
|
# GENERAL FUNCTIONS
|
|
def getStyleFileData(self, filename):
|
|
"""
|
|
Open the specified file and strip out unwanted whitespace and
|
|
empty lines. Return file as list, one file line per element.
|
|
"""
|
|
fname = Filename(self.stylePathPrefix +
|
|
'/alpha/DIRECT/LevelEditor/StyleFiles/' +
|
|
filename)
|
|
f = open(fname.toOsSpecific(), 'r')
|
|
rawData = f.readlines()
|
|
f.close()
|
|
styleData = []
|
|
for line in rawData:
|
|
l = string.strip(line)
|
|
if l:
|
|
styleData.append(l)
|
|
return styleData
|
|
|
|
# UTILITY FUNCTIONS
|
|
def getAttribute(self, attribute):
|
|
""" Return specified attribute for current neighborhood """
|
|
levelAttribute = self.attributeDictionary[attribute]
|
|
# Get attribute for current neighborhood
|
|
if (type(levelAttribute) == types.DictionaryType):
|
|
levelAttribute = levelAttribute[self.getEditMode()]
|
|
return levelAttribute
|
|
|
|
def getCatalogCode(self, category, i):
|
|
return DNASTORE.getCatalogCode(category, i)
|
|
|
|
def getCatalogCodes(self, category):
|
|
numCodes = DNASTORE.getNumCatalogCodes(category)
|
|
codes = []
|
|
for i in range(numCodes):
|
|
codes.append(DNASTORE.getCatalogCode(category, i))
|
|
return codes
|
|
|
|
def getCatalogCodesSuffix(self, category, suffix):
|
|
codes = self.getCatalogCodes(category)
|
|
orientedCodes = []
|
|
for code in codes:
|
|
if code[-3:] == suffix:
|
|
orientedCodes.append(code)
|
|
return orientedCodes
|
|
|
|
def setEditMode(self, mode):
|
|
"""Set current neighborhood editing mode"""
|
|
self._editMode = mode
|
|
|
|
def getEditMode(self):
|
|
"""Get current neighborhood editing mode"""
|
|
return self._editMode
|
|
|
|
class LevelAttribute:
|
|
"""Class which encapsulates a pie menu and a set of items"""
|
|
def __init__(self, name):
|
|
# Record name
|
|
self.name = name
|
|
# Pie menu used to pick an option
|
|
self._menu = None
|
|
# Dictionary of available options
|
|
self._dict = None
|
|
self._list = []
|
|
# Currently selected option
|
|
self._current = None
|
|
def setCurrent(self, newValue, fEvent = 1):
|
|
self._current = newValue
|
|
# Send event if specified
|
|
if fEvent:
|
|
messenger.send('select_' + self.name, [self._current])
|
|
def setMenu(self,menu):
|
|
self._menu = menu
|
|
self._menu.action = self.setCurrent
|
|
def setDict(self,dict):
|
|
self._dict = dict
|
|
# Create a list from the dictionary
|
|
self._list = dict.values()
|
|
# Initialize current to first item
|
|
if (len(self._list) > 0):
|
|
self._current = self._list[0]
|
|
def setList(self,list):
|
|
self._list = list
|
|
# Create a dictionary from the list
|
|
self._dict = {}
|
|
count = 0
|
|
for item in list:
|
|
self._dict[count] = item
|
|
count = count + 1
|
|
# Initialize current to first item
|
|
if (len(self._list) > 0):
|
|
self._current = self._list[0]
|
|
def getCurrent(self):
|
|
return self._current
|
|
def getMenu(self):
|
|
return self._menu
|
|
def getDict(self):
|
|
return self._dict
|
|
def getList(self):
|
|
return self._list
|
|
|
|
class DNAFlatBuildingStyle:
|
|
"""Class to hold attributes of a building style"""
|
|
def __init__(self, building = None, styleList = None, name = 'bldg_style'):
|
|
self.name = name
|
|
if building:
|
|
# Passed in a building
|
|
self.copy(building)
|
|
elif styleList != None:
|
|
# Passed in a list of style-height pairs
|
|
self.styleList = []
|
|
self.heightList = []
|
|
for pair in styleList:
|
|
self.add(pair[0], pair[1])
|
|
else:
|
|
# Use default style/height
|
|
self.styleList = [DNAWallStyle()]
|
|
self.heightList = [10]
|
|
|
|
def add(self, style, height):
|
|
self.styleList.append(style)
|
|
self.heightList.append(height)
|
|
|
|
def copy(self, building):
|
|
self.styleList = []
|
|
self.heightList = DNAGetWallHeights(building)[0]
|
|
for i in range(building.getNumChildren()):
|
|
child = building.at(i)
|
|
if DNAClassEqual(child, DNA_WALL):
|
|
wallStyle = DNAWallStyle(wall = child)
|
|
self.styleList.append(wallStyle)
|
|
|
|
def output(self, file = sys.stdout):
|
|
def createHeightCode(s = self):
|
|
def joinHeights(h1,h2):
|
|
return '%s_%s' % (h1, h2)
|
|
hl = map(ROUND_INT, s.heightList)
|
|
return reduce(joinHeights, hl)
|
|
file.write('buildingStyle: %s\n' % createHeightCode())
|
|
for style in self.styleList:
|
|
style.output(file)
|
|
file.write('endBuildingStyle\n')
|
|
|
|
class DNAWallStyle:
|
|
"""Class to hold attributes of a wall style (textures, colors, etc)"""
|
|
def __init__(self, wall = None, name = 'wall_style'):
|
|
# First initialize everything
|
|
self.name = name
|
|
self.wall_texture = 'wall_md_blank_ur'
|
|
self.wall_color = Vec4(1.0)
|
|
self.window_count = 2
|
|
self.window_texture = None
|
|
self.window_color = Vec4(1.0)
|
|
self.window_awning_texture = None
|
|
self.window_awning_color = Vec4(1.0)
|
|
self.door_texture = None
|
|
self.door_color = Vec4(1.0)
|
|
self.door_awning_texture = None
|
|
self.door_awning_color = Vec4(1.0)
|
|
self.cornice_texture = None
|
|
self.cornice_color = Vec4(1.0)
|
|
# Then copy the specifics for the wall if input
|
|
if wall:
|
|
self.copy(wall)
|
|
|
|
def copy(self, wall):
|
|
self.wall_texture = wall.getCode()
|
|
self.wall_color = wall.getColor()
|
|
for i in range(wall.getNumChildren()):
|
|
child = wall.at(i)
|
|
if DNAClassEqual(child, DNA_WINDOWS):
|
|
self.window_count = child.getWindowCount()
|
|
self.window_texture = child.getCode()
|
|
self.window_color = child.getColor()
|
|
# MRM: Check for awnings here
|
|
elif DNAClassEqual(child, DNA_DOOR):
|
|
self.door_texture = child.getCode()
|
|
self.door_color = child.getColor()
|
|
# MRM: Check for awnings here
|
|
elif DNAClassEqual(child, DNA_CORNICE):
|
|
self.cornice_texture = child.getCode()
|
|
self.cornice_color = child.getColor()
|
|
|
|
def output(self, file = sys.stdout):
|
|
""" Output style description to a file """
|
|
def writeAttributes(f, type, s = self):
|
|
color = s[type + '_color']
|
|
f.write('%s_texture: %s\n' % (type, s[type + '_texture']))
|
|
f.write('%s_color: Vec4(%.2f, %.2f, %.2f, 1.0)\n' %
|
|
(type, color[0], color[1], color[2]))
|
|
file.write('wallStyle\n')
|
|
writeAttributes(file, 'wall')
|
|
if self['window_texture']:
|
|
writeAttributes(file, 'window')
|
|
file.write('window_count: %s\n' % self['window_count'])
|
|
if self['window_awning_texture']:
|
|
writeAttributes(file, 'window_awning')
|
|
if self['door_texture']:
|
|
writeAttributes(file, 'door')
|
|
if self['door_awning_texture']:
|
|
writeAttributes(file, 'door_awning')
|
|
if self['cornice_texture']:
|
|
writeAttributes(file, 'cornice')
|
|
file.write('endWallStyle\n')
|
|
|
|
# Convience functions to facilitate class use
|
|
def __setitem__(self, index, item):
|
|
self.__dict__[index] = item
|
|
|
|
def __getitem__(self, index):
|
|
return self.__dict__[index]
|
|
|
|
def __repr__(self):
|
|
return(
|
|
'Name: %s\n' % self.name +
|
|
'Wall Texture: %s\n' % self.wall_texture +
|
|
'Wall Color: %s\n' % self.wall_color +
|
|
'Window Texture: %s\n' % self.window_texture +
|
|
'Window Color: %s\n' % self.window_color +
|
|
'Window Awning Texture: %s\n' % self.window_awning_texture +
|
|
'Window Awning Color: %s\n' % self.window_awning_color +
|
|
'Door Texture: %s\n' % self.door_texture +
|
|
'Door Color: %s\n' % self.door_color +
|
|
'Door Awning Texture: %s\n' % self.door_awning_texture +
|
|
'Door Awning Color: %s\n' % self.door_awning_color +
|
|
'Cornice Texture: %s\n' % self.cornice_texture +
|
|
'Cornice Color: %s\n' % self.cornice_color
|
|
)
|
|
|
|
class OldLevelEditor(NodePath, PandaObject):
|
|
pass
|
|
|
|
class LevelEditorPanel(Pmw.MegaToplevel):
|
|
def __init__(self, levelEditor, parent = None, **kw):
|
|
|
|
INITOPT = Pmw.INITOPT
|
|
optiondefs = (
|
|
('title', 'Toontown Level Editor', None),
|
|
)
|
|
self.defineoptions(kw, optiondefs)
|
|
|
|
Pmw.MegaToplevel.__init__(self, parent, title = self['title'])
|
|
|
|
self.levelEditor = levelEditor
|
|
self.styleManager = self.levelEditor.styleManager
|
|
self.fUpdateSelected = 1
|
|
# Handle to the toplevels hull
|
|
hull = self.component('hull')
|
|
|
|
balloon = self.balloon = Pmw.Balloon(hull)
|
|
# Start with balloon help disabled
|
|
self.balloon.configure(state = 'none')
|
|
|
|
menuFrame = Frame(hull, relief = GROOVE, bd = 2)
|
|
menuFrame.pack(fill = X, expand = 1)
|
|
|
|
menuBar = Pmw.MenuBar(menuFrame, hotkeys = 1, balloon = balloon)
|
|
menuBar.pack(side = LEFT, expand = 1, fill = X)
|
|
menuBar.addmenu('Level Editor', 'Level Editor Operations')
|
|
menuBar.addmenuitem('Level Editor', 'command',
|
|
'Load DNA from specified file',
|
|
label = 'Load DNA...',
|
|
command = self.levelEditor.loadSpecifiedDNAFile)
|
|
menuBar.addmenuitem('Level Editor', 'command',
|
|
'Save DNA data to specified file',
|
|
label = 'Save DNA As...',
|
|
command = self.levelEditor.saveToSpecifiedDNAFile)
|
|
menuBar.addmenuitem('Level Editor', 'command',
|
|
'Save DNA File',
|
|
label = 'Save DNA',
|
|
command = self.levelEditor.outputDNADefaultFile)
|
|
menuBar.addmenuitem('Level Editor', 'command',
|
|
'Edit Visibility Groups',
|
|
label = 'Edit Vis Groups',
|
|
command = self.levelEditor.editDNAVisGroups)
|
|
menuBar.addmenuitem('Level Editor', 'command',
|
|
'Reset level',
|
|
label = 'Reset level',
|
|
command = self.levelEditor.reset)
|
|
menuBar.addmenuitem('Level Editor', 'command',
|
|
'Exit Level Editor Panel',
|
|
label = 'Exit',
|
|
command = self.levelEditor.destroy)
|
|
|
|
menuBar.addmenu('Style', 'Style Operations')
|
|
menuBar.addmenuitem('Style', 'command',
|
|
"Save Selected Object's Color",
|
|
label = 'Save Color',
|
|
command = self.levelEditor.saveColor)
|
|
menuBar.addmenuitem('Style', 'command',
|
|
"Save Selected Wall's Style",
|
|
label = 'Save Wall Style',
|
|
command = self.levelEditor.saveWallStyle)
|
|
menuBar.addmenuitem('Style', 'command',
|
|
"Save Selected Buildings's Style",
|
|
label = 'Save Bldg Style',
|
|
command = self.levelEditor.saveBuildingStyle)
|
|
menuBar.addmenuitem('Style', 'command',
|
|
'Reload Color Palettes',
|
|
label = 'Reload Colors',
|
|
command = self.styleManager.createColorAttributes)
|
|
menuBar.addmenuitem('Style', 'command',
|
|
'Reload Wall Style Palettes',
|
|
label = 'Reload Wall Styles',
|
|
command = self.styleManager.createWallStyleAttributes)
|
|
menuBar.addmenuitem('Style', 'command',
|
|
'Reload Building Style Palettes',
|
|
label = 'Reload Bldg Styles',
|
|
command = self.styleManager.createBuildingStyleAttributes)
|
|
|
|
menuBar.addmenu('Help', 'Level Editor Help Operations')
|
|
self.toggleBalloonVar = IntVar()
|
|
self.toggleBalloonVar.set(0)
|
|
menuBar.addmenuitem('Help', 'checkbutton',
|
|
'Toggle balloon help',
|
|
label = 'Balloon Help',
|
|
variable = self.toggleBalloonVar,
|
|
command = self.toggleBalloon)
|
|
|
|
self.editMenu = Pmw.ComboBox(
|
|
menuFrame, labelpos = W,
|
|
label_text = 'Edit Mode:', entry_width = 12,
|
|
selectioncommand = self.levelEditor.setEditMode, history = 0,
|
|
scrolledlist_items = NEIGHBORHOODS)
|
|
self.editMenu.selectitem(NEIGHBORHOODS[0])
|
|
self.editMenu.pack(side = 'left', expand = 0)
|
|
|
|
|
|
# Create the notebook pages
|
|
notebook = Pmw.NoteBook(hull)
|
|
notebook.pack(fill = BOTH, expand = 1)
|
|
streetsPage = notebook.add('Streets')
|
|
toonBuildingsPage = notebook.add('Toon Bldgs')
|
|
landmarkBuildingsPage = notebook.add('Landmark Bldgs')
|
|
# suitBuildingsPage = notebook.add('Suit Buildings')
|
|
propsPage = notebook.add('Props')
|
|
sceneGraphPage = notebook.add('SceneGraph')
|
|
|
|
self.addStreetButton = Button(
|
|
streetsPage,
|
|
text = 'ADD STREET',
|
|
command = self.addStreet)
|
|
self.addStreetButton.pack(fill = 'x')
|
|
self.streetSelector = Pmw.ComboBox(
|
|
streetsPage,
|
|
dropdown = 0,
|
|
listheight = 200,
|
|
labelpos = W,
|
|
label_text = 'Street type:',
|
|
label_width = 12,
|
|
label_anchor = W,
|
|
entry_width = 30,
|
|
selectioncommand = self.setStreetModuleType,
|
|
scrolledlist_items = map(lambda s: s[7:],
|
|
self.styleManager.getCatalogCodes(
|
|
'street'))
|
|
)
|
|
self.streetModuleType = self.styleManager.getCatalogCode('street',0)
|
|
self.streetSelector.selectitem(self.streetModuleType[7:])
|
|
self.streetSelector.pack(expand = 1, fill = 'both')
|
|
|
|
self.addToonBuildingButton = Button(
|
|
toonBuildingsPage,
|
|
text = 'ADD TOON BUILDING',
|
|
command = self.addFlatBuilding)
|
|
self.addToonBuildingButton.pack(fill = 'x')
|
|
self.toonBuildingSelector = Pmw.ComboBox(
|
|
toonBuildingsPage,
|
|
dropdown = 0,
|
|
listheight = 200,
|
|
labelpos = W,
|
|
label_width = 12,
|
|
label_anchor = W,
|
|
label_text = 'Toon bldg type:',
|
|
entry_width = 30,
|
|
selectioncommand = self.setFlatBuildingType,
|
|
scrolledlist_items = ['random20', 'random30'] + BUILDING_TYPES
|
|
)
|
|
self.toonBuildingType = 'random20'
|
|
self.toonBuildingSelector.selectitem(self.toonBuildingType)
|
|
self.toonBuildingSelector.pack(expand = 1, fill = 'both')
|
|
|
|
#self.toonBuildingWidthScale = EntryScale.EntryScale(
|
|
#toonBuildingsPage, min = 1.0, max = 30.0,
|
|
# resolution = 0.01, text = 'Wall Width',
|
|
# command = self.updateSelectedWallWidth)
|
|
#self.toonBuildingWidthScale.pack(fill = 'x')
|
|
|
|
self.addLandmarkBuildingButton = Button(
|
|
landmarkBuildingsPage,
|
|
text = 'ADD LANDMARK BUILDING',
|
|
command = self.addLandmark)
|
|
self.addLandmarkBuildingButton.pack(fill = 'x')
|
|
self.landmarkBuildingSelector = Pmw.ComboBox(
|
|
landmarkBuildingsPage,
|
|
dropdown = 0,
|
|
listheight = 200,
|
|
labelpos = W,
|
|
label_width = 12,
|
|
label_anchor = W,
|
|
label_text = 'Landmark Building type:',
|
|
entry_width = 30,
|
|
selectioncommand = self.setLandmarkType,
|
|
scrolledlist_items = map(lambda s: s[14:],
|
|
self.styleManager.getCatalogCodes(
|
|
'toon_landmark'))
|
|
)
|
|
self.landmarkType = self.styleManager.getCatalogCode(
|
|
'toon_landmark',0)
|
|
self.landmarkBuildingSelector.selectitem(
|
|
self.styleManager.getCatalogCode('toon_landmark',0)[14:])
|
|
self.landmarkBuildingSelector.pack(expand = 1, fill = 'both')
|
|
|
|
self.addPropsButton = Button(
|
|
propsPage,
|
|
text = 'ADD PROP',
|
|
command = self.addProp)
|
|
self.addPropsButton.pack(fill = 'x')
|
|
self.propSelector = Pmw.ComboBox(
|
|
propsPage,
|
|
dropdown = 0,
|
|
listheight = 200,
|
|
labelpos = W,
|
|
label_width = 12,
|
|
label_anchor = W,
|
|
label_text = 'Prop type:',
|
|
entry_width = 30,
|
|
selectioncommand = self.setPropType,
|
|
scrolledlist_items = map(lambda s: s[5:],
|
|
self.styleManager.getCatalogCodes('prop'))
|
|
)
|
|
self.propType = self.styleManager.getCatalogCode('prop',0)
|
|
self.propSelector.selectitem(
|
|
self.styleManager.getCatalogCode('prop',0)[5:])
|
|
self.propSelector.pack(expand = 1, fill = 'both')
|
|
# Compact down notebook
|
|
notebook.setnaturalsize()
|
|
|
|
self.colorEntry = VectorWidgets.ColorEntry(
|
|
hull, text = 'Select Color',
|
|
command = self.updateSelectedObjColor)
|
|
self.colorEntry.menu.add_command(
|
|
label = 'Save Color', command = self.levelEditor.saveColor)
|
|
self.colorEntry.pack(fill = 'x')
|
|
|
|
buttonFrame = Frame(hull)
|
|
self.fMapVis = IntVar()
|
|
self.fMapVis.set(0)
|
|
self.mapSnapButton = Checkbutton(buttonFrame,
|
|
text = 'Map Vis',
|
|
width = 6,
|
|
variable = self.fMapVis,
|
|
command = self.toggleMapVis)
|
|
self.mapSnapButton.pack(side = 'left', expand = 1, fill = 'x')
|
|
|
|
self.fXyzSnap = IntVar()
|
|
self.fXyzSnap.set(1)
|
|
self.xyzSnapButton = Checkbutton(buttonFrame,
|
|
text = 'XyzSnap',
|
|
width = 6,
|
|
variable = self.fXyzSnap,
|
|
command = self.toggleXyzSnap)
|
|
self.xyzSnapButton.pack(side = 'left', expand = 1, fill = 'x')
|
|
|
|
self.fHprSnap = IntVar()
|
|
self.fHprSnap.set(1)
|
|
self.hprSnapButton = Checkbutton(buttonFrame,
|
|
text = 'HprSnap',
|
|
width = 6,
|
|
variable = self.fHprSnap,
|
|
command = self.toggleHprSnap)
|
|
self.hprSnapButton.pack(side = 'left', expand = 1, fill = 'x')
|
|
|
|
self.fPlaneSnap = IntVar()
|
|
self.fPlaneSnap.set(1)
|
|
self.planeSnapButton = Checkbutton(buttonFrame,
|
|
text = 'PlaneSnap',
|
|
width = 6,
|
|
variable = self.fPlaneSnap)
|
|
self.planeSnapButton.pack(side = 'left', expand = 1, fill = 'x')
|
|
|
|
self.fGrid = IntVar()
|
|
self.fGrid.set(0)
|
|
direct.gridButton = Checkbutton(buttonFrame,
|
|
text = 'Show Grid',
|
|
width = 6,
|
|
variable = self.fGrid,
|
|
command = self.toggleGrid)
|
|
direct.gridButton.pack(side = 'left', expand = 1, fill = 'x')
|
|
|
|
buttonFrame.pack(expand = 1, fill = 'x')
|
|
|
|
buttonFrame2 = Frame(hull)
|
|
self.groupButton = Button(
|
|
buttonFrame2,
|
|
text = 'New Group',
|
|
command = self.levelEditor.createNewGroup)
|
|
self.groupButton.pack(side = 'left', expand = 1, fill = 'x')
|
|
|
|
self.visGroupButton = Button(
|
|
buttonFrame2,
|
|
text = 'New Vis Group',
|
|
command = self.createNewVisGroup)
|
|
self.visGroupButton.pack(side = 'left', expand = 1, fill = 'x')
|
|
|
|
self.setParentButton = Button(
|
|
buttonFrame2,
|
|
text = 'Set Parent',
|
|
command = self.levelEditor.setActiveParent)
|
|
self.setParentButton.pack(side = 'left', expand = 1, fill = 'x')
|
|
|
|
self.reparentButton = Button(
|
|
buttonFrame2,
|
|
text = 'Re Parent',
|
|
command = self.levelEditor.reparentSelected)
|
|
self.reparentButton.pack(side = 'left', expand = 1, fill = 'x')
|
|
|
|
buttonFrame2.pack(fill = 'x')
|
|
|
|
buttonFrame3 = Frame(hull)
|
|
self.isolateButton = Button(
|
|
buttonFrame3,
|
|
text = 'Isolate Selected',
|
|
command = direct.isolate)
|
|
self.isolateButton.pack(side = 'left', expand = 1, fill = 'x')
|
|
|
|
self.showAllButton = Button(
|
|
buttonFrame3,
|
|
text = 'Show All',
|
|
command = self.showAll)
|
|
self.showAllButton.pack(side = 'left', expand = 1, fill = 'x')
|
|
|
|
buttonFrame3.pack(fill = 'x')
|
|
|
|
buttonFrame4 = Frame(hull)
|
|
self.driveMode = IntVar()
|
|
self.driveMode.set(1)
|
|
self.driveModeButton = Radiobutton(
|
|
buttonFrame4,
|
|
text = 'Drive Mode',
|
|
value = 0,
|
|
variable = self.driveMode,
|
|
command = self.levelEditor.useDriveMode)
|
|
self.driveModeButton.pack(side = 'left', expand = 1, fill = 'x')
|
|
directModeButton = Radiobutton(
|
|
buttonFrame4,
|
|
text = 'DIRECT Fly',
|
|
value = 1,
|
|
variable = self.driveMode,
|
|
command = self.levelEditor.useDirectFly)
|
|
directModeButton.pack(side = 'left', expand = 1, fill = 'x')
|
|
buttonFrame4.pack(fill = 'x')
|
|
|
|
self.sceneGraphExplorer = SceneGraphExplorer(
|
|
parent = sceneGraphPage,
|
|
root = self.levelEditor,
|
|
menuItems = ['Set Parent', 'Reparent', 'Add Group',
|
|
'Add Vis Group', 'Set Color',
|
|
'Set Name'])
|
|
self.sceneGraphExplorer.pack(expand = 1, fill = 'both')
|
|
|
|
# Make sure input variables processed
|
|
self.initialiseoptions(LevelEditorPanel)
|
|
|
|
def toggleGrid(self):
|
|
if self.fGrid.get():
|
|
direct.grid.enable()
|
|
else:
|
|
direct.grid.disable()
|
|
|
|
def toggleXyzSnap(self):
|
|
direct.grid.setXyzSnap(self.fXyzSnap.get())
|
|
|
|
def toggleHprSnap(self):
|
|
direct.grid.setHprSnap(self.fXyzSnap.get())
|
|
|
|
def toggleMapVis(self):
|
|
self.levelEditor.toggleMapVis(self.fMapVis.get())
|
|
|
|
def showAll(self):
|
|
direct.showAll(self.levelEditor.NPToplevel)
|
|
|
|
def createNewVisGroup(self):
|
|
self.levelEditor.createNewGroup(type = 'vis')
|
|
|
|
def setStreetModuleType(self,name):
|
|
self.streetModuleType = 'street_' + name
|
|
self.levelEditor.setCurrent('street_texture', self.streetModuleType)
|
|
|
|
def addStreet(self):
|
|
self.levelEditor.addStreet(self.streetModuleType)
|
|
|
|
def setFlatBuildingType(self,name):
|
|
self.toonBuildingType = name
|
|
self.levelEditor.setCurrent('building_type', self.toonBuildingType)
|
|
|
|
def addFlatBuilding(self):
|
|
self.levelEditor.addFlatBuilding(self.toonBuildingType)
|
|
|
|
def setLandmarkType(self,name):
|
|
self.landmarkType = 'toon_landmark_' + name
|
|
self.levelEditor.setCurrent('toon_landmark_texture', self.landmarkType)
|
|
|
|
def addLandmark(self):
|
|
self.levelEditor.addLandmark(self.landmarkType)
|
|
|
|
def setPropType(self,name):
|
|
self.propType = 'prop_' + name
|
|
self.levelEditor.setCurrent('prop_texture', self.propType)
|
|
|
|
def addProp(self):
|
|
self.levelEditor.addProp(self.propType)
|
|
|
|
def updateSelectedWallWidth(self, strVal):
|
|
self.levelEditor.updateSelectedWallWidth(string.atof(strVal))
|
|
|
|
def setCurrentColor(self, colorVec, fUpdate = 0):
|
|
# Turn on/off update of selected before updating entry
|
|
self.fUpdateSelected = fUpdate
|
|
self.colorEntry.set([int(colorVec[0] * 255.0),
|
|
int(colorVec[1] * 255.0),
|
|
int(colorVec[2] * 255.0),
|
|
255])
|
|
|
|
def setResetColor(self, colorVec):
|
|
self.colorEntry['resetValue'] = (
|
|
[int(colorVec[0] * 255.0),
|
|
int(colorVec[1] * 255.0),
|
|
int(colorVec[2] * 255.0),
|
|
255])
|
|
|
|
def updateSelectedObjColor(self, color):
|
|
try:
|
|
obj = self.levelEditor.DNATarget
|
|
if self.fUpdateSelected & (obj != None):
|
|
objClass = DNAGetClassType(obj)
|
|
if ((objClass.eq(DNA_WALL)) |
|
|
(objClass.eq(DNA_WINDOWS)) |
|
|
(objClass.eq(DNA_DOOR)) |
|
|
(objClass.eq(DNA_CORNICE)) |
|
|
(objClass.eq(DNA_PROP))
|
|
):
|
|
self.levelEditor.setDNATargetColor(
|
|
VBase4((color[0]/255.0),
|
|
(color[1]/255.0),
|
|
(color[2]/255.0),
|
|
1.0))
|
|
except AttributeError:
|
|
pass
|
|
# Default is to update selected
|
|
self.fUpdateSelected = 1
|
|
|
|
def toggleBalloon(self):
|
|
if self.toggleBalloonVar.get():
|
|
self.balloon.configure(state = 'balloon')
|
|
else:
|
|
self.balloon.configure(state = 'none')
|
|
|
|
class VisGroupsEditor(Pmw.MegaToplevel):
|
|
def __init__(self, levelEditor, visGroups = ['None'],
|
|
parent = None, **kw):
|
|
|
|
INITOPT = Pmw.INITOPT
|
|
optiondefs = (
|
|
('title', 'Visability Groups Editor', None),
|
|
)
|
|
self.defineoptions(kw, optiondefs)
|
|
|
|
Pmw.MegaToplevel.__init__(self, parent, title = self['title'])
|
|
|
|
self.levelEditor = levelEditor
|
|
self.visGroups = visGroups
|
|
self.visGroupNames = map(lambda pair: pair[1].getName(),
|
|
self.visGroups)
|
|
# Initialize dictionary of visibility relationships
|
|
self.visDict = {}
|
|
# Group we are currently setting visGroups for
|
|
self.target = None
|
|
# Flag to enable/disable toggleVisGroup command
|
|
self.fCommand = 1
|
|
|
|
# Handle to the toplevels hull
|
|
hull = self.component('hull')
|
|
|
|
balloon = self.balloon = Pmw.Balloon(hull)
|
|
# Start with balloon help disabled
|
|
self.balloon.configure(state = 'none')
|
|
|
|
menuFrame = Frame(hull, relief = GROOVE, bd = 2)
|
|
menuFrame.pack(fill = X, expand = 1)
|
|
|
|
menuBar = Pmw.MenuBar(menuFrame, hotkeys = 1, balloon = balloon)
|
|
menuBar.pack(side = LEFT, expand = 1, fill = X)
|
|
menuBar.addmenu('Vis Groups Editor',
|
|
'Visability Groups Editor Operations')
|
|
menuBar.addmenuitem('Vis Groups Editor', 'command',
|
|
'Exit Visability Groups Editor',
|
|
label = 'Exit',
|
|
command = self.preDestroy)
|
|
|
|
menuBar.addmenu('Help', 'Visability Groups Editor Help Operations')
|
|
self.toggleBalloonVar = IntVar()
|
|
self.toggleBalloonVar.set(0)
|
|
menuBar.addmenuitem('Help', 'checkbutton',
|
|
'Toggle balloon help',
|
|
label = 'Balloon Help',
|
|
variable = self.toggleBalloonVar,
|
|
command = self.toggleBalloon)
|
|
|
|
# Create a combo box to choose target vis group
|
|
self.targetSelector = Pmw.ComboBox(
|
|
hull, labelpos = W, label_text = 'Target Vis Group:',
|
|
entry_width = 12, selectioncommand = self.selectVisGroup,
|
|
scrolledlist_items = self.visGroupNames)
|
|
self.targetSelector.selectitem(self.visGroupNames[0])
|
|
self.targetSelector.pack(expand = 1, fill = X)
|
|
|
|
# Scrolled frame to hold radio selector
|
|
sf = Pmw.ScrolledFrame(hull, horizflex = 'elastic',
|
|
usehullsize = 1, hull_width = 200,
|
|
hull_height = 400)
|
|
frame = sf.interior()
|
|
sf.pack(padx=5, pady=3, fill = 'both', expand = 1)
|
|
|
|
# Add vis groups selector
|
|
self.selected = Pmw.RadioSelect(frame, selectmode=MULTIPLE,
|
|
orient = VERTICAL,
|
|
pady = 0,
|
|
command = self.toggleVisGroup)
|
|
for groupInfo in self.visGroups:
|
|
nodePath = groupInfo[0]
|
|
group = groupInfo[1]
|
|
name = group.getName()
|
|
self.selected.add(name, width = 12)
|
|
# Assemble list of groups visible from this group
|
|
visible = []
|
|
for i in range(group.getNumVisibles()):
|
|
visible.append(group.getVisibleName(i))
|
|
visible.sort()
|
|
self.visDict[name] = [nodePath, group, visible]
|
|
# Pack the widget
|
|
self.selected.pack(expand = 1, fill = X)
|
|
# And make sure scrolled frame is happy
|
|
sf.reposition()
|
|
|
|
buttonFrame = Frame(hull)
|
|
buttonFrame.pack(fill='x', expand = 1)
|
|
|
|
self.showMode = IntVar()
|
|
self.showMode.set(0)
|
|
self.showAllButton = Radiobutton(buttonFrame, text = 'Show All',
|
|
value = 0, indicatoron = 1,
|
|
variable = self.showMode,
|
|
command = self.refreshVisibility)
|
|
self.showAllButton.pack(side = LEFT, fill = 'x', expand = 1)
|
|
self.showActiveButton = Radiobutton(buttonFrame, text = 'Show Target',
|
|
value = 1, indicatoron = 1,
|
|
variable = self.showMode,
|
|
command = self.refreshVisibility)
|
|
self.showActiveButton.pack(side = LEFT, fill = 'x', expand = 1)
|
|
|
|
# Make sure input variables processed
|
|
self.initialiseoptions(VisGroupsEditor)
|
|
|
|
# Switch to current target's list
|
|
self.selectVisGroup(self.visGroupNames[0])
|
|
|
|
def selectVisGroup(self, target):
|
|
print 'Setting vis options for group:', target
|
|
# Record current target
|
|
oldTarget = self.target
|
|
# Record new target
|
|
self.target = target
|
|
# Deselect buttons from old target (first deactivating command)
|
|
self.fCommand = 0
|
|
if oldTarget:
|
|
visList = self.visDict[oldTarget][2]
|
|
for group in visList:
|
|
self.selected.invoke(self.selected.index(group))
|
|
# Now set buttons to reflect state of new target
|
|
visList = self.visDict[target][2]
|
|
for group in visList:
|
|
self.selected.invoke(self.selected.index(group))
|
|
# Reactivate command
|
|
self.fCommand = 1
|
|
# Update scene
|
|
self.refreshVisibility()
|
|
|
|
def toggleVisGroup(self, groupName, state):
|
|
if self.fCommand:
|
|
targetInfo = self.visDict[self.target]
|
|
target = targetInfo[1]
|
|
visList = targetInfo[2]
|
|
groupNP = self.visDict[groupName][0]
|
|
group = self.visDict[groupName][1]
|
|
# MRM: Add change in visibility here
|
|
# Show all vs. show active
|
|
if state == 1:
|
|
print 'Vis Group:', self.target, 'adding group:', groupName
|
|
if groupName not in visList:
|
|
visList.append(groupName)
|
|
target.addVisible(groupName)
|
|
# Update vis and color
|
|
groupNP.show()
|
|
groupNP.setColor(1,0,0,1)
|
|
else:
|
|
print 'Vis Group:', self.target, 'removing group:', groupName
|
|
if groupName in visList:
|
|
visList.remove(groupName)
|
|
target.removeVisible(groupName)
|
|
# Update vis and color
|
|
if self.showMode.get() == 1:
|
|
groupNP.hide()
|
|
groupNP.clearColor()
|
|
|
|
def refreshVisibility(self):
|
|
# Get current visibility list for target
|
|
targetInfo = self.visDict[self.target]
|
|
visList = targetInfo[2]
|
|
for key in self.visDict.keys():
|
|
groupNP = self.visDict[key][0]
|
|
if key in visList:
|
|
groupNP.show()
|
|
if key == self.target:
|
|
groupNP.setColor(0,1,0,1)
|
|
else:
|
|
groupNP.setColor(1,0,0,1)
|
|
else:
|
|
if self.showMode.get() == 0:
|
|
groupNP.show()
|
|
else:
|
|
groupNP.hide()
|
|
groupNP.clearColor()
|
|
|
|
def preDestroy(self):
|
|
# First clear level editor variable
|
|
self.levelEditor.vgpanel = None
|
|
self.destroy()
|
|
|
|
def toggleBalloon(self):
|
|
if self.toggleBalloonVar.get():
|
|
self.balloon.configure(state = 'balloon')
|
|
else:
|
|
self.balloon.configure(state = 'none')
|