Browse Source

[WIP] Panda3D Level Editor

Gyedo Jeon 16 years ago
parent
commit
648730f3bb

+ 38 - 0
direct/src/leveleditor/FileMgr.py

@@ -0,0 +1,38 @@
+import os
+import imp
+
+import ObjectGlobals as OG
+
+class FileMgr:
+    """ To handle data file """
+    
+    def __init__(self, editor):
+        self.editor = editor
+
+    def saveToFile(self, fileName):
+        try:
+            f = open(fileName, 'w')
+            f.write("from pandac.PandaModules import *\n")
+            f.write("\nobjectMgr = base.le.objectMgr\n")
+            f.write("# temporary place holder for nodepath\n")
+            f.write("objects = {}\n")
+            saveData = self.editor.objectMgr.getSaveData()
+            for data in saveData:
+                f.write(data)
+                f.write('\n')
+            f.close()
+        except IOError:
+            print 'failed to save %s'%fileName
+            if f:
+                f.close()
+
+    def loadFromFile(self, fileName):
+        dirname, moduleName = os.path.split(fileName)
+        if moduleName.endswith('.py'):
+            moduleName = moduleName[:-3]
+
+        file, pathname, description = imp.find_module(moduleName, [dirname])
+        try:
+            module = imp.load_module(moduleName, file, pathname, description)
+        except:
+            print 'failed to load %s'%fileName

+ 26 - 5531
direct/src/leveleditor/LevelEditor.py

@@ -1,5532 +1,27 @@
-
-from pandac.PandaModules import *
-from direct.directbase.DirectStart import *
-from direct.showbase.DirectObject import DirectObject
-from PieMenu import *
-import direct.gui.DirectGuiGlobals as DGG
-from direct.showbase.TkGlobal import *
-from direct.directtools.DirectUtil import *
-from direct.directtools.DirectGeometry import *
-from direct.tkwidgets.SceneGraphExplorer import *
-from direct.directnotify import DirectNotifyGlobal
-from tkMessageBox import showinfo
-from tkFileDialog import *
-from Tkinter import *
-#from whrandom import *
-from random import *
-from direct.tkwidgets import Floater
-from direct.tkwidgets import VectorWidgets
-import string
-import os
-import getopt
-import sys
-#import whrandom
-import random
-import types
-from direct.task import Task
-import Pmw
-import __builtin__
-
-# [gjeon] to control avatar movement in drive mode
-from direct.controls import ControlManager
-from direct.controls import GravityWalker
-from direct.controls import NonPhysicsWalker
-from direct.interval.LerpInterval import LerpFunctionInterval
-
-from otp.avatar import LocalAvatar
-
-from toontown.toon import RobotToon
-from otp.otpbase import OTPGlobals
-
-from LevelStyleManager import *
-
-# Force direct and tk to be on
-base.startDirect(fWantDirect = 1, fWantTk = 1)
-
-visualizeZones = base.config.GetBool("visualize-zones", 0)
-dnaDirectory = Filename.expandFrom(base.config.GetString("dna-directory", "$TTMODELS/src/dna"))
-fUseCVS = base.config.GetBool("level-editor-use-cvs", 1)
-useSnowTree = base.config.GetBool("use-snow-tree", 0)
-
-# 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', 'sign_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', 'sign', 'door_double', 'door_single', 'cornice', 'toon_landmark',
-             'prop', 'street']
-BUILDING_TYPES = ['10_10', '20', '10_20', '20_10', '10_10_10',
-                  '4_21', '3_22', '4_13_8', '3_13_9', '10',
-                  '12_8', '13_9_8', '4_10_10',  '4_10', '4_20',
-                  ]
-BUILDING_HEIGHTS = [10, 14, 20, 24, 25, 30]
-NUM_WALLS = [1, 2, 3]
-LANDMARK_SPECIAL_TYPES = ['', 'hq', 'gagshop', 'clotheshop', 'petshop', 'kartshop']
-
-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_30x20': [(Vec3(30.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_20x40_15': [(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_20x60': [(Vec3(20.0, 0, 0), Vec3(0)),
-                     (Vec3(0), Vec3(0))],
-    'street_40x60': [(Vec3(40.0, 0, 0), Vec3(0)),
-                     (Vec3(0), Vec3(0))],
-    'street_40x40_15': [(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_tight_corner': [(Vec3(40.0, 0, 0), Vec3(0)),
-                           (Vec3(0), Vec3(0))],
-    'street_tight_corner_mirror': [(Vec3(40.0, 0, 0), Vec3(0)),
-                           (Vec3(0), Vec3(0))],
-    'street_double_corner': [(Vec3(40.0, 0, 0), Vec3(0)),
-                           (Vec3(0), Vec3(0))],
-    'street_curved_corner': [(Vec3(40.0, 0, 0), Vec3(0)),
-                           (Vec3(0), Vec3(0))],
-    'street_curved_corner_15': [(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_divided_transition_15': [(Vec3(40.0, 0, 0), Vec3(0)),
-                                  (Vec3(0), Vec3(0))],
-    'street_divided_40x70_15': [(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_square_courtyard': [(Vec3(0.0, 0, 0), Vec3(0)),
-                            (Vec3(0), Vec3(0))],
-    'street_courtyard_70': [(Vec3(0.0, 0, 0), Vec3(0)),
-                            (Vec3(0), Vec3(0))],
-    'street_courtyard_70_exit': [(Vec3(0.0, 0, 0), Vec3(0)),
-                                 (Vec3(0), Vec3(0))],
-    'street_courtyard_90': [(Vec3(0.0, 0, 0), Vec3(0)),
-                            (Vec3(0), Vec3(0))],
-    'street_courtyard_90_exit': [(Vec3(0.0, 0, 0), Vec3(0)),
-                                 (Vec3(0), Vec3(0))],
-    'street_courtyard_70_15': [(Vec3(0.0, 0, 0), Vec3(0)),
-                               (Vec3(0), Vec3(0))],
-    'street_courtyard_70_15_exit': [(Vec3(0.0, 0, 0), Vec3(0)),
-                                    (Vec3(0), Vec3(0))],
-    'street_courtyard_90_15': [(Vec3(0.0, 0, 0), Vec3(0)),
-                            (Vec3(0), Vec3(0))],
-    'street_courtyard_90_15_exit': [(Vec3(0.0, 0, 0), Vec3(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))],
-    'street_sunken_40x40': [(Vec3(40.0, 0, 0), Vec3(0)),
-                            (Vec3(0), Vec3(0))],
-    }
-
-# NEIGHBORHOOD DATA
-# If you run this from the command line you can pass in the hood codes
-# you want to load. For example:
-#    ppython LevelEditor.py DD TT BR
-#
-if sys.argv[1:]:
-    try:
-        opts, pargs = getopt.getopt(sys.argv[1:], '')
-        hoods = pargs
-    except Exception, e:
-        print e
-# If you do not run from the command line, we just load all of them
-# or you can hack this up for your own purposes.
-else:
-    hoodString = base.config.GetString('level-editor-hoods',
-                                       'TT DD BR DG DL MM CC CL CM CS GS GZ OZ PA')
-    hoods = string.split(hoodString)
-
-# The list of neighborhoods to edit
-hoodIds = {'TT': 'toontown_central',
-           'DD': 'donalds_dock',
-           'MM': 'minnies_melody_land',
-           'BR': 'the_burrrgh',
-           'DG': 'daisys_garden',
-           'DL': 'donalds_dreamland',
-           'CC': 'cog_hq_bossbot',
-           'CL': 'cog_hq_lawbot',
-           'CM': 'cog_hq_cashbot',
-           'CS': 'cog_hq_sellbot',
-           'GS': 'goofy_speedway',
-           'OZ': 'outdoor_zone',
-           'GZ': 'golf_zone',
-           'PA': 'party_zone',
-           }
-
-# Init neighborhood arrays
-NEIGHBORHOODS = []
-NEIGHBORHOOD_CODES = {}
-for hoodId in hoods:
-    if hoodIds.has_key(hoodId):
-        hoodName = hoodIds[hoodId]
-        NEIGHBORHOOD_CODES[hoodName] = hoodId
-        NEIGHBORHOODS.append(hoodName)
-    else:
-        print 'Error: no hood defined for: ', hoodId
-
-# Load DNA
-try:
-    if dnaLoaded:
-        pass
-except NameError:
-    print "Loading LevelEditor for hoods: ", hoods
-    # DNAStorage instance for storing level DNA info
-
-    # We need to use the __builtin__.foo syntax, not the
-    # __builtins__["foo"] syntax, since this file runs at the top
-    # level.
-    __builtin__.DNASTORE = DNASTORE = DNAStorage()
-
-    # Load the generic storage files
-    loadDNAFile(DNASTORE, 'dna/storage.dna', CSDefault, 1)
-    loadDNAFile(DNASTORE, 'phase_4/dna/storage.dna', CSDefault, 1)
-    loadDNAFile(DNASTORE, 'phase_5/dna/storage_town.dna', CSDefault, 1)
-    # loadDNAFile(DNASTORE, 'phase_5.5/dna/storage_estate.dna', CSDefault, 1)
-    # loadDNAFile(DNASTORE, 'phase_5.5/dna/storage_house_interior.dna', CSDefault, 1)
-    # Load all the neighborhood specific storage files
-    if 'TT' in hoods:
-        loadDNAFile(DNASTORE, 'phase_4/dna/storage_TT.dna', CSDefault, 1)
-        loadDNAFile(DNASTORE, 'phase_4/dna/storage_TT_sz.dna', CSDefault, 1)
-        loadDNAFile(DNASTORE, 'phase_5/dna/storage_TT_town.dna', CSDefault, 1)
-    if 'DD' in hoods:
-        loadDNAFile(DNASTORE, 'phase_6/dna/storage_DD.dna', CSDefault, 1)
-        loadDNAFile(DNASTORE, 'phase_6/dna/storage_DD_sz.dna', CSDefault, 1)
-        loadDNAFile(DNASTORE, 'phase_6/dna/storage_DD_town.dna', CSDefault, 1)
-    if 'MM' in hoods:
-        loadDNAFile(DNASTORE, 'phase_6/dna/storage_MM.dna', CSDefault, 1)
-        loadDNAFile(DNASTORE, 'phase_6/dna/storage_MM_sz.dna', CSDefault, 1)
-        loadDNAFile(DNASTORE, 'phase_6/dna/storage_MM_town.dna', CSDefault, 1)
-    if 'BR' in hoods:
-        loadDNAFile(DNASTORE, 'phase_8/dna/storage_BR.dna', CSDefault, 1)
-        loadDNAFile(DNASTORE, 'phase_8/dna/storage_BR_sz.dna', CSDefault, 1)
-        loadDNAFile(DNASTORE, 'phase_8/dna/storage_BR_town.dna', CSDefault, 1)
-    if 'DG' in hoods:
-        loadDNAFile(DNASTORE, 'phase_8/dna/storage_DG.dna', CSDefault, 1)
-        loadDNAFile(DNASTORE, 'phase_8/dna/storage_DG_sz.dna', CSDefault, 1)
-        loadDNAFile(DNASTORE, 'phase_8/dna/storage_DG_town.dna', CSDefault, 1)
-    if 'DL' in hoods:
-        loadDNAFile(DNASTORE, 'phase_8/dna/storage_DL.dna', CSDefault, 1)
-        loadDNAFile(DNASTORE, 'phase_8/dna/storage_DL_sz.dna', CSDefault, 1)
-        loadDNAFile(DNASTORE, 'phase_8/dna/storage_DL_town.dna', CSDefault, 1)
-    if 'CS' in hoods:
-        loadDNAFile(DNASTORE, 'phase_9/dna/storage_CS.dna', CSDefault, 1)
-    if 'GS' in hoods:
-        loadDNAFile(DNASTORE, 'phase_4/dna/storage_GS.dna', CSDefault, 1)
-        loadDNAFile(DNASTORE, 'phase_4/dna/storage_GS_sz.dna', CSDefault, 1)
-    if 'OZ' in hoods:
-        loadDNAFile(DNASTORE, 'phase_6/dna/storage_OZ.dna', CSDefault, 1)
-        loadDNAFile(DNASTORE, 'phase_6/dna/storage_OZ_sz.dna', CSDefault, 1)
-    if 'GZ' in hoods:
-        loadDNAFile(DNASTORE, 'phase_6/dna/storage_GZ.dna', CSDefault, 1)
-        loadDNAFile(DNASTORE, 'phase_6/dna/storage_GZ_sz.dna', CSDefault, 1)
-    if 'CC' in hoods:
-        loadDNAFile(DNASTORE, 'phase_12/dna/storage_CC_sz.dna', CSDefault, 1)        
-    if 'PA' in hoods:
-        loadDNAFile(DNASTORE, 'phase_13/dna/storage_party_sz.dna', CSDefault, 1)
-    __builtin__.dnaLoaded = 1
-
-
-class LevelEditor(NodePath, DirectObject):
-    """Class used to create a Toontown LevelEditor object"""
-    notify = DirectNotifyGlobal.directNotify.newCategory('LevelEditor')
-        
-    # Init the list of callbacks:
-    selectedNodePathHookHooks=[]
-    deselectedNodePathHookHooks=[]
-
-    # 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
-    # DNAVisGroup: Current DNAVisGroup that new objects get added to
-    # NPVisGroup: Corresponding Node Path
-    # selectedDNARoot: DNA Node of currently selected object
-    # selectedNPRoot: Corresponding Node Path
-    # DNATarget: Subcomponent being modified by Pie Menu
-    def __init__(self, hoods = hoods):
-        # 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'))
-
-        # Enable replaceSelected by default:
-        self.replaceSelectedEnabled=1
-
-        self.removeHookList=[self.landmarkBlockRemove]
-
-        # Start block ID at 0 (it will be incremented before use (to 1)):
-        self.landmarkBlock=0
-
-        # Create ancillary objects
-        # Style manager for keeping track of styles/colors
-        self.styleManager = LevelStyleManager(NEIGHBORHOODS, NEIGHBORHOOD_CODES)
-        # Load neighborhood maps
-        self.createLevelMaps()
-        # Marker for showing next insertion point
-        self.createInsertionMarker()
-        # Create level Editor Panel
-        self.panel = LevelEditorPanel(self)
-
-        # Used to store whatever edges and points are loaded in the level
-        self.edgeDict = {}
-        self.np2EdgeDict = {}
-        self.pointDict = {}
-        self.point2edgeDict = {}
-        self.cellDict = {}
-
-        self.visitedPoints = []
-        self.visitedEdges = []
-
-        self.zoneLabels = []
-
-
-
-        # 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 = [
-            # Node path events
-            ('preRemoveNodePath', self.removeNodePathHook),
-            # Actions in response to DIRECT operations
-            ('DIRECT_selectedNodePath', self.selectedNodePathHook),
-            ('DIRECT_deselectedNodePath', self.deselectedNodePathHook),
-            ('DIRECT_manipulateObjectCleanup', self.updateSelectedPose),
-            ('DIRECT_nodePathSetName', self.setName),
-            ('DIRECT_activeParent', self.setActiveParent),
-            ('DIRECT_reparent', self.reparent),
-            ('RGBPanel_setColor', self.setColor),
-            # Actions in response to Level Editor Panel operations
-            ('SGE_Add Group', self.addGroup),
-            ('SGE_Add Vis Group', self.addVisGroup),
-            # Actions in response to Pie Menu interaction
-            ('select_building_style_all', 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_sign_color', self.setDNATargetColor),
-            ('select_sign_orientation', self.setDNATargetOrientation),
-            ('select_sign_texture', self.setDNATargetCode, ['sign']),
-            ('select_baseline_style', self.panel.setSignBaselineStyle),
-            ('select_door_color', self.setDNATargetColor),
-            ('select_door_orientation', self.setDNATargetOrientation),
-            ('select_door_single_texture', self.setDNATargetCode, ['door']),
-            ('select_door_double_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),
-            ('j', self.jumpToInsertionPoint),
-            ('arrow_left', self.keyboardXformSelected, ['left', 'xlate']),
-            ('arrow_right', self.keyboardXformSelected, ['right', 'xlate']),
-            ('arrow_up', self.keyboardXformSelected, ['up','xlate']),
-            ('arrow_down', self.keyboardXformSelected, ['down','xlate']),
-            ('control-arrow_left', self.keyboardXformSelected, ['left', 'rotate']),
-            ('control-arrow_right', self.keyboardXformSelected, ['right', 'rotate']),
-            ('control-arrow_up', self.keyboardXformSelected, ['up', 'rotate']),
-            ('control-arrow_down', self.keyboardXformSelected, ['down', 'rotate']),
-            ('shift-s', self.placeSuitPoint),
-            ('shift-c', self.placeBattleCell),
-            ('k', self.addToLandmarkBlock),
-            ('shift-k', self.toggleShowLandmarkBlock),
-            ('%', self.pdbBreak),
-
-            ('page_up', self.pageUp),
-            ('page_down', self.pageDown),
-            ]
-
-        self.overrideEvents = [
-            ('page_up', base.direct),
-            ('page_down',  base.direct)
-            ]
-
-        # Initialize state
-        # Make sure direct is running
-        base.direct.enable()
-        # And only the appropriate handles are showing
-        base.direct.widget.disableHandles(['x-ring', 'x-disc',
-                                           'y-ring', 'y-disc',
-                                           'z-post'])
-        # Initialize camera
-        base.camLens.setNear(1.0)
-        base.camLens.setFar(3000)
-        base.direct.camera.setPos(0, -10, 10)
-        # Initialize drive mode
-        self.configureDriveModeCollisionData()
-        # Init visibility variables
-        self.__zoneId = None
-        # Hide (disable) grid initially
-        self.showGrid(0)
-        # Create variable for vis groups panel
-        self.vgpanel = None
-        # Start off enabled
-        self.enable()
-
-        # SUIT POINTS
-        # Create a sphere model to show suit points
-        self.suitPointMarker = loader.loadModel('models/misc/sphere')
-        self.suitPointMarker.setScale(0.25)
-
-        # Initialize the suit points
-        self.startSuitPoint = None
-        self.endSuitPoint = None
-        self.currentSuitPointType = DNASuitPoint.STREETPOINT
-
-        # BATTLE CELLS
-        self.battleCellMarker = loader.loadModel('models/misc/sphere')
-        self.battleCellMarker.setName('battleCellMarker')
-        self.battleCellMarker.setScale(1)
-        self.currentBattleCellType = "20w 20l"
-
-        # Update panel
-        # Editing the first hood id on the list
-        self.outputFile = None
-        self.setEditMode(NEIGHBORHOODS[0])
-        # Start of with first item in lists
-        self.panel.streetSelector.selectitem(0)
-        self.panel.streetSelector.invoke()
-        self.panel.toonBuildingSelector.selectitem(0)
-        self.panel.toonBuildingSelector.invoke()
-        self.panel.landmarkBuildingSelector.selectitem(0)
-        self.panel.landmarkBuildingSelector.invoke()
-        self.panel.propSelector.selectitem(0)
-        self.panel.propSelector.invoke()
-        # Start off with 20 foot buildings
-        self.panel.twentyFootButton.invoke()
-        # Update scene graph explorer
-        self.panel.sceneGraphExplorer.update()
-
-        # Karting
-        # the key is the barricade number,  the data is a two element list,
-        # first number is the first bldg group that uses this
-        # the second is the last bldg group that uses this
-        self.outerBarricadeDict = {}
-        self.innerBarricadeDict = {}
-
-        # [gjeon] to find out currently moving camera in maya mode
-        self.mouseMayaCamera = True
-
-        # [gjeon] to find out drive mode stat
-        self.fDrive = False
-
-        # [gjeon] to control drive mode
-        self.controlManager = None
-        self.avatar = None
-
-        self.fov = 60
-        self.isPageUp=0
-        self.isPageDown=0        
- 
-    # ENABLE/DISABLE
-    def enable(self):
-        """ Enable level editing and show level """
-        # Make sure level is visible
-        self.reparentTo(base.direct.group)
-        self.show()
-
-        # [gjeon] Ignore overridden events
-        for event in self.overrideEvents:
-            event[1].ignore(event[0])
-        
-        # 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
-        base.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.remove('insertionMarkerTask')
-
-    def reset(self, fDeleteToplevel = 1, fCreateToplevel = 1,
-              fUpdateExplorer = 1):
-        """
-        Reset level and re-initialize main class variables
-        Pass in the new top level group
-        """
-        # Reset path markers
-        self.resetPathMarkers()
-        # Reset battle cell markers
-        self.resetBattleCellMarkers()
-
-        if fDeleteToplevel:
-            # First destroy existing scene-graph/DNA hierarchy
-            self.deleteToplevel()
-
-        # Clear DNASTORE
-        DNASTORE.resetDNAGroups()
-        # Reset DNA VIS Groups
-        DNASTORE.resetDNAVisGroups()
-        DNASTORE.resetSuitPoints()
-        DNASTORE.resetBattleCells()
-
-        # 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
-        base.direct.grid.setPosHprScale(0, 0, 0, 0, 0, 0, 1, 1, 1)
-        # The selected DNA Object/NodePath
-        self.selectedDNARoot = None
-        self.selectedNPRoot = None
-        self.selectedSuitPoint = None
-        self.lastLandmarkBuildingDNA = None
-        self.showLandmarkBlockToggleGroup = 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.lastSign = None
-        self.lastWall = None
-        self.lastBuilding = None
-        # Code of last selected object (for autopositionGrid)
-        self.snapList = []
-        # Last menu used
-        self.activeMenu = None
-        # For highlighting suit paths
-        self.visitedPoints = []
-        self.visitedEdges = []
-        # Update scene graph explorer
-        if fUpdateExplorer:
-            self.panel.sceneGraphExplorer.update()
-
-        self.outputFile = None
-        self.panel["title"] = 'Level Editor: No file loaded'
-
-    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.removeNode()
-
-    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
-        self.VGParent = None
-        # Add toplevel node path for suit points
-        self.suitPointToplevel = self.NPToplevel.attachNewNode('suitPoints')
-
-    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 """
-        # Turn off collision traversal
-        self.traversalOff()
-        # Turn on collisions
-        self.collisionsOff()
-        # Turn on visiblity
-        self.visibilityOff()
-        base.camera.wrtReparentTo(render)
-        # Reset cam
-        base.camera.iPos(base.cam)
-        base.cam.iPosHpr()
-        # Renable mouse
-        self.enableMouse()
-        base.direct.enable()
-
-        # [gjeon]  disable avatar and controlManager
-        if (self.avatar):
-            self.avatar.reparentTo(hidden)
-            self.avatar.stopUpdateSmartCamera()
-        if (self.controlManager):
-            self.controlManager.disable()
-
-        self.fDrive = False
-
-    def lerpCameraP(self, p, time):
-        """
-        lerp the camera P over time (used by the battle)
-        """
-        taskMgr.remove('cam-p-lerp')
-        if self.avatar:
-            self.avatar.stopUpdateSmartCamera()
-        def setCamP(p):
-            base.camera.setP(p)
-
-        if self.isPageUp:
-            fromP = 36.8699
-        elif self.isPageDown:
-            fromP = -27.5607
-        else:
-            fromP = 0
-
-        self.camLerpInterval = LerpFunctionInterval(setCamP,
-            fromData=fromP, toData=p, duration=time,
-            name='cam-p-lerp')
-        self.camLerpInterval.start()
-
-    def clearPageUpDown(self):
-        if self.isPageDown or self.isPageUp:
-            self.lerpCameraP(0, 0.6)
-            self.isPageDown = 0
-            self.isPageUp = 0
-            #self.setCameraPositionByIndex(self.cameraIndex)
-
-        if self.avatar:
-            self.avatar.startUpdateSmartCamera()
-            
-    def pageUp(self):
-        if not self.isPageUp:
-            self.lerpCameraP(36.8699, 0.6)
-            self.isPageDown = 0
-            self.isPageUp = 1
-            #self.setCameraPositionByIndex(self.cameraIndex)
-        else:
-            self.clearPageUpDown()
-
-    def pageDown(self):
-        if not self.isPageDown:
-            self.lerpCameraP(-27.5607, 0.6)
-            self.isPageUp = 0
-            self.isPageDown = 1
-            #self.setCameraPositionByIndex(self.cameraIndex)
-        else:
-            self.clearPageUpDown()            
-
-    def useDriveMode(self):
-        """ Lerp down to eye level then switch to Drive mode """
-        # create avatar and set it's location to the camera
-        if (self.avatar == None):
-            #self.avatar = RobotToon.RobotToon()
-            self.avatar = LEAvatar(None, None, None)
-            base.localAvatar = self.avatar
-            self.avatar.doId = 0
-            self.avatar.robot = RobotToon.RobotToon()
-            self.avatar.robot.reparentTo(self.avatar)
-            self.avatar.setHeight(self.avatar.robot.getHeight())
-            self.avatar.setName("The Inspector")
-            self.avatar.robot.loop('neutral')
-
-        self.avatar.setPos(base.camera.getPos())
-        self.avatar.reparentTo(render)
-
-##         pos = base.direct.camera.getPos()
-##         pos.setZ(4.0)
-##         hpr = base.direct.camera.getHpr()
-##         hpr.set(hpr[0], 0.0, 0.0)
-##         t = base.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
-        self.switchToDriveMode(None)
-        self.fDrive = True
-        #[gjeon] deselect
-        base.direct.selected.deselect(base.direct.selected.last)
-
-    def switchToDriveMode(self, state):
-        """ Disable direct camera manipulation and enable player drive mode """
-        #base.direct.minimumConfiguration()
-        #base.direct.manipulationControl.disableManipulation()
-        # Update vis data
-        self.initVisibilityData()
-##         # Switch to drive mode
-##         base.useDrive()
-##         # Move cam up and back
-##         base.cam.setPos(0, -5, 4)
-##         # And move down and forward to compensate
-##         base.camera.setPos(base.camera, 0, 5, -4)
-##         # Make sure we're where we want to be
-##         pos = base.direct.camera.getPos()
-##         pos.setZ(0.0)
-##         hpr = base.direct.camera.getHpr()
-##         hpr.set(hpr[0], 0.0, 0.0)
-##         # Fine tune the drive mode
-##         base.mouseInterface.node().setPos(pos)
-##         base.mouseInterface.node().setHpr(hpr)
-##         base.mouseInterface.node().setForwardSpeed(0)
-##         base.mouseInterface.node().setReverseSpeed(0)
-
-        base.camera.wrtReparentTo(self.avatar)
-        base.camera.setHpr(0, 0, 0)
-        #base.camera.setPos(0, 0, 0)
-        base.camera.setPos(0, -11.8125, 3.9375)
-
-        base.camLens.setFov(VBase2(60, 46.8265))
-
-        #self.initializeSmartCameraCollisions()
-        #self._smartCamEnabled = False
-        
-        # Turn on collisions
-        if self.panel.fColl.get():
-            self.collisionsOn()
-        # Turn on visiblity
-        if self.panel.fVis.get():
-            self.visibilityOn()
-        # Turn on collision traversal
-        if self.panel.fColl.get() or self.panel.fVis.get():
-            self.traversalOn()
-
-        if (self.controlManager == None):
-            # create player movement controls,camera controls, and avatar
-            self.controlManager = ControlManager.ControlManager()
-            avatarRadius = 1.4
-            floorOffset = OTPGlobals.FloorOffset
-            reach = 4.0
-
-            #walkControls=GravityWalker.GravityWalker(gravity = -32.1740 * 2.0)
-            walkControls=NonPhysicsWalker.NonPhysicsWalker()
-            walkControls.setWallBitMask(OTPGlobals.WallBitmask)
-            walkControls.setFloorBitMask(OTPGlobals.FloorBitmask)
-            walkControls.initializeCollisions(self.cTrav, self.avatar,
-                                              avatarRadius, floorOffset, reach)
-            self.controlManager.add(walkControls, "walk")
-            self.controlManager.use("walk", self)
-
-            # set speeds after adding controls to the control manager
-            self.controlManager.setSpeeds(
-                OTPGlobals.ToonForwardSpeed,
-                OTPGlobals.ToonJumpForce,
-                OTPGlobals.ToonReverseSpeed,
-                OTPGlobals.ToonRotateSpeed
-                )
-        else:
-            self.controlManager.enable()
-
-        self.avatarAnimTask = taskMgr.add(self.avatarAnimate, 'avatarAnimTask', 24)
-        self.avatar.startUpdateSmartCamera()
-        
-        self.avatarMoving = 0
-
-    #--------------------------------------------------------------------------
-    # Function:   animate avatar model based on if it is moving
-    # Parameters: none
-    # Changes:
-    # Returns:
-    #--------------------------------------------------------------------------
-    def avatarAnimate(self,task=None):
-        if (self.controlManager):
-            moving = self.controlManager.currentControls.speed or self.controlManager.currentControls.slideSpeed or self.controlManager.currentControls.rotationSpeed
-            if (moving and
-                self.avatarMoving == 0):
-                self.clearPageUpDown()
-                # moving, play walk anim
-                if (self.controlManager.currentControls.speed < 0 or
-                    self.controlManager.currentControls.rotationSpeed):
-                    self.avatar.robot.loop('walk')
-                else:
-                    self.avatar.robot.loop('run')
-                self.avatarMoving = 1
-            elif (moving == 0 and
-                  self.avatarMoving == 1):
-                # no longer moving, play neutral anim
-                self.avatar.robot.loop('neutral')
-                self.avatarMoving = 0
-        return Task.cont
-
-    def configureDriveModeCollisionData(self):
-        """
-        Set up the local avatar for collisions
-        """
-        # Set up the collision sphere
-        # This is a sphere on the ground to detect barrier collisions
-        self.cSphere = CollisionSphere(0.0, 0.0, 0.0, 1.5)
-        self.cSphereNode = CollisionNode('cSphereNode')
-        self.cSphereNode.addSolid(self.cSphere)
-        self.cSphereNodePath = camera.attachNewNode(self.cSphereNode)
-        self.cSphereNodePath.hide()
-        self.cSphereBitMask = BitMask32.bit(0)
-        self.cSphereNode.setFromCollideMask(self.cSphereBitMask)
-        self.cSphereNode.setIntoCollideMask(BitMask32.allOff())
-
-        # Set up the collison ray
-        # This is a ray cast from your head down to detect floor polygons
-        self.cRay = CollisionRay(0.0, 0.0, 6.0, 0.0, 0.0, -1.0)
-        self.cRayNode = CollisionNode('cRayNode')
-        self.cRayNode.addSolid(self.cRay)
-        self.cRayNodePath = camera.attachNewNode(self.cRayNode)
-        self.cRayNodePath.hide()
-        self.cRayBitMask = BitMask32.bit(1)
-        self.cRayNode.setFromCollideMask(self.cRayBitMask)
-        self.cRayNode.setIntoCollideMask(BitMask32.allOff())
-
-        # set up wall collision mechanism
-        self.pusher = CollisionHandlerPusher()
-        self.pusher.setInPattern("enter%in")
-        self.pusher.setOutPattern("exit%in")
-
-        # set up floor collision mechanism
-        self.lifter = CollisionHandlerFloor()
-        self.lifter.setInPattern("on-floor")
-        self.lifter.setOutPattern("off-floor")
-        self.floorOffset = 0.1
-        self.lifter.setOffset(self.floorOffset)
-
-        # Limit our rate-of-fall with the lifter.
-        # If this is too low, we actually "fall" off steep stairs
-        # and float above them as we go down. I increased this
-        # from 8.0 to 16.0 to prevent this
-        self.lifter.setMaxVelocity(16.0)
-
-        # set up the collision traverser
-        self.cTrav = CollisionTraverser("LevelEditor")
-        self.cTrav.setRespectPrevTransform(1)
-
-        # activate the collider with the traverser and pusher
-        #self.pusher.addCollider(self.cSphereNodePath, base.camera, base.drive.node())
-        #self.lifter.addCollider(self.cRayNodePath, base.camera, base.drive.node())
-        # A map of zone ID's to a list of nodes that are visible from
-        # that zone.
-        self.nodeDict = {}
-        # A map of zone ID's to the particular node that corresponds
-        # to that zone.
-        self.zoneDict = {}
-        # A list of all visible nodes
-        self.nodeList = []
-        # Flag for bootstrapping visibility
-        self.fVisInit = 0
-
-    def traversalOn(self):
-        base.cTrav = self.cTrav
-
-    def traversalOff(self):
-        base.cTrav = 0
-
-    def collisionsOff(self):
-        self.cTrav.removeCollider(self.cSphereNodePath)
-
-    def collisionsOn(self):
-        self.collisionsOff()
-        self.cTrav.addCollider(self.cSphereNodePath, self.pusher)
-
-    def toggleCollisions(self):
-        if self.panel.fColl.get():
-            self.collisionsOn()
-            self.traversalOn()
-        else:
-            self.collisionsOff()
-            if (not (self.panel.fColl.get() or self.panel.fVis.get())):
-                self.traversalOff()
-
-    def initVisibilityData(self):
-        # First make sure everything is shown
-        self.showAllVisibles()
-        # A map of zone ID's to a list of nodes that are visible from
-        # that zone.
-        self.nodeDict = {}
-        # A map of zone ID's to the particular node that corresponds
-        # to that zone.
-        self.zoneDict = {}
-        # A list of all visible nodes
-        self.nodeList = []
-        # NOTE: this should change to find the groupnodes in
-        # the dna storage instead of searching through the tree
-        for i in range(DNASTORE.getNumDNAVisGroups()):
-            groupFullName = DNASTORE.getDNAVisGroupName(i)
-            groupName = self.extractGroupName(groupFullName)
-            zoneId = int(groupName)
-            self.nodeDict[zoneId] = []
-            self.zoneDict[zoneId] = self.NPToplevel.find("**/" + groupName)
-
-            # TODO: we only need to look from the top of the hood
-            # down one level to find the vis groups as an optimization
-            groupNode = self.NPToplevel.find("**/" + groupFullName)
-            if groupNode.isEmpty():
-                print "Could not find visgroup"
-            self.nodeList.append(groupNode)
-            for j in range(DNASTORE.getNumVisiblesInDNAVisGroup(i)):
-                visName = DNASTORE.getVisibleName(i, j)
-                visNode = self.NPToplevel.find("**/" + visName)
-                self.nodeDict[zoneId].append(visNode)
-        # Rename the floor polys to have the same name as the
-        # visgroup they are in... This makes visibility possible.
-        self.renameFloorPolys(self.nodeList)
-        # Init vis flag
-        self.fVisInit = 1
-
-    def extractGroupName(self, groupFullName):
-        # The Idea here is that group names may have extra flags associated
-        # with them that tell more information about what is special about
-        # the particular vis zone. A normal vis zone might just be "13001",
-        # but a special one might be "14356:safe_zone" or
-        # "345:safe_zone:exit_zone"... These are hypotheticals. The main
-        # idea is that there are colon separated flags after the initial
-        # zone name.
-        return(string.split(groupFullName, ":", 1)[0])
-
-    def renameFloorPolys(self, nodeList):
-        for i in nodeList:
-            # Get all the collision nodes in the vis group
-            collNodePaths = i.findAllMatches("**/+CollisionNode")
-            numCollNodePaths = collNodePaths.getNumPaths()
-            visGroupName = i.node().getName()
-            for j in range(numCollNodePaths):
-                collNodePath = collNodePaths.getPath(j)
-                bitMask = collNodePath.node().getIntoCollideMask()
-                if bitMask.getBit(1):
-                    # Bit 1 is the floor collision bit. This renames
-                    # all floor collision polys to the same name as their
-                    # visgroup.
-                    collNodePath.node().setName(visGroupName)
-
-    def hideAllVisibles(self):
-        for i in self.nodeList:
-            i.hide()
-
-    def showAllVisibles(self):
-        for i in self.nodeList:
-            i.show()
-            i.clearColor()
-
-    def visibilityOn(self):
-        self.visibilityOff()
-        # Accept event
-        self.accept("on-floor", self.enterZone)
-        # Add collider
-        self.cTrav.addCollider(self.cRayNodePath, self.lifter)
-        # Reset lifter
-        self.lifter.clear()
-        # Reset flag
-        self.fVisInit = 1
-
-    def visibilityOff(self):
-        self.ignore("on-floor")
-        self.cTrav.removeCollider(self.cRayNodePath)
-        self.showAllVisibles()
-
-    def toggleVisibility(self):
-        if self.panel.fVis.get():
-            self.visibilityOn()
-            self.traversalOn()
-        else:
-            self.visibilityOff()
-            if (not (self.panel.fColl.get() or self.panel.fVis.get())):
-                self.traversalOff()
-
-    def enterZone(self, newZone):
-        return
-        """
-        Puts the toon in the indicated zone.  newZone may either be a
-        CollisionEntry object as determined by a floor polygon, or an
-        integer zone id.  It may also be None, to indicate no zone.
-        """
-        # First entry into a zone, hide everything
-        if self.fVisInit:
-            self.hideAllVisibles()
-            self.fVisInit = 0
-        # Get zone id
-        if isinstance(newZone, CollisionEntry):
-            # Get the name of the collide node
-            try:
-                newZoneId = int(newZone.getIntoNode().getName())
-            except:
-                newZoneId = 0
-        else:
-            newZoneId = newZone
-        # Ensure we have vis data
-        assert self.nodeDict
-        # Hide the old zone (if there is one)
-        if self.__zoneId != None:
-            for i in self.nodeDict[self.__zoneId]:
-                i.hide()
-        # Show the new zone
-        if newZoneId != None:
-            for i in self.nodeDict[newZoneId]:
-                i.show()
-        # Make sure we changed zones
-        if newZoneId != self.__zoneId:
-            if self.panel.fVisZones.get():
-                # Set a color override on our zone to make it obvious what
-                # zone we're in.
-                if self.__zoneId != None:
-                    self.zoneDict[self.__zoneId].clearColor()
-                if newZoneId != None:
-                    self.zoneDict[newZoneId].setColor(0, 0, 1, 1, 100)
-            # The new zone is now old
-            self.__zoneId = newZoneId
-
-    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('DIRECT-mouse3', self.levelHandleMouse3)
-        self.accept('DIRECT-mouse3Up', self.levelHandleMouse3Up)
-
-    def disableMouse(self):
-        """ Disable Pie Menu interaction """
-        # Disable handling of mouse events
-        self.ignore('DIRECT-mouse3')
-        self.ignore('DIRECT-mouse3Up')
-
-    # LEVEL OBJECT MANAGEMENT FUNCTIONS
-    def findDNANode(self, nodePath):
-        """ Find node path's DNA Object in DNAStorage (if any) """
-        if nodePath:
-            return DNASTORE.findDNAGroup(nodePath.node())
-        else:
-            return None
-
-    def replaceSelected(self):
-        if self.replaceSelectedEnabled:
-            # Update visible geometry using new DNA
-            newRoot = self.replace(self.selectedNPRoot, self.selectedDNARoot)
-            # Reselect node path and respawn followSelectedNodePathTask
-            base.direct.select(newRoot)
-
-    def replace(self, nodePath, dnaNode):
-        """ Replace a node path with the results of a DNANode traversal """
-        # Find node path's parent
-        if not nodePath:
-            return None
-        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
-        try:
-            newNodePath = dnaNode.traverse(parent, DNASTORE, 1)
-        except Exception:
-            self.notify.debug("Couldn't traverse existing DNA! Do not trust replace")
-            return None
-        # 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
-            base.direct.deselect(nodePath)
-            # Now you can get rid of the node path
-            nodePath.removeNode()
-
-    def removeNodePathHook(self, nodePath):
-        dnaNode = self.findDNANode(nodePath)
-        # Does the node path correspond to a DNA Object
-        if dnaNode:
-            for hook in self.removeHookList:
-                hook(dnaNode, nodePath)
-            # 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)
-        else:
-            pointOrCell, type = self.findPointOrCell(nodePath)
-            if pointOrCell and type:
-                if (type == 'suitPointMarker'):
-                    print 'Suit Point:', pointOrCell
-                    if DNASTORE.removeSuitPoint(pointOrCell):
-                        print "Removed from DNASTORE"
-                    else:
-                        print "Not found in DNASTORE"
-                    # Remove point from pointDict
-                    del(self.pointDict[pointOrCell])
-                    # Remove point from visitedPoints list
-                    if pointOrCell in self.visitedPoints:
-                        self.visitedPoints.remove(pointOrCell)
-                    # Update edge related dictionaries
-                    for edge in self.point2edgeDict[pointOrCell]:
-                        # Is it still in edge dict?
-                        oldEdgeLine = self.edgeDict.get(edge, None)
-                        if oldEdgeLine:
-                            del self.edgeDict[edge]
-                            oldEdgeLine.reset()
-                            del oldEdgeLine
-                        # Find other endpoints of edge and clear out
-                        # corresponding point2edgeDict entry
-                        startPoint = edge.getStartPoint()
-                        endPoint = edge.getEndPoint()
-                        if pointOrCell == startPoint:
-                            self.point2edgeDict[endPoint].remove(edge)
-                        elif pointOrCell == endPoint:
-                            self.point2edgeDict[startPoint].remove(edge)
-                        # Is it in the visited edges list?
-                        if edge in self.visitedEdges:
-                            self.visitedEdges.remove(edge)
-                    # Now delete point2edgeDict entry for this point
-                    del(self.point2edgeDict[pointOrCell])
-                elif (type == 'battleCellMarker'):
-                    # Get parent vis group
-                    visGroupNP, visGroupDNA = self.findParentVisGroup(
-                        nodePath)
-                    print 'Battle Cell:', pointOrCell
-                    # Remove cell from vis group
-                    if visGroupNP and visGroupDNA:
-                        if visGroupDNA.removeBattleCell(pointOrCell):
-                            print "Removed from Vis Group"
-                        else:
-                            print "Not found in Vis Group"
-                    else:
-                        print "Parent Vis Group not found"
-                    # Remove cell from DNASTORE
-                    if DNASTORE.removeBattleCell(pointOrCell):
-                        print "Removed from DNASTORE"
-                    else:
-                        print "Not found in DNASTORE"
-                    # Remove cell from cellDict
-                    del(self.cellDict[pointOrCell])
-
-    def reparent(self, nodePath, oldParent, newParent):
-        """ Move node path (and its DNA) to active parent """
-        # Does the node path correspond to a DNA Object
-        dnaNode = self.findDNANode(nodePath)
-        if dnaNode:
-            # Find old parent DNA
-            oldParentDNANode = self.findDNANode(oldParent)
-            # Remove DNA from old parent
-            if oldParentDNANode:
-                oldParentDNANode.remove(dnaNode)
-            if newParent:
-                # Update active parent just to be safe
-                self.setActiveParent(newParent)
-                # Move DNA to new parent (if active parent set)
-                if self.DNAParent != None:
-                    self.DNAParent.add(dnaNode)
-                    # It is, is it a DNA_NODE (i.e. it has pos/hpr/scale)?
-                    # Update pose to reflect new relationship
-                    if DNAIsDerivedFrom(dnaNode, DNA_NODE):
-                        # Update DNA
-                        self.updatePose(dnaNode, nodePath)
-        elif newParent:
-            # See if this node path is a suit edge
-            suitEdge, oldVisGroup = self.np2EdgeDict.get(nodePath.id(), (None, None))
-            # And see if the new parent is a vis group
-            newVisGroupNP, newVisGroupDNA = self.findParentVisGroup(newParent)
-            if suitEdge and DNAClassEqual(newVisGroupDNA, DNA_VIS_GROUP):
-                # If so, remove suit edge from old vis group and add it to the new group
-                oldVisGroup.removeSuitEdge(suitEdge)
-                # Update suit edge to reflect new zone ID
-                suitEdge.setZoneId(newVisGroupDNA.getName())
-                newVisGroupDNA.addSuitEdge(suitEdge)
-                # Update np2EdgeDict to reflect changes
-                self.np2EdgeDict[nodePath.id()] = [suitEdge, newVisGroupDNA]
-
-    def setActiveParent(self, nodePath = None):
-        """ Set NPParent and DNAParent to node path and its DNA """
-        # 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 setName(self, nodePath, newName):
-        """ Set name of nodePath's DNA (if it exists) """
-        # 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, nodePathList):
-        """
-        Update the DNA database to reflect selected objects current positions
-        """
-        for selectedNode in nodePathList:
-            # 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(base.direct.grid)
-                    snapPos = base.direct.grid.computeSnapPoint(pos)
-                    if self.panel.fPlaneSnap.get():
-                        zheight = 0
-                    else:
-                        zheight = snapPos[2]
-                    selectedNode.setPos(base.direct.grid,
-                                        snapPos[0], snapPos[1], zheight)
-                    # Angle snap
-                    h = base.direct.grid.computeSnapAngle(selectedNode.getH())
-                    if base.direct.grid.getHprSnap():
-                        selectedNode.setH(h)
-                    if selectedNode == base.direct.selected.last:
-                        self.setLastAngle(h)
-                    # Update DNA
-                    self.updatePose(dnaObject, selectedNode)
-            else:
-                pointOrCell, type = self.findPointOrCell(selectedNode)
-                if pointOrCell and type:
-                    # First snap selected node path to grid
-                    pos = selectedNode.getPos(base.direct.grid)
-                    snapPos = base.direct.grid.computeSnapPoint(pos)
-                    if self.panel.fPlaneSnap.get():
-                        zheight = 0
-                    else:
-                        zheight = snapPos[2]
-                    selectedNode.setPos(
-                        base.direct.grid,
-                        snapPos[0], snapPos[1], zheight)
-                    newPos = selectedNode.getPos(self.NPToplevel)
-                    # Update DNA
-                    pointOrCell.setPos(newPos)
-                    if (type == 'suitPointMarker'):
-                        print "Found suit point!", pointOrCell
-                        # Ok, now update all the lines into that node
-                        for edge in self.point2edgeDict[pointOrCell]:
-                            # Is it still in edge dict?
-                            oldEdgeLine = self.edgeDict.get(edge, None)
-                            if oldEdgeLine:
-                                del self.edgeDict[edge]
-                                oldEdgeLine.reset()
-                                oldEdgeLine.removeNode()
-                                del oldEdgeLine
-                                newEdgeLine = self.drawSuitEdge(
-                                    edge, self.NPParent)
-                                self.edgeDict[edge] = newEdgeLine
-                    elif (type == 'battleCellMarker'):
-                        print "Found battle cell!", pointOrCell
-
-    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 """
-        # What is the current building type?
-        buildingType = self.getCurrent('building_type')
-        # If random
-        if buildingType == 'random':
-            # Generate height list based on current building height
-            buildingHeight = self.getCurrent('building_height')
-            heightList = self.getRandomHeightList(buildingHeight)
-            # Convert height list to building type
-            buildingType = createHeightCode(heightList)
-        else:
-            # Use specified height list
-            heightList = map(string.atof, string.split(buildingType, '_'))
-            height = calcHeight(heightList)
-            # Is this a never before seen height list?  If so, record it.
-            try:
-                attr = self.getAttribute(`height` + '_ft_wall_heights')
-                if heightList not in attr.getList():
-                    print 'Adding new height list entry'
-                    attr.add(heightList)
-            except KeyError:
-                print 'Non standard height building'
-
-        # See if this building type corresponds to existing style dict
-        try:
-            dict = self.getDict(buildingType + '_styles')
-        except KeyError:
-            # Nope
-            dict = {}
-
-        # No specific dict or empty dict, try to pick a dict
-        # based on number of walls
-        if not dict:
-            # How many walls?
-            numWalls = len(heightList)
-            # Get the building_style attribute dictionary for
-            # this number of walls
-            dict = self.getDict(`numWalls` + '_wall_styles')
-
-        if not dict:
-            # Still no dict, create new random style using height list
-            styleList = []
-            # Build up wall styles
-            for height in heightList:
-                wallStyle = self.getRandomDictionaryEntry(
-                    self.getDict('wall_style'))
-                styleList.append((wallStyle, height))
-            # Create new random flat building style
-            style = DNAFlatBuildingStyle(styleList = styleList)
-        else:
-            # Pick a style
-            style = self.getRandomDictionaryEntry(dict)
-
-        # Set style....finally
-        self.styleManager.setDNAFlatBuildingStyle(
-            dnaNode, style, width = self.getRandomWallWidth(),
-            heightList = heightList, name = name)
-
-    def getRandomHeightList(self, buildingHeight):
-        # Select a list of wall heights
-        heightLists = self.getList(`buildingHeight` + '_ft_wall_heights')
-        l = len(heightLists)
-        if l > 0:
-            # If a list exists for this building height, pick randomly
-            return heightLists[randint(0, l - 1)]
-        else:
-            # No height lists exists for this building height, generate
-            chance = randint(0, 100)
-            if buildingHeight <= 10:
-                return [buildingHeight]
-            elif buildingHeight <= 14:
-                return [4, 10]
-            elif buildingHeight <= 20:
-                if chance <= 30:
-                    return [20]
-                elif chance <= 80:
-                    return [10, 10]
-                else:
-                    return [12, 8]
-            elif buildingHeight <= 24:
-                if chance <= 50:
-                    return [4, 10, 10]
-                else:
-                    return [4, 20]
-            elif buildingHeight <= 25:
-                if chance <= 25:
-                    return [3, 22]
-                elif chance <= 50:
-                    return [4, 21]
-                elif chance <= 75:
-                    return [3, 13, 9]
-                else:
-                    return [4, 13, 8]
-            else:
-                if chance <= 20:
-                    return [10, 20]
-                elif chance <= 35:
-                    return [20, 10]
-                elif chance <= 75:
-                    return [10, 10, 10]
-                else:
-                    return [13, 9, 8]
-
-    def getRandomWallWidth(self):
-        chance = randint(0, 100)
-        if chance <= 15:
-            return 5.0
-        elif chance <= 30:
-            return 10.0
-        elif chance <= 65:
-            return 15.0
-        elif chance <= 85:
-            return 20.0
-        else:
-            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'))
-
-        # Position it
-        # First kill autoposition task so grid can jump to its final
-        # destination (part of cleanup
-        taskMgr.remove('autoPositionGrid')
-        # Now find where to put node path
-        if (hotKey is not None) and nodeClass.eq(DNA_PROP):
-            # If its a prop and a copy, place it based upon current
-            # mouse position
-            hitPt = self.getGridIntersectionPoint()
-            # Attach a node, so we can set pos with respect to:
-            tempNode = hidden.attachNewNode('tempNode')
-            # Place it:
-            tempNode.setPos(base.direct.grid, hitPt)
-            # Copy the pos to where we really want it:
-            dnaNode.setPos(tempNode.getPos())
-            # Clean up:
-            tempNode.removeNode()
-        else:
-            # Place the new node path at the current grid origin
-            dnaNode.setPos(base.direct.grid.getPos())
-        # Initialize angle to match last object
-        dnaNode.setHpr(Vec3(self.getLastAngle(), 0, 0))
-
-        # 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()
-
-        # Reset last Code (for autoPositionGrid)
-        if DNAClassEqual(dnaNode, DNA_STREET):
-            self.snapList = self.getSnapPoint(dnaNode.getCode())
-        # Select the instance
-        base.direct.select(newNodePath)
-        self.lastNodePath = newNodePath
-        # Update grid to get ready for the next object
-        self.autoPositionGrid()
-
-    def getSnapPoint(self, code):
-        return OBJECT_SNAP_POINTS.get(code, [(Vec3(0.0, 0, 0), Vec3(0)), (Vec3(0), Vec3(0))])
-
-    def addGroup(self, nodePath):
-        """ Add a new DNA Node Group to the specified Node Path """
-        # Set the node path as the current parent
-        base.direct.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
-        base.direct.setActiveParent(nodePath)
-        # Add a new group to the selected parent
-        self.createNewGroup(type = 'vis')
-
-    def createNewGroup(self, type = 'dna'):
-        print "createNewGroup"
-        """ 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 = 'tb0:'+ buildingType + '_DNARoot')
-        # Now place new building in the world
-        self.initDNANode(newDNAFlatBuilding)
-
-    def getNextLandmarkBlock(self):
-        self.landmarkBlock=self.landmarkBlock+1
-        return str(self.landmarkBlock)
-
-    def addLandmark(self, landmarkType, specialType):
-        # Record new landmark type
-        self.setCurrent('toon_landmark_texture', landmarkType)
-        # And create new landmark building
-        block=self.getNextLandmarkBlock()
-        newDNALandmarkBuilding = DNALandmarkBuilding(
-            'tb'+block+':'+landmarkType + '_DNARoot')
-        newDNALandmarkBuilding.setCode(landmarkType)
-        newDNALandmarkBuilding.setBuildingType(specialType)
-        newDNALandmarkBuilding.setPos(VBase3(0))
-        newDNALandmarkBuilding.setHpr(VBase3(0))
-        # Headquarters do not have doors
-        if specialType not in ['hq', 'kartshop']:
-            newDNADoor = self.createDoor('landmark_door')
-            newDNALandmarkBuilding.add(newDNADoor)
-        # Now place new landmark building in the world
-        self.initDNANode(newDNALandmarkBuilding)
-
-    def addProp(self, propType):
-        print "addProp %s " % 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')
-        newDNAStreet.setCurbTexture(
-            'street_curb_' + 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, type):
-        if (type == 'landmark_door'):
-            newDNADoor = DNADoor('door')
-            print "createDoor %s" % type
-            if not (self.getCurrent('door_double_texture')):
-                doorStyles = self.styleManager.attributeDictionary['door_double_texture'].getList()[1:]
-                defaultDoorStyle = random.choice(doorStyles)
-                self.setCurrent('door_double_texture', defaultDoorStyle)
-            newDNADoor.setCode(self.getCurrent('door_double_texture'))
-            print "doorcolor = %s" % self.getCurrent('door_color')
-            newDNADoor.setColor(self.getCurrent('door_color'))
-        elif (type == 'door'):
-            newDNADoor = DNAFlatDoor('door')
-            if not (self.getCurrent('door_single_texture')):
-                doorStyles = self.styleManager.attributeDictionary['door_single_texture'].getList()[1:]
-                defaultDoorStyle = random.choice(doorStyles)
-                self.setCurrent('door_single_texture', defaultDoorStyle)
-            newDNADoor.setCode(self.getCurrent('door_single_texture'))
-            newDNADoor.setColor(self.getCurrent('door_color'))
-        return newDNADoor
-
-    def createSign(self):
-        if not (self.getCurrent('sign_texture')):
-            defaultSignStyle = self.styleManager.attributeDictionary['sign_texture'].getList()[0]
-            self.setCurrent('sign_texture', defaultSignStyle)
-        newDNASign = DNASign('sign')
-        newDNASign.setCode(self.getCurrent('sign_texture'))
-        newDNASign.setColor(self.getCurrent('sign_color'))
-        #newDNASign.setColor(VBase4(0.0, 1.0, 0.0, 1.0))
-        #newDNASign.setScale(VBase3(2.0, 1.0, 2.0))
-
-        baseline = DNASignBaseline('baseline')
-        baseline.setCode("humanist")
-        baseline.setColor(VBase4(0.0, 0.0, 0.0, 1.0))
-        #baseline.setKern(1.0)
-        #baseline.setWiggle(30.0)
-        #baseline.setStumble(1.0)
-        #baseline.setStomp(10.0)
-        #baseline.setWidth(16.0)
-        #baseline.setHeight(16.0)
-        baseline.setScale(VBase3(0.7, 1.0, 0.7))
-        newDNASign.add(baseline)
-
-        #graphic = DNASignGraphic('graphic')
-        #graphic.setCode("sign_general1")
-        #graphic.setColor(VBase4(1.0, 1.0, 0.0, 0.5))
-        #graphic.setScale(VBase3(.2, 1.0, .2))
-        #graphic.setHpr(VBase3(0.0, 0.0, 90.0))
-        #baseline.add(graphic)
-
-        DNASetBaselineString(baseline, "Toon Shop")
-
-        return newDNASign
-
-    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 removeSign(self, sign, parent):
-        self.setCurrent('sign_color', sign.getColor())
-        DNARemoveChildOfClass(parent, DNA_SIGN)
-
-    def removeDoor(self, door, parent):
-        self.setCurrent('door_color', door.getColor())
-        DNARemoveChildOfClass(parent, DNA_FLAT_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, modifiers):
-        # import pdb; pdb.set_trace()
-        if base.direct.cameraControl.useMayaCamControls and modifiers == 4: # alt is down, use maya controls
-            self.mouseMayaCamera = True
-            return
-        else:
-            self.mouseMayaCamera = False
-        
-        # 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, modifiers)
-            # Check menuMode
-            if menuMode == None:
-                return
-            # 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_FLAT_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 base.direct.gotControl(modifiers):
-                menuMode = 'prop_color'
-            elif base.direct.gotAlt(modifiers) and self.panel.currentBaselineDNA:
-                menuMode = 'baseline_style'
-            elif base.direct.gotShift(modifiers):
-                menuMode = 'sign_texture'
-                self.DNATarget = DNAGetChildOfClass(dnaObject, DNA_SIGN)
-                self.DNATargetParent = dnaObject
-            else:
-                menuMode = 'prop_texture'
-        elif DNAClassEqual(dnaObject, DNA_LANDMARK_BUILDING):
-            # INSERT HERE
-            # LANDMARK BUILDING OPERATIONS
-            menuMode = self.getLandmarkBuildingMode(dnaObject, modifiers)
-            if string.find(menuMode, 'door') >= 0:
-                self.DNATarget = DNAGetChildOfClass(dnaObject, DNA_DOOR)
-                self.DNATargetParent = dnaObject
-            elif string.find(menuMode, 'sign') >= 0:
-                self.DNATarget = DNAGetChildOfClass(dnaObject, DNA_SIGN)
-                self.DNATargetParent = dnaObject
-            else:
-                self.DNATarget = dnaObject
-        elif DNAClassEqual(dnaObject, DNA_STREET):
-            # STREET OPERATIONS
-            self.DNATarget = dnaObject
-            menuMode = 'street_texture'
-
-        # No valid menu mode, get the hell out of here!
-        if menuMode == None:
-            return
-
-        # 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()[-2:]
-            elif menuMode == 'building_width':
-                state = self.DNATarget.getWidth()
-            elif menuMode == 'window_count':
-                state = self.DNATarget.getWindowCount()
-            elif menuMode == 'building_style_all':
-                # Extract the building style from the current building
-                state = DNAFlatBuildingStyle(building = self.DNATarget)
-            elif menuMode == 'baseline_style':
-                # Extract the baseline style
-                state = DNABaselineStyle(
-                    baseline = self.panel.currentBaselineDNA)
-            elif menuMode == 'wall_style':
-                # Extract the wall style from the current wall
-                state = DNAWallStyle(wall = self.DNATarget)
-        self.activeMenu.setInitialState(state)
-
-        # Spawn active menu's task
-        self.activeMenu.spawnPieMenuTask()
-
-    def getLandmarkBuildingMode(self, dnaObject, modifiers):
-        # Where are we hitting the building?
-        hitPt = self.getWallIntersectionPoint(self.selectedNPRoot)
-        if hitPt[2] < 10.0:
-            # Do door operations
-            if base.direct.gotControl(modifiers):
-                menuMode = 'door_color'
-            elif base.direct.gotAlt(modifiers):
-                menuMode = 'door_orientation'
-            else:
-                menuMode = 'door_double_texture'
-        else:
-            # Do sign operations
-            if base.direct.gotControl(modifiers):
-                menuMode = 'sign_color'
-            elif base.direct.gotAlt(modifiers) and self.panel.currentBaselineDNA:
-                menuMode = 'baseline_style'
-            elif base.direct.gotAlt(modifiers):
-                menuMode = 'sign_orientation'
-            else:
-                menuMode = 'sign_texture'
-        return menuMode
-
-    def getFlatBuildingMode(self, dnaObject, modifiers):
-        # Where are we hitting the building?
-        hitPt = self.getWallIntersectionPoint(self.selectedNPRoot)
-        wallNum = self.computeWallNum(dnaObject, hitPt)
-        if wallNum < 0:
-            # Do building related operations
-            if base.direct.gotAlt(modifiers):
-                menuMode = 'building_width'
-            else:
-                menuMode = 'building_style_all'
-        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 base.direct.gotControl(modifiers):
-                    menuMode = 'cornice_color'
-                elif base.direct.gotAlt(modifiers):
-                    menuMode = 'cornice_orientation'
-                else:
-                    menuMode = 'cornice_texture'
-            elif ((xPt < 0.3) or (xPt > 0.7)):
-                # Do wall operations
-                if base.direct.gotControl(modifiers):
-                    menuMode = 'wall_color'
-                elif base.direct.gotAlt(modifiers):
-                    menuMode = 'wall_orientation'
-                elif base.direct.gotShift(modifiers):
-                    menuMode = 'wall_texture'
-                else:
-                    menuMode = 'wall_style'
-            elif (zPt < 0.4):
-                # Do door operations
-                if base.direct.gotControl(modifiers):
-                    menuMode = 'door_color'
-                elif base.direct.gotAlt(modifiers):
-                    menuMode = 'door_orientation'
-                else:
-                    menuMode = 'door_single_texture'
-            else:
-                # Do window operations
-                if base.direct.gotControl(modifiers):
-                    menuMode = 'window_color'
-                elif base.direct.gotAlt(modifiers):
-                    menuMode = 'window_orientation'
-                elif base.direct.gotShift(modifiers):
-                    menuMode = 'window_count'
-                else:
-                    menuMode = 'window_texture'
-        return menuMode, wallNum
-
-    def levelHandleMouse3Up(self):
-        if self.mouseMayaCamera:
-            return
-        
-        if self.activeMenu:
-            self.activeMenu.removePieMenuTask()
-        # Update panel color if appropriate
-        if self.DNATarget:
-            objClass = DNAGetClassType(self.DNATarget)
-            if ((objClass.eq(DNA_WALL)) or
-                (objClass.eq(DNA_WINDOWS)) or
-                (objClass.eq(DNA_DOOR)) or
-                (objClass.eq(DNA_FLAT_DOOR)) or
-                (objClass.eq(DNA_CORNICE)) or
-                (objClass.eq(DNA_PROP))
-                ):
-                self.panel.setCurrentColor(self.DNATarget.getColor())
-                
-##        b1 = DirectButton(text = ("Button1", "click!", "roll", "disabled"),
-##                  text_scale=0.1, borderWidth = (0.01, 0.01),
-##                  relief=2)
-##
-##        b2 = DirectButton(text = ("Button2", "click!", "roll", "disabled"),
-##                  text_scale=0.1, borderWidth = (0.01, 0.01),
-##                  relief=2)
-##
-##        l1 = DirectLabel(text = "Test1", text_scale=0.1)
-##        l2 = DirectLabel(text = "Test2", text_scale=0.1)
-##        l3 = DirectLabel(text = "Test3", text_scale=0.1)
-##
-##        numItemsVisible = 4
-##        itemHeight = 0.11
-##
-##        myScrolledList = DirectScrolledList(
-##                decButton_pos= (0.35, 0, 0.53),
-##                decButton_text = "Dec",
-##                decButton_text_scale = 0.04,
-##                decButton_borderWidth = (0.005, 0.005),
-##
-##                incButton_pos= (0.35, 0, -0.02),
-##                incButton_text = "Inc",
-##                incButton_text_scale = 0.04,
-##                incButton_borderWidth = (0.005, 0.005),
-##
-##                frameSize = (0.0, 0.7, -0.05, 0.59),
-##                frameColor = (1,0,0,0.5),
-##                pos = (-1, 0, 0),
-##                items = [b1, b2],
-##                numItemsVisible = numItemsVisible,
-##                forceHeight = itemHeight,
-##                itemFrame_frameSize = (-0.2, 0.2, -0.37, 0.11),
-##                itemFrame_pos = (0.35, 0, 0.4),
-##        )
-##
-##        myScrolledList.addItem(l1)
-##        myScrolledList.addItem(l2)
-##        myScrolledList.addItem(l3)
-##
-##        for fruit in ['apple', 'pear', 'banana', 'orange']:
-##            l = DirectLabel(text = fruit, text_scale=0.1)
-##            myScrolledList.addItem(l)
-##
-##        myScrolledList.reparentTo(aspect2d)
-
-
-    def setDNATargetColor(self, color):
-        if self.DNATarget:
-            self.DNATarget.setColor(color)
-            self.replaceSelected()
-
-    def setDNATargetCode(self, type, code):
-        if (self.DNATarget != None) and (code != None):
-            # Update code
-            self.DNATarget.setCode(code)
-        elif (self.DNATarget != None) and (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 == 'sign'):
-                self.removeSign(self.DNATarget, self.DNATargetParent)
-            elif (type == 'landmark_door'):
-                self.removeLandmarkDoor(self.DNATarget, self.DNATargetParent)
-            elif (type == 'door'):
-                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) and (code != None):
-            # Add new object
-            if (type == 'cornice'):
-                self.DNATarget = self.createCornice()
-            elif (type == 'sign'):
-                self.DNATarget = self.createSign()
-            elif (type == 'landmark_door'):
-                self.DNATarget = self.createDoor('landmark_door')
-            elif (type == 'door'):
-                self.DNATarget = self.createDoor('door')
-            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()
-            if self.DNATarget:
-                self.DNATargetParent.add(self.DNATarget)
-        # Update visible representation
-        self.replaceSelected()
-
-    def setDNATargetOrientation(self, orientation):
-        if (self.DNATarget != None) and (orientation != None):
-            oldCode = self.DNATarget.getCode()[:-2]
-            # Suit walls only have two orientations!
-            if oldCode.find('wall_suit') >= 0:
-                orientation = 'u' + orientation[1]
-            self.DNATarget.setCode(oldCode+orientation)
-            self.replaceSelected()
-
-    def setBuildingStyle(self, style):
-        if (self.DNATarget != None) and (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) and (count != 0):
-            self.DNATarget.setWindowCount(count)
-        elif (self.DNATarget != None) and (count == 0):
-            # Remove windows and clear out DNATarget
-            self.removeWindows(self.DNATarget, self.DNATargetParent)
-            self.DNATarget = None
-        elif (self.DNATarget == None) and (count != 0):
-            self.DNATarget = self.createWindows()
-            self.DNATargetParent.add(self.DNATarget)
-        self.replaceSelected()
-
-    def setWallStyle(self, style):
-        if (self.DNATarget != None) and (style != None):
-            self.styleManager.setDNAWallStyle(
-                self.DNATarget, style,
-                self.DNATarget.getHeight())
-            self.replaceSelected()
-
-    def setColor(self, nodePath, r, g, b, a):
-        """ This is used to set color of dnaNode subparts """
-        dnaNode = self.findDNANode(nodePath)
-        if dnaNode:
-            objClass = DNAGetClassType(dnaNode)
-            if ((objClass.eq(DNA_WALL)) or
-                (objClass.eq(DNA_WINDOWS)) or
-                (objClass.eq(DNA_DOOR)) or
-                (objClass.eq(DNA_FLAT_DOOR)) or
-                (objClass.eq(DNA_CORNICE)) or
-                (objClass.eq(DNA_PROP)) or
-                (objClass.eq(DNA_SIGN)) or
-                (objClass.eq(DNA_SIGN_BASELINE)) or
-                (objClass.eq(DNA_SIGN_TEXT)) or
-                (objClass.eq(DNA_SIGN_GRAPHIC))
-                ):
-                # Update dna information
-                dnaNode.setColor(VBase4(r/255.0, g/255.0, b/255.0, a/255.0))
-
-    # 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
-        self.selectedSuitPoint = None
-        # Now process newly selected node path
-        dnaParent = None
-        dnaNode = self.findDNANode(nodePath)
-        if base.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
-            base.direct.deselect(nodePath)
-            # And select parent
-            base.direct.select(dnaParent, base.direct.fShift)
-        elif dnaNode:
-            # We got a valid node path/DNA object, continue
-            self.selectedNPRoot = nodePath
-            self.selectedDNARoot = dnaNode
-            # Reset last landmark
-            if DNAClassEqual(dnaNode, DNA_LANDMARK_BUILDING):
-                self.lastLandmarkBuildingDNA=dnaNode
-                if self.showLandmarkBlockToggleGroup:
-                    # Toggle old highlighting off:
-                    self.toggleShowLandmarkBlock()
-                    # Toggle on the the new highlighting:
-                    self.toggleShowLandmarkBlock()
-            # Reset last Code (for autoPositionGrid)
-            if DNAClassEqual(dnaNode, DNA_STREET):
-                self.snapList = self.getSnapPoint(dnaNode.getCode())
-        else:
-            pointOrCell, type = self.findPointOrCell(nodePath)
-            if pointOrCell and (type == 'suitPointMarker'):
-                print "Found suit point!", pointOrCell
-                self.selectedSuitPoint = pointOrCell
-            elif pointOrCell and (type == 'battleCellMarker'):
-                print "Found battle cell!", pointOrCell
-            else:
-                if nodePath.getName() != 'suitEdge':
-                    suitEdge = self.findSuitEdge(nodePath.getParent())
-                    if suitEdge:
-                        # Yes, deselect currently selected node path
-                        base.direct.deselect(nodePath)
-                        # And select parent
-                        base.direct.select(suitEdge, base.direct.fShift)
-
-        # Let others know that something new may be selected:
-        for i in self.selectedNodePathHookHooks:
-                i()
-
-        if self.fDrive:
-            base.direct.deselect(nodePath)
-
-    def deselectedNodePathHook(self, nodePath):
-        # Clear out old root variables
-        self.selectedDNARoot = None
-        self.selectedNPRoot = None
-        self.selectedSuitPoint = None
-        # Let others know:
-        for i in self.deselectedNodePathHookHooks:
-                i()
-
-    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())
-
-    def findSuitEdge(self, nodePath):
-        """ Walk up a node path's ancestry looking for a suit edge """
-        # Check current node's name for suitEdge marker
-        if (nodePath.getName() == 'suitEdge'):
-            # Its a suitEdge
-            return nodePath
-        else:
-            # If reached the top: fail
-            if not nodePath.hasParent():
-                return None
-            else:
-                # Try parent
-                return self.findSuitEdge(nodePath.getParent())
-
-    def findPointOrCell(self, nodePath):
-        """
-        Walk up a node path's ancestry to see if its a suit point marker
-        or a battle cell marker
-        """
-        # Check current node's name for root marker
-        if (nodePath.getName() == 'suitPointMarker'):
-            # Its a suit point marker!
-            # See if point is in pointDict
-            point = self.getSuitPointFromNodePath(nodePath)
-            return point, 'suitPointMarker'
-        elif (nodePath.getName() == 'battleCellMarker'):
-            # Its a battle cell marker
-            # See if cell is in cell Dict
-            cell = self.getBattleCellFromNodePath(nodePath)
-            return cell, 'battleCellMarker'
-        else:
-            # If reached the top: fail
-            if not nodePath.hasParent():
-                return None, None
-            else:
-                # Try parent
-                return self.findPointOrCell(nodePath.getParent())
-
-    # MANIPULATION FUNCTIONS
-    def keyboardRotateSelected(self, arrowDirection):
-        """ Rotate selected objects using arrow keys """
-        # Get snap angle
-        if base.direct.fShift:
-            oldSnapAngle = base.direct.grid.snapAngle
-            base.direct.grid.setSnapAngle(1.0)
-        snapAngle = base.direct.grid.snapAngle
-        # Compute new angle
-        if ((arrowDirection == 'left') or (arrowDirection == 'up')):
-            self.setLastAngle(self.getLastAngle() + snapAngle)
-        else:
-            self.setLastAngle(self.getLastAngle() - snapAngle)
-
-        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 base.direct.selected:
-            selectedNode.setHpr(self.getLastAngle(), 0, 0)
-        # Snap objects to grid and update DNA if necessary
-        self.updateSelectedPose(base.direct.selected.getSelectedAsList())
-        if base.direct.fShift:
-            base.direct.grid.setSnapAngle(oldSnapAngle)
-
-    def keyboardTranslateSelected(self, arrowDirection):
-        gridToCamera = base.direct.grid.getMat(base.direct.camera)
-        camXAxis = gridToCamera.xformVec(X_AXIS)
-        xxDot = camXAxis.dot(X_AXIS)
-        xzDot = camXAxis.dot(Z_AXIS)
-
-        # what is the current grid spacing?
-        if base.direct.fShift:
-            # If shift, divide grid spacing by 10.0
-            oldGridSpacing = base.direct.grid.gridSpacing
-            # Use back door to set grid spacing to avoid grid update
-            base.direct.grid.gridSpacing = base.direct.grid.gridSpacing/10.0
-        deltaMove = base.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 base.direct.selected:
-            # Move it
-            selectedNode.setPos(base.direct.grid,
-                                selectedNode.getPos(base.direct.grid) + deltaPos)
-        # Snap objects to grid and update DNA if necessary
-        self.updateSelectedPose(base.direct.selected.getSelectedAsList())
-        # Restore grid spacing
-        if base.direct.fShift:
-            # Use back door to set grid spacing to avoid grid update
-            base.direct.grid.gridSpacing = oldGridSpacing
-
-    def keyboardXformSelected(self, arrowDirection, mode):
-        if mode == 'rotate':
-            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.getChildren()
-        for child in children:
-            childVisGroups = (childVisGroups + self.getDNAVisGroups(child))
-        return childVisGroups
-
-    def findParentVisGroup(self, nodePath):
-        """ Find the containing vis group """
-        dnaNode = self.findDNANode(nodePath)
-        if dnaNode:
-            if DNAClassEqual(dnaNode, DNA_VIS_GROUP):
-                return nodePath, dnaNode
-        elif nodePath.hasParent():
-            return self.findParentVisGroup(nodePath.getParent())
-        # Fall through
-        return None, None
-
-    def showGrid(self, flag):
-        """ toggle direct grid """
-        if flag:
-            base.direct.grid.enable()
-        else:
-            base.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')
-        if map:
-            map.setTransparency(1)
-            map.setColor(Vec4(1, 1, 1, .4))
-            self.mapDictionary[neighborhood] = map
-            # Make sure this item isn't pickable
-            base.direct.addUnpickable(neighborhood + '_layout')
-
-    def selectMap(self, neighborhood):
-        if self.activeMap:
-            self.activeMap.reparentTo(hidden)
-        if self.mapDictionary.has_key(neighborhood):
-            self.activeMap = self.mapDictionary[neighborhood]
-            self.activeMap.reparentTo(self.levelMap)
-
-    def toggleMapVis(self, flag):
-        if flag:
-            self.levelMap.reparentTo(base.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):
-        taskMgr.add(self.insertionMarkerTask, 'insertionMarkerTask')
-
-    def insertionMarkerTask(self, state):
-        self.insertionMarker.setPosHpr(base.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) and (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, fLerp = 1):
-        taskMgr.remove('autoPositionGrid')
-        # Move grid to prepare for placement of next object
-        selectedNode = base.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)
-
-            if fLerp:
-                # Position grid for placing next object
-                # Eventually we need to setHpr too
-                t = base.direct.grid.lerpPosHpr(
-                    deltaPos, deltaHpr, 0.25,
-                    other = selectedNode,
-                    blendType = 'easeInOut',
-                    task = 'autoPositionGrid')
-                t.deltaPos = deltaPos
-                t.deltaHpr = deltaHpr
-                t.selectedNode = selectedNode
-                t.setUponDeath(self.autoPositionCleanup)
-            else:
-                base.direct.grid.setPosHpr(selectedNode, deltaPos, deltaHpr)
-
-        if self.mouseMayaCamera:
-            return
-        # Also move the camera
-        taskMgr.remove('autoMoveDelay')
-        handlesToCam = base.direct.widget.getPos(base.direct.camera)
-        handlesToCam = handlesToCam * (base.direct.dr.near/handlesToCam[1])
-        if ((abs(handlesToCam[0]) > (base.direct.dr.nearWidth * 0.4)) or
-            (abs(handlesToCam[2]) > (base.direct.dr.nearHeight * 0.4))):
-            taskMgr.remove('manipulateCamera')
-            base.direct.cameraControl.centerCamIn(0.5)
-
-    def autoPositionCleanup(self, state):
-        base.direct.grid.setPosHpr(state.selectedNode, state.deltaPos,
-                              state.deltaHpr)
-        if base.direct.grid.getHprSnap():
-            # Clean up grid angle
-            base.direct.grid.setH(ROUND_TO(base.direct.grid.getH(),
-                                      base.direct.grid.snapAngle))
-
-    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, selectedNode):
-        """
-        Return point of intersection between building's wall and line from cam
-        through mouse.
-        """
-        # Find mouse point on near plane
-        mouseX = base.direct.dr.mouseX
-        mouseY = base.direct.dr.mouseY
-        nearX = (math.tan(deg2Rad(base.direct.dr.fovH)/2.0) *
-                 mouseX * base.direct.dr.near)
-        nearZ = (math.tan(deg2Rad(base.direct.dr.fovV)/2.0) *
-                 mouseY * base.direct.dr.near)
-        # Initialize points
-        mCam2Wall = base.direct.camera.getMat(selectedNode)
-        mouseOrigin = Point3(0)
-        mouseOrigin.assign(mCam2Wall.getRow3(3))
-        mouseDir = Vec3(0)
-        mouseDir.set(nearX, base.direct.dr.near, nearZ)
-        mouseDir.assign(mCam2Wall.xformVec(mouseDir))
-        # Calc intersection point
-        return planeIntersect(mouseOrigin, mouseDir, ZERO_POINT, NEG_Y_AXIS)
-
-    def getGridSnapIntersectionPoint(self):
-        """
-        Return point of intersection between ground plane and line from cam
-        through mouse. Return false, if nothing selected. Snap to grid.
-        """
-        return base.direct.grid.computeSnapPoint(self.getGridIntersectionPoint())
-
-    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 = base.direct.dr.mouseX
-        mouseY = base.direct.dr.mouseY
-        nearX = (math.tan(deg2Rad(base.direct.dr.fovH)/2.0) *
-                 mouseX * base.direct.dr.near)
-        nearZ = (math.tan(deg2Rad(base.direct.dr.fovV)/2.0) *
-                 mouseY * base.direct.dr.near)
-        # Initialize points
-        mCam2Grid = base.direct.camera.getMat(base.direct.grid)
-        mouseOrigin = Point3(0)
-        mouseOrigin.assign(mCam2Grid.getRow3(3))
-        mouseDir = Vec3(0)
-        mouseDir.set(nearX, base.direct.dr.near, nearZ)
-        mouseDir.assign(mCam2Grid.xformVec(mouseDir))
-        # Calc intersection point
-        return planeIntersect(mouseOrigin, mouseDir, ZERO_POINT, Z_AXIS)
-
-    def jumpToInsertionPoint(self):
-        """ Move selected object to insertion point """
-        selectedNode = base.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(base.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()
-
-    # CVS OPERATIONS
-    def cvsUpdate(self, filename):
-        dirname = os.path.dirname(filename)
-        if not os.path.isdir(dirname):
-            print 'Cannot CVS update %s: invalid directory' % (filename)
-            return
-
-        basename = os.path.basename(filename)
-        cwd = os.getcwd()
-        os.chdir(dirname)
-        cvsCommand = 'cvs update ' + basename
-        print cvsCommand
-        os.system(cvsCommand)
-        os.chdir(cwd)
-
-    def cvsAdd(self, filename):
-        dirname = os.path.dirname(filename)
-        if not os.path.isdir(dirname):
-            print 'Cannot CVS add %s: invalid directory' % (filename)
-            return
-
-        basename = os.path.basename(filename)
-        cwd = os.getcwd()
-        os.chdir(dirname)
-        cvsCommand = 'cvs add ' + basename
-        print cvsCommand
-        os.system(cvsCommand)
-        os.chdir(cwd)
-
-    def cvsUpdateAll(self):
-        # Update the entire dna source directory.
-        dirname = dnaDirectory.toOsSpecific()
-        if not os.path.isdir(dirname):
-            print 'Cannot CVS commit: invalid directory'
-            return
-
-        cwd = os.getcwd()
-        os.chdir(dirname)
-        cvsCommand = 'cvs update -dP'
-        print cvsCommand
-        os.system(cvsCommand)
-        os.chdir(cwd)
-
-    def cvsCommitAll(self):
-        # cvs commit always commits the entire dna source directory.
-        dirname = dnaDirectory.toOsSpecific()
-        if not os.path.isdir(dirname):
-            print 'Cannot CVS commit: invalid directory'
-            return
-
-        cwd = os.getcwd()
-        os.chdir(dirname)
-        cvsCommand = 'cvs commit -m "level editor"'
-        print cvsCommand
-        os.system(cvsCommand)
-        os.chdir(cwd)
-
-    # STYLE/DNA FILE FUNCTIONS
-    def loadSpecifiedDNAFile(self):
-        path = dnaDirectory.toOsSpecific()
-        if not os.path.isdir(path):
-            print 'LevelEditor Warning: Invalid default DNA directory!'
-            print 'Using current directory'
-            path = '.'
-        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)
-            self.outputFile = dnaFilename
-        print "Finished Load: ", dnaFilename
-
-    def saveToSpecifiedDNAFile(self):
-        path = dnaDirectory.toOsSpecific()
-        if not os.path.isdir(path):
-            print 'LevelEditor Warning: Invalid DNA save directory!'
-            print 'Using current directory'
-            path = '.'
-        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)
-            self.outputFile = dnaFilename
-
-    def loadDNAFromFile(self, filename):
-        print filename
-        # Reset level, destroying existing scene/DNA hierarcy
-        self.reset(fDeleteToplevel = 1, fCreateToplevel = 0,
-                   fUpdateExplorer = 0)
-        # Now load in new file
-        if fUseCVS:
-            self.cvsUpdate(filename)
-        try:
-            node = loadDNAFile(DNASTORE, Filename.fromOsSpecific(filename).cStr(), CSDefault, 1)
-        except Exception:
-            self.notify.debug("Couldn't load specified DNA file. Please make sure storage code has been specified in Config.prc file")
-        if node.getNumParents() == 1:
-            # If the node already has a parent arc when it's loaded, we must
-            # be using the level editor and we want to preserve that arc.
-            newNPToplevel = NodePath(node)
-            newNPToplevel.reparentTo(hidden)
-        else:
-            # Otherwise, we should create a new arc for the node.
-            newNPToplevel = hidden.attachNewNode(node)
-
-        # Make sure the topmost file DNA object gets put under DNARoot
-        newDNAToplevel = self.findDNANode(newNPToplevel)
-
-        # reset the landmark block number:
-        (self.landmarkBlock, needTraverse)=self.findHighestLandmarkBlock(
-            newDNAToplevel, newNPToplevel)
-
-        # Update toplevel variables
-        if needTraverse:
-            self.createToplevel(newDNAToplevel)
-        else:
-            self.createToplevel(newDNAToplevel, newNPToplevel)
-
-        # Create visible representations of all the paths and battle cells
-        self.createSuitPaths()
-        self.hideSuitPaths()
-        self.createBattleCells()
-        self.hideBattleCells()
-        # Set the title bar to have the filename to make it easier
-        # to remember what file you are working on
-        self.panel["title"] = 'Level Editor: ' + os.path.basename(filename)
-        self.panel.sceneGraphExplorer.update()
-
-    def outputDNADefaultFile(self):
-        outputFile = self.outputFile
-        if outputFile == None:
-            outputFile = self.neighborhood + '_working.dna'
-        file = os.path.join(dnaDirectory.toOsSpecific(), outputFile)
-        self.outputDNA(file)
-
-    def outputDNA(self, filename):
-        print 'Saving DNA to: ', filename
-        binaryFilename = Filename(filename)
-        binaryFilename.setBinary()
-        self.DNAData.writeDna(binaryFilename, 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_FLAT_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(dnaDirectory.getFullpath() +
-                             '/stylefiles/' + filename)
-            f = open(fname.toOsSpecific(), 'ab')
-            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 saveStyle(self, filename, style):
-        # A generic routine to append a new style definition to one of
-        # the style files.
-
-        fname = Filename(dnaDirectory.getFullpath() +
-                         '/stylefiles/' + filename)
-        # We use binary mode to avoid Windows' end-of-line convention
-        f = open(fname.toOsSpecific(), 'ab')
-        # Add a blank line
-        f.write('\n')
-        # Now output style details to file
-        style.output(f)
-        # Close the file
-        f.close()
-
-    def saveBaselineStyle(self):
-        if self.panel.currentBaselineDNA:
-            # Valid baseline, add style to file
-            filename = self.neighborhood + '_baseline_styles.txt'
-            style = DNABaselineStyle(self.panel.currentBaselineDNA)
-            self.saveStyle(filename, style)
-
-    def saveWallStyle(self):
-        if self.lastWall:
-            # Valid wall, add style to file
-            filename = self.neighborhood + '_wall_styles.txt'
-            style = DNAWallStyle(self.lastWall)
-            self.saveStyle(filename, style)
-
-    def saveBuildingStyle(self):
-        dnaObject = self.selectedDNARoot
-        if dnaObject:
-            if DNAClassEqual(dnaObject, DNA_FLAT_BUILDING):
-                # Valid wall, add style to file
-                filename = self.neighborhood + '_building_styles.txt'
-                style = DNAFlatBuildingStyle(dnaObject)
-                self.saveStyle(filename, style)
-                return
-        print 'Must select building before saving building style'
-
-    # 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) or (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]
-        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_burrrgh':
-            self.outputDir = 'TheBurrrgh'
-        elif neighborhood == 'daisys_garden':
-            self.outputDir = 'DaisysGarden'
-        elif neighborhood == 'donalds_dreamland':
-            self.outputDir = 'DonaldsDreamland'
-        self.panel.editMenu.selectitem(neighborhood)
-        self.styleManager.setEditMode(neighborhood)
-        self.panel.updateHeightList(self.getCurrent('building_height'))
-        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 hasAttribute(self, attribute):
-        """ Return specified attribute for current neighborhood """
-        return self.styleManager.hasAttribute(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
-
-    def drawSuitEdge(self, edge, parent):
-        # Draw a line from start to end
-        edgeLine = LineNodePath(parent)
-        edgeLine.lineNode.setName('suitEdge')
-        edgeLine.setColor(VBase4(0.0, 0.0, 0.5, 1))
-        edgeLine.setThickness(1)
-        edgeLine.reset()
-        # We need to draw the arrow relative to the parent, but the
-        # point positions are relative to the NPToplevel. So get the
-        # start and end positions relative to the parent, then draw
-        # the arrow using those points
-        tempNode = self.NPToplevel.attachNewNode('tempNode')
-        mat = self.NPToplevel.getMat(parent)
-        relStartPos = Point3(mat.xformPoint(edge.getStartPoint().getPos()))
-        relEndPos = Point3(mat.xformPoint(edge.getEndPoint().getPos()))
-        # Compute offset: a vector rotated 90 degrees clockwise
-        offset = Vec3(relEndPos - relStartPos)
-        offset.normalize()
-        offset *= 0.1
-        a = offset[0]
-        offset.setX(offset[1])
-        offset.setY(-1 * a)
-        # Just to get it above the street
-        offset.setZ(0.05)
-        # Add offset to start and end to help differentiate lines
-        relStartPos += offset
-        relEndPos += offset
-        # Draw arrow
-        edgeLine.drawArrow(relStartPos,
-                           relEndPos,
-                           15, # arrow angle
-                           1) # arrow length
-        edgeLine.create()
-        # Add a clickable sphere
-        marker = self.suitPointMarker.copyTo(edgeLine)
-        marker.setName('suitEdgeMarker')
-        midPos = (relStartPos + relEndPos)/2.0
-        marker.setPos(midPos)
-        # Adjust color of highlighted lines
-        if edge in self.visitedEdges:
-            NodePath.setColor(edgeLine, 1, 0, 0, 1)
-        # Clean up:
-        tempNode.removeNode()
-        return edgeLine
-
-    def drawSuitPoint(self, suitPoint, pos, type, parent):
-        marker = self.suitPointMarker.copyTo(parent)
-        marker.setName("suitPointMarker")
-        marker.setPos(pos)
-        if (type == DNASuitPoint.STREETPOINT):
-            marker.setColor(0, 0, 0.6)
-            marker.setScale(0.4)
-        elif (type == DNASuitPoint.FRONTDOORPOINT):
-            marker.setColor(0, 0, 1)
-            marker.setScale(0.5)
-        elif (type == DNASuitPoint.SIDEDOORPOINT):
-            marker.setColor(0, 0.6, 0.2)
-            marker.setScale(0.5)
-        # Highlight if necessary
-        if suitPoint in self.visitedPoints:
-            marker.setColor(1, 0, 0, 1)
-        return marker
-
-    def placeSuitPoint(self):
-        v = self.getGridSnapIntersectionPoint()
-        # get the absolute pos relative to the top level.
-        # That is what gets stored in the point
-        mat = base.direct.grid.getMat(self.NPToplevel)
-        absPos = Point3(mat.xformPoint(v))
-        print 'Suit point: ' + `absPos`
-        # Store the point in the DNA. If this point is already in there,
-        # it returns the existing point
-        suitPoint = DNASTORE.storeSuitPoint(self.currentSuitPointType, absPos)
-        print "placeSuitPoint: ", suitPoint
-        # In case the point returned is a different type, update our type
-        self.currentSuitPointType = suitPoint.getPointType()
-        if not self.pointDict.has_key(suitPoint):
-            marker = self.drawSuitPoint(suitPoint,
-                absPos, self.currentSuitPointType,
-                self.suitPointToplevel)
-            self.pointDict[suitPoint] = marker
-        self.currentSuitPointIndex = suitPoint.getIndex()
-
-        if self.startSuitPoint:
-            self.endSuitPoint = suitPoint
-            # Make a new dna edge
-            if DNAClassEqual(self.DNAParent, DNA_VIS_GROUP):
-                zoneId = self.DNAParent.getName()
-
-                suitEdge = DNASuitEdge(
-                    self.startSuitPoint, self.endSuitPoint, zoneId)
-                DNASTORE.storeSuitEdge(suitEdge)
-                # Add edge to the current vis group so it can be written out
-                self.DNAParent.addSuitEdge(suitEdge)
-                # Draw a line to represent the edge
-                edgeLine = self.drawSuitEdge(suitEdge, self.NPParent)
-                # Store the line in a dict so we can hide/show them
-                self.edgeDict[suitEdge] = edgeLine
-                self.np2EdgeDict[edgeLine.id()] = [suitEdge, self.DNAParent]
-                # Store the edge on each point in case we move the point
-                # we can update the edge
-                for point in [self.startSuitPoint, self.endSuitPoint]:
-                    if self.point2edgeDict.has_key(point):
-                        self.point2edgeDict[point].append(suitEdge)
-                    else:
-                        self.point2edgeDict[point] = [suitEdge]
-
-                # If this is a building point, you need edges in both directions
-                # so just make the other edge automatically
-                if ((self.startSuitPoint.getPointType() == DNASuitPoint.FRONTDOORPOINT)
-                    or (self.startSuitPoint.getPointType() == DNASuitPoint.SIDEDOORPOINT)):
-
-                    suitEdge = DNASuitEdge(
-                        self.endSuitPoint, self.startSuitPoint, zoneId)
-                    DNASTORE.storeSuitEdge(suitEdge)
-                    # Add edge to the current vis group so it can be written out
-                    self.DNAParent.addSuitEdge(suitEdge)
-                    # Draw a line to represent the edge
-                    edgeLine = self.drawSuitEdge(suitEdge, self.NPParent)
-                    # Store the line in a dict so we can hide/show them
-                    self.edgeDict[suitEdge] = edgeLine
-                    self.np2EdgeDict[edgeLine.id()] = [suitEdge, self.DNAParent]
-                    for point in [self.startSuitPoint, self.endSuitPoint]:
-                        if self.point2edgeDict.has_key(point):
-                            self.point2edgeDict[point].append(suitEdge)
-                        else:
-                            self.point2edgeDict[point] = [suitEdge]
-
-                print 'Added dnaSuitEdge to zone: ' + zoneId
-            else:
-                print 'Error: DNAParent is not a dnaVisGroup. Did not add edge'
-            # Reset
-            self.startSuitPoint = None
-            self.endSuitPoint = None
-
-        else:
-            # First point, store it
-            self.startSuitPoint = suitPoint
-
-    def highlightConnected(self, nodePath = None, fReversePath = 0):
-        if nodePath == None:
-            nodePath = base.direct.selected.last
-        if nodePath:
-            suitPoint = self.findPointOrCell(nodePath)[0]
-            if suitPoint:
-                self.clearPathHighlights()
-                self.highlightConnectedRec(suitPoint, fReversePath)
-
-    def highlightConnectedRec(self, suitPoint, fReversePath):
-        nodePath = self.pointDict.get(suitPoint, None)
-        if nodePath:
-            # highlight marker
-            nodePath.setColor(1, 0, 0, 1)
-            # Add point to visited points
-            self.visitedPoints.append(suitPoint)
-            # highlight connected edges
-            for edge in self.point2edgeDict[suitPoint]:
-                if ((fReversePath or (suitPoint == edge.getStartPoint())) and
-                    (edge not in self.visitedEdges)):
-                    edgeLine = self.edgeDict[edge]
-                    # Call node path not LineNodePath setColor
-                    NodePath.setColor(edgeLine, 1, 0, 0, 1)
-                    # Add edge to visited edges
-                    self.visitedEdges.append(edge)
-                    # Color components connected to the edge
-                    if fReversePath:
-                        startPoint = edge.getStartPoint()
-                        if startPoint not in self.visitedPoints:
-                            self.highlightConnectedRec(startPoint,
-                                                       fReversePath)
-                    endPoint = edge.getEndPoint()
-                    type = endPoint.getPointType()
-                    if ((endPoint not in self.visitedPoints) and
-                        (fReversePath or (type == DNASuitPoint.STREETPOINT))):
-                        self.highlightConnectedRec(endPoint,
-                                                   fReversePath)
-
-    def clearPathHighlights(self):
-        for point in self.pointDict.keys():
-            type = point.getPointType()
-            marker = self.pointDict[point]
-            if (type == DNASuitPoint.STREETPOINT):
-                marker.setColor(0, 0, 0.6)
-            elif (type == DNASuitPoint.FRONTDOORPOINT):
-                marker.setColor(0, 0, 1)
-            elif (type == DNASuitPoint.SIDEDOORPOINT):
-                marker.setColor(0, 0.6, 0.2)
-        for edge in self.edgeDict.values():
-            edge.clearColor()
-        self.visitedPoints = []
-        self.visitedEdges = []
-
-    def drawBattleCell(self, cell, parent):
-        marker = self.battleCellMarker.copyTo(parent)
-        # Greenish
-        marker.setColor(0.25, 0.75, 0.25, 0.5)
-        marker.setTransparency(1)
-        marker.setPos(cell.getPos())
-        # scale to radius which is width/2
-        marker.setScale(cell.getWidth()/2.0,
-                        cell.getHeight()/2.0,
-                        1)
-        return marker
-
-    def placeBattleCell(self):
-        # Store the battle cell in the current vis group
-        if not DNAClassEqual(self.DNAParent, DNA_VIS_GROUP):
-            print 'Error: DNAParent is not a dnaVisGroup. Did not add battle cell'
-            return
-
-        v = self.getGridSnapIntersectionPoint()
-        mat = base.direct.grid.getMat(self.NPParent)
-        absPos = Point3(mat.xformPoint(v))
-        if (self.currentBattleCellType == '20w 20l'):
-            cell = DNABattleCell(20, 20, absPos)
-        elif (self.currentBattleCellType == '20w 30l'):
-            cell = DNABattleCell(20, 30, absPos)
-        elif (self.currentBattleCellType == '30w 20l'):
-            cell = DNABattleCell(30, 20, absPos)
-        elif (self.currentBattleCellType == '30w 30l'):
-            cell = DNABattleCell(30, 30, absPos)
-        # Store the battle cell in the storage
-        DNASTORE.storeBattleCell(cell)
-        # Draw the battle cell
-        marker = self.drawBattleCell(cell, self.NPParent)
-        # Keep a handy dict of the visible markers
-        self.cellDict[cell] = marker
-        # Store the battle cell in the current vis group
-        self.DNAParent.addBattleCell(cell)
-
-    def createSuitPaths(self):
-        # Points
-        numPoints = DNASTORE.getNumSuitPoints()
-        for i in range(numPoints):
-            point = DNASTORE.getSuitPointAtIndex(i)
-            marker = self.drawSuitPoint(point,
-                point.getPos(), point.getPointType(),
-                self.suitPointToplevel)
-            self.pointDict[point] = marker
-
-        # Edges
-        visGroups = self.getDNAVisGroups(self.NPToplevel)
-        for visGroup in visGroups:
-            np = visGroup[0]
-            dnaVisGroup = visGroup[1]
-            numSuitEdges = dnaVisGroup.getNumSuitEdges()
-            for i in range(numSuitEdges):
-                edge = dnaVisGroup.getSuitEdge(i)
-                edgeLine = self.drawSuitEdge(edge, np)
-                self.edgeDict[edge] = edgeLine
-                self.np2EdgeDict[edgeLine.id()] = [edge, dnaVisGroup]
-                # Store the edge on each point in case we move the point
-                # we can update the edge
-                for point in [edge.getStartPoint(), edge.getEndPoint()]:
-                    if self.point2edgeDict.has_key(point):
-                        self.point2edgeDict[point].append(edge)
-                    else:
-                        self.point2edgeDict[point] = [edge]
-
-    def getSuitPointFromNodePath(self, nodePath):
-        """
-        Given a node path, attempt to find the point, nodePath pair
-        in the pointDict. If it is there, return the point. If we
-        cannot find it, return None.
-        TODO: a reverse lookup pointDict would speed this up quite a bit
-        """
-        for point, marker in self.pointDict.items():
-            if marker.eq(nodePath):
-                return point
-        return None
-
-    def resetPathMarkers(self):
-        for edge, edgeLine in self.edgeDict.items():
-            if not edgeLine.isEmpty():
-                edgeLine.reset()
-                edgeLine.removeNode()
-        self.edgeDict = {}
-        self.np2EdgeDict = {}
-        for point, marker in self.pointDict.items():
-            if not marker.isEmpty():
-                marker.removeNode()
-        self.pointDict = {}
-
-    def hideSuitPaths(self):
-        for edge, edgeLine in self.edgeDict.items():
-            edgeLine.hide()
-        for point, marker in self.pointDict.items():
-            marker.hide()
-
-    def showSuitPaths(self):
-        for edge, edgeLine in self.edgeDict.items():
-            edgeLine.show()
-        for point, marker in self.pointDict.items():
-            marker.show()
-
-    def createBattleCells(self):
-        # Edges
-        visGroups = self.getDNAVisGroups(self.NPToplevel)
-        for visGroup in visGroups:
-            np = visGroup[0]
-            dnaVisGroup = visGroup[1]
-            numCells = dnaVisGroup.getNumBattleCells()
-            for i in range(numCells):
-                cell = dnaVisGroup.getBattleCell(i)
-                marker = self.drawBattleCell(cell, np)
-                self.cellDict[cell] = marker
-
-    def resetBattleCellMarkers(self):
-        for cell, marker in self.cellDict.items():
-            if not marker.isEmpty():
-                marker.remove()
-        self.cellDict = {}
-
-    def hideBattleCells(self):
-        for cell, marker in self.cellDict.items():
-            marker.hide()
-
-    def showBattleCells(self):
-        for cell, marker in self.cellDict.items():
-            marker.show()
-
-    def getBattleCellFromNodePath(self, nodePath):
-        """
-        Given a node path, attempt to find the battle cell, nodePath pair
-        in the cellDict. If it is there, return the cell. If we
-        cannot find it, return None.
-        TODO: a reverse lookup cellDict would speed this up quite a bit
-        """
-        for cell, marker in self.cellDict.items():
-            if marker.eq(nodePath):
-                return cell
-        return None
-
-    def toggleZoneColors(self):
-        if self.panel.zoneColor.get():
-            self.colorZones()
-        else:
-            self.clearZoneColors()
-
-    def colorZones(self):
-        # Give each zone a random color to see them better
-        visGroups = self.getDNAVisGroups(self.NPToplevel)
-        for visGroup in visGroups:
-            np = visGroup[0]
-            np.setColor(0.5 + random()/2.0,
-                        0.5 + random()/2.0,
-                        0.5 + random()/2.0)
-
-    def clearZoneColors(self):
-        # Clear random colors
-        visGroups = self.getDNAVisGroups(self.NPToplevel)
-        for visGroup in visGroups:
-            np = visGroup[0]
-            np.clearColor()
-
-    def labelZones(self):
-        # Label each zone
-        # First clear out old labels if any
-        self.clearZoneLabels()
-        visGroups = self.getDNAVisGroups(self.NPToplevel)
-        from direct.gui import DirectGui
-        for np, dna in visGroups:
-            name = dna.getName()
-            label = DirectGui.DirectLabel(text = name,
-                                          parent = np.getParent(),
-                                          relief = None, scale = 3)
-            label.setBillboardPointEye(0)
-            center = np.getBounds().getCenter()
-            label.setPos(center[0], center[1], .1)
-            self.zoneLabels.append(label)
-
-    def clearZoneLabels(self):
-        for label in self.zoneLabels:
-            label.removeNode()
-        self.zoneLabels = []
-
-    def getBlockFromName(self, name):
-        block=name[2:name.find(':')]
-        return block
-
-    def addToLandmarkBlock(self):
-        dnaRoot=self.selectedDNARoot
-        if dnaRoot and self.lastLandmarkBuildingDNA:
-            if DNAClassEqual(dnaRoot, DNA_FLAT_BUILDING):
-                n=dnaRoot.getName()
-                n=n[n.find(':'):]
-                block=self.lastLandmarkBuildingDNA.getName()
-                block=block[2:block.find(':')]
-                dnaRoot.setName('tb'+block+n)
-                self.replaceSelected()
-                # If we're highlighting the landmark blocks:
-                if self.showLandmarkBlockToggleGroup:
-                    # then highlight this one:
-                    np=self.selectedNPRoot
-                    self.showLandmarkBlockToggleGroup.append(np)
-                    np.setColor(1, 0, 0, 1)
-        elif self.selectedSuitPoint and self.lastLandmarkBuildingDNA:
-            block=self.lastLandmarkBuildingDNA.getName()
-            block=block[2:block.find(':')]
-            print ("associate point with building: " + str(block))
-            self.selectedSuitPoint.setLandmarkBuildingIndex(int(block))
-            marker = self.pointDict[self.selectedSuitPoint]
-            marker.setColor(1, 0, 0, 1)
-            marker.setScale(1.0)
-
-    def findHighestLandmarkBlock(self, dnaRoot, npRoot):
-        npc=npRoot.findAllMatches("**/*:toon_landmark_*")
-        highest=0
-        for i in range(npc.getNumPaths()):
-            path=npc.getPath(i)
-            block=path.getName()
-            block=int(block[2:block.find(':')])
-            if (block > highest):
-                highest=block
-        # Make a list of flat building names, outside of the
-        # recursive function:
-        self.flatNames=['random'] + BUILDING_TYPES
-        self.flatNames=map(lambda n: n+'_DNARoot', self.flatNames)
-        # Search/recurse the dna:
-        newHighest=self.convertToLandmarkBlocks(highest, dnaRoot)
-        # Get rid of the list of flat building names:
-        del self.flatNames
-
-        needToTraverse = (highest!=newHighest)
-        return (newHighest, needToTraverse)
-
-    def convertToLandmarkBlocks(self, block, dnaRoot):
-        """
-        Find all the buildings without landmark blocks and
-        assign them one.
-        """
-        for i in range(dnaRoot.getNumChildren()):
-            child = dnaRoot.at(i)
-            if DNAClassEqual(child, DNA_LANDMARK_BUILDING):
-                # Landmark buildings:
-                name=child.getName()
-                if name.find('toon_landmark_')==0:
-                    block=block+1
-                    child.setName('tb'+str(block)+':'+name)
-            elif DNAClassEqual(child, DNA_FLAT_BUILDING):
-                # Flat buildings:
-                name=child.getName()
-                if (name in self.flatNames):
-                    child.setName('tb0:'+name)
-            else:
-                block = self.convertToLandmarkBlocks(block, child)
-        return block
-
-    def revertLandmarkBlock(self, block):
-        """
-        un-block flat buildings (set them to block zero).
-        """
-        npc=self.NPToplevel.findAllMatches("**/tb"+block+":*_DNARoot")
-        for i in range(npc.getNumPaths()):
-            nodePath=npc.getPath(i)
-            name=nodePath.getName()
-            if name[name.find(':'):][:15] != ':toon_landmark_':
-                name='tb0'+name[name.find(':'):]
-                dna=self.findDNANode(nodePath)
-                dna.setName(name)
-                nodePath=self.replace(nodePath, dna)
-                # If we're highlighting the landmark blocks:
-                if self.showLandmarkBlockToggleGroup:
-                    # then highlight this one:
-                    self.showLandmarkBlockToggleGroup.append(nodePath)
-                    nodePath.setColor(0, 1, 0, 1)
-
-    def landmarkBlockRemove(self, dna, nodePath):
-        if dna:
-            name=dna.getName()
-            # Get the underscore index within the name:
-            usIndex=name.find(':')
-            if name[usIndex:][:15] == ':toon_landmark_':
-                block=name[2:usIndex]
-                self.lastLandmarkBuildingDNA=None
-                self.revertLandmarkBlock(block)
-
-    def toggleShowLandmarkBlock(self):
-        dna=self.lastLandmarkBuildingDNA
-        if dna:
-            if not self.showLandmarkBlockToggleGroup:
-                group=[]
-                block=dna.getName()
-                block=block[2:block.find(':')]
-
-                # Get current landmark buildings:
-                npc=self.NPToplevel.findAllMatches("**/tb"+block+":*_DNARoot")
-                for i in range(npc.getNumPaths()):
-                    nodePath=npc.getPath(i)
-                    group.append(nodePath)
-                    nodePath.setColor(1, 0, 0, 1)
-
-                # Get block zero buildings (i.e. non-blocked):
-                npc=self.NPToplevel.findAllMatches("**/tb0:*_DNARoot")
-                for i in range(npc.getNumPaths()):
-                    nodePath=npc.getPath(i)
-                    group.append(nodePath)
-                    nodePath.setColor(0, 1, 0, 1)
-
-                # Get the suit point for this lb
-                for point, marker in self.pointDict.items():
-                    if ((point.getPointType() == DNASuitPoint.FRONTDOORPOINT)
-                        or (point.getPointType() == DNASuitPoint.SIDEDOORPOINT)):
-                        lbIndex = point.getLandmarkBuildingIndex()
-                        if (lbIndex == int(block)):
-                            marker.setColor(1, 0, 0, 1)
-                            marker.setScale(1.0)
-                            # There should only be one, so break now
-                        elif (lbIndex == -1):
-                            # This point belongs to no block
-                            marker.setColor(0, 1, 0, 1)
-
-                self.showLandmarkBlockToggleGroup=group
-            else:
-                for i in self.showLandmarkBlockToggleGroup:
-                    i.clearColor()
-                for point, marker in self.pointDict.items():
-                    if (point.getPointType() == DNASuitPoint.FRONTDOORPOINT):
-                        marker.setColor(0, 0, 1, 1)
-                        marker.setScale(0.5)
-                    elif (point.getPointType() == DNASuitPoint.SIDEDOORPOINT):
-                        marker.setColor(0, 0.6, 0.2, 1)
-                        marker.setScale(0.5)
-                self.showLandmarkBlockToggleGroup=None
-
-    def pdbBreak(self):
-        pdb.set_trace()
-
-    def reparentStreetBuildings(self, nodePath):
-        dnaNode = self.findDNANode(nodePath)
-        if dnaNode:
-            if (DNAClassEqual(dnaNode, DNA_FLAT_BUILDING) or
-                DNAClassEqual(dnaNode, DNA_LANDMARK_BUILDING)):
-                base.direct.reparent(nodePath, fWrt = 1)
-        children = nodePath.getChildren()
-        for child in children:
-            self.reparentStreetBuildings(child)
-
-    def consolidateStreetBuildings(self):
-        # First put everything under the ATR group so the leftover
-        # can be easily deleted
-        originalChildren = self.NPToplevel.getChildren()
-        self.addGroup(self.NPToplevel)
-        atrGroup = self.NPParent
-        atrGroup.setName('ATR')
-        self.setName(atrGroup, 'ATR')
-        base.direct.setActiveParent(atrGroup)
-        for child in originalChildren:
-            base.direct.reparent(child)
-        # Now create a new group with just the buildings
-        self.addGroup(self.NPToplevel)
-        newGroup = self.NPParent
-        newGroup.setName('LongStreet')
-        self.setName(newGroup, 'LongStreet')
-        base.direct.setActiveParent(newGroup)
-        self.reparentStreetBuildings(self.NPToplevel)
-        return newGroup
-
-    def makeNewBuildingGroup(self, sequenceNum, side, curveName):
-        print "-------------------------- new building group %s  curveName=%s------------------------" % (sequenceNum, curveName)
-        # Now create a new group with just the buildings
-        self.addGroup(self.NPToplevel)
-        newGroup = self.NPParent
-        groupName = ''
-        #if curveName == "urban_curveside_inner_1_1":
-        #    import pdb; pdb.set_trace()
-
-        if 'curveside' in curveName:
-            #we want to preserve which group the side street is closest to
-            print "special casing %s" % curveName
-            parts = curveName.split('_')
-            groupName = 'Buildings_' + side + "-" + parts[3] + "_" + parts[4]
-            print "groupname = %s" % groupName
-        else:
-            groupName = 'Buildings_' + side + "-" + str(sequenceNum)
-        newGroup.setName(groupName)
-        self.setName(newGroup, groupName)
-        base.direct.setActiveParent(newGroup)
-
-        if 'barricade_curve' in curveName:
-            parts = curveName.split('_')
-            origBarricadeNum = parts[3]
-            self.updateBarricadeDict(side, int(origBarricadeNum),  sequenceNum)
-
-    def adjustPropChildren(self, nodePath, maxPropOffset = -4):
-        for np in nodePath.getChildren():
-            dnaNode = self.findDNANode(np)
-            if dnaNode:
-                if DNAClassEqual(dnaNode, DNA_PROP):
-                    if np.getY() < maxPropOffset:
-                        np.setY(maxPropOffset)
-                        self.updateSelectedPose([np])
-
-    def getBuildingWidth(self, bldg):
-        dnaNode = self.findDNANode(bldg)
-        bldgWidth = 0
-        if DNAClassEqual(dnaNode, DNA_FLAT_BUILDING):
-            bldgWidth = dnaNode.getWidth()
-        elif DNAClassEqual(dnaNode, DNA_LANDMARK_BUILDING):
-            objectCode = dnaNode.getCode()
-            if objectCode[-2:-1] == 'A':
-                bldgWidth = 25.0
-            elif objectCode[-2:-1] == 'B':
-                bldgWidth = 15.0
-            elif objectCode[-2:-1] == 'C':
-                bldgWidth = 20.0
-        return bldgWidth
-
-    def calcLongStreetLength(self, bldgs):
-        streetLength = 0
-        for bldg in bldgs:
-            streetLength += self.getBuildingWidth(bldg)
-        return streetLength
-
-    def addStreetUnits(self, streetLength):
-        base.direct.grid.setPosHpr(0, -40, 0, 0, 0, 0)
-        currLength = 0
-        while (currLength < streetLength):
-            self.addStreet('street_80x40')
-            currLength += 80
-
-    def makeLongStreet(self):
-        bldgGroup = self.consolidateStreetBuildings()
-        bldgs = bldgGroup.getChildren()
-        numBldgs = len(bldgs)
-        streetLength = self.calcLongStreetLength(bldgs)/2.0
-        ref = None
-        base.direct.grid.fXyzSnap = 0
-        currLength = 0
-        for i in range(numBldgs):
-            bldg = bldgs[i]
-            if ref == None:
-                base.direct.grid.iPosHpr(bldgGroup)
-            else:
-                ref.select()
-                self.autoPositionGrid(fLerp = 0)
-            if base.direct.grid.getX() >= streetLength:
-                base.direct.grid.setPosHpr(base.direct.grid, 0, -40, 0, 180, 0, 0)
-            bldg.iPosHpr(base.direct.grid)
-            self.updateSelectedPose([bldg])
-            self.adjustPropChildren(bldg)
-            ref = bldg
-        self.addStreetUnits(streetLength)
-
-    def loadStreetCurve(self):
-        path = '.'
-        streetCurveFilename = askopenfilename(
-            defaultextension = '.egg',
-            filetypes = (('Egg files', '*.egg'),
-                         ('Bam files', '*.bam'),
-                         ('Maya files', '*.mb'),
-                         ('All files', '*')),
-            initialdir = path,
-            title = 'Load Curve File',
-            parent = self.panel.component('hull'))
-        if streetCurveFilename:
-            modelFile = loader.loadModel(Filename.fromOsSpecific(streetCurveFilename))
-            #curves = modelFile.findAllMatches('**/+ClassicNurbsCurve')
-            curves = {'inner':[], 'outer':[],'innersidest':[], 'outersidest':[]}
-            curvesInner = modelFile.findAllMatches('**/*curve_inner*')
-            print("-------------- curvesInner-----------------")
-            print curvesInner
-            curvesOuter = modelFile.findAllMatches('**/*curve_outer*')
-            print("---------------- curvesOuter---------------")
-            print curvesOuter
-            curveInnerSideSts = modelFile.findAllMatches('**/*curveside_inner*')
-            print("--------- curveInnerSideSts----------")
-            print curveInnerSideSts
-
-            curveOuterSideSts = modelFile.findAllMatches('**/*curveside_outer*')
-            print("----------- curveOuterSideSits ----------")
-            print curveOuterSideSts
-
-            # return an ordered list
-            for i in range(len(curvesInner) +1): #RAU don't forget, these curves are 1 based
-                curve = modelFile.find('**/*curve_inner_'+str(i))
-                if not curve.isEmpty():
-                    # Mark whether it is a section of buildings or trees
-                    curveType = curve.getName().split("_")[0]
-                    curves['inner'].append([curve.node(), curveType])
-
-            for i in range(len(curvesOuter) +1):
-                curve = modelFile.find('**/*curve_outer_'+str(i))
-                if not curve.isEmpty():
-                    # Mark whether it is a section of buildings or trees
-                    curveType = curve.getName().split("_")[0]
-                    curves['outer'].append([curve.node(), curveType])
-
-            maxNum = len(curvesInner)
-            if len(curvesOuter) > maxNum:
-                maxNum = len(curvesOuter)
-
-            maxNum += 2; #track ends in a barricade, and add 1 since 1 based
-            #RAU also do special processing for the side streets
-            # side streets are numbered differently and could be non consecutive
-            # curveside_inner_28_1, curveside_outer_28_1, curveside_inner_28_2,
-            # curveside_outer_28_2 (two side streets closest to main building track 28)
-            for i in range(maxNum):
-                for barricade in ['innerbarricade','outerbarricade']:
-                    curve = modelFile.find('**/*curveside_inner_'+ barricade + '_' + str(i))
-                    if not curve.isEmpty():
-                        # Mark whether it is a section of buildings or trees
-                        curveType = curve.getName().split("_")[0]
-                        curves['innersidest'].append([curve.node(), curveType])
-                        print "adding innersidest %s" % curve.getName()
-
-            for i in range(maxNum):
-                for barricade in ['innerbarricade','outerbarricade']:
-                    curve = modelFile.find('**/*curveside_outer_'+ barricade + '_' + str(i))
-                    if not curve.isEmpty():
-                        # Mark whether it is a section of buildings or trees
-                        curveType = curve.getName().split("_")[0]
-                        curves['outersidest'].append([curve.node(), curveType])
-                        print "adding outersidest %s" % curve.getName()
-
-
-            print "loaded curves: %s" % curves
-            return curves
-        else:
-            return None
-
-    def duplicateFlatBuilding(self, oldDNANode):
-        # Yes, make a new copy of the dnaNode
-        print "a"
-        dnaNode = oldDNANode.__class__(oldDNANode)
-        print "b"
-        dnaNode.setWidth(oldDNANode.getWidth())
-        # Add the DNA to the active parent
-        print "c"
-        self.DNAParent.add(dnaNode)
-        # And create the geometry
-        print "d %s" % (oldDNANode)
-        newNodePath = dnaNode.traverse(self.NPParent, DNASTORE, 1)
-        print "e"
-        return newNodePath
-
-    def getBldg(self, bldgIndex, bldgs, forceDuplicate = False):
-        numBldgs = len(bldgs)
-        if bldgIndex < numBldgs and not forceDuplicate:
-            # Use original
-            print "using original bldg"
-            bldg = bldgs[bldgIndex]
-            bldgIndex += 1
-        else:
-            # Make a copy
-            oldBldg = bldgs[bldgIndex % numBldgs]
-            bldgIndex += 1
-
-            oldBldg.select()
-            oldDNANode = self.findDNANode(oldBldg)
-            nodeClass = DNAGetClassType(oldDNANode)
-            if nodeClass.eq(DNA_LANDMARK_BUILDING):
-                print "making landmark copy"
-                # Remove white and dark grey doors from color list
-                colorList = self.getAttribute('door_color').getList()
-                colorList = colorList[1:3] + colorList[4:len(colorList)]
-                # Set a random door color
-                doorColor = random.choice(colorList)
-                self.setCurrent('door_color', doorColor)
-                self.addLandmark(oldDNANode.getCode(), oldDNANode.getBuildingType())
-                bldg = self.lastNodePath
-            else:
-                print "making flatbuilding copy"
-                bldg = self.duplicateFlatBuilding(oldDNANode)
-        return bldg, bldgIndex
-
-    def updateBarricadeDict(self, side, barricadeOrigNum, curBldgGroupIndex):
-        barricadeDict = None
-        if (side == 'outer'):
-            barricadeDict = self.outerBarricadeDict
-        elif (side == 'inner'):
-            barricadeDict = self.innerBarricadeDict
-        else:
-            print("unhandled side %s" % side)
-            return
-
-        if not barricadeDict.has_key(barricadeOrigNum):
-            barricadeDict[barricadeOrigNum] = [curBldgGroupIndex, curBldgGroupIndex]
-
-        if curBldgGroupIndex < barricadeDict[barricadeOrigNum][0]:
-            barricadeDict[barricadeOrigNum][0] = curBldgGroupIndex
-
-        if barricadeDict[barricadeOrigNum][1] < curBldgGroupIndex:
-            barricadeDict[barricadeOrigNum][1] = curBldgGroupIndex
-
-        print "---------- %s barricadeDict origNum=%d  data=(%d, %d)" %(side, barricadeOrigNum, barricadeDict[barricadeOrigNum][0], barricadeDict[barricadeOrigNum][1])
-
-    def makeStreetAlongCurve(self):
-        curves = self.loadStreetCurve()
-        if curves == None:
-            return
-
-        self.outerBarricadeDict = {}
-        self.innerBarricadeDict = {}
-
-
-        base.direct.grid.fXyzSnap = 0
-        base.direct.grid.fHprSnap = 0
-        self.panel.fPlaneSnap.set(0)
-        bldgGroup = self.consolidateStreetBuildings()
-        bldgs = bldgGroup.getChildren()
-
-        # streetWidth puts buildings on the edge of the street, not the middle
-        currPoint = Point3(0)
-        bldgIndex = 0
-
-        #populate side streets
-        self.makeSideStreets(curves)
-
-        # Populate buildings on both sides of the street
-        #sides = ['inner', 'outer','innersidest','outersidest']
-        sides = ['inner', 'outer']
-        maxGroupWidth = 500
-        for side in sides:
-            print "Building street for %s side" % side
-            # Subdivide the curve into different groups.
-            bldgGroupIndex = 0
-            curGroupWidth = 0
-            curveName = '';
-            if len(curves[side]):
-                initialCurve, initialCurveType = curves[side][0]
-                if initialCurve:
-                    curveName = initialCurve.getName()
-            self.makeNewBuildingGroup(bldgGroupIndex, side, curveName)
-
-            for curve, curveType in curves[side]:
-                print "----------------- curve(%s, %s): %s --------------- " % (side, curve.getName(), curve)
-                #import pdb; pdb.set_trace()
-                currT = 0
-                endT = curve.getMaxT()
-
-                while currT < endT:
-                    if curveType == 'urban':
-                        bldg, bldgIndex = self.getBldg(bldgIndex, bldgs)
-                        curve.getPoint(currT, currPoint)
-
-                        if side == "inner" or side == "innersidest":
-                            heading = 90
-                        else:
-                            heading = -90
-                        bldg.setPos(currPoint)
-                        bldgWidth = self.getBuildingWidth(bldg)
-
-                        curGroupWidth += bldgWidth
-                        # Adjust grid orientation based upon next point along curve
-                        currT, currPoint = self.findBldgEndPoint(bldgWidth, curve, currT, currPoint, rd = 0)
-                        bldg.lookAt(Point3(currPoint))
-                        bldg.setH(bldg, heading)
-
-                        # Shift building forward if it is on the out track, since we just turned it away from
-                        # the direction of the track
-                        if side == "outer" or side == "outersidest":
-                            bldg.setPos(currPoint)
-
-                        self.updateSelectedPose([bldg])
-                        self.adjustPropChildren(bldg)
-                        base.direct.reparent(bldg, fWrt = 1)
-                        print bldgIndex
-                    elif curveType == 'trees':
-                        curve.getPoint(currT, currPoint)
-                        # trees are spaced anywhere from 40-80 ft apart
-                        treeWidth = random.randint(40, 80)
-                        curGroupWidth += treeWidth
-                        # Adjust grid orientation based upon next point along curve
-                        currT, currPoint = self.findBldgEndPoint(treeWidth, curve, currT, currPoint, rd = 0)
-
-                        # Add some trees
-                        tree = random.choice(["prop_tree_small_ul",
-                                                "prop_tree_small_ur",
-                                                "prop_tree_large_ur",
-                                                "prop_tree_large_ul"])
-
-                        #use snow if necessaryy
-
-                        if (useSnowTree):
-                            tree = random.choice(["prop_snow_tree_small_ul",
-                                                    "prop_snow_tree_small_ur",
-                                                    "prop_snow_tree_large_ur",
-                                                    "prop_snow_tree_large_ul"])
-
-
-                        self.addProp(tree)
-                        for selectedNode in base.direct.selected:
-                            # Move it
-                            selectedNode.setPos(currPoint)
-                            # Snap objects to grid and update DNA if necessary
-                            self.updateSelectedPose(base.direct.selected.getSelectedAsList())
-                    elif curveType == 'bridge':
-                        # Don't add any dna for the bridge sections, but add the length
-                        # of the bridge so we can increment our building groups correctly
-                        print "adding bridge (%s), curT = %s" % (side, currT)
-                        bridgeWidth = 1050
-                        curGroupWidth += bridgeWidth
-                        #currT, currPoint = self.findBldgEndPoint(bridgeWidth, curve, currT, currPoint, rd = 0)
-                        print "currT after adding bridge = %s" % currT
-                        # force move to next curve
-                        currT = endT + 1
-                    elif curveType == 'tunnel':
-                        # Don't add any dna for the tunnel sections, but add the length
-                        # of the bridge so we can increment our building groups correctly
-                        print "adding tunnel (%s), curT = %s" % (side, currT)
-                        tunnelWidth = 775
-                        curGroupWidth += tunnelWidth
-                        #currT, currPoint = self.findBldgEndPoint(tunnelWidth, curve, currT, currPoint, rd = 0)
-                        print "currT after adding tunnel = %s" % currT
-                        # force move to next curve
-                        currT = endT + 1
-                    elif curveType == 'barricade':
-                        print "adding barricade (%s) %s, curT = %d" % (side, curve.getName(), currT)
-                        barricadeWidth = curve.calcLength()
-                        print "barricade width = %f" % barricadeWidth
-
-                        simple =1
-                        if (simple):
-                            curGroupWidth += barricadeWidth
-                            # force move to next curve
-                            currT = endT + 1
-                        else:
-                            #add a prop_tree to force it to be shown
-                            curve.getPoint(currT, currPoint)
-                            #trees are spaced anywhere from 40-80 ft apart
-                            #treeWidth = random.randint(40, 80)
-                            treeWidth = barricadeWidth
-                            curGroupWidth += treeWidth
-                            # Adjust grid orientation based upon next point along curve
-                            currT, currPoint = self.findBldgEndPoint(treeWidth, curve, currT, currPoint, rd = 0)
-
-                            # Add some trees
-                            tree = random.choice(["prop_snow_tree_small_ul",
-                                                "prop_snow_tree_small_ur",
-                                                "prop_snow_tree_large_ur",
-                                                "prop_snow_tree_large_ul"])
-                            self.addProp(tree)
-                            for selectedNode in base.direct.selected:
-                                # Move it
-                                selectedNode.setPos(currPoint)
-                                # Snap objects to grid and update DNA if necessary
-                                self.updateSelectedPose(base.direct.selected.getSelectedAsList())
-
-
-
-
-
-                    # Check if we need a new group yet
-                    if curGroupWidth > maxGroupWidth:
-                        print "curGroupWidth %s > %s" % (curGroupWidth, maxGroupWidth)
-                        diffGroup = curGroupWidth - maxGroupWidth
-                        while diffGroup > 0:
-                            bldgGroupIndex += 1
-                            self.makeNewBuildingGroup(bldgGroupIndex, side, curve.getName())
-                            print "adding group %s (%s)" % (bldgGroupIndex, diffGroup)
-                            diffGroup -= maxGroupWidth
-                        curGroupWidth = 0
-                    print currT, curGroupWidth
-
-
-    def makeSideStreets(self, curves):
-        """ Each side in a sidestreet MUST be in 1 building group, otherwise the 2nd half
-        of a building group could be very far away. This would cause the stashing and
-        unstashing code to go off kilter.
-        """
-
-        base.direct.grid.fXyzSnap = 0
-        base.direct.grid.fHprSnap = 0
-        self.panel.fPlaneSnap.set(0)
-        bldgGroup = self.consolidateStreetBuildings()
-        bldgs = bldgGroup.getChildren()
-
-        # streetWidth puts buildings on the edge of the street, not the middle
-        currPoint = Point3(0)
-        bldgIndex = 0
-
-        # Populate buildings on both sides of the street
-        #sides = ['inner', 'outer','innersidest','outersidest']
-        sides = ['innersidest', 'outersidest']
-        maxGroupWidth = 50000
-        for side in sides:
-            print "Building street for %s side" % side
-            # Subdivide the curve into different groups.
-            bldgGroupIndex = 0
-            curGroupWidth = 0
-
-
-            for curve, curveType in curves[side]:
-                print "----------------- curve(%s, %s): %s --------------- " % (side, curve.getName(), curve)
-                #import pdb; pdb.set_trace()
-                currT = 0
-                endT = curve.getMaxT()
-
-                #RAU side streets still too long, lets try arbitrarily dividing it in half
-                #endT = endT / 2
-
-                print ("endT = %f" % endT)
-                #if (maxGroupWidth < endT):
-                #    self.notify.debug("changing endT from %f to %f" % (endT, maxGroupWidth))
-                #    endT = maxGroupWidth
-
-
-                currGroupWidth = 0
-                self.makeNewBuildingGroup(bldgGroupIndex, side, curve.getName())
-
-                while currT < endT:
-                    if curveType == 'urban':
-                        bldg, bldgIndex = self.getBldg(bldgIndex, bldgs, forceDuplicate = True)
-                        curve.getPoint(currT, currPoint)
-
-                        if side == "inner" or side == "innersidest":
-                            heading = 90
-                        else:
-                            heading = -90
-                        bldg.setPos(currPoint)
-                        bldgWidth = self.getBuildingWidth(bldg)
-
-                        curGroupWidth += bldgWidth
-                        # Adjust grid orientation based upon next point along curve
-                        currT, currPoint = self.findBldgEndPoint(bldgWidth, curve, currT, currPoint, rd = 0)
-                        bldg.lookAt(Point3(currPoint))
-                        bldg.setH(bldg, heading)
-
-                        # Shift building forward if it is on the out track, since we just turned it away from
-                        # the direction of the track
-                        if side == "outer" or side == "outersidest":
-                            bldg.setPos(currPoint)
-
-                        self.updateSelectedPose([bldg])
-                        self.adjustPropChildren(bldg)
-                        base.direct.reparent(bldg, fWrt = 1)
-                        print bldgIndex
-                    elif curveType == 'trees':
-                        curve.getPoint(currT, currPoint)
-                        # trees are spaced anywhere from 40-80 ft apart
-                        treeWidth = random.randint(40, 80)
-                        curGroupWidth += treeWidth
-                        # Adjust grid orientation based upon next point along curve
-                        currT, currPoint = self.findBldgEndPoint(treeWidth, curve, currT, currPoint, rd = 0)
-
-                        # Add some trees
-                        tree = random.choice(["prop_tree_small_ul",
-                                                "prop_tree_small_ur",
-                                                "prop_tree_large_ur",
-                                                "prop_tree_large_ul"])
-
-                        #use snow tree if necessary
-                        if (useSnowTree):
-                            tree = random.choice(["prop_snow_tree_small_ul",
-                                                    "prop_snow_tree_small_ur",
-                                                    "prop_snow_tree_large_ur",
-                                                    "prop_snow_tree_large_ul"])
-
-
-                        self.addProp(tree)
-                        for selectedNode in base.direct.selected:
-                            # Move it
-                            selectedNode.setPos(currPoint)
-                            # Snap objects to grid and update DNA if necessary
-                            self.updateSelectedPose(base.direct.selected.getSelectedAsList())
-                    elif curveType == 'bridge':
-                        # Don't add any dna for the bridge sections, but add the length
-                        # of the bridge so we can increment our building groups correctly
-                        print "adding bridge (%s), curT = %s" % (side, currT)
-                        bridgeWidth = 1050
-                        curGroupWidth += bridgeWidth
-                        #currT, currPoint = self.findBldgEndPoint(bridgeWidth, curve, currT, currPoint, rd = 0)
-                        print "currT after adding bridge = %s" % currT
-                        # force move to next curve
-                        currT = endT + 1
-                    elif curveType == 'tunnel':
-                        # Don't add any dna for the tunnel sections, but add the length
-                        # of the bridge so we can increment our building groups correctly
-                        print "adding tunnel (%s), curT = %s" % (side, currT)
-                        tunnelWidth = 775
-                        curGroupWidth += tunnelWidth
-                        #currT, currPoint = self.findBldgEndPoint(tunnelWidth, curve, currT, currPoint, rd = 0)
-                        print "currT after adding tunnel = %s" % currT
-                        # force move to next curve
-                        currT = endT + 1
-                    elif curveType == 'barricade':
-                        print "adding barricade (%s) %s, curT = %d" % (side, curve.getName(), currT)
-                        barricadeWidth = curve.calcLength()
-                        print "barricade width = %f" % barricadeWidth
-
-                        simple =1
-                        if (simple):
-                            curGroupWidth += barricadeWidth
-                            # force move to next curve
-                            currT = endT + 1
-                        else:
-                            #add a prop_tree to force it to be shown
-                            curve.getPoint(currT, currPoint)
-                            #trees are spaced anywhere from 40-80 ft apart
-                            #treeWidth = random.randint(40, 80)
-                            treeWidth = barricadeWidth
-                            curGroupWidth += treeWidth
-                            # Adjust grid orientation based upon next point along curve
-                            currT, currPoint = self.findBldgEndPoint(treeWidth, curve, currT, currPoint, rd = 0)
-
-                            # Add some trees
-                            tree = random.choice(["prop_snow_tree_small_ul",
-                                                "prop_snow_tree_small_ur",
-                                                "prop_snow_tree_large_ur",
-                                                "prop_snow_tree_large_ul"])
-                            self.addProp(tree)
-                            for selectedNode in base.direct.selected:
-                                # Move it
-                                selectedNode.setPos(currPoint)
-                                # Snap objects to grid and update DNA if necessary
-                                self.updateSelectedPose(base.direct.selected.getSelectedAsList())
-
-                #done with for loop, increment bldgGroupIndex
-                bldgGroupIndex += 1
-
-    def findBldgEndPoint(self, bldgWidth, curve, currT, currPoint,
-                         startT = None, endT = None, tolerance = 0.1, rd = 0):
-        if startT == None:
-            startT = currT
-        if endT == None:
-            endT = curve.getMaxT()
-        if rd > 100:
-            import pdb
-            pdb.set_trace()
-        midT = (startT + endT)/2.0
-        midPoint = Point3(0)
-        curve.getPoint(midT, midPoint)
-        separation = Vec3(midPoint - currPoint).length()
-        error = separation - bldgWidth
-        #print error, startT, midT
-        if abs(error) < tolerance:
-            return midT, midPoint
-        elif error > 0:
-            # Mid point was beyond building end point, focus on first half
-            return self.findBldgEndPoint(bldgWidth, curve, currT, currPoint, startT = startT, endT = midT,
-                                         rd = rd + 1)
-        else:
-            # End point beyond Mid point, focus on second half
-            # But make sure buildind end point is not beyond curve end point
-            endPoint = Point3(0)
-            curve.getPoint(endT, endPoint)
-            separation = Vec3(endPoint - currPoint).length()
-            if bldgWidth > separation:
-                # Must have reached end of the curve
-                return endT, endPoint
-            else:
-                return self.findBldgEndPoint(bldgWidth, curve, currT, currPoint, startT = midT, endT = endT,
-                                             rd = rd + 1)
-
-class OldLevelEditor(NodePath, DirectObject):
-    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')
-        hull.geometry('400x625')
-
-        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)
-
-        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',
-                            'CVS update directory',
-                            label = 'CVS update',
-                            command = self.levelEditor.cvsUpdateAll)
-        menuBar.addmenuitem('Level Editor', 'command',
-                            'CVS commit directory',
-                            label = 'CVS commit',
-                            command = self.levelEditor.cvsCommitAll)
-        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',
-                            'Make Long Street',
-                            label = 'Make Long Street',
-                            command = self.levelEditor.makeLongStreet)
-        menuBar.addmenuitem('Level Editor', 'command',
-                            'Make Street Along Curve',
-                            label = 'Make Street Along Curve',
-                            command = self.levelEditor.makeStreetAlongCurve)
-        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 Baseline's Style",
-                            label = 'Save Baseline Style',
-                            command = self.levelEditor.saveBaselineStyle)
-        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 Baseline Style Palettes',
-                            label = 'Reload Baseline Styles',
-                            command = self.styleManager.createBaselineStyleAttributes)
-        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 = 18,
-            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
-        self.notebook = Pmw.NoteBook(hull)
-        self.notebook.pack(fill = BOTH, expand = 1)
-        streetsPage = self.notebook.add('Streets')
-        toonBuildingsPage = self.notebook.add('Toon Bldgs')
-        landmarkBuildingsPage = self.notebook.add('Landmark Bldgs')
-        # suitBuildingsPage = self.notebook.add('Suit Buildings')
-        propsPage = self.notebook.add('Props')
-        signPage = self.notebook.add('Signs')
-        suitPathPage = self.notebook.add('Paths')
-        battleCellPage = self.notebook.add('Cells')
-        sceneGraphPage = self.notebook.add('SceneGraph')
-        self.notebook['raisecommand'] = self.updateInfo
-
-        # STREETS
-        Label(streetsPage, text = 'Streets',
-              font=('MSSansSerif', 14, 'bold')).pack(expand = 0)
-        self.addStreetButton = Button(
-            streetsPage,
-            text = 'ADD STREET',
-            command = self.addStreet)
-        self.addStreetButton.pack(fill = X, padx = 20, pady = 10)
-        streets = map(lambda s: s[7:],
-                      self.styleManager.getCatalogCodes(
-            'street'))
-        streets.sort()
-        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 = streets
-            )
-        self.streetModuleType = self.styleManager.getCatalogCode('street', 0)
-        self.streetSelector.selectitem(self.streetModuleType[7:])
-        self.streetSelector.pack(expand = 1, fill = BOTH)
-
-        # TOON BUILDINGS
-        Label(toonBuildingsPage, text = 'Toon Buildings',
-              font=('MSSansSerif', 14, 'bold')).pack(expand = 0)
-        self.addToonBuildingButton = Button(
-            toonBuildingsPage,
-            text = 'ADD TOON BUILDING',
-            command = self.addFlatBuilding)
-        self.addToonBuildingButton.pack(fill = X, padx = 20, pady = 10)
-        self.toonBuildingSelector = Pmw.ComboBox(
-            toonBuildingsPage,
-            dropdown = 0,
-            listheight = 200,
-            labelpos = W,
-            label_width = 12,
-            label_anchor = W,
-            label_text = 'Bldg type:',
-            entry_width = 30,
-            selectioncommand = self.setFlatBuildingType,
-            scrolledlist_items = (['random'] + BUILDING_TYPES)
-            )
-        bf = Frame(toonBuildingsPage)
-        Label(bf, text = 'Building Height:').pack(side = LEFT, expand = 0)
-        self.heightMode = IntVar()
-        self.heightMode.set(20)
-        self.tenFootButton = Radiobutton(
-            bf,
-            text = '10 ft',
-            value = 10,
-            variable = self.heightMode,
-            command = self.setFlatBuildingHeight)
-        self.tenFootButton.pack(side = LEFT, expand = 1, fill = X)
-        self.fourteenFootButton = Radiobutton(
-            bf,
-            text = '14 ft',
-            value = 14,
-            variable = self.heightMode,
-            command = self.setFlatBuildingHeight)
-        self.fourteenFootButton.pack(side = LEFT, expand = 1, fill = X)
-        self.twentyFootButton = Radiobutton(
-            bf,
-            text = '20 ft',
-            value = 20,
-            variable = self.heightMode,
-            command = self.setFlatBuildingHeight)
-        self.twentyFootButton.pack(side = LEFT, expand = 1, fill = X)
-        self.twentyFourFootButton = Radiobutton(
-            bf,
-            text = '24 ft',
-            value = 24,
-            variable = self.heightMode,
-            command = self.setFlatBuildingHeight)
-        self.twentyFourFootButton.pack(side = LEFT, expand = 1, fill = X)
-        self.twentyFiveFootButton = Radiobutton(
-            bf,
-            text = '25 ft',
-            value = 25,
-            variable = self.heightMode,
-            command = self.setFlatBuildingHeight)
-        self.twentyFiveFootButton.pack(side = LEFT, expand = 1, fill = X)
-        self.thirtyFootButton = Radiobutton(
-            bf,
-            text = '30 ft',
-            value = 30,
-            variable = self.heightMode,
-            command = self.setFlatBuildingHeight)
-        self.thirtyFootButton.pack(side = LEFT, expand = 1, fill = X)
-        bf.pack(fill = X)
-
-        self.toonBuildingType = 'random'
-        self.toonBuildingSelector.selectitem(self.toonBuildingType)
-        self.toonBuildingSelector.pack(expand = 1, fill = BOTH)
-
-        # LANDMARK BUILDINGS
-        Label(landmarkBuildingsPage, text = 'Landmark Buildings',
-              font=('MSSansSerif', 14, 'bold')).pack(expand = 0)
-
-        """
-        self.landmarkHQIntVar = IntVar()
-        self.landmarkHQIntVar.set(0)
-        self.landmarkHQButton = Checkbutton(
-            landmarkBuildingsPage,
-            text = 'HQ',
-            variable=self.landmarkHQIntVar,
-            command=self.setLandmarkHQ)
-        self.landmarkHQButton.pack(side = LEFT, expand = 1, fill = X)
-        """
-
-        self.addLandmarkBuildingButton = Button(
-            landmarkBuildingsPage,
-            text = 'ADD LANDMARK BUILDING',
-            command = self.addLandmark)
-        self.addLandmarkBuildingButton.pack(fill = X, padx = 20, pady = 10)
-        bldgs = map(lambda s: s[14:],
-                    self.styleManager.getCatalogCodes(
-            'toon_landmark'))
-        bldgs.sort()
-        self.landmarkBuildingSelector = Pmw.ComboBox(
-            landmarkBuildingsPage,
-            dropdown = 0,
-            listheight = 200,
-            labelpos = W,
-            label_width = 12,
-            label_anchor = W,
-            label_text = 'Bldg type:',
-            entry_width = 30,
-            selectioncommand = self.setLandmarkType,
-            scrolledlist_items = bldgs
-            )
-        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.landmarkBuildingSpecialSelector = Pmw.ComboBox(
-            landmarkBuildingsPage,
-            dropdown = 0,
-            listheight = 100,
-            labelpos = W,
-            label_width = 12,
-            label_anchor = W,
-            label_text = 'Special type:',
-            entry_width = 30,
-            selectioncommand = self.setLandmarkSpecialType,
-            scrolledlist_items = LANDMARK_SPECIAL_TYPES
-            )
-        self.landmarkSpecialType = LANDMARK_SPECIAL_TYPES[0]
-        self.landmarkBuildingSpecialSelector.selectitem(
-            LANDMARK_SPECIAL_TYPES[0])
-        self.landmarkBuildingSpecialSelector.pack(expand = 0)
-
-        # SIGNS
-        Label(signPage, text = 'Signs',
-              font=('MSSansSerif', 14, 'bold')).pack(expand = 0)
-        self.currentSignDNA=None
-        self.currentBaselineDNA=None
-        self.levelEditor.selectedNodePathHookHooks.append(self.updateSignPage)
-
-        gridFrame = Frame(signPage)
-        signSelectedFrame = Frame(gridFrame)
-
-        self.currentBaselineIndex=0
-        self.baselineMenu = Pmw.ComboBox(
-            signSelectedFrame,
-            labelpos = W,
-            label_text = 'Selected:', entry_width = 24,
-            selectioncommand = self.selectSignBaseline,
-            history = 0, # unique = 0,
-            scrolledlist_items = ['<the sign>'])
-        self.baselineMenu.selectitem(self.currentBaselineIndex)
-        self.baselineMenu.pack(side = LEFT, expand = 1, fill = X)
-
-        self.baselineAddButton = Button(
-            signSelectedFrame,
-            text="Add Baseline", command=self.addBaseline)
-        self.baselineAddButton.pack(side = LEFT, expand = 1, fill = X)
-
-        self.baselineDeleteButton = Button(
-            signSelectedFrame,
-            text="Del", command=self.deleteSignItem)
-        self.baselineDeleteButton.pack(side = LEFT, expand = 1, fill = X)
-
-        signSelectedFrame.grid(row=0, column=0, columnspan=6)
-
-        self.baselineString=StringVar()
-        self.baselineString.trace("wu", self.signBaselineTrace)
-        self.baselineTextBox = Entry(
-            gridFrame, width = 24,
-            textvariable=self.baselineString)
-        self.baselineTextBox.grid(row=1, column=0, columnspan=3)
-
-        fontList = [""]+self.styleManager.getCatalogCodes('font')
-        self.fontMenu = Pmw.ComboBox(
-            gridFrame, labelpos = W,
-            label_text = 'Font:', entry_width = 12,
-            selectioncommand = self.setSignBaslineFont, history = 0,
-            scrolledlist_items = fontList)
-        self.fontMenu.selectitem(0)
-        self.fontMenu.grid(row=1, column=3, columnspan=3)
-
-        graphicList = self.styleManager.getCatalogCodes('graphic')
-        self.graphicMenu = Pmw.ComboBox(
-            gridFrame, labelpos = W,
-            label_text = 'Add Graphic:', entry_width = 24,
-            selectioncommand = self.addSignGraphic, history = 0,
-            scrolledlist_items = graphicList)
-        self.graphicMenu.selectitem(0)
-        self.graphicMenu.grid(row=2, column=0, columnspan=4)
-
-        signButtonFrame = Frame(gridFrame)
-
-        self.bigFirstLetterIntVar = IntVar()
-        self.bigFirstLetterCheckbutton = Checkbutton(
-            signButtonFrame,
-            text = 'Big First Letter',
-            variable=self.bigFirstLetterIntVar, command=self.setBigFirstLetter)
-        self.bigFirstLetterCheckbutton.pack(
-            side = LEFT, expand = 1, fill = X)
-
-        self.allCapsIntVar = IntVar()
-        self.allCapsCheckbutton = Checkbutton(
-            signButtonFrame,
-            text = 'All Caps',
-            variable=self.allCapsIntVar, command=self.setAllCaps)
-        self.allCapsCheckbutton.pack(side = LEFT, expand = 1, fill = X)
-
-        self.dropShadowIntVar = IntVar()
-        self.dropShadowCheckbutton = Checkbutton(
-            signButtonFrame,
-            text = 'Drop Shadow',
-            variable=self.dropShadowIntVar, command=self.setDropShadow)
-        self.dropShadowCheckbutton.pack(side = LEFT, expand = 1, fill = X)
-
-        signButtonFrame.grid(row=3, column=0, columnspan=6)
-
-        self.addKernFloater = Floater.Floater(
-            gridFrame,
-            text='Kern',
-            #maxVelocity=1.0,
-            command=self.setSignBaselineKern)
-        self.addKernFloater.grid(row=4, column=0, rowspan=2, columnspan=3,
-                                 sticky = EW)
-        self.addWiggleFloater = Floater.Floater(
-            gridFrame,
-            text='Wiggle',
-            #maxVelocity=10.0,
-            command=self.setSignBaselineWiggle)
-        self.addWiggleFloater.grid(row=6, column=0, rowspan=2, columnspan=3,
-                                 sticky = EW)
-        self.addStumbleFloater = Floater.Floater(
-            gridFrame,
-            text='Stumble',
-            #maxVelocity=1.0,
-            command=self.setSignBaselineStumble)
-        self.addStumbleFloater.grid(row=8, column=0, rowspan=2, columnspan=3,
-                                 sticky = EW)
-        self.addStompFloater = Floater.Floater(
-            gridFrame,
-            text='Stomp',
-            #maxVelocity=1.0,
-            command=self.setSignBaselineStomp)
-        self.addStompFloater.grid(row=10, column=0, rowspan=2, columnspan=3,
-                                 sticky = EW)
-        self.addCurveFloater = Floater.Floater(
-            gridFrame,
-            text='Curve',
-            #maxVelocity = 1.0,
-            command=self.setSignBaselineCurve)
-        self.addCurveFloater.grid(row=12, column=0, rowspan=2, columnspan=3,
-                                 sticky = EW)
-        self.addXFloater = Floater.Floater(
-            gridFrame,
-            text='X',
-            #maxVelocity=1.0,
-            command=self.setDNATargetX)
-        self.addXFloater.grid(row=4, column=3, rowspan=2, columnspan=3,
-                                 sticky = EW)
-        self.addZFloater = Floater.Floater(
-            gridFrame,
-            text='Z',
-            #maxVelocity=1.0,
-            command=self.setDNATargetZ)
-        self.addZFloater.grid(row=6, column=3, rowspan=2, columnspan=3,
-                                 sticky = EW)
-        self.addScaleXFloater = Floater.Floater(
-            gridFrame,
-            text='Scale X',
-            #maxVelocity=1.0,
-            command=self.setDNATargetScaleX)
-        self.addScaleXFloater.grid(row=8, column=3, rowspan=2, columnspan=3,
-                                 sticky = EW)
-        self.addScaleZFloater = Floater.Floater(
-            gridFrame,
-            text='Scale Z',
-            #maxVelocity=1.0,
-            command=self.setDNATargetScaleZ)
-        self.addScaleZFloater.grid(row=10, column=3, rowspan=2, columnspan=3,
-                                 sticky = EW)
-        self.addRollFloater = Floater.Floater(
-            gridFrame,
-            text='Roll',
-            #maxVelocity=10.0,
-            command=self.setDNATargetRoll)
-        self.addRollFloater.grid(row=12, column=3, rowspan=2, columnspan=3,
-                                 sticky = EW)
-
-        gridFrame.pack(fill=BOTH)
-
-        # PROPS
-        Label(propsPage, text = 'Props',
-              font=('MSSansSerif', 14, 'bold')).pack(expand = 0)
-        self.addPropsButton = Button(
-            propsPage,
-            text = 'ADD PROP',
-            command = self.addProp)
-        self.addPropsButton.pack(fill = X, padx = 20, pady = 10)
-        codes = (self.styleManager.getCatalogCodes('prop') +
-                 self.styleManager.getCatalogCodes('holiday_prop'))
-        codes.sort()
-        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 = codes
-            )
-        self.propType = self.styleManager.getCatalogCode('prop', 0)
-        self.propSelector.selectitem(
-            self.styleManager.getCatalogCode('prop', 0))
-        self.propSelector.pack(expand = 1, fill = BOTH)
-
-        # SUIT PATHS
-        Label(suitPathPage, text = 'Suit Paths',
-              font=('MSSansSerif', 14, 'bold')).pack(expand = 0)
-
-        spButtons = Frame(suitPathPage)
-        self.fPaths = IntVar()
-        self.fPaths.set(0)
-        self.pathButton = Checkbutton(spButtons,
-                                      text = 'Show Paths',
-                                      width = 6,
-                                      variable = self.fPaths,
-                                      command = self.toggleSuitPaths)
-        self.pathButton.pack(side = LEFT, expand = 1, fill = X)
-
-        self.zoneColor = IntVar()
-        self.zoneColor.set(0)
-        self.colorZoneButton1 = Checkbutton(
-            spButtons,
-            text = 'Color Zones', width = 6,
-            variable = self.zoneColor,
-            command = self.levelEditor.toggleZoneColors)
-        self.colorZoneButton1.pack(side = LEFT, expand = 1, fill = X)
-        spButtons.pack(fill = X)
-
-        spButtons = Frame(suitPathPage)
-        Label(spButtons, text = 'Highlight:').pack(side = LEFT, fill = X)
-        self.highlightConnectedButton = Button(
-            spButtons,
-            text = 'Forward',
-            width = 6,
-            command = self.levelEditor.highlightConnected)
-        self.highlightConnectedButton.pack(side = LEFT, expand = 1, fill = X)
-
-        self.highlightConnectedButton2 = Button(
-            spButtons,
-            text = 'Connected',
-            width = 6,
-            command = lambda s = self: s.levelEditor.highlightConnected(fReversePath = 1))
-        self.highlightConnectedButton2.pack(side = LEFT, expand = 1, fill = X)
-
-        self.clearHighlightButton = Button(
-            spButtons,
-            text = 'Clear',
-            width = 6,
-            command = self.levelEditor.clearPathHighlights)
-        self.clearHighlightButton.pack(side = LEFT, expand = 1, fill = X)
-        spButtons.pack(fill = X, pady = 4)
-
-        self.suitPointSelector = Pmw.ComboBox(
-            suitPathPage,
-            dropdown = 0,
-            listheight = 200,
-            labelpos = W,
-            label_text = 'Point type:',
-            label_width = 12,
-            label_anchor = W,
-            entry_width = 30,
-            selectioncommand = self.setSuitPointType,
-            scrolledlist_items = ['street', 'front door', 'side door']
-            )
-        self.suitPointSelector.selectitem('street')
-        self.suitPointSelector.pack(expand = 1, fill = BOTH)
-
-        # BATTLE CELLS
-        Label(battleCellPage, text = 'Battle Cells',
-              font=('MSSansSerif', 14, 'bold')).pack(expand = 0)
-        bcButtons = Frame(battleCellPage)
-        self.fCells = IntVar()
-        self.fCells.set(0)
-        self.cellButton = Checkbutton(bcButtons,
-                                      text = 'Show Cells',
-                                      width = 6,
-                                      variable = self.fCells,
-                                      command = self.toggleBattleCells)
-        self.cellButton.pack(side = LEFT, expand = 1, fill = X)
-
-        self.colorZoneButton2 = Checkbutton(
-            bcButtons,
-            text = 'Color Zones', width = 6,
-            variable = self.zoneColor,
-            command = self.levelEditor.toggleZoneColors)
-        self.colorZoneButton2.pack(side = LEFT, expand = 1, fill = X)
-        bcButtons.pack(fill = X)
-
-        self.battleCellSelector = Pmw.ComboBox(
-            battleCellPage,
-            dropdown = 0,
-            listheight = 200,
-            labelpos = W,
-            label_text = 'Cell type:',
-            label_width = 12,
-            label_anchor = W,
-            entry_width = 30,
-            selectioncommand = self.setBattleCellType,
-            scrolledlist_items = ['20w 20l', '20w 30l', '30w 20l', '30w 30l']
-            )
-        self.battleCellSelector.selectitem('20w 20l')
-        self.battleCellSelector.pack(expand = 1, fill = BOTH)
-
-        # SCENE GRAPH EXPLORER
-        Label(sceneGraphPage, text = 'Level Scene Graph',
-              font=('MSSansSerif', 14, 'bold')).pack(expand = 0)
-        self.sceneGraphExplorer = SceneGraphExplorer(
-            parent = sceneGraphPage,
-            nodePath = self.levelEditor,
-            menuItems = ['Add Group', 'Add Vis Group'])
-        self.sceneGraphExplorer.pack(expand = 1, fill = BOTH)
-
-        # Compact down notebook
-        self.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)
-
-        def toggleWidgetHandles(s = self):
-            if s.fPlaneSnap.get():
-                base.direct.widget.disableHandles(['x-ring', 'x-disc',
-                                              'y-ring', 'y-disc',
-                                              'z-post'])
-            else:
-                base.direct.widget.enableHandles('all')
-        self.fPlaneSnap = IntVar()
-        self.fPlaneSnap.set(1)
-        self.planeSnapButton = Checkbutton(buttonFrame,
-                                           text = 'PlaneSnap',
-                                           width = 6,
-                                           variable = self.fPlaneSnap,
-                                           command = toggleWidgetHandles)
-        self.planeSnapButton.pack(side = LEFT, expand = 1, fill = X)
-
-        self.fGrid = IntVar()
-        self.fGrid.set(0)
-        base.direct.gridButton = Checkbutton(buttonFrame,
-                                      text = 'Show Grid',
-                                      width = 6,
-                                      variable = self.fGrid,
-                                      command = self.toggleGrid)
-        base.direct.gridButton.pack(side = LEFT, expand = 1, fill = X)
-
-        self.fMaya = IntVar()
-        self.fMaya.set(1)
-        self.mayaButton = Checkbutton(buttonFrame,
-                                      text = 'Maya Cam',
-                                      width = 6,
-                                      variable = self.fMaya,
-                                      command = self.toggleMaya)
-        self.mayaButton.pack(side = LEFT, expand = 1, fill = X)
+"""
+This is just a sample code.
+
+LevelEditor, ObjectHandler, ObjectPalette should be rewritten
+to be game specific.
+"""
+
+from LevelEditorBase import *
+from ObjectHandler import *
+from ObjectPalette import *
+from LevelEditorUI import *
+
+class LevelEditor(LevelEditorBase):
+    """ Class for Panda3D LevelEditor """ 
+    def __init__(self):
+        LevelEditorBase.__init__(self)
+        # If you have your own ObjectPalette and ObjectHandler
+        # connect them in your own LevelEditor class
+        self.objectPalette = ObjectPalette()
+        self.objectHandler = ObjectHandler(self)
+
+        # LevelEditorUI class must declared after ObjectPalette
+        self.ui = LevelEditorUI(self)
         
-        #Make maya mode on by default
-        self.toggleMaya()
-
-        buttonFrame.pack(fill = X)
-
-        buttonFrame4 = Frame(hull)
-        self.driveMode = IntVar()
-        self.driveMode.set(1)
-        self.directModeButton = Radiobutton(
-            buttonFrame4,
-            text = 'DIRECT Fly',
-            value = 1,
-            variable = self.driveMode,
-            command = self.levelEditor.useDirectFly)
-        self.directModeButton.pack(side = LEFT, fill = X, expand = 1)
-        self.driveModeButton = Radiobutton(
-            buttonFrame4,
-            text = 'Drive',
-            value = 0,
-            variable = self.driveMode,
-            command = self.levelEditor.useDriveMode)
-        self.driveModeButton.pack(side = LEFT, fill = X, expand = 1)
-
-        self.fColl = IntVar()
-        self.fColl.set(1)
-        base.direct.collButton = Checkbutton(
-            buttonFrame4,
-            text = 'Collide',
-            variable = self.fColl,
-            command = self.levelEditor.toggleCollisions)
-        base.direct.collButton.pack(side = LEFT, expand = 1, fill = X)
-
-        self.fVis = IntVar()
-        self.fVis.set(1)
-        base.direct.visButton = Checkbutton(
-            buttonFrame4,
-            text = 'Visibility',
-            variable = self.fVis,
-            command = self.levelEditor.toggleVisibility)
-        base.direct.visButton.pack(side = LEFT, expand = 1, fill = X)
-
-        self.fVisZones = IntVar()
-        self.fVisZones.set(visualizeZones)
-        base.direct.visZonesButton = Checkbutton(
-            buttonFrame4,
-            text = 'Show Zones',
-            variable = self.fVisZones)
-        base.direct.visZonesButton.pack(side = LEFT, expand = 1, fill = X)
-
-        buttonFrame4.pack(fill = X, padx = 5)
-
-        buttonFrame = Frame(hull)
-        self.fLabel = IntVar()
-        self.fLabel.set(0)
-        self.labelButton = Checkbutton(buttonFrame,
-                                       text = 'Show Zone Labels',
-                                       width = 6,
-                                       variable = self.fLabel,
-                                       command = self.toggleZoneLabels)
-        self.labelButton.pack(side = LEFT, expand = 1, fill = X)
-
-        self.selectButton = Button(buttonFrame,
-                                   text = 'Place Selected',
-                                   width = 6,
-                                   command = lambda: last.place()
-                                   )
-        self.selectButton.pack(side = LEFT, expand = 1, fill = X)
-        buttonFrame.pack(fill = X)
-
-        # Make sure input variables processed
-        self.initialiseoptions(LevelEditorPanel)
-
-    def updateInfo(self, page):
-        if page == 'Signs':
-            self.updateSignPage()
-
-    # [gjeon] to toggle maya cam mode
-    def toggleMaya(self):
-        base.direct.cameraControl.lockRoll = self.fMaya.get()
-        direct.cameraControl.useMayaCamControls = self.fMaya.get()
-
-    def toggleGrid(self):
-        if self.fGrid.get():
-            base.direct.grid.enable()
-        else:
-            base.direct.grid.disable()
-
-    def toggleSuitPaths(self):
-        if self.fPaths.get():
-            self.levelEditor.showSuitPaths()
-        else:
-            self.levelEditor.hideSuitPaths()
-
-    def toggleBattleCells(self):
-        if self.fCells.get():
-            self.levelEditor.showBattleCells()
-        else:
-            self.levelEditor.hideBattleCells()
-
-    def toggleZoneLabels(self):
-        if self.fLabel.get():
-            self.levelEditor.labelZones()
-        else:
-            self.levelEditor.clearZoneLabels()
-
-    def toggleXyzSnap(self):
-        base.direct.grid.setXyzSnap(self.fXyzSnap.get())
-
-    def toggleHprSnap(self):
-        base.direct.grid.setHprSnap(self.fXyzSnap.get())
-
-    def toggleMapVis(self):
-        self.levelEditor.toggleMapVis(self.fMapVis.get())
-
-    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 setFlatBuildingHeight(self):
-        height = self.heightMode.get()
-        self.levelEditor.setCurrent('building_height', height)
-        self.updateHeightList(height)
-
-    def updateHeightList(self, height):
-        # Update combo box
-        heightList = self.levelEditor.getList(`height` + '_ft_wall_heights')
-        self.toonBuildingSelector.setlist(
-            ['random'] + map(createHeightCode, heightList))
-        self.toonBuildingSelector.selectitem(0)
-        self.toonBuildingSelector.invoke()
-
-    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 signPanelSync(self):
-        self.baselineMenu.delete(1, END)
-        sign=self.findSignFromDNARoot()
-        if not sign:
-            return
-        baselineList = DNAGetChildren(sign, DNA_SIGN_BASELINE)
-        for baseline in baselineList:
-            s=DNAGetBaselineString(baseline)
-            self.baselineMenu.insert(END, s)
-        self.baselineMenu.selectitem(0)
-        self.selectSignBaseline(0)
-
-    def updateSignPage(self):
-        if (self.notebook.getcurselection() == 'Signs'):
-            sign=self.findSignFromDNARoot()
-            # Only update if it's not the current sign:
-            if (self.currentSignDNA != sign):
-                self.currentSignDNA = sign
-                self.signPanelSync()
-
-    def findSignFromDNARoot(self):
-        dnaRoot=self.levelEditor.selectedDNARoot
-        if not dnaRoot:
-            return
-        objClass=DNAGetClassType(dnaRoot)
-        if (objClass.eq(DNA_LANDMARK_BUILDING)
-                or objClass.eq(DNA_PROP)):
-            target=DNAGetChildRecursive(dnaRoot, DNA_SIGN)
-            return target
-
-    def selectSignBaseline(self, val):
-        if not self.currentSignDNA:
-            return
-        # Temporarily undefine DNATarget (this will speed
-        # up setting the values, because the callbacks won't
-        # call self.levelEditor.replaceSelected() with each
-        # setting):
-        self.levelEditor.DNATarget=None
-        self.currentBaselineDNA=None
-        target=None
-        index=self.currentBaselineIndex=int((self.baselineMenu.curselection())[0])
-        if (index==0):
-            target=self.currentSignDNA
-            # Unset some ui elements:
-            self.baselineString.set('')
-            self.fontMenu.selectitem(0)
-            self.addCurveFloater.set(0)
-            self.addKernFloater.set(0)
-            self.addWiggleFloater.set(0)
-            self.addStumbleFloater.set(0)
-            self.addStompFloater.set(0)
-            self.bigFirstLetterIntVar.set(0)
-            self.allCapsIntVar.set(0)
-            self.dropShadowIntVar.set(0)
-        else:
-            target=DNAGetChild(self.currentSignDNA, DNA_SIGN_BASELINE, index-1)
-            if target:
-                # Update panel info:
-                s = DNAGetBaselineString(target)
-                self.baselineString.set(s)
-                self.fontMenu.selectitem(target.getCode())
-                try:
-                    val = 1.0/target.getWidth()
-                except ZeroDivisionError:
-                    val = 0.0
-                self.addCurveFloater.set(val)
-                self.addKernFloater.set(target.getKern())
-                self.addWiggleFloater.set(target.getWiggle())
-                self.addStumbleFloater.set(target.getStumble())
-                self.addStompFloater.set(target.getStomp())
-                flags=target.getFlags()
-                self.bigFirstLetterIntVar.set('b' in flags)
-                self.allCapsIntVar.set('c' in flags)
-                self.dropShadowIntVar.set('d' in flags)
-
-                self.currentBaselineDNA=target
-        if target:
-            pos=target.getPos()
-            self.addXFloater.set(pos[0])
-            self.addZFloater.set(pos[2])
-            scale=target.getScale()
-            self.addScaleXFloater.set(scale[0])
-            self.addScaleZFloater.set(scale[2])
-            hpr=target.getHpr()
-            self.addRollFloater.set(hpr[2])
-
-            self.levelEditor.DNATarget=target
-
-    def deleteSignItem(self):
-        """Delete the selected sign or sign baseline"""
-        if (self.currentBaselineDNA):
-            # Remove the baseline:
-            assert int((self.baselineMenu.curselection())[0]) == self.currentBaselineIndex
-            DNARemoveChildOfClass(self.currentSignDNA, DNA_SIGN_BASELINE,
-                self.currentBaselineIndex-1)
-            self.baselineMenu.delete(self.currentBaselineIndex)
-            self.baselineMenu.selectitem(0)
-            self.currentBaselineIndex=0
-            self.currentBaselineDNA=None
-            self.selectSignBaseline(0)
-            self.levelEditor.replaceSelected()
-        elif (self.currentSignDNA):
-            # Remove the sign:
-            assert int((self.baselineMenu.curselection())[0]) == 0
-            le = self.levelEditor
-            le.removeSign(le.DNATarget, le.DNATargetParent)
-            self.currentBaselineDNA=None
-            self.currentSignDNA=None
-            self.levelEditor.replaceSelected()
-
-    def signBaselineTrace(self, a, b, mode):
-        #print self, a, b, mode, self.baselineString.get()
-        baseline=self.currentBaselineDNA
-        if baseline:
-            s = self.baselineString.get()
-            self.setBaselineString(s)
-
-    def addSignGraphic(self, code):
-        """
-        Create a new baseline with a graphic and
-        add it to the current sign
-        """
-        sign=self.findSignFromDNARoot()
-        if sign:
-            graphic=DNASignGraphic()
-            graphic.setCode(code)
-            baseline=DNASignBaseline()
-            baseline.add(graphic)
-            sign.add(baseline)
-            # Show the UI to the new baseline:
-            self.levelEditor.DNATarget=baseline
-            self.baselineMenu.insert(END, '['+code+']')
-            current=self.baselineMenu.size()-1
-            self.baselineMenu.selectitem(current)
-            self.selectSignBaseline(current)
-            self.levelEditor.replaceSelected()
-
-    def addBaseline(self):
-        sign=self.findSignFromDNARoot()
-        if sign:
-            baseline=DNASignBaseline()
-            text="Zoo"
-            DNASetBaselineString(baseline, text)
-            sign.add(baseline)
-            # Show the UI to the new baseline:
-            self.levelEditor.DNATarget=baseline
-            self.baselineMenu.insert(END, text)
-            current=self.baselineMenu.size()-1
-            self.baselineMenu.selectitem(current)
-            self.selectSignBaseline(current)
-            self.levelEditor.replaceSelected()
-
-    def addBaselineItem(self):
-        pass
-
-    def selectSignBaselineItem(self, val):
-        baseline=self.currentBaselineDNA
-        if baseline:
-            baseline.setCode(val)
-            self.levelEditor.replaceSelected()
-
-    def setSignBaselineStyle(self, val):
-        baseline=self.currentBaselineDNA
-        if baseline == None:
-            print "\n\nbaseline == None"
-            return #skyler: This isn't working yet.
-            #               As a workaround, select the baseline from the tk panel.
-            # Try to find the first baseline in the sign:
-            sign=self.findSignFromDNARoot()
-            if sign:
-                self.currentSignDNA = sign
-                baseline=DNAGetChild(sign, DNA_SIGN_BASELINE)
-        if baseline and val:
-            self.levelEditor.DNATarget=baseline
-            self.currentBaselineDNA=baseline
-            settings=val
-            self.levelEditor.replaceSelectedEnabled=0
-
-            # Don't set string: self.baselineString.set('')
-            if settings['curve'] != None:
-                self.addCurveFloater.set(settings['curve'])
-            if settings['kern'] != None:
-                self.addKernFloater.set(settings['kern'])
-            if settings['wiggle'] != None:
-                self.addWiggleFloater.set(settings['wiggle'])
-            if settings['stumble'] != None:
-                self.addStumbleFloater.set(settings['stumble'])
-            if settings['stomp'] != None:
-                self.addStompFloater.set(settings['stomp'])
-
-            flags=settings['flags']
-            if flags != None:
-                self.bigFirstLetterIntVar.set('b' in flags)
-                self.setBigFirstLetter()
-
-                self.allCapsIntVar.set('c' in flags)
-                self.setAllCaps()
-
-                self.dropShadowIntVar.set('d' in flags)
-                self.setDropShadow()
-
-            code = settings['code']
-            if code != None:
-                self.fontMenu.selectitem(code)
-                self.setSignBaslineFont(code)
-
-            if settings['x'] != None:
-                self.addXFloater.set(settings['x'])
-            if settings['z'] != None:
-                self.addZFloater.set(settings['z'])
-            if settings['scaleX'] != None:
-                self.addScaleXFloater.set(settings['scaleX'])
-            if settings['scaleZ'] != None:
-                self.addScaleZFloater.set(settings['scaleZ'])
-            if settings['roll'] != None:
-                self.addRollFloater.set(settings['roll'])
-
-            color = settings['color']
-            if color != None:
-                #self.updateSelectedObjColor(settings['color'])
-                self.setCurrentColor(color)
-                self.setResetColor(color)
-                baseline.setColor(color)
-
-            self.levelEditor.replaceSelectedEnabled=1
-            self.levelEditor.replaceSelected()
-
-
-    def setBaselineString(self, val):
-        baseline=self.currentBaselineDNA
-        if baseline:
-            DNASetBaselineString(baseline, val)
-            self.baselineMenu.delete(self.currentBaselineIndex)
-            self.baselineMenu.insert(self.currentBaselineIndex, val)
-            self.baselineMenu.selectitem(self.currentBaselineIndex)
-            self.levelEditor.replaceSelected()
-
-    def adjustBaselineFlag(self, newValue, flagChar):
-        baseline=self.currentBaselineDNA
-        if baseline:
-            flags=baseline.getFlags()
-            if newValue:
-                 if not flagChar in flags:
-                     # Add the flag:
-                     baseline.setFlags(flags+flagChar)
-            elif flagChar in flags:
-                 # Remove the flag:
-                 flags=string.join(flags.split(flagChar), '')
-                 baseline.setFlags(flags)
-            self.levelEditor.replaceSelected()
-
-    def setLandmarkSpecialType(self, type):
-        self.landmarkSpecialType = type
-        if self.levelEditor.lastLandmarkBuildingDNA:
-            self.levelEditor.lastLandmarkBuildingDNA.setBuildingType(self.landmarkSpecialType)
-
-    def setBigFirstLetter(self):
-        self.adjustBaselineFlag(self.bigFirstLetterIntVar.get(), 'b')
-
-    def setAllCaps(self):
-        self.adjustBaselineFlag(self.allCapsIntVar.get(), 'c')
-
-    def setDropShadow(self):
-        self.adjustBaselineFlag(self.dropShadowIntVar.get(), 'd')
-
-    def setSignBaslineFont(self, val):
-        target=self.levelEditor.DNATarget
-        if target and (DNAGetClassType(target).eq(DNA_SIGN_BASELINE)
-                or DNAGetClassType(target).eq(DNA_SIGN_TEXT)):
-            target.setCode(val)
-            self.levelEditor.replaceSelected()
-
-    def setSignBaselineCurve(self, val):
-        baseline=self.currentBaselineDNA
-        if baseline:
-            try:
-                val=1.0/val
-            except ZeroDivisionError:
-                val=0.0
-            baseline.setWidth(val)
-            baseline.setHeight(val)
-            self.levelEditor.replaceSelected()
-
-    def setSignBaselineKern(self, val):
-        baseline=self.currentBaselineDNA
-        if baseline:
-            baseline.setKern(val)
-            self.levelEditor.replaceSelected()
-
-    def setSignBaselineWiggle(self, val):
-        baseline=self.currentBaselineDNA
-        if baseline:
-            baseline.setWiggle(val)
-            self.levelEditor.replaceSelected()
-
-    def setSignBaselineStumble(self, val):
-        baseline=self.currentBaselineDNA
-        if baseline:
-            baseline.setStumble(val)
-            self.levelEditor.replaceSelected()
-
-    def setSignBaselineStomp(self, val):
-        baseline=self.currentBaselineDNA
-        if baseline:
-            baseline.setStomp(val)
-            self.levelEditor.replaceSelected()
-
-    def setDNATargetX(self, val):
-        target=self.levelEditor.DNATarget
-        if target:
-            pos=target.getPos()
-            pos=VBase3(val, pos[1], pos[2])
-            target.setPos(pos)
-            self.levelEditor.replaceSelected()
-
-    def setDNATargetZ(self, val):
-        target=self.levelEditor.DNATarget
-        if target:
-            pos=target.getPos()
-            pos=VBase3(pos[0], pos[1], val)
-            target.setPos(pos)
-            self.levelEditor.replaceSelected()
-
-    def setDNATargetScaleX(self, val):
-        target=self.levelEditor.DNATarget
-        if target:
-            scale=target.getScale()
-            scale=VBase3(val, scale[1], scale[2])
-            target.setScale(scale)
-            self.levelEditor.replaceSelected()
-
-    def setDNATargetScaleZ(self, val):
-        target=self.levelEditor.DNATarget
-        if target:
-            scale=target.getScale()
-            scale=VBase3(scale[0], scale[1], val)
-            target.setScale(scale)
-            self.levelEditor.replaceSelected()
-
-    def setDNATargetRoll(self, val):
-        target=self.levelEditor.DNATarget
-        if target:
-            hpr=target.getHpr()
-            hpr=VBase3(hpr[0], hpr[1], val)
-            target.setHpr(hpr)
-            self.levelEditor.replaceSelected()
-
-    def addLandmark(self):
-        self.levelEditor.addLandmark(self.landmarkType, self.landmarkSpecialType)
-
-    def setPropType(self, name):
-        self.propType = 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 setSuitPointType(self, name):
-        if (name == "street"):
-            self.levelEditor.currentSuitPointType = DNASuitPoint.STREETPOINT
-        elif (name == "front door"):
-            self.levelEditor.currentSuitPointType = DNASuitPoint.FRONTDOORPOINT
-        elif (name == "side door"):
-            self.levelEditor.currentSuitPointType = DNASuitPoint.SIDEDOORPOINT
-        print self.levelEditor.currentSuitPointType
-
-    def setBattleCellType(self, name):
-        self.levelEditor.currentBattleCellType = name
-
-    def updateSelectedObjColor(self, color):
-        try:
-            obj = self.levelEditor.DNATarget
-            if self.fUpdateSelected and (obj != None):
-                objClass = DNAGetClassType(obj)
-                if ((objClass.eq(DNA_WALL)) or
-                    (objClass.eq(DNA_WINDOWS)) or
-                    (objClass.eq(DNA_DOOR)) or
-                    (objClass.eq(DNA_FLAT_DOOR)) or
-                    (objClass.eq(DNA_CORNICE)) or
-                    (objClass.eq(DNA_PROP)) or
-                    (objClass.eq(DNA_SIGN)) or
-                    (objClass.eq(DNA_SIGN_BASELINE)) or
-                    (objClass.eq(DNA_SIGN_TEXT)) or
-                    (objClass.eq(DNA_SIGN_GRAPHIC))
-                    ):
-                    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')
-
-# [gjeon] for LevelEditor specific Avatar
-class LEAvatar(LocalAvatar.LocalAvatar):
-    def __init__(self, cr, chatMgr, chatAssistant, passMessagesThrough = False):
-        LocalAvatar.LocalAvatar.__init__(self,  cr, chatMgr, chatAssistant, passMessagesThrough)
-
-    def getAutoRun(self):
-        return 0
-
-base.l = LevelEditor()
-run()
-
+        # When you define your own LevelEditor class inheriting LevelEditorBase
+        # you should call self.initialize() at the end of __init__() function
+        self.initialize()

+ 138 - 0
direct/src/leveleditor/LevelEditorBase.py

@@ -0,0 +1,138 @@
+"""
+Base class for Level Editor
+
+You should write your own LevelEditor class inheriting this.
+Refer LevelEditor.py for example.
+"""
+
+from pandac.PandaModules import *
+from direct.showbase.ShowBase import *
+from direct.showbase.DirectObject import *
+from direct.directtools.DirectGlobals import *
+
+base = ShowBase(False)
+
+from ObjectMgr import *
+from FileMgr import *
+
+class LevelEditorBase(DirectObject):
+    """ Base Class for Panda3D LevelEditor """ 
+    def __init__(self):
+        #loadPrcFileData('startup', 'window-type none')
+        self.actionEvents = []
+        self.objectMgr = ObjectMgr(self)
+        self.fileMgr = FileMgr(self)
+        
+    def initialize(self):
+        """ You should call this in your __init__ method of inherited LevelEditor class """
+        fTk = base.config.GetBool('want-tk', 0)
+        fWx = 0
+        base.startDirect(fWantTk = fTk, fWantWx = fWx)
+        base.closeWindow(base.win)
+        base.win = base.winList[3]
+
+        base.direct.cameraControl.lockRoll = True
+        base.direct.setFScaleWidgetByCam(1)
+
+        unpickables = [
+            "z-guide",
+            "y-guide",
+            "x-guide",
+            "x-disc-geom",
+            "x-ring-line",
+            "x-post-line",
+            "y-disc-geom",
+            "y-ring-line",
+            "y-post-line",
+            "z-disc-geom",
+            "z-ring-line",
+            "z-post-line",
+            "centerLines",
+            "majorLines",
+            "minorLines",
+            "Sphere",]
+
+        for unpickable in unpickables:
+            base.direct.addUnpickable(unpickable)
+
+        base.direct.manipulationControl.optionalSkipFlags |= SKIP_UNPICKABLE
+        base.direct.manipulationControl.fAllowMarquee = 1
+        base.direct.manipulationControl.supportMultiView()
+        base.direct.cameraControl.useMayaCamControls = 1
+        
+        # [gjeon] to handle delete here first
+        base.direct.ignore('DIRECT-delete')
+        
+        # [gjeon] do not use the old way of finding current DR
+        base.direct.drList.tryToGetCurrentDr = False
+
+        # specifiy what obj can be 'selected' as objects
+        base.direct.selected.addTag('OBJRoot')
+
+        self.actionEvents.extend([
+            # Node path events
+            ('DIRECT-delete', self.handleDelete),
+            ('preRemoveNodePath', self.removeNodePathHook),
+            ('DIRECT_selectedNodePath_fMulti_fTag', self.selectedNodePathHook),
+            ('DIRECT_deselectAll', self.deselectAll),
+            ])
+
+        # 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])        
+
+    def removeNodePathHook(self, nodePath):
+        if nodePath is None:
+            return
+        base.direct.deselect(nodePath)
+        self.objectMgr.removeObjectByNodePath(nodePath)
+
+        if (base.direct.selected.last != None and nodePath.compareTo(base.direct.selected.last)==0):
+            # if base.direct.selected.last is refering to this
+            # removed obj, clear the reference
+            if (hasattr(__builtins__,'last')):
+                __builtins__.last = None
+            else:
+                __builtins__['last'] = None
+            base.direct.selected.last = None
+
+    def handleDelete(self):
+        reply = wx.MessageBox("Do you want to delete selected?", "Delete?",
+                              wx.YES_NO | wx.ICON_QUESTION)
+        if reply == wx.YES:
+            base.direct.removeAllSelected()
+            self.objectMgr.deselectAll()
+        else:
+            # need to reset COA
+            dnp = base.direct.selected.last
+            # Update camera controls coa to this point
+            # Coa2Camera = Coa2Dnp * Dnp2Camera
+            mCoa2Camera = dnp.mCoa2Dnp * dnp.getMat(base.direct.camera)
+            row = mCoa2Camera.getRow(3)
+            coa = Vec3(row[0], row[1], row[2])
+            base.direct.cameraControl.updateCoa(coa)
+
+    def selectedNodePathHook(self, nodePath, fMultiSelect = 0, fSelectTag = 1):
+        # handle unpickable nodepath
+        if nodePath.getName() in base.direct.iRay.unpickable:
+            base.direct.deselect(nodePath)
+            return
+
+        self.objectMgr.selectObject(nodePath)
+
+    def deselectAll(self):
+        self.objectMgr.deselectAll()
+
+    def reset(self):
+        base.direct.deselectAll()
+        self.objectMgr.reset()
+
+    def save(self, fileName):
+        self.fileMgr.saveToFile(fileName)
+
+    def load(self, fileName):
+        self.reset()
+        self.fileMgr.loadFromFile(fileName)

+ 6 - 2
direct/src/leveleditor/LevelEditorStart.py

@@ -1,3 +1,7 @@
-from direct.directbase.DirectStart import *
 import LevelEditor
-base.l = LevelEditor.LevelEditor()
+
+base.le = LevelEditor.LevelEditor()
+# You should define LevelEditor instance as
+# base.le so it can be reached in global scope
+
+run()

+ 138 - 0
direct/src/leveleditor/LevelEditorUI.py

@@ -0,0 +1,138 @@
+import wx
+import os
+from wx.lib.agw import fourwaysplitter as FWS
+
+from pandac.PandaModules import *
+from direct.wxwidgets.WxAppShell import *
+
+from ViewPort import *
+from ObjectPaletteUI import *
+from ObjectPropertyUI import *
+
+class PandaTextDropTarget(wx.TextDropTarget):
+    def __init__(self, editor):
+        wx.TextDropTarget.__init__(self)
+        self.editor = editor
+
+    def OnDropText(self, x, y, text):
+        self.editor.objectMgr.addNewObject(text)
+
+class LevelEditorUI(WxAppShell):
+    """ Class for Panda3D LevelEditor """ 
+    frameWidth = 800
+    frameHeight = 600
+    appversion      = '0.1'
+    appname         = 'Panda3D Level Editor'
+    
+    def __init__(self, editor, *args, **kw):
+        # Create the Wx app
+        self.wxApp = wx.App(redirect = False)
+        self.wxApp.SetAppName("Panda3D LevelEditor")
+        self.wxApp.SetClassName("P3DLevelEditor")
+        self.editor = editor
+
+        if not kw.get('size'):
+            kw['size'] = wx.Size(self.frameWidth, self.frameHeight)
+        WxAppShell.__init__(self, *args, **kw)        
+
+        self.initialize()
+        self.Bind(wx.EVT_SET_FOCUS, self.onSetFocus)
+        self.Bind(wx.EVT_KEY_DOWN, self.onSetFocus)
+
+    def createMenu(self):
+        menuItem = self.menuFile.Insert(0, -1 , "&New")
+        self.Bind(wx.EVT_MENU, self.onNew, menuItem)
+        
+        menuItem = self.menuFile.Insert(1, -1 , "&Load")
+        self.Bind(wx.EVT_MENU, self.onLoad, menuItem)
+
+        menuItem = self.menuFile.Insert(2, -1 , "&Save")
+        self.Bind(wx.EVT_MENU, self.onSave, menuItem)
+        
+    def createInterface(self):
+        self.createMenu()
+        
+        self.mainFrame = wx.SplitterWindow(self, style = wx.SP_3D | wx.SP_BORDER)
+        self.leftFrame = wx.SplitterWindow(self.mainFrame, style = wx.SP_3D | wx.SP_BORDER)
+        self.baseFrame = wx.SplitterWindow(self.mainFrame, style = wx.SP_3D | wx.SP_BORDER)
+        self.viewFrame = FWS.FourWaySplitter(self.baseFrame, style=wx.SP_LIVE_UPDATE)
+        self.rightFrame = wx.SplitterWindow(self.baseFrame, style = wx.SP_3D | wx.SP_BORDER)
+
+        self.topView = Viewport.makeTop(self.viewFrame)
+        self.viewFrame.AppendWindow(self.topView)
+
+        self.frontView = Viewport.makeFront(self.viewFrame)
+        self.viewFrame.AppendWindow(self.frontView)
+
+        self.leftView = Viewport.makeLeft(self.viewFrame)
+        self.viewFrame.AppendWindow(self.leftView)
+
+        self.perspView = Viewport.makePerspective(self.viewFrame)
+        self.viewFrame.AppendWindow(self.perspView)
+
+        self.leftBarUpPane = wx.Panel(self.leftFrame)
+        self.leftBarDownPane = wx.Panel(self.leftFrame)
+        self.rightBarUpPane = wx.Panel(self.rightFrame)
+        self.rightBarDownPane = wx.Panel(self.rightFrame)
+
+        self.leftFrame.SplitHorizontally(self.leftBarUpPane, self.leftBarDownPane)
+        self.rightFrame.SplitHorizontally(self.rightBarUpPane, self.rightBarDownPane)
+        self.mainFrame.SplitVertically(self.leftFrame, self.baseFrame, 200)
+        self.baseFrame.SplitVertically(self.viewFrame, self.rightFrame, 600)
+        
+        self.viewFrame.SetDropTarget(PandaTextDropTarget(self.editor))
+
+        self.leftFrame.SetSashGravity(0.5)
+        self.rightFrame.SetSashGravity(0.5)        
+        self.baseFrame.SetSashGravity(1.0)
+
+        sizer = wx.BoxSizer(wx.VERTICAL)
+        sizer.Add(self.mainFrame, 1, wx.EXPAND, 0)
+        self.SetSizer(sizer); self.Layout()
+
+        self.objectPaletteUI = ObjectPaletteUI(self.leftBarUpPane, self.editor)
+        self.objectPropertyUI = ObjectPropertyUI(self.rightBarUpPane, self.editor)
+
+    def onSetFocus(self):
+        print 'wx got focus'
+
+    def appInit(self):
+        """Overridden from WxAppShell.py."""
+        # Create a new event loop (to overide default wxEventLoop)
+        self.evtLoop = wx.EventLoop()
+        self.oldLoop = wx.EventLoop.GetActive()
+        wx.EventLoop.SetActive(self.evtLoop)
+        taskMgr.add(self.wxStep, "evtLoopTask")
+
+    def initialize(self):
+        """Initializes the viewports and editor."""
+        self.Update()
+        ViewportManager.updateAll()
+        self.wxStep()
+        ViewportManager.initializeAll()
+        # Position the camera
+        if base.trackball != None:
+          base.trackball.node().setPos(0, 30, 0)
+          base.trackball.node().setHpr(0, 15, 0)
+
+    def wxStep(self, task = None):
+        """A step in the WX event loop. You can either call this yourself or use as task."""
+        while self.evtLoop.Pending():
+          self.evtLoop.Dispatch()
+        self.wxApp.ProcessIdle()
+        if task != None: return task.cont
+
+    def onNew(self, evt):
+        self.editor.reset()
+
+    def onLoad(self, evt):
+        dialog = wx.FileDialog(None, "Choose a file", os.getcwd(), "", "*.py", wx.OPEN)
+        if dialog.ShowModal() == wx.ID_OK:
+            self.editor.load(dialog.GetPath())
+        dialog.Destroy()
+
+    def onSave(self, evt):
+        dialog = wx.FileDialog(None, "Choose a file", os.getcwd(), "", "*.py", wx.SAVE)
+        if dialog.ShowModal() == wx.ID_OK:
+            self.editor.save(dialog.GetPath())
+        dialog.Destroy()

+ 46 - 0
direct/src/leveleditor/ObjectGlobals.py

@@ -0,0 +1,46 @@
+"""
+This contains constants related with obj handling.
+"""
+
+# index for obj data structure
+OBJ_UID = 0
+OBJ_NP = 1
+OBJ_DEF = 2
+OBJ_MODEL = 3
+OBJ_PROP = 4
+
+# supported UI types
+PROP_UI_ENTRY = '_PropUIEntry'
+PROP_UI_COMBO = '_PropUIComboBox'
+PROP_UI_RADIO = '_PropUIRadio'
+PROP_UI_CHECK = '_PropUICheckBox'
+PROP_UI_SLIDE = '_PropUISlider'
+PROP_UI_SPIN = '_PropUISpinner'
+
+# index for property definition
+PROP_TYPE = 0
+PROP_DATATYPE = 1
+PROP_FUNC = 2
+PROP_DEFAULT = 3
+PROP_RANGE = 4
+
+# index for slider UI
+RANGE_MIN = 0
+RANGE_MAX = 1
+RANGE_RATE = 2
+
+# index for create/update function declaration
+FUNC_NAME = 0
+FUNC_ARGS = 1
+
+# data type of property value
+PROP_INT = 0 # int type value
+PROP_BOOL = 1 # bool type value
+PROP_FLOAT = 2 # float type value
+PROP_STR = 3 # string type value
+
+TYPE_CONV = {PROP_INT: int, PROP_BOOL: bool, PROP_FLOAT: float, PROP_STR: str}
+
+# these dynamic args should be used in update function declaration
+ARG_VAL = '_arg_val' # value from UI
+ARG_OBJ = '_arg_object' # obj information data structure

+ 77 - 0
direct/src/leveleditor/ObjectHandler.py

@@ -0,0 +1,77 @@
+"""
+This is just a sample code.
+
+LevelEditor, ObjectHandler, ObjectPalette should be rewritten
+to be game specific.
+"""
+
+from direct.actor import Actor
+
+import ObjectGlobals as OG
+
+class ObjectHandler:
+    """ ObjectHandler will create and update objects """
+    
+    def __init__(self, editor):
+        self.editor = editor
+
+    def createDoubleSmiley(self, horizontal=True):
+        root = render.attachNewNode('doubleSmiley')
+        a = loader.loadModel('models/smiley.egg')
+        b = loader.loadModel('models/smiley.egg')
+        if horizontal:
+            a.setName('left')
+            b.setName('right')
+            a.setPos(-1, 0, 0)
+            b.setPos(1, 0, 0)
+        else:
+            a.setName('top')
+            b.setName('bottom')
+            a.setPos(0, 0, 1)
+            b.setPos(0, 0, -1)
+
+        a.reparentTo(root)
+        b.reparentTo(root)
+        return root
+    
+    def updateDoubleSmiley(self, val, obj):
+        objNP = obj[OG.OBJ_NP]
+        if objNP.find('left'):
+            objNP.find('left').setPos(-1 * val, 0, 0)
+            objNP.find('right').setPos(val, 0, 0)
+        else:
+            objNP.find('top').setPos(0, 0, 1 * val)
+            objNP.find('bottom').setPos(0, 0, -1 * val)
+
+    def updateSmiley(self, val, obj):
+        objNP = obj[OG.OBJ_NP]
+        base.direct.deselectAll()
+        for child in objNP.findAllMatches("+GeomNode"):
+            child.removeNode()
+
+        for i in range(val):
+            a = loader.loadModel(obj[OG.OBJ_MODEL])
+            b = a.find("+GeomNode")
+            b.setPos(0, i*2, 0)
+            b.reparentTo(objNP)
+            a.removeNode()
+
+        base.direct.select(objNP)
+
+    def createPanda(self):
+        pandaActor = PandaActor()
+        return pandaActor
+
+    def createGrass(self):
+        environ = loader.loadModel("models/environment.egg")
+        environ.setScale(0.25,0.25,0.25)
+        environ.setPos(-8,42,0)
+        return environ
+
+class PandaActor(Actor.Actor):
+    def __init__(self):
+        Actor.Actor.__init__(self, "models/panda-model.egg",{"walk":"models/panda-walk4.egg"})
+        self.setScale(0.005)
+        self.loop("walk")
+
+    

+ 421 - 0
direct/src/leveleditor/ObjectMgr.py

@@ -0,0 +1,421 @@
+"""
+Defines ObjectMgr
+"""
+
+import os, time, wx
+
+from direct.task import Task
+from pandac.PandaModules import *
+
+import ObjectGlobals as OG
+
+class ObjectMgr:
+    """ ObjectMgr will create, manage, update objects in the scene """
+    
+    def __init__(self, editor):
+        self.editor = editor
+
+        # main obj repository of objects in the scene
+        self.objects = {}
+        self.npIndex = {}
+        self.saveData = []
+
+        self.lastUid = ''
+        self.lastUidMode = 0
+        self.currNodePath = None   
+
+    def reset(self):
+        self.deselectAll()
+
+        for id in self.objects.keys():
+            self.objects[id][OG.OBJ_NP].removeNode()
+            del self.objects[id]
+
+        for np in self.npIndex.keys():
+            del self.npIndex[np]
+               
+        self.objects = {}
+        self.npIndex = {}
+        self.saveData = []
+
+    def genUniqueId(self):
+        # [gjeon] to solve the problem of unproper $USERNAME
+        userId = os.path.basename(os.path.expandvars('$USERNAME'))
+        if userId == '':
+            userId = base.config.GetString("le-user-id")
+        if userId == '':
+            userId = 'unknown'
+        newUid = str(time.time()) + userId
+        # prevent duplicates from being generated in the same frame (this can
+        # happen when creating several new objects at once)
+        if (self.lastUid == newUid):
+            # append a value to the end to uniquify the id
+            newUid = newUid + str(self.lastUidMod)
+            self.lastUidMod = self.lastUidMod + 1
+        else:
+            self.lastUid = newUid
+            self.lastUidMod = 0
+        return newUid
+
+    def addNewObject(self, typeName, uid = None, model = None, parent=None):
+        """ function to add new obj to the scene """
+        if parent is None:
+            parent = render
+
+        objDef = self.editor.objectPalette.findItem(typeName)
+        newobj = None
+        if objDef and type(objDef) != dict:
+            if objDef.createFunction:
+                funcName = objDef.createFunction[OG.FUNC_NAME]
+                funcArgs = objDef.createFunction[OG.FUNC_ARGS]
+                if funcName.startswith('.'):
+                    # when it's using default objectHandler
+                    func = Functor(eval("base.le.objectHandler%s"%funcName))
+                else:
+                    # when it's not using default objectHandler, whole name of the handling obj
+                    # should be included in function name
+                    func = Functor(eval(funcName))
+
+                # create new obj using function and keyword arguments defined in ObjectPalette
+                newobj = func(**funcArgs)
+
+            elif objDef.model is not None:
+                # since this obj is simple model let's load the model
+                if model is None:
+                    model = objDef.model
+                newobj = loader.loadModel(model)
+
+            if newobj is None:
+                return None
+
+            newobj.reparentTo(parent)
+            newobj.setTag('OBJRoot','1')
+
+            if uid is None:
+                uid = self.genUniqueId()
+                isLoadingArea = False
+            else:
+                isLoadingArea = True
+
+            # populate obj data using default values
+            properties = {}
+            for key in objDef.properties.keys():
+                properties[key] = objDef.properties[key][OG.PROP_DEFAULT]
+
+            # insert obj data to main repository
+            self.objects[uid] = [uid, newobj, objDef, model, properties]
+            self.npIndex[NodePath(newobj)] = uid
+
+            if not isLoadingArea:
+                base.direct.select(newobj)
+
+        return newobj
+
+    def removeObjectByNodePath(self, nodePath):
+        uid = self.npIndex.get(nodePath)
+        if uid:
+            del self.objects[uid]
+            del self.npIndex[nodePath]
+
+    def findObjectById(self, uid):
+        return self.objects.get(uid)
+
+    def findObjectByNodePath(self, nodePath):
+        uid = self.npIndex.get(nodePath)
+        if uid is None:
+            return None
+        else:
+            return self.objects[uid]
+
+    def deselectAll(self):
+        self.currNodePath = None
+        taskMgr.remove('_le_updateObjectUITask')
+        self.editor.ui.objectPropertyUI.clearPropUI()
+
+    def selectObject(self, nodePath):
+        obj = self.findObjectByNodePath(nodePath)
+        if obj is None:
+            return
+
+        self.currNodePath = obj[OG.OBJ_NP]
+        self.spawnUpdateObjectUITask()
+        self.updateObjectPropertyUI(obj)
+
+    def updateObjectPropertyUI(self, obj):
+        objDef = obj[OG.OBJ_DEF]
+        objProp = obj[OG.OBJ_PROP]
+        self.editor.ui.objectPropertyUI.updateProps(obj)
+
+    def onEnterObjectPropUI(self, event):
+        taskMgr.remove('_le_updateObjectUITask')        
+        
+    def onLeaveObjectPropUI(self, event):
+        self.spawnUpdateObjectUITask()
+        
+    def spawnUpdateObjectUITask(self):
+        if self.currNodePath is None:
+            return
+
+        taskMgr.remove('_le_updateObjectUITask')
+        t = Task.Task(self.updateObjectUITask)
+        t.np = self.currNodePath
+        taskMgr.add(t, '_le_updateObjectUITask')
+        
+    def updateObjectUITask(self, state):
+        self.editor.ui.objectPropertyUI.propX.setValue(state.np.getX())
+        self.editor.ui.objectPropertyUI.propY.setValue(state.np.getY())
+        self.editor.ui.objectPropertyUI.propZ.setValue(state.np.getZ())
+
+        h = state.np.getH()
+        while h < 0:
+            h = h + 360.0
+
+        while h > 360:
+            h = h - 360.0
+
+        p = state.np.getP()
+        while p < 0:
+            p = p + 360.0
+
+        while p > 360:
+            p = p - 360.0
+
+        r = state.np.getR()
+        while r < 0:
+            r = r + 360.0
+
+        while r > 360:
+            r = r - 360.0 
+            
+        self.editor.ui.objectPropertyUI.propH.setValue(h)
+        self.editor.ui.objectPropertyUI.propP.setValue(p)
+        self.editor.ui.objectPropertyUI.propR.setValue(r)        
+
+        self.editor.ui.objectPropertyUI.propSX.setValue(state.np.getSx())
+        self.editor.ui.objectPropertyUI.propSY.setValue(state.np.getSy())
+        self.editor.ui.objectPropertyUI.propSZ.setValue(state.np.getSz())
+        
+        return Task.cont
+        
+    def updateObjectTransform(self, event):
+        if self.currNodePath is None:
+            return
+
+        np = self.currNodePath
+        np.setX(float(self.editor.ui.objectPropertyUI.propX.getValue()))
+        np.setY(float(self.editor.ui.objectPropertyUI.propY.getValue()))
+        np.setZ(float(self.editor.ui.objectPropertyUI.propZ.getValue()))
+
+        h = float(self.editor.ui.objectPropertyUI.propH.getValue())
+        while h < 0:
+            h = h + 360.0
+
+        while h > 360:
+            h = h - 360.0
+
+        p = float(self.editor.ui.objectPropertyUI.propP.getValue())
+        while p < 0:
+            p = p + 360.0
+
+        while p > 360:
+            p = p - 360.0
+
+        r = float(self.editor.ui.objectPropertyUI.propR.getValue())
+        while r < 0:
+            r = r + 360.0
+
+        while r > 360:
+            r = r - 360.0 
+            
+        np.setH(h)
+        np.setP(p)
+        np.setR(r)
+
+        np.setSx(float(self.editor.ui.objectPropertyUI.propSX.getValue()))
+        np.setSy(float(self.editor.ui.objectPropertyUI.propSY.getValue()))
+        np.setSz(float(self.editor.ui.objectPropertyUI.propSZ.getValue()))        
+
+    def updateObjectModel(self, event, obj):
+        """ replace object's model """
+        model = event.GetString()
+        if model is None:
+            return
+
+        if obj[OG.OBJ_MODEL] != model:
+            base.direct.deselectAll()
+
+            objNP = obj[OG.OBJ_NP]
+
+            # load new model
+            newobj = loader.loadModel(model)
+            newobj.setTag('OBJRoot','1')
+
+            # reparent children
+            objNP.findAllMatches("=OBJRoot").reparentTo(newobj)
+            
+            # reparent to parent
+            newobj.reparentTo(objNP.getParent())
+
+            # copy transform
+            newobj.setPos(objNP.getPos())
+            newobj.setHpr(objNP.getHpr())
+            newobj.setScale(objNP.getScale())
+
+            # delete old geom
+            del self.npIndex[NodePath(objNP)]
+            objNP.removeNode()
+
+            # register new geom
+            obj[OG.OBJ_NP] = newobj
+            obj[OG.OBJ_MODEL] = model
+            self.npIndex[NodePath(newobj)] = obj[OG.OBJ_UID]
+            
+            base.direct.select(newobj)        
+
+    def updateObjectProperty(self, event, obj, propName):
+        """
+        When an obj's property is updated in UI,
+        this will update it's value in data structure.
+        And call update function if defined.        
+        """
+        
+        objDef = obj[OG.OBJ_DEF]
+        objProp = obj[OG.OBJ_PROP]
+        
+        propDef = objDef.properties[propName]
+        if propDef is None:
+            return
+
+        propType = propDef[OG.PROP_TYPE]
+        propDataType = propDef[OG.PROP_DATATYPE]
+        
+        if propType == OG.PROP_UI_SLIDE:
+            if len(propDef) <= OG.PROP_RANGE:
+                return
+
+            strVal = event.GetString()
+            if strVal == '':
+                min = float(propDef[OG.PROP_RANGE][OG.RANGE_MIN])
+                max = float(propDef[OG.PROP_RANGE][OG.RANGE_MAX])
+                intVal = event.GetInt()
+                if intVal is None:
+                    return
+                val = intVal / 100.0 * (max - min) + min
+            else:
+                val = strVal
+
+        elif propType == OG.PROP_UI_ENTRY:
+            val = event.GetString()
+
+        elif propType == OG.PROP_UI_SPIN:
+            val = event.GetInt()
+
+        elif propType == OG.PROP_UI_CHECK:
+            if event.GetInt():
+                val = True
+            else:
+                val = False
+
+        elif propType == OG.PROP_UI_RADIO:
+            val = event.GetString()
+
+        elif propType == OG.PROP_UI_COMBO:
+            val = event.GetString()
+
+        else:
+            # unsupported property type
+            return
+
+        # now update object prop value and call update function
+        self.updateObjectPropValue(obj, propName, val)
+
+    def updateObjectPropValue(self, obj, propName, val):
+        """
+        Update object property value and
+        call update function if defined.         
+        """
+        
+        objDef = obj[OG.OBJ_DEF]
+        objProp = obj[OG.OBJ_PROP]
+        
+        propDef = objDef.properties[propName]
+        propDataType = propDef[OG.PROP_DATATYPE]
+
+        val = OG.TYPE_CONV[propDataType](val)
+        objProp[propName] = val
+
+        if propDef[OG.PROP_FUNC] is None:
+            return
+        
+        funcName = propDef[OG.PROP_FUNC][OG.FUNC_NAME]
+        funcArgs = propDef[OG.PROP_FUNC][OG.FUNC_ARGS]
+
+        if funcName.startswith('.'):
+            func = Functor(eval("base.le.objectHandler%s"%funcName))
+        else:
+            func = Functor(eval(funcName))
+
+        # populate keyword arguments
+        kwargs = {}
+        for key in funcArgs.keys():
+            if funcArgs[key] == OG.ARG_VAL:
+                kwargs[key] = val
+            elif funcArgs[key] == OG.ARG_OBJ:
+                kwargs[key] = obj
+            else:
+                kwargs[key] = funcArgs[key]
+
+        # finally call update function
+        func(**kwargs)
+
+    def updateObjectProperties(self, nodePath, propValues):
+        """
+        When a saved level is loaded,
+        update an object's properties
+        And call update function if defined.
+        """
+
+        obj = self.findObjectByNodePath(nodePath)
+        if obj:
+            for propName in propValues:
+                self.updateObjectPropValue(obj, propName, propValues[propName])
+
+    def traverse(self, parent, parentId = None):
+        """
+        Trasverse scene graph to gather data for saving
+        """
+        
+        for child in parent.getChildren():
+            if child.hasTag('OBJRoot'):
+                obj = self.findObjectByNodePath(child)
+
+                if obj:
+                    uid = obj[OG.OBJ_UID]
+                    np = obj[OG.OBJ_NP]
+                    objDef = obj[OG.OBJ_DEF]
+                    objModel = obj[OG.OBJ_MODEL]
+                    objProp = obj[OG.OBJ_PROP]
+
+                    if parentId:
+                        parentStr = "objects['%s']"%parentId
+                    else:
+                        parentStr = "None"
+
+                    if objModel is None:
+                        self.saveData.append("\nobjects['%s'] = objectMgr.addNewObject('%s', '%s', None, %s)"%(uid, objDef.name, uid, parentStr))
+                    else:
+                        self.saveData.append("\nobjects['%s'] = objectMgr.addNewObject('%s', '%s', '%s', %s)"%(uid, objDef.name, uid, objModel, parentStr))
+                    self.saveData.append("if objects['%s']:"%uid)
+                    self.saveData.append("    objects['%s'].setPos(%s)"%(uid, np.getPos()))
+                    self.saveData.append("    objects['%s'].setHpr(%s)"%(uid, np.getHpr()))
+                    self.saveData.append("    objects['%s'].setScale(%s)"%(uid, np.getScale()))
+                    self.saveData.append("    objectMgr.updateObjectProperties(objects['%s'], %s)"%(uid,objProp))
+                    
+                self.traverse(child, uid)
+
+    def getSaveData(self):
+        self.saveData = []
+        self.traverse(render)
+        return self.saveData
+    
+    

+ 111 - 0
direct/src/leveleditor/ObjectPalette.py

@@ -0,0 +1,111 @@
+"""
+This is just a sample code.
+
+LevelEditor, ObjectHandler, ObjectPalette should be rewritten
+to be game specific.
+
+You can define object template class inheriting ObjectBase
+to define properties shared by multiple object types.
+When you are defining properties
+you should specify their name, UI type, data type,
+update function, default value, and value range.
+
+Then you need implement ObjectPalette class inheriting ObjectPaletteBase,
+and in the populate function you can define ObjectPalette tree structure.
+"""
+
+from ObjectPaletteBase import *
+
+class ObjectProp(ObjectBase):
+    def __init__(self, *args, **kw):
+        ObjectBase.__init__(self, *args, **kw)
+        self.properties['Abc'] =[OG.PROP_UI_RADIO, # UI type
+                                 OG.PROP_STR,      # data type
+                                 None,             # update function
+                                 'a',              # default value
+                                 ['a', 'b', 'c']]  # value range
+
+
+class ObjectSmiley(ObjectProp):
+    def __init__(self, *args, **kw):
+        ObjectProp.__init__(self, *args, **kw)
+        self.properties['123'] = [OG.PROP_UI_COMBO,
+                                  OG.PROP_INT,
+                                  None,
+                                  1,
+                                  [1, 2, 3]]
+
+
+class ObjectDoubleSmileys(ObjectProp):
+    def __init__(self, *args, **kw):
+        ObjectProp.__init__(self, *args, **kw)
+        self.properties['Distance'] = [OG.PROP_UI_SLIDE,
+                                                    OG.PROP_FLOAT,
+                                                    ('.updateDoubleSmiley',
+                                                     {'val':OG.ARG_VAL, 'obj':OG.ARG_OBJ}),
+                                                    # In this case, an update function for property is defined
+                                                    # so whenever you change the value of this property from UI
+                                                    # this update function will be called with these arguments.
+                                                    # OG.ARG_VAL will be replaced by the value from UI.
+                                                    # OG.ARG_OBJ will be replaced by object data structure.
+                                                    # When an update function is starting with .
+                                                    # it means this function belongs to the default objectHandler.
+                                                    1.0, [0, 10, 0.1]]
+
+        
+class ObjectPalette(ObjectPaletteBase):
+    def __init__(self):
+        ObjectPaletteBase.__init__(self)
+
+    def populate(self):
+        # Create a group called 'Prop' in the ObjectPalette tree
+        self.add('Prop')
+
+        # Create a group called 'Double Smileys' under 'Prop' group
+        self.add('Double Smileys', 'Prop')
+
+        # Add an object type 'Smiley' which is inheriting ObjectSmiley template
+        # and have following properties.
+        self.add(ObjectSmiley(name='Smiley',
+                              model='models/smiley.egg',
+                              models=['models/smiley.egg',
+                                      'models/frowney.egg',
+                                      'models/jack.egg'],
+                              # when an object is just a simple geometry, you can define
+                              # model, and models like this
+                              # instead of defining createFunction
+                              properties={'Happy':[OG.PROP_UI_CHECK,
+                                                   OG.PROP_BOOL,
+                                                   None,
+                                                   True],
+                                          'Number':[OG.PROP_UI_SPIN,
+                                                    OG.PROP_INT,
+                                                    ('.updateSmiley',
+                                                     {'val':OG.ARG_VAL, 'obj':OG.ARG_OBJ}),
+                                                    1, [1, 10]],
+                                        }),
+                 'Prop') # This object type will be added under the 'Prop' group.        
+        self.add(ObjectDoubleSmileys(name='H Double Smiley',
+                                     createFunction = ('.createDoubleSmiley', {})),
+                                     # When the createFunction is defined like this,
+                                     # this function will be called to create the object.
+                                     # When a create function is starting with .
+                                     # it means this function belongs to the default objectHandler.
+                 'Double Smileys')
+
+        self.add(ObjectDoubleSmileys(name='V Double Smiley',
+                                     createFunction = ('.createDoubleSmiley', {'horizontal':False})),
+                                     # You can specify argument for the create function, too
+                 'Double Smileys')
+
+        self.add('Animal')
+        self.add(ObjectBase(name='Panda',
+                            createFunction = ('.createPanda', {}),
+                            properties = {}),
+                 'Animal')
+
+        self.add('BG')
+        self.add(ObjectBase(name='Grass',
+                            createFunction = ('.createGrass', {}),
+                            properties = {}),
+                 'BG')

+ 84 - 0
direct/src/leveleditor/ObjectPaletteBase.py

@@ -0,0 +1,84 @@
+import ObjectGlobals as OG
+
+class ObjectBase:
+    """ Base class for obj definitions """
+    
+    def __init__(self, name='', createFunction = None, model = None, models= [], properties={},
+                 movable = True):
+        self.name = name
+        self.createFunction = createFunction
+        self.model = model
+        self.models = models
+        self.properties = properties
+        self.movable = movable
+
+class ObjectPaletteBase:
+    """
+    Base class for objectPalette
+
+    You should write your own ObjectPalette class inheriting this.
+    Refer ObjectPalette.py for example.
+    """
+    
+    def __init__(self):
+        self.data = {}
+        self.populate()
+
+    def insertItem(self, item, parentName, data, obj=None):
+        """
+        You can insert item to obj palette tree.
+
+        'item' is the name to be inserted, it can be either a group or obj.
+        If item is a group 'obj' will be None.
+        'parentName' is the name of parent under where this item will be inserted.
+        'data' is used in recursive searching.
+        
+        """
+
+        if type(data) != dict:
+            return
+
+        if parentName is None:
+            # when adding a group to the root
+            assert item not in data.keys()
+
+            if obj is None:
+                data[item] = {}
+            else:
+                data[item] = obj
+            return
+
+        if parentName in data.keys():
+            if obj is None:
+                data[parentName][item] = {}
+            else:
+                data[parentName][item] = obj
+        else:
+            for key in data.keys():
+                self.insertItem(item, parentName, data[key], obj)
+                
+    def add(self, item, parentName = None):
+        if type(item) == str:
+            self.insertItem(item, parentName, self.data, None)
+        else:
+            self.insertItem(item.name, parentName, self.data, item)
+
+    def findItem(self, name, data=None):
+        if data is None:
+            data = self.data
+
+        if type(data) != dict:
+            return None
+
+        if name in data.keys():
+            return data[name]
+        else:
+            for key in data.keys():
+                result = self.findItem(name, data[key])
+                if result:
+                    return result
+            return None
+        
+    def populate(self):
+        # You should implement this in subclass
+        raise NotImplementedError('populate() must be implemented in ObjectPalette.py')

+ 53 - 0
direct/src/leveleditor/ObjectPaletteUI.py

@@ -0,0 +1,53 @@
+"""
+Defines ObjectPalette tree UI
+"""
+import wx
+import cPickle as pickle
+from ObjectPalette import *
+
+class ObjectPaletteUI(wx.Panel):
+    def __init__(self, parent, editor):
+        wx.Panel.__init__(self, parent)
+
+        self.editor = editor
+
+        self.palette = self.editor.objectPalette
+        self.tree = wx.TreeCtrl(self)
+        root = self.tree.AddRoot('Objects')
+        self.addTreeNodes(root, self.palette.data)
+
+        sizer = wx.BoxSizer(wx.VERTICAL)
+        sizer.Add(self.tree, 1, wx.EXPAND, 0)
+        self.SetSizer(sizer); self.Layout()
+
+        parentSizer = wx.BoxSizer(wx.VERTICAL)
+        parentSizer.Add(self, 1, wx.EXPAND, 0)
+        parent.SetSizer(parentSizer); parent.Layout()
+
+        self.tree.Bind(wx.EVT_TREE_SEL_CHANGED, self.onSelected)
+        self.tree.Bind(wx.EVT_TREE_BEGIN_DRAG, self.onBeginDrag)
+
+    def addTreeNodes(self, parentItem, items):
+        for key in items.keys():
+            newItem = self.tree.AppendItem(parentItem, key)
+            if type(items[key]) == dict:
+                self.addTreeNodes(newItem, items[key])
+            else:
+                self.tree.SetItemPyData(newItem, items[key])
+
+    def onSelected(self, event):
+        data = self.tree.GetItemPyData(event.GetItem())
+        if data:
+            print data.properties
+
+    def onBeginDrag(self, event):
+        item = event.GetItem()
+
+        if item != self.tree.GetRootItem(): # prevent dragging root item
+            text = self.tree.GetItemText(item)
+            print "Starting drag'n'drop with %s..." % repr(text)
+
+            tdo = wx.TextDataObject(text)
+            tds = wx.DropSource(self.tree)
+            tds.SetData(tdo)
+            tds.DoDragDrop(True)

+ 263 - 0
direct/src/leveleditor/ObjectPropertyUI.py

@@ -0,0 +1,263 @@
+"""
+UI for object property control
+"""
+import wx
+from wx.lib.scrolledpanel import ScrolledPanel
+
+from direct.wxwidgets.WxSlider import *
+
+import ObjectGlobals as OG
+
+
+class ObjectPropUI(wx.Panel):
+    """
+    Base class for ObjectPropUIs,
+    It consists of label area and ui area.
+    """
+    def __init__(self, parent, label):
+        wx.Panel.__init__(self, parent)
+        self.parent = parent
+        self.label = wx.StaticText(self, label=label)
+        self.uiPane = wx.Panel(self)
+        sizer = wx.BoxSizer(wx.VERTICAL)
+        sizer.Add(self.label)
+        sizer.Add(self.uiPane, 1, wx.EXPAND, 0)
+        self.SetSizer(sizer)
+
+    def setValue(self, value):
+        self.ui.SetValue(value)
+
+    def getValue(self):
+        return self.ui.GetValue()
+
+    def bindFunc(self, inFunc, outFunc, valFunc = None):
+        self.ui.Bind(wx.EVT_ENTER_WINDOW, inFunc)
+        self.ui.Bind(wx.EVT_LEAVE_WINDOW, outFunc)
+        if valFunc:
+            self.ui.Bind(self.eventType, valFunc)
+
+class ObjectPropUIEntry(ObjectPropUI):
+    """ UI for string value properties """
+    def __init__(self, parent, label):
+        ObjectPropUI.__init__(self, parent, label)
+        self.ui = wx.TextCtrl(self.uiPane, -1)
+        self.eventType = wx.EVT_TEXT_ENTER
+        self.Layout()
+
+    def setValue(self, value):
+        self.ui.SetValue(str(value))
+
+class ObjectPropUISlider(ObjectPropUI):
+    """ UI for float value properties """
+    def __init__(self, parent, label, value, minValue, maxValue):
+        ObjectPropUI.__init__(self, parent, label)
+        self.ui = WxSlider(self.uiPane, -1, value, minValue, maxValue,
+                           pos = (0,0), size=(140, -1),
+                           style=wx.SL_HORIZONTAL | wx.SL_LABELS)
+        self.ui.Enable()
+        self.Layout()
+
+    def bindFunc(self, inFunc, outFunc, valFunc = None):
+        self.ui.Bind(wx.EVT_ENTER_WINDOW, inFunc)
+        self.ui.Bind(wx.EVT_LEAVE_WINDOW, outFunc)
+        self.ui.textValue.Bind(wx.EVT_ENTER_WINDOW, inFunc)
+        self.ui.textValue.Bind(wx.EVT_LEAVE_WINDOW, outFunc)
+
+        if valFunc:
+            self.ui.bindFunc(valFunc)
+
+
+class ObjectPropUISpinner(ObjectPropUI):
+    """ UI for int value properties """
+    def __init__(self, parent, label, value, minValue, maxValue):
+        ObjectPropUI.__init__(self, parent, label)
+        self.ui = wx.SpinCtrl(self.uiPane, -1, "", min=minValue, max=maxValue, initial=value)
+        self.eventType = wx.EVT_SPIN
+        self.Layout()
+
+
+class ObjectPropUICheck(ObjectPropUI):
+    def __init__(self, parent, label, value):
+        ObjectPropUI.__init__(self, parent, label)
+        self.ui = wx.CheckBox(self.uiPane, -1, "", size=(50, 30))
+        self.setValue(value)
+        self.eventType = wx.EVT_CHECKBOX
+        self.Layout()
+
+
+class ObjectPropUIRadio(ObjectPropUI):
+    def __init__(self, parent, label, value, valueList):
+        ObjectPropUI.__init__(self, parent, label)
+        self.ui = wx.RadioBox(self.uiPane, -1, "", choices=valueList, majorDimension=1, style=wx.RA_SPECIFY_COLS)
+        self.setValue(value)
+        self.eventType = wx.EVT_RADIOBOX
+        self.Layout()
+
+    def setValue(self, value):
+        self.ui.SetStringSelection(value)
+
+    def getValue(self):
+        return self.ui.GetStringSelection()
+
+
+class ObjectPropUICombo(ObjectPropUI):
+    def __init__(self, parent, label, value, valueList):
+        ObjectPropUI.__init__(self, parent, label)
+        self.ui = wx.Choice(self.uiPane, -1, choices=valueList)
+        self.setValue(value)
+        self.eventType = wx.EVT_CHOICE
+        self.Layout()
+
+    def setValue(self, value):
+        self.ui.SetStringSelection(value)
+
+    def getValue(self):
+        return self.ui.GetStringSelection()
+
+
+class ObjectPropertyUI(ScrolledPanel):
+    def __init__(self, parent, editor):
+        self.editor = editor
+        ScrolledPanel.__init__(self, parent)
+
+        parentSizer = wx.BoxSizer(wx.VERTICAL)
+        parentSizer.Add(self, 1, wx.EXPAND, 0)
+        parent.SetSizer(parentSizer); parent.Layout()
+
+    def clearPropUI(self):
+        sizer = self.GetSizer()
+        if sizer is not None:
+            sizer.Remove(self.propPane)
+            self.propPane.Destroy()
+            self.SetSizer(None)
+        self.Layout()
+        self.SetupScrolling(self, scroll_y = True, rate_y = 20)
+        
+    def updateProps(self, obj):
+        self.clearPropUI()
+
+        self.propPane = wx.Panel(self)
+        mainSizer = wx.BoxSizer(wx.VERTICAL)
+        mainSizer.Add(self.propPane, 1, wx.EXPAND, 0)
+        self.SetSizer(mainSizer)
+
+        self.propX = ObjectPropUIEntry(self.propPane, 'X')
+        self.propY = ObjectPropUIEntry(self.propPane, 'Y')
+        self.propZ = ObjectPropUIEntry(self.propPane, 'Z')
+
+        self.propH = ObjectPropUISlider(self.propPane, 'H', 0, 0, 360)
+        self.propP = ObjectPropUISlider(self.propPane, 'P', 0, 0, 360)
+        self.propR = ObjectPropUISlider(self.propPane, 'R', 0, 0, 360)
+
+        self.propSX = ObjectPropUIEntry(self.propPane, 'SX')
+        self.propSY = ObjectPropUIEntry(self.propPane, 'SY')
+        self.propSZ = ObjectPropUIEntry(self.propPane, 'SZ')
+
+        transformProps = [self.propX, self.propY, self.propZ, self.propH, self.propP, self.propR,
+                       self.propSX, self.propSY, self.propSZ]
+
+        sizer = wx.BoxSizer(wx.VERTICAL)
+        sizer.AddMany([self.propX, self.propY, self.propZ, self.propH, self.propP, self.propR,
+                       self.propSX, self.propSY, self.propSZ])
+
+        for transformProp in transformProps:
+            transformProp.bindFunc(self.editor.objectMgr.onEnterObjectPropUI,
+                                   self.editor.objectMgr.onLeaveObjectPropUI,
+                                   self.editor.objectMgr.updateObjectTransform)
+        
+        objDef = obj[OG.OBJ_DEF]
+
+        if objDef.model is not None:
+            propUI = ObjectPropUICombo(self.propPane, 'model', obj[OG.OBJ_MODEL], objDef.models)
+            sizer.Add(propUI)            
+
+            propUI.bindFunc(self.editor.objectMgr.onEnterObjectPropUI,
+                            self.editor.objectMgr.onLeaveObjectPropUI,
+                            lambda p0=None, p1=obj: self.editor.objectMgr.updateObjectModel(p0, p1))
+
+        for key in objDef.properties.keys():
+            propDef = objDef.properties[key]
+            propType = propDef[OG.PROP_TYPE]
+            propDataType = propDef[OG.PROP_DATATYPE]
+            value = obj[OG.OBJ_PROP].get(key)
+
+            if propType == OG.PROP_UI_ENTRY:
+                propUI = ObjectPropUIEntry(self.propPane, key)
+                propUI.setValue(value)
+                sizer.Add(propUI)
+
+            elif propType == OG.PROP_UI_SLIDE:
+                if len(propDef) <= OG.PROP_RANGE:
+                    continue
+                propRange = propDef[OG.PROP_RANGE]
+
+                if value is None:
+                    continue
+
+                propUI = ObjectPropUISlider(self.propPane, key, value, propRange[OG.RANGE_MIN], propRange[OG.RANGE_MAX])
+                sizer.Add(propUI)
+
+            elif propType == OG.PROP_UI_SPIN:
+                if len(propDef) <= OG.PROP_RANGE:
+                    continue
+                propRange = propDef[OG.PROP_RANGE]
+
+                if value is None:
+                    continue
+
+                propUI = ObjectPropUISpinner(self.propPane, key, value, propRange[OG.RANGE_MIN], propRange[OG.RANGE_MAX])
+                sizer.Add(propUI)                
+
+            elif propType == OG.PROP_UI_CHECK:
+                if value is None:
+                    continue
+
+                propUI = ObjectPropUICheck(self.propPane, key, value)
+                sizer.Add(propUI)                  
+
+            elif propType == OG.PROP_UI_RADIO:
+                if len(propDef) <= OG.PROP_RANGE:
+                    continue
+                propRange = propDef[OG.PROP_RANGE]
+                
+                if value is None:
+                    continue
+
+                if propDataType != OG.PROP_STR:
+                    for i in range(len(propRange)):
+                        propRange[i] = str(propRange[i])
+
+                    value = str(value)
+
+                propUI = ObjectPropUIRadio(self.propPane, key, value, propRange)
+                sizer.Add(propUI)
+
+            elif propType == OG.PROP_UI_COMBO:
+                if len(propDef) <= OG.PROP_RANGE:
+                    continue
+                propRange = propDef[OG.PROP_RANGE]                
+
+                if value is None:
+                    continue
+
+                if propDataType != OG.PROP_STR:
+                    for i in range(len(propRange)):
+                        propRange[i] = str(propRange[i])
+
+                    value = str(value)
+
+                propUI = ObjectPropUICombo(self.propPane, key, value, propRange)
+                sizer.Add(propUI)
+
+            else:
+                # unspported property type
+                continue
+
+            propUI.bindFunc(self.editor.objectMgr.onEnterObjectPropUI,
+                            self.editor.objectMgr.onLeaveObjectPropUI,
+                            lambda p0=None, p1=obj, p2=key: self.editor.objectMgr.updateObjectProperty(p0, p1, p2))
+
+
+        self.propPane.SetSizer(sizer);
+        self.Layout()
+        self.SetupScrolling(self, scroll_y = True, rate_y = 20)

+ 263 - 0
direct/src/leveleditor/ViewPort.py

@@ -0,0 +1,263 @@
+"""
+Contains classes useful for 3D viewports.
+
+Originally written by pro-rsoft,
+Modified by gjeon.
+"""
+
+__all__ = ["Viewport", "ViewportManager", "ViewportMenu"]
+
+from direct.showbase.DirectObject import DirectObject
+from direct.directtools.DirectGrid import DirectGrid
+from direct.showbase.ShowBase import WindowControls
+from pandac.PandaModules import WindowProperties, OrthographicLens, Point3, BitMask32
+import wx
+
+HORIZONTAL = wx.SPLIT_HORIZONTAL
+VERTICAL   = wx.SPLIT_VERTICAL
+CREATENEW  = 99
+VPLEFT     = 10
+VPFRONT    = 11
+VPTOP      = 12
+VPPERSPECTIVE = 13
+
+TopCameraBitmask = BitMask32.bit(0)
+FrontCameraBitmask = BitMask32.bit(1)
+LeftCameraBitmask = BitMask32.bit(2)
+PerspCameraBitmask = BitMask32.bit(3)
+
+CameraBitmasks = {'persp':PerspCameraBitmask,
+                 'left':LeftCameraBitmask,
+                 'front':FrontCameraBitmask,
+                 'top':TopCameraBitmask}
+
+class ViewportManager:
+  """Manages the global viewport stuff."""
+  viewports = []
+  gsg = None
+
+  @staticmethod
+  def initializeAll(*args, **kwargs):
+    """Calls initialize() on all the viewports."""
+    for v in ViewportManager.viewports:
+      v.initialize(*args, **kwargs)
+  
+  @staticmethod
+  def updateAll(*args, **kwargs):
+    """Calls Update() on all the viewports."""
+    for v in ViewportManager.viewports:
+      v.Update(*args, **kwargs)
+  
+  @staticmethod
+  def layoutAll(*args, **kwargs):
+    """Calls Layout() on all the viewports."""
+    for v in ViewportManager.viewports:
+      v.Layout(*args, **kwargs)
+
+class Viewport(wx.Panel, DirectObject):
+  """Class representing a 3D Viewport."""
+  CREATENEW  = CREATENEW
+  VPLEFT     = VPLEFT
+  VPFRONT    = VPFRONT
+  VPTOP      = VPTOP
+  VPPERSPECTIVE = VPPERSPECTIVE
+  def __init__(self, name, *args, **kwargs):
+    self.name = name
+    DirectObject.__init__(self)
+    wx.Panel.__init__(self, *args, **kwargs)
+
+    ViewportManager.viewports.append(self)
+    self.win = None
+    self.camera = None
+    self.lens = None
+    self.camPos = None
+    self.camLookAt = None
+    self.initialized = False
+    self.grid = None
+    #self.Bind(wx.EVT_RIGHT_DOWN, self.onRightDown)
+
+  def initialize(self):
+    self.Update()
+    wp = WindowProperties()
+    wp.setOrigin(0, 0)
+    wp.setSize(self.ClientSize.GetWidth(), self.ClientSize.GetHeight())
+    assert self.GetHandle() != 0
+    wp.setParentWindow(self.GetHandle())
+
+    # initializing panda window
+    base.windowType = "onscreen"
+    props = WindowProperties.getDefault()
+    props.addProperties(wp)
+    self.win = base.openWindow(props = props, gsg = ViewportManager.gsg)
+    if self.win:
+      self.cam2d = base.makeCamera2d(self.win)
+      self.cam2d.node().setCameraMask(CameraBitmasks[self.name])
+      
+    if ViewportManager.gsg == None:
+      ViewportManager.gsg = self.win.getGsg()
+    self.cam = base.camList[-1]
+    self.camera = render.attachNewNode(self.name)
+    #self.camera.setName(self.name)
+    #self.camera.reparentTo(render)
+    self.cam.reparentTo(self.camera)
+    self.camNode = self.cam.node()
+
+    self.camNode.setCameraMask(CameraBitmasks[self.name])
+
+    bt = base.setupMouse(self.win, True)
+    mw = bt.getParent()
+    mk = mw.getParent()
+    winCtrl = WindowControls(
+                self.win, mouseWatcher=mw,
+                cam=self.camera,
+                camNode = self.camNode,
+                cam2d=None,
+                mouseKeyboard =mk)
+    base.setupWindowControls(winCtrl)
+
+    self.initialized = True
+    if self.lens != None:      self.cam.node().setLens(self.lens)
+    if self.camPos != None:    self.camera.setPos(self.camPos)
+    if self.camLookAt != None: self.camera.lookAt(self.camLookAt)
+
+    self.camLens = self.camNode.getLens()
+
+    self.Bind(wx.EVT_SIZE, self.onSize)
+##     self.accept("wheel_down", self.zoomOut)
+##     self.accept("wheel_up", self.zoomIn)
+##     self.accept("page_down", self.zoomOut)
+##     self.accept("page_down-repeat", self.zoomOut)
+##     self.accept("page_up", self.zoomIn)
+##     self.accept("page_up-repeat", self.zoomIn)
+    #self.accept("mouse3", self.onRightDown)
+  
+  def close(self):
+    """Closes the viewport."""
+    if self.initialized:
+      Window.close(self)
+    ViewportManager.viewports.remove(self)
+  
+  def onSize(self, evt):
+    """Invoked when the viewport is resized."""
+    if self.win != None:
+      wp = WindowProperties()
+      wp.setOrigin(0, 0)
+      newWidth = self.ClientSize.GetWidth()
+      newHeight = self.ClientSize.GetHeight()
+      wp.setSize(newWidth, newHeight)
+      self.win.requestProperties(wp)
+
+      if hasattr(base, "direct") and base.direct:
+        for dr in base.direct.drList:
+          if dr.camNode == self.camNode:
+            dr.updateFilmSize(newWidth, newHeight)
+            break
+      
+  def onRightDown(self, evt = None):
+    print "RightDown captured by wx"
+    """Invoked when the viewport is right-clicked."""
+    menu = ViewportMenu(self)
+    if evt == None:
+      mpos = wx.GetMouseState()
+      mpos = self.ScreenToClient((mpos.x, mpos.y))
+    else:
+      mpos = evt.GetPosition()
+    self.Update()
+    self.PopupMenu(menu, mpos)
+    menu.Destroy()
+  
+  def zoomOut(self):
+    self.camera.setY(self.camera, -MOUSE_ZOO_SPEED)
+  
+  def zoomIn(self):
+    self.camera.setY(self.camera,  MOUSE_ZOOM_SPEED)
+  
+  @staticmethod
+  def make(parent, vpType = None):
+    """Safe constructor that also takes CREATENEW, VPLEFT, VPTOP, etc."""
+    if vpType == None or vpType == CREATENEW:
+      return Viewport(parent)
+    if isinstance(vpType, Viewport): return vpType
+    if vpType == VPLEFT:  return Viewport.makeLeft(parent)
+    if vpType == VPFRONT: return Viewport.makeFront(parent)
+    if vpType == VPTOP:   return Viewport.makeTop(parent)
+    if vpType == VPPERSPECTIVE:  return Viewport.makePerspective(parent)
+    raise TypeError, "Unknown viewport type: %s" % vpType
+  
+  @staticmethod
+  def makeOrthographic(parent, name, campos):
+    v = Viewport(name, parent)
+    v.lens = OrthographicLens()
+    v.lens.setFilmSize(30)
+    v.camPos = campos
+    v.camLookAt = Point3(0, 0, 0)
+    v.grid = DirectGrid(parent=render)
+    if name == 'left':
+      v.grid.setHpr(0, 0, 90)
+      #v.grid.gridBack.findAllMatches("**/+GeomNode")[0].setName("_leftViewGridBack")
+      v.grid.hide(PerspCameraBitmask)
+      v.grid.hide(FrontCameraBitmask)
+      v.grid.hide(TopCameraBitmask)
+    elif name == 'front':
+      v.grid.setHpr(90, 0, 90)
+      #v.grid.gridBack.findAllMatches("**/+GeomNode")[0].setName("_frontViewGridBack")
+      v.grid.hide(PerspCameraBitmask)
+      v.grid.hide(LeftCameraBitmask)
+      v.grid.hide(TopCameraBitmask)
+    else:
+      #v.grid.gridBack.findAllMatches("**/+GeomNode")[0].setName("_topViewGridBack")
+      v.grid.hide(PerspCameraBitmask)
+      v.grid.hide(LeftCameraBitmask)
+      v.grid.hide(FrontCameraBitmask)
+    return v
+  
+  @staticmethod
+  def makePerspective(parent):
+    v = Viewport('persp', parent)
+    v.camPos = Point3(30, 30, 30)
+    v.camLookAt = Point3(0, 0, 0)
+
+    v.grid = DirectGrid(parent=render)
+    #v.grid.gridBack.findAllMatches("**/+GeomNode")[0].setName("_perspViewGridBack")
+    v.grid.hide(FrontCameraBitmask)
+    v.grid.hide(LeftCameraBitmask)
+    v.grid.hide(TopCameraBitmask)
+    return v
+  
+  @staticmethod
+  def makeLeft(parent): return Viewport.makeOrthographic(parent, 'left', Point3(100, 0, 0))
+  @staticmethod
+  def makeFront(parent): return Viewport.makeOrthographic(parent, 'front', Point3(0, 100, 0))
+  @staticmethod
+  def makeTop(parent): return Viewport.makeOrthographic(parent, 'top', Point3(0, 0, 100))
+
+class ViewportMenu(wx.Menu):
+  """Represents a menu that appears when right-clicking a viewport."""
+  def __init__(self, viewport):
+    wx.Menu.__init__(self)
+    self.viewport = viewport
+    self.addItem("&Refresh", self.viewport.Update)
+    self.addItem("&Background Color...", self.onChooseColor)
+  
+  def addItem(self, name, call = None, id = None):
+    if id == None: id = wx.NewId()
+    item = wx.MenuItem(self, id, name)
+    self.AppendItem(item)
+    if call != None:
+      self.Bind(wx.EVT_MENU, call, item)
+  
+  def onChooseColor(self, evt = None):
+    """Change the background color of the viewport."""
+    data = wx.ColourData()
+    bgcolor = self.viewport.win.getClearColor()
+    bgcolor = bgcolor[0] * 255.0, bgcolor[1] * 255.0, bgcolor[2] * 255.0
+    data.SetColour(bgcolor)
+    dlg = wx.ColourDialog(self, data)
+    try:
+      if dlg.ShowModal() == wx.ID_OK:
+        data = dlg.GetColourData().GetColour()
+        data = data[0] / 255.0, data[1] / 255.0, data[2] / 255.0
+        self.viewport.win.setClearColor(*data)
+    finally:
+      dlg.Destroy()
+

+ 54 - 0
direct/src/leveleditor/testData.py

@@ -0,0 +1,54 @@
+from pandac.PandaModules import *
+
+objectMgr = base.le.objectMgr
+# temporary place holder for nodepath
+objects = {}
+
+objects['1252538687.73gjeon'] = objectMgr.addNewObject('Smiley', '1252538687.73gjeon', 'models/smiley.egg', None)
+if objects['1252538687.73gjeon']:
+    objects['1252538687.73gjeon'].setPos(Point3(8.66381, 0, 7.13246))
+    objects['1252538687.73gjeon'].setHpr(VBase3(180, 0, 0))
+    objects['1252538687.73gjeon'].setScale(VBase3(1, 1, 1))
+    objectMgr.updateObjectProperties(objects['1252538687.73gjeon'], {'123': 1, 'Abc': 'a', 'Number': 1, 'Happy': True})
+
+objects['1252538690.2gjeon'] = objectMgr.addNewObject('H Double Smiley', '1252538690.2gjeon', None, objects['1252538687.73gjeon'])
+if objects['1252538690.2gjeon']:
+    objects['1252538690.2gjeon'].setPos(Point3(0, 0, 2.12046))
+    objects['1252538690.2gjeon'].setHpr(VBase3(0, 0, 0))
+    objects['1252538690.2gjeon'].setScale(VBase3(1, 1, 1))
+    objectMgr.updateObjectProperties(objects['1252538690.2gjeon'], {'Distance': 2.0, 'Abc': 'a'})
+
+objects['1252539696.69gjeon'] = objectMgr.addNewObject('H Double Smiley', '1252539696.69gjeon', None, objects['1252538687.73gjeon'])
+if objects['1252539696.69gjeon']:
+    objects['1252539696.69gjeon'].setPos(Point3(-9.53674e-006, 0, 0))
+    objects['1252539696.69gjeon'].setHpr(VBase3(0, 0, 0))
+    objects['1252539696.69gjeon'].setScale(VBase3(1, 1, 1))
+    objectMgr.updateObjectProperties(objects['1252539696.69gjeon'], {'Distance': 2.0, 'Abc': 'a'})
+
+objects['1252539897.22gjeon'] = objectMgr.addNewObject('H Double Smiley', '1252539897.22gjeon', None, objects['1252538687.73gjeon'])
+if objects['1252539897.22gjeon']:
+    objects['1252539897.22gjeon'].setPos(Point3(0.06987, 0, -2.12046))
+    objects['1252539897.22gjeon'].setHpr(VBase3(0, 0, 0))
+    objects['1252539897.22gjeon'].setScale(VBase3(1, 1, 1))
+    objectMgr.updateObjectProperties(objects['1252539897.22gjeon'], {'Distance': 2.0, 'Abc': 'a'})
+
+objects['1252538689.13gjeon'] = objectMgr.addNewObject('V Double Smiley', '1252538689.13gjeon', None, objects['1252538687.73gjeon'])
+if objects['1252538689.13gjeon']:
+    objects['1252538689.13gjeon'].setPos(Point3(6.07152, 0, -0.863791))
+    objects['1252538689.13gjeon'].setHpr(VBase3(0, 0, 0))
+    objects['1252538689.13gjeon'].setScale(VBase3(1, 1, 1))
+    objectMgr.updateObjectProperties(objects['1252538689.13gjeon'], {'Distance': 1.0, 'Abc': 'a'})
+
+objects['1252539974.19gjeon'] = objectMgr.addNewObject('Smiley', '1252539974.19gjeon', 'models/frowney.egg', objects['1252538689.13gjeon'])
+if objects['1252539974.19gjeon']:
+    objects['1252539974.19gjeon'].setPos(Point3(0.06987, 0, 3.09209))
+    objects['1252539974.19gjeon'].setHpr(VBase3(0, 0, 0))
+    objects['1252539974.19gjeon'].setScale(VBase3(1, 1, 1))
+    objectMgr.updateObjectProperties(objects['1252539974.19gjeon'], {'123': 1, 'Abc': 'a', 'Number': 1, 'Happy': True})
+
+objects['1252623762.9gjeon'] = objectMgr.addNewObject('Panda', '1252623762.9gjeon', None, None)
+if objects['1252623762.9gjeon']:
+    objects['1252623762.9gjeon'].setPos(Point3(0, 0, 0))
+    objects['1252623762.9gjeon'].setHpr(VBase3(172.8, 0, 0))
+    objects['1252623762.9gjeon'].setScale(VBase3(0.005, 0.005, 0.005))
+    objectMgr.updateObjectProperties(objects['1252623762.9gjeon'], {'Distance': 1.0, 'Abc': 'a'})