refactor model things
[carveJwlIkooP6JGAAIwe30JlM.git] / blender_export.py
index a7e714c2fae4a76661b19f7a9fcfa0e1f9412be1..ce4e8264af2fb32dfcdb3efbf73c98fd6733fb7c 100644 (file)
@@ -1,5 +1,20 @@
+# 
+# =============================================================================
+# 
+# Copyright  .        . .       -----, ,----- ,---.   .---.
+# 2021-2022  |\      /| |           /  |      |    | |    /|
+#            | \    / | +--        /   +----- +---'  |   / |
+#            |  \  /  | |         /    |      |   \  |  /  |
+#            |   \/   | |        /     |      |    \ | /   |
+#            '        ' '--' [] '----- '----- '     ' '---'  SOFTWARE
+# 
+# =============================================================================
+# 
+#       Python exporter for Blender, compiles .mdl format for Skate Rift.
 #
-# Copyright (C) 2021-2022 Mt.ZERO Software, Harry Godden - All Rights Reserved
+# Its really slow, sorry, I don't know how to speed it up.
+# Also not sure why you need to put # before {} in code blocks, there is errors 
+# otherwise
 #
 
 import bpy, math, gpu
@@ -116,17 +131,29 @@ class mdl_keyframe(Structure):
                ("s",c_float*3)]
 #}
 
-# Entity types
-# ==========================================
+# ---------------------------------------------------------------------------- #
+#                                                                              #
+#                            Entity definitions                                #
+#                                                                              #
+# ---------------------------------------------------------------------------- #
 #
 # ctypes _fields_ defines the data which is filled in by:
 #  def encode_obj( _, node, node_def ):
 #
 # gizmos get drawn into the viewport via:
 #  @staticmethod
-#  def editor_interface( object ):
+#  def draw_scene_helpers( obj ):
+#
+# editor enterface, simiraliy:
+#  @staticmethod
+#  def editor_interface( layout, obj ):
 #
 
+# Classtype 1
+#
+#  Purpose: A rift. must target another gate, the target gate can not have more 
+#           than one target nodes of its own.
+#
 class classtype_gate(Structure):
 #{
    _pack_ = 1
@@ -155,8 +182,62 @@ class classtype_gate(Structure):
          _.dims[2] = obj.cv_data.v0[2]
       #}
    #}
+
+   @staticmethod
+   def draw_scene_helpers( obj ):
+   #{
+      global cv_view_verts, cv_view_colours
+
+      if obj.type == 'MESH':
+         dims = obj.data.cv_data.v0
+      else:
+         dims = obj.cv_data.v0
+
+      vs = [None]*9
+      c = Vector((0,0,dims[2]))
+
+      vs[0] = obj.matrix_world @ Vector((-dims[0],0.0,-dims[1]+dims[2]))
+      vs[1] = obj.matrix_world @ Vector((-dims[0],0.0, dims[1]+dims[2]))
+      vs[2] = obj.matrix_world @ Vector(( dims[0],0.0, dims[1]+dims[2]))
+      vs[3] = obj.matrix_world @ Vector(( dims[0],0.0,-dims[1]+dims[2]))
+      vs[4] = obj.matrix_world @ (c+Vector((-1,0,-2)))
+      vs[5] = obj.matrix_world @ (c+Vector((-1,0, 2)))
+      vs[6] = obj.matrix_world @ (c+Vector(( 1,0, 2)))
+      vs[7] = obj.matrix_world @ (c+Vector((-1,0, 0)))
+      vs[8] = obj.matrix_world @ (c+Vector(( 1,0, 0)))
+
+      indices = [(0,1),(1,2),(2,3),(3,0),(4,5),(5,6),(7,8)]
+
+      for l in indices:
+         v0 = vs[l[0]]
+         v1 = vs[l[1]]
+         cv_view_verts += [(v0[0],v0[1],v0[2])]
+         cv_view_verts += [(v1[0],v1[1],v1[2])]
+         cv_view_colours += [(1,1,0,1),(1,1,0,1)]
+
+      sw = (0.4,0.4,0.4,0.2)
+      if obj.cv_data.target != None:
+         cv_draw_arrow( obj.location, obj.cv_data.target.location, sw )
+   #}
+
+   @staticmethod
+   def editor_interface( layout, obj ):
+   #{
+      layout.prop( obj.cv_data, "target" )
+
+      mesh = obj.data
+      layout.label( text=F"(i) Data is stored in {mesh.name}" )
+      layout.prop( mesh.cv_data, "v0", text="Gate dimensions" )
+   #}
 #}
 
+# Classtype 3
+#
+#  Purpose: player can reset here, its a safe place
+#           spawns can share the same name, the closest one will be picked
+#           
+#           when the world loads it will pick the one named 'start' first.
+#
 class classtype_spawn(Structure):
 #{
    _pack_ = 1
@@ -167,8 +248,41 @@ class classtype_spawn(Structure):
       node.classtype = 3
       _.pstr_alias = encoder_process_pstr( node_def['obj'].cv_data.strp )
    #}
+
+   @staticmethod
+   def draw_scene_helpers( obj ):
+   #{
+      global cv_view_verts, cv_view_colours
+
+      vs = [None]*4
+      vs[0] = obj.matrix_world @ Vector((0,0,0))
+      vs[1] = obj.matrix_world @ Vector((0,2,0))
+      vs[2] = obj.matrix_world @ Vector((0.5,1,0))
+      vs[3] = obj.matrix_world @ Vector((-0.5,1,0))
+      indices = [(0,1),(1,2),(1,3)]
+
+      for l in indices:
+      #{
+         v0 = vs[l[0]]
+         v1 = vs[l[1]]
+         
+         cv_view_verts += [(v0[0],v0[1],v0[2])]
+         cv_view_verts += [(v1[0],v1[1],v1[2])]
+         cv_view_colours += [(0,1,1,1),(0,1,1,1)]
+      #}
+   #}
+
+   @staticmethod
+   def editor_interface( layout, obj ):
+   #{
+      layout.prop( obj.cv_data, "strp", text="Alias" )
+   #}
 #}
 
+# Classtype 4
+#
+#  Purpose: Tells the game to draw water HERE, at this entity. 
+#
 class classtype_water(Structure):
 #{
    _pack_ = 1
@@ -181,6 +295,10 @@ class classtype_water(Structure):
    #}
 #}
 
+# Classtype 8
+#
+#  Purpose: Defines a route node and links to up to two more nodes
+#
 class classtype_route_node(Structure):
 #{
    _pack_ = 1
@@ -197,12 +315,44 @@ class classtype_route_node(Structure):
       if obj.cv_data.target1 != None: 
          _.target1 = obj.cv_data.target1.cv_data.uid
    #}
+
+   @staticmethod
+   def draw_scene_helpers( obj ):
+   #{
+      global cv_view_verts, cv_view_colours
+
+      sw = Vector((0.4,0.4,0.4,0.2))
+      sw2 = Vector((1.5,0.2,0.2,0.0))
+      if obj.cv_data.target != None:
+         cv_draw_bpath( obj, obj.cv_data.target, sw, sw )
+      if obj.cv_data.target1 != None:
+         cv_draw_bpath( obj, obj.cv_data.target1, sw, sw )
+
+      cv_draw_bhandle( obj,  1.0, (0.8,0.8,0.8,1.0) )
+      cv_draw_bhandle( obj, -1.0, (0.4,0.4,0.4,1.0) )
+
+      p1 = obj.location+ \
+            obj.matrix_world.to_quaternion() @ Vector((0,0,-6+1.5))
+      cv_draw_arrow( obj.location, p1, sw )
+   #}
+
+   @staticmethod
+   def editor_interface( layout, obj ):
+   #{
+      layout.prop( obj.cv_data, "target", text="Left" )
+      layout.prop( obj.cv_data, "target1", text="Right" )
+   #}
 #}
 
+# Classtype 9
+#
+#  Purpose: Defines a route, its 'starting' point, and the colour to use for it
+#
 class classtype_route(Structure):
 #{
    _pack_ = 1
    _fields_ = [("id_start",c_uint32),
+               ("pstr_name",c_uint32),
                ("colour",c_float*3)]
 
    def encode_obj(_, node,node_def):
@@ -213,12 +363,118 @@ class classtype_route(Structure):
       _.colour[0] = obj.cv_data.colour[0]
       _.colour[1] = obj.cv_data.colour[1]
       _.colour[2] = obj.cv_data.colour[2]
+      _.pstr_name = encoder_process_pstr( obj.cv_data.strp )
 
       if obj.cv_data.target != None: 
          _.id_start = obj.cv_data.target.cv_data.uid
    #}
+
+   @staticmethod
+   def draw_scene_helpers( obj ):
+   #{
+      global cv_view_verts, cv_view_colours, cv_view_course_i
+
+      if obj.cv_data.target:
+         cv_draw_arrow( obj.location, obj.cv_data.target.location, [1,1,1,1] )
+      
+      # Tries to simulate how we do it in the game
+      #
+      stack = [None]*64
+      stack_i = [0]*64
+      stack[0] = obj.cv_data.target
+      si = 1
+      loop_complete = False
+
+      while si > 0:
+      #{
+         if stack_i[si-1] == 2:
+         #{
+            si -= 1
+            continue
+
+            if si == 0: # Loop failed to complete
+               break
+         #}
+
+         node = stack[si-1]
+
+         targets = [None,None]
+         targets[0] = node.cv_data.target
+
+         if node.cv_data.classtype == 'classtype_route_node':
+         #{
+            targets[1] = node.cv_data.target1
+         #}
+         
+         nextnode = targets[stack_i[si-1]]
+         stack_i[si-1] += 1
+
+         if nextnode != None: # branch
+         #{
+            if nextnode == stack[0]: # Loop completed
+            #{
+               loop_complete = True
+               break
+            #}
+
+            valid=True
+            for sj in range(si):
+            #{
+               if stack[sj] == nextnode: # invalidated path
+               #{
+                  valid=False
+                  break
+               #}
+            #}
+
+            if valid:
+            #{
+               stack_i[si] = 0
+               stack[si] = nextnode
+               si += 1
+               continue
+            #}
+         #}
+      #}
+
+      if loop_complete:
+      #{
+         cc = Vector((obj.cv_data.colour[0],\
+                      obj.cv_data.colour[1],\
+                      obj.cv_data.colour[2],\
+                      1.0))
+
+         for sj in range(si):
+         #{
+            sk = (sj+1)%si
+
+            if stack[sj].cv_data.classtype == 'classtype_gate' and \
+               stack[sk].cv_data.classtype == 'classtype_gate':
+            #{
+               dist = (stack[sj].location-stack[sk].location).magnitude
+               cv_draw_sbpath( stack[sj], stack[sk], cc*0.4, cc, dist, dist )
+            #}
+            else:
+               cv_draw_bpath( stack[sj], stack[sk], cc, cc )
+         #}
+
+         cv_view_course_i += 1
+      #}
+   #}
+
+   @staticmethod
+   def editor_interface( layout, obj ):
+   #{
+      layout.prop( obj.cv_data, "target", text="'Start' from" )
+      layout.prop( obj.cv_data, "colour" )
+      layout.prop( obj.cv_data, "strp", text="Name" )
+   #}
 #}
 
+# Classtype 12
+#
+#  Purpose: links an mesh node to a type 11
+#
 class classtype_skin(Structure):
 #{
    _pack_ = 1
@@ -233,6 +489,10 @@ class classtype_skin(Structure):
    #}
 #}
 
+# Classtype 11
+#
+#  Purpose: defines the allocation requirements for a skeleton
+#
 class classtype_skeleton(Structure):
 #{
    _pack_ = 1
@@ -254,6 +514,11 @@ class classtype_skeleton(Structure):
    #}
 #}
 
+
+# Classtype 10
+#
+#  Purpose: intrinsic bone type, stores collision information and limits too
+#
 class classtype_bone(Structure):
 #{
    _pack_ = 1
@@ -306,21 +571,127 @@ class classtype_bone(Structure):
    #}
 #}
 
+# Classtype 100
+#
+#  Purpose: sends a signal to another entity
+#
+class classtype_trigger(Structure):
+#{
+   _pack_ = 1
+   _fields_ = [("target",c_uint32)]
+
+   def encode_obj(_, node,node_def ):
+   #{
+      node.classtype = 100
+      if node_def['obj'].cv_data.target:
+         _.target = node_def['obj'].cv_data.target.cv_data.uid
+   #}
+
+   @staticmethod
+   def draw_scene_helpers( obj ):
+   #{
+      global cv_view_verts, cv_view_colours
+      cv_draw_ucube( obj.matrix_world, [0,1,0,1] )
+
+      if obj.cv_data.target:
+         cv_draw_arrow( obj.location, obj.cv_data.target.location, [1,1,1,1] )
+   #}
+
+   @staticmethod
+   def editor_interface( layout, obj ):
+   #{
+      layout.prop( obj.cv_data, "target", text="Triggers" )
+   #}
+#}
 
-# TO BE REPLACED
+# Classtype 101
+# 
+#  Purpose: Gives the player an achievement.
+#           No cheating! You shouldn't use this entity anyway, since only ME can
+#           add achievements to the steam ;)
 #
-class classtype_achievement_box(Structure):
+class classtype_logic_achievement(Structure):
 #{
    _pack_ = 1
-   _fields_ = [("pstr_name",c_uint32),
-               ("trigger",c_uint32)]
+   _fields_ = [("pstr_name",c_uint32)]
 
    def encode_obj(_, node,node_def ):
    #{
-      node.classtype = 0
+      node.classtype = 101
+      _.pstr_name = encoder_process_pstr( node_def['obj'].cv_data.strp )
+   #}
+
+   @staticmethod
+   def editor_interface( layout, obj ):
+   #{
+      layout.prop( obj.cv_data, "strp", text="Achievement ID" )
    #}
 #}
 
+# Classtype 102
+#
+#  Purpose: sends a signal to another entity
+#
+class classtype_logic_relay(Structure):
+#{
+   _pack_ = 1
+   _fields_ = [("targets",c_uint32*4)]
+
+   def encode_obj(_, node,node_def ):
+   #{
+      node.classtype = 102
+      obj = node_def['obj']
+      if obj.cv_data.target:
+         _.targets[0] = obj.cv_data.target.cv_data.uid
+      if obj.cv_data.target1:
+         _.targets[1] = obj.cv_data.target1.cv_data.uid
+      if obj.cv_data.target2:
+         _.targets[2] = obj.cv_data.target2.cv_data.uid
+      if obj.cv_data.target3:
+         _.targets[3] = obj.cv_data.target3.cv_data.uid
+   #}
+
+   @staticmethod
+   def draw_scene_helpers( obj ):
+   #{
+      global cv_view_verts, cv_view_colours
+
+      if obj.cv_data.target:
+         cv_draw_arrow( obj.location, obj.cv_data.target.location, [1,1,1,1] )
+      if obj.cv_data.target1:
+         cv_draw_arrow( obj.location, obj.cv_data.target1.location, [1,1,1,1] )
+      if obj.cv_data.target2:
+         cv_draw_arrow( obj.location, obj.cv_data.target2.location, [1,1,1,1] )
+      if obj.cv_data.target3:
+         cv_draw_arrow( obj.location, obj.cv_data.target3.location, [1,1,1,1] )
+   #}
+
+   @staticmethod
+   def editor_interface( layout, obj ):
+   #{
+      layout.prop( obj.cv_data, "target", text="Triggers" )
+      layout.prop( obj.cv_data, "target1", text="Triggers" )
+      layout.prop( obj.cv_data, "target2", text="Triggers" )
+      layout.prop( obj.cv_data, "target3", text="Triggers" )
+   #}
+#}
+
+# Classtype 14
+#
+#  Purpose: Plays some audio (44100hz .ogg vorbis only)
+#           NOTE: There is a 32mb limit on the audio buffer, world audio is
+#                 decompressed and stored in signed 16 bit integers (2 bytes)
+#                 per sample.
+#
+#   volume: not used if has 3D flag
+#    flags: 
+#           AUDIO_FLAG_LOOP        0x1
+#           AUDIO_FLAG_ONESHOT     0x2  (DONT USE THIS, it breaks semaphores)
+#           AUDIO_FLAG_SPACIAL_3D  0x4  (Probably what you want)
+#           AUDIO_FLAG_AUTO_START  0x8  (Play when the world starts)
+#           ......
+#           the rest are just internal flags, only use the above 3.
+#
 class classtype_audio(Structure):
 #{
    _pack_ = 1
@@ -335,29 +706,45 @@ class classtype_audio(Structure):
       obj = node_def['obj']
 
       _.pstr_file = encoder_process_pstr( obj.cv_data.strp )
-      _.flags = obj.cv_data.intp
+
+      flags = 0x00
+      if obj.cv_data.bp0: flags |= 0x1
+      if obj.cv_data.bp1: flags |= 0x4
+      if obj.cv_data.bp2: flags |= 0x8
+
+      _.flags = flags
       _.volume = obj.cv_data.fltp
    #}
    
    @staticmethod
-   def editor_interface(yada):
+   def editor_interface( layout, obj ):
    #{
-      pass
+      layout.prop( obj.cv_data, "strp" )
+
+      layout.prop( obj.cv_data, "bp0", text = "Looping" )
+      layout.prop( obj.cv_data, "bp1", text = "3D Audio" )
+      layout.prop( obj.cv_data, "bp2", text = "Auto Start" )
    #}
 
    @staticmethod
-   def draw_scene_helpers(yada):
+   def draw_scene_helpers( obj ):
    #{
-      pass
+      global cv_view_verts, cv_view_colours
+
+      cv_draw_sphere( obj.location, obj.scale[0], [1,1,0,1] )
    #}
 #}
 
+# ---------------------------------------------------------------------------- #
+#                                                                              #
+#                                Compiler section                              #
+#                                                                              #
+# ---------------------------------------------------------------------------- #
 
 # Current encoder state
 #
 g_encoder = None
 
-
 # Reset encoder
 #
 def encoder_init():
@@ -585,7 +972,6 @@ def encoder_build_scene_graph( collection ):
             for b in n.data.bones:
                if not b.parent:
                   _extendb( tree, b, d+1 )
-            #}
          #}
          
          # Recurse into children of this object
@@ -1279,459 +1665,439 @@ def write_model(collection_name):
    print( F"Completed {collection_name}.mdl" )
 #}
 
-
-# Clicky clicky GUI
-# ------------------------------------------------------------------------------
+# ---------------------------------------------------------------------------- #
+#                                                                              #
+#                                 GUI section                                  #
+#                                                                              #
+# ---------------------------------------------------------------------------- #
 
 cv_view_draw_handler = None
 cv_view_shader = gpu.shader.from_builtin('3D_SMOOTH_COLOR')
+cv_view_verts = []
+cv_view_colours = []
+cv_view_course_i = 0
 
-def cv_draw():
-   global cv_view_shader
-   cv_view_shader.bind()
-   gpu.state.depth_mask_set(False)
-   gpu.state.line_width_set(2.0)
-   gpu.state.face_culling_set('BACK')
-   gpu.state.depth_test_set('LESS')
-   gpu.state.blend_set('NONE')
-
-   verts = []
-   colours = []
-
-   #def drawbezier(p0,h0,p1,h1,c0,c1):
-   #   nonlocal verts, colours
-
-   #   verts += [p0]
-   #   verts += [h0]
-   #   colours += [(0.5,0.5,0.5,1.0),(0.5,0.5,0.5,1)]
-   #   verts += [p1]
-   #   verts += [h1]
-   #   colours += [(1.0,1.0,1,1),(1,1,1,1)]
-   #   
-   #   last = p0
-   #   for i in range(10):
-   #      t = (i+1)/10
-   #      a0 = 1-t
-
-   #      tt = t*t
-   #      ttt = tt*t
-   #      p=ttt*p1+(3*tt-3*ttt)*h1+(3*ttt-6*tt+3*t)*h0+(3*tt-ttt-3*t+1)*p0
-   #      verts += [(last[0],last[1],last[2])]
-   #      verts += [(p[0],p[1],p[2])]
-   #      colours += [c0*a0+c1*(1-a0),c0*a0+c1*(1-a0)]
-   #      last = p
-
-   course_count = 0
-
-   def drawbhandle(obj, direction, colour):
-      nonlocal verts, colours
-      p0 = obj.location
-      h0 = obj.matrix_world @ Vector((0,direction,0))
-      verts += [p0]
-      verts += [h0]
-      colours += [colour,colour]
-
-   def drawbezier(p0,h0,p1,h1,c0,c1):
-      nonlocal verts, colours
-
-      last = p0
-      for i in range(10):
-         t = (i+1)/10
-         a0 = 1-t
-
-         tt = t*t
-         ttt = tt*t
-         p=ttt*p1+(3*tt-3*ttt)*h1+(3*ttt-6*tt+3*t)*h0+(3*tt-ttt-3*t+1)*p0
-         verts += [(last[0],last[1],last[2])]
-         verts += [(p[0],p[1],p[2])]
-         colours += [c0*a0+c1*(1-a0),c0*a0+c1*(1-a0)]
-         last = p
-
-   def drawsbpath(o0,o1,c0,c1,s0,s1):
-      nonlocal course_count
-      
-      offs = ((course_count % 2)*2-1) * course_count * 0.02
-
-      p0 = o0.matrix_world @ Vector((offs,  0,0))
-      h0 = o0.matrix_world @ Vector((offs, s0,0))
-      p1 = o1.matrix_world @ Vector((offs,  0,0))
-      h1 = o1.matrix_world @ Vector((offs,-s1,0))
-      drawbezier(p0,h0,p1,h1,c0,c1)
-
-   def drawbpath(o0,o1,c0,c1):
-      drawsbpath(o0,o1,c0,c1,1.0,1.0)
+# Draw axis alligned sphere at position with radius
+#
+def cv_draw_sphere( pos, radius, colour ):
+#{
+   global cv_view_verts, cv_view_colours
+   
+   ly = pos + Vector((0,0,radius))
+   lx = pos + Vector((0,radius,0))
+   lz = pos + Vector((0,0,radius))
+   
+   pi = 3.14159265358979323846264
 
-   def drawbline(p0,p1,c0,c1):
-      nonlocal verts, colours
-      verts += [p0,p1]
-      colours += [c0,c1]
+   for i in range(16):
+   #{
+      t = ((i+1.0) * 1.0/16.0) * pi * 2.0
+      s = math.sin(t)
+      c = math.cos(t)
 
-   for obj in bpy.context.collection.objects:
-      if obj.type == 'ARMATURE':
-         for bone in obj.data.bones:
-            if bone.cv_data.collider and obj.data.pose_position == 'REST':
-               c = bone.head_local
-               a = bone.cv_data.v0
-               b = bone.cv_data.v1
-               
-               vs = [None]*8
-               vs[0]=obj.matrix_world@Vector((c[0]+a[0],c[1]+a[1],c[2]+a[2]))
-               vs[1]=obj.matrix_world@Vector((c[0]+a[0],c[1]+b[1],c[2]+a[2]))
-               vs[2]=obj.matrix_world@Vector((c[0]+b[0],c[1]+b[1],c[2]+a[2]))
-               vs[3]=obj.matrix_world@Vector((c[0]+b[0],c[1]+a[1],c[2]+a[2]))
-               vs[4]=obj.matrix_world@Vector((c[0]+a[0],c[1]+a[1],c[2]+b[2]))
-               vs[5]=obj.matrix_world@Vector((c[0]+a[0],c[1]+b[1],c[2]+b[2]))
-               vs[6]=obj.matrix_world@Vector((c[0]+b[0],c[1]+b[1],c[2]+b[2]))
-               vs[7]=obj.matrix_world@Vector((c[0]+b[0],c[1]+a[1],c[2]+b[2]))
-
-               indices = [(0,1),(1,2),(2,3),(3,0),(4,5),(5,6),(6,7),(7,4),\
-                          (0,4),(1,5),(2,6),(3,7)]
-
-               for l in indices:
-                  v0 = vs[l[0]]
-                  v1 = vs[l[1]]
-                  verts += [(v0[0],v0[1],v0[2])]
-                  verts += [(v1[0],v1[1],v1[2])]
-                  colours += [(0.5,0.5,0.5,0.5),(0.5,0.5,0.5,0.5)]
-
-               center=obj.matrix_world@c
-               
-               def _angle_lim( major, minor, amin, amax, colour ):
-                  nonlocal verts, colours
-                  f = 0.05
-                  ay = major*f
-                  ax = minor*f
-
-                  for x in range(16):
-                     t0 = x/16
-                     t1 = (x+1)/16
-                     a0 = amin*(1.0-t0)+amax*t0
-                     a1 = amin*(1.0-t1)+amax*t1
-
-                     p0 = c + major*f*math.cos(a0) + minor*f*math.sin(a0)
-                     p1 = c + major*f*math.cos(a1) + minor*f*math.sin(a1)
-
-                     p0=obj.matrix_world @ p0
-                     p1=obj.matrix_world @ p1
-                     verts += [p0,p1]
-                     colours += [colour,colour]
-
-                     if x == 0:
-                        verts += [p0,c]
-                        colours += [colour,colour]
-                     if x == 15:
-                        verts += [p1,c]
-                        colours += [colour,colour]
-
-                  verts += [c+major*1.2*f,c+major*f*0.8]
-                  colours += [colour,colour]
-                  
-               if bone.cv_data.con0:
-                  _angle_lim( Vector((0,1,0)),Vector((0,0,1)), \
-                              bone.cv_data.mins[0], bone.cv_data.maxs[0], \
-                              (1,0,0,1))
-                  _angle_lim( Vector((0,0,1)),Vector((1,0,0)), \
-                              bone.cv_data.mins[1], bone.cv_data.maxs[1], \
-                              (0,1,0,1))
-                  _angle_lim( Vector((1,0,0)),Vector((0,1,0)), \
-                              bone.cv_data.mins[2], bone.cv_data.maxs[2], \
-                              (0,0,1,1))
-               
+      py = pos + Vector((s*radius,0.0,c*radius))
+      px = pos + Vector((s*radius,c*radius,0.0))
+      pz = pos + Vector((0.0,s*radius,c*radius))
 
-      if obj.cv_data.classtype == 'classtype_gate':
-         if obj.type == 'MESH':
-            dims = obj.data.cv_data.v0
-         else:
-            dims = obj.cv_data.v0
+      cv_view_verts += [ px, lx ]
+      cv_view_verts += [ py, ly ]
+      cv_view_verts += [ pz, lz ]
 
-         vs = [None]*9
-         c = Vector((0,0,dims[2]))
+      cv_view_colours += [ colour, colour, colour, colour, colour, colour ]
 
-         vs[0] = obj.matrix_world @ Vector((-dims[0],0.0,-dims[1]+dims[2]))
-         vs[1] = obj.matrix_world @ Vector((-dims[0],0.0, dims[1]+dims[2]))
-         vs[2] = obj.matrix_world @ Vector(( dims[0],0.0, dims[1]+dims[2]))
-         vs[3] = obj.matrix_world @ Vector(( dims[0],0.0,-dims[1]+dims[2]))
-         vs[4] = obj.matrix_world @ (c+Vector((-1,0,-2)))
-         vs[5] = obj.matrix_world @ (c+Vector((-1,0, 2)))
-         vs[6] = obj.matrix_world @ (c+Vector(( 1,0, 2)))
-         vs[7] = obj.matrix_world @ (c+Vector((-1,0, 0)))
-         vs[8] = obj.matrix_world @ (c+Vector(( 1,0, 0)))
+      ly = py
+      lx = px
+      lz = pz
+   #}
+   cv_draw_lines()
+#}
 
-         indices = [(0,1),(1,2),(2,3),(3,0),(4,5),(5,6),(7,8)]
+# Draw transformed -1 -> 1 cube
+#
+def cv_draw_ucube( transform, colour ):
+#{
+   global cv_view_verts, cv_view_colours
 
-         for l in indices:
-            v0 = vs[l[0]]
-            v1 = vs[l[1]]
-            verts += [(v0[0],v0[1],v0[2])]
-            verts += [(v1[0],v1[1],v1[2])]
-            colours += [(1,1,0,1),(1,1,0,1)]
-
-         sw = (0.4,0.4,0.4,0.2)
-         if obj.cv_data.target != None:
-            drawbline( obj.location, obj.cv_data.target.location, sw,sw )
-
-      elif obj.cv_data.classtype == 'classtype_route_node':
-         sw = Vector((0.4,0.4,0.4,0.2))
-         sw2 = Vector((1.5,0.2,0.2,0.0))
-         if obj.cv_data.target != None:
-            drawbpath( obj, obj.cv_data.target, sw, sw )
-         if obj.cv_data.target1 != None:
-            drawbpath( obj, obj.cv_data.target1, sw, sw )
-
-         drawbhandle( obj,  1.0, (0.8,0.8,0.8,1.0) )
-         drawbhandle( obj, -1.0, (0.4,0.4,0.4,1.0) )
-
-         p1 = obj.location+ \
-               obj.matrix_world.to_quaternion() @ Vector((0,0,-6+1.5))
-         drawbline( obj.location, p1, sw,sw2 )
-
-      elif obj.cv_data.classtype == 'classtype_achievement_box':
-         a = Vector((-1,-1,-1))
-         b = Vector((1,1,1))
-         
-         vs = [None]*8
-         vs[0] = obj.matrix_world @ Vector((a[0], a[1], a[2]))
-         vs[1] = obj.matrix_world @ Vector((a[0], b[1], a[2]))
-         vs[2] = obj.matrix_world @ Vector((b[0], b[1], a[2]))
-         vs[3] = obj.matrix_world @ Vector((b[0], a[1], a[2]))
-         vs[4] = obj.matrix_world @ Vector((a[0], a[1], b[2]))
-         vs[5] = obj.matrix_world @ Vector((a[0], b[1], b[2]))
-         vs[6] = obj.matrix_world @ Vector((b[0], b[1], b[2]))
-         vs[7] = obj.matrix_world @ Vector((b[0], a[1], b[2]))
+   a = Vector((-1,-1,-1))
+   b = Vector((1,1,1))
+   
+   vs = [None]*8
+   vs[0] = transform @ Vector((a[0], a[1], a[2]))
+   vs[1] = transform @ Vector((a[0], b[1], a[2]))
+   vs[2] = transform @ Vector((b[0], b[1], a[2]))
+   vs[3] = transform @ Vector((b[0], a[1], a[2]))
+   vs[4] = transform @ Vector((a[0], a[1], b[2]))
+   vs[5] = transform @ Vector((a[0], b[1], b[2]))
+   vs[6] = transform @ Vector((b[0], b[1], b[2]))
+   vs[7] = transform @ Vector((b[0], a[1], b[2]))
+
+   indices = [(0,1),(1,2),(2,3),(3,0),(4,5),(5,6),(6,7),(7,4),\
+              (0,4),(1,5),(2,6),(3,7)]
+
+   for l in indices:
+   #{
+      v0 = vs[l[0]]
+      v1 = vs[l[1]]
+      cv_view_verts += [(v0[0],v0[1],v0[2])]
+      cv_view_verts += [(v1[0],v1[1],v1[2])]
+      cv_view_colours += [(0,1,0,1),(0,1,0,1)]
+   #}
+   cv_draw_lines()
+#}
 
-         indices = [(0,1),(1,2),(2,3),(3,0),(4,5),(5,6),(6,7),(7,4),\
-                    (0,4),(1,5),(2,6),(3,7)]
+# Draw line with colour
+#
+def cv_draw_line( p0, p1, colour ):
+#{
+   global cv_view_verts, cv_view_colours
 
-         for l in indices:
-            v0 = vs[l[0]]
-            v1 = vs[l[1]]
-            verts += [(v0[0],v0[1],v0[2])]
-            verts += [(v1[0],v1[1],v1[2])]
-            colours += [(0,1,0,1),(0,1,0,1)]
-
-         if obj.cv_data.target != None:
-            vs = [None]*2
-            vs[0] = obj.location
-            vs[1] = obj.cv_data.target.location
-            indices = [(0,1)]
-            for l in indices:
-               v0 = vs[l[0]]
-               v1 = vs[l[1]]
-               verts += [(v0[0],v0[1],v0[2])]
-               verts += [(v1[0],v1[1],v1[2])]
-               colours += [(0,1,1,1),(0,1,1,1)]
-
-
-      elif obj.cv_data.classtype == 'classtype_block':
-         a = obj.data.cv_data.v0
-         b = obj.data.cv_data.v1
-         
-         vs = [None]*8
-         vs[0] = obj.matrix_world @ Vector((a[0], a[1], a[2]))
-         vs[1] = obj.matrix_world @ Vector((a[0], b[1], a[2]))
-         vs[2] = obj.matrix_world @ Vector((b[0], b[1], a[2]))
-         vs[3] = obj.matrix_world @ Vector((b[0], a[1], a[2]))
-         vs[4] = obj.matrix_world @ Vector((a[0], a[1], b[2]))
-         vs[5] = obj.matrix_world @ Vector((a[0], b[1], b[2]))
-         vs[6] = obj.matrix_world @ Vector((b[0], b[1], b[2]))
-         vs[7] = obj.matrix_world @ Vector((b[0], a[1], b[2]))
+   cv_view_verts += [p0,p1]
+   cv_view_colours += [colour, colour]
+   cv_draw_lines()
+#}
 
-         indices = [(0,1),(1,2),(2,3),(3,0),(4,5),(5,6),(6,7),(7,4),\
-                    (0,4),(1,5),(2,6),(3,7)]
+# Draw line with colour(s)
+#
+def cv_draw_line2( p0, p1, c0, c1 ):
+#{
+   global cv_view_verts, cv_view_colours
 
-         for l in indices:
-            v0 = vs[l[0]]
-            v1 = vs[l[1]]
-            verts += [(v0[0],v0[1],v0[2])]
-            verts += [(v1[0],v1[1],v1[2])]
-            colours += [(1,1,0,1),(1,1,0,1)]
-
-      elif obj.cv_data.classtype == 'classtype_capsule':
-         h = obj.data.cv_data.v0[0]
-         r = obj.data.cv_data.v0[1]
-
-         vs = [None]*10
-         vs[0] = obj.matrix_world @ Vector((0.0,0.0, h*0.5  ))
-         vs[1] = obj.matrix_world @ Vector((0.0,0.0,-h*0.5  ))
-         vs[2] = obj.matrix_world @ Vector((  r,0.0, h*0.5-r))
-         vs[3] = obj.matrix_world @ Vector(( -r,0.0, h*0.5-r))
-         vs[4] = obj.matrix_world @ Vector((  r,0.0,-h*0.5+r))
-         vs[5] = obj.matrix_world @ Vector(( -r,0.0,-h*0.5+r))
-         vs[6] = obj.matrix_world @ Vector((0.0, r , h*0.5-r))
-         vs[7] = obj.matrix_world @ Vector((0.0,-r , h*0.5-r))
-         vs[8] = obj.matrix_world @ Vector((0.0, r ,-h*0.5+r))
-         vs[9] = obj.matrix_world @ Vector((0.0,-r ,-h*0.5+r))
-
-         indices = [(0,1),(2,3),(4,5),(6,7),(8,9)]
+   cv_view_verts += [p0,p1]
+   cv_view_colours += [c0,c1]
+   cv_draw_lines()
+#}
 
-         for l in indices:
-            v0 = vs[l[0]]
-            v1 = vs[l[1]]
-            verts += [(v0[0],v0[1],v0[2])]
-            verts += [(v1[0],v1[1],v1[2])]
-            colours += [(0.5,1,0,1),(0.5,1,0,1)]
-
-      elif obj.cv_data.classtype == 'classtype_spawn':
-         vs = [None]*4
-         vs[0] = obj.matrix_world @ Vector((0,0,0))
-         vs[1] = obj.matrix_world @ Vector((0,2,0))
-         vs[2] = obj.matrix_world @ Vector((0.5,1,0))
-         vs[3] = obj.matrix_world @ Vector((-0.5,1,0))
-         indices = [(0,1),(1,2),(1,3)]
-         for l in indices:
-            v0 = vs[l[0]]
-            v1 = vs[l[1]]
-            verts += [(v0[0],v0[1],v0[2])]
-            verts += [(v1[0],v1[1],v1[2])]
-            colours += [(0,1,1,1),(0,1,1,1)]
-      
-      elif obj.cv_data.classtype == 'classtype_route':
-         vs = [None]*2
-         vs[0] = obj.location
-         vs[1] = obj.cv_data.target.location
-         indices = [(0,1)]
-         for l in indices:
-            v0 = vs[l[0]]
-            v1 = vs[l[1]]
-            verts += [(v0[0],v0[1],v0[2])]
-            verts += [(v1[0],v1[1],v1[2])]
-            colours += [(0,1,1,1),(0,1,1,1)]
-
-         stack = [None]*64
-         stack_i = [0]*64
-         stack[0] = obj.cv_data.target
-         si = 1
-         loop_complete = False
-
-         while si > 0:
-            if stack_i[si-1] == 2:
-               si -= 1
-               continue
+# Just the tx because we dont really need ty for this app
+#
+def cv_tangent_basis_tx( n, tx ):
+#{
+   if abs( n[0] ) >= 0.57735027:
+   #{
+      tx[0] =  n[1]
+      tx[1] = -n[0]
+      tx[2] =  0.0
+   #}
+   else:
+   #{
+      tx[0] =  0.0
+      tx[1] =  n[2]
+      tx[2] = -n[1]
+   #}
 
-               if si == 0: # Loop failed to complete
-                  break
+   tx.normalize()
+#}
 
-            node = stack[si-1]
+# Draw coloured arrow
+#
+def cv_draw_arrow( p0, p1, c0 ):
+#{
+   global cv_view_verts, cv_view_colours
 
-            targets = [None,None]
-            targets[0] = node.cv_data.target
+   n = p1-p0
+   midpt = p0 + n*0.5
+   n.normalize()
 
-            if node.cv_data.classtype == 'classtype_route_node':
-               targets[1] = node.cv_data.target1
-            
-            nextnode = targets[stack_i[si-1]]
-            stack_i[si-1] += 1
+   tx = Vector((1,0,0))
+   cv_tangent_basis_tx( n, tx )
+   
+   cv_view_verts += [p0,p1, midpt+(tx-n)*0.15,midpt, midpt+(-tx-n)*0.15,midpt ]
+   cv_view_colours += [c0,c0,c0,c0,c0,c0]
+   cv_draw_lines()
+#}
 
-            if nextnode != None: # branch
-               if nextnode == stack[0]: # Loop completed
-                  loop_complete = True
-                  break
+# Drawhandles of a bezier control point
+#
+def cv_draw_bhandle( obj, direction, colour ):
+#{
+   global cv_view_verts, cv_view_colours
 
-               valid=True
-               for sj in range(si):
-                  if stack[sj] == nextnode: # invalidated path
-                     valid=False
-                     break
+   p0 = obj.location
+   h0 = obj.matrix_world @ Vector((0,direction,0))
 
-               if valid:
-                  stack_i[si] = 0
-                  stack[si] = nextnode
-                  si += 1
-                  continue
+   cv_view_verts += [p0]
+   cv_view_verts += [h0]
+   cv_view_colours += [colour,colour]
+   cv_draw_lines()
+#}
 
-         if loop_complete:
-            cc = Vector((obj.cv_data.colour[0],\
-                         obj.cv_data.colour[1],\
-                         obj.cv_data.colour[2],\
-                         1.0))
+# Draw a bezier curve (at fixed resolution 10)
+#
+def cv_draw_bezier( p0,h0,p1,h1,c0,c1 ):
+#{
+   global cv_view_verts, cv_view_colours
 
-            for sj in range(si):
-               sk = (sj+1)%si
+   last = p0
+   for i in range(10):
+   #{
+      t = (i+1)/10
+      a0 = 1-t
 
-               if stack[sj].cv_data.classtype == 'classtype_gate' and \
-                  stack[sk].cv_data.classtype == 'classtype_gate':
-                  dist = (stack[sj].location-stack[sk].location).magnitude
-                  drawsbpath( stack[sj], stack[sk], cc*0.4, cc, dist, dist )
+      tt = t*t
+      ttt = tt*t
+      p=ttt*p1+(3*tt-3*ttt)*h1+(3*ttt-6*tt+3*t)*h0+(3*tt-ttt-3*t+1)*p0
 
-               else:
-                  drawbpath( stack[sj], stack[sk], cc, cc )
+      cv_view_verts += [(last[0],last[1],last[2])]
+      cv_view_verts += [(p[0],p[1],p[2])]
+      cv_view_colours += [c0*a0+c1*(1-a0),c0*a0+c1*(1-a0)]
 
-            course_count += 1
+      last = p
+   #}
+   cv_draw_lines()
+#}
 
-      elif obj.cv_data.classtype == 'classtype_car_path':
-         v0 = obj.matrix_world.to_quaternion() @ Vector((0,1,0))
-         c0 = Vector((v0.x*0.5+0.5, v0.y*0.5+0.5, 0.0, 1.0))
-         drawbhandle( obj, 1.0, (0.9,0.9,0.9,1.0) )
+# I think this one extends the handles of the bezier otwards......
+#
+def cv_draw_sbpath( o0,o1,c0,c1,s0,s1 ):
+#{
+   global cv_view_course_i
+   
+   offs = ((cv_view_course_i % 2)*2-1) * cv_view_course_i * 0.02
 
-         if obj.cv_data.target != None:
-            v1 = obj.cv_data.target.matrix_world.to_quaternion()@Vector((0,1,0))
-            c1 = Vector((v1.x*0.5+0.5, v1.y*0.5+0.5, 0.0, 1.0))
+   p0 = o0.matrix_world @ Vector((offs,  0,0))
+   h0 = o0.matrix_world @ Vector((offs, s0,0))
+   p1 = o1.matrix_world @ Vector((offs,  0,0))
+   h1 = o1.matrix_world @ Vector((offs,-s1,0))
 
-            drawbhandle( obj.cv_data.target, -1.0, (0.5,0.5,0.5,1.0) )
-            drawbpath( obj, obj.cv_data.target, c0, c1 )
+   cv_draw_bezier( p0,h0,p1,h1,c0,c1 )
+   cv_draw_lines()
+#}
 
-         if obj.cv_data.target1 != None:
-            v1 = obj.cv_data.target1.matrix_world.to_quaternion()@Vector((0,1,0))
-            c1 = Vector((v1.x*0.5+0.5, v1.y*0.5+0.5, 0.0, 1.0))
+# Flush the lines buffers. This is called often because god help you if you want
+# to do fixed, fast buffers in this catastrophic programming language.
+#
+def cv_draw_lines():
+#{
+   global cv_view_shader, cv_view_verts, cv_view_colours
 
-            drawbhandle( obj.cv_data.target1, -1.0, (0.5,0.5,0.5,1.0) )
-            drawbpath( obj, obj.cv_data.target1, c0, c1 )
+   if len(cv_view_verts) < 2:
+      return
 
    lines = batch_for_shader(\
          cv_view_shader, 'LINES', \
-         { "pos":verts, "color":colours })
+         { "pos":cv_view_verts, "color":cv_view_colours })
 
    lines.draw( cv_view_shader )
 
+   cv_view_verts = []
+   cv_view_colours = []
+#}
+
+# I dont remember what this does exactly
+#
+def cv_draw_bpath( o0,o1,c0,c1 ):
+#{
+   cv_draw_sbpath( o0,o1,c0,c1,1.0,1.0 )
+#}
+
+# Semi circle to show the limit. and some lines
+#
+def draw_limit( obj, center, major, minor, amin, amax, colour ):
+#{
+   global cv_view_verts, cv_view_colours
+   f = 0.05
+   ay = major*f
+   ax = minor*f
+
+   for x in range(16):
+   #{
+      t0 = x/16
+      t1 = (x+1)/16
+      a0 = amin*(1.0-t0)+amax*t0
+      a1 = amin*(1.0-t1)+amax*t1
+
+      p0 = center + major*f*math.cos(a0) + minor*f*math.sin(a0)
+      p1 = center + major*f*math.cos(a1) + minor*f*math.sin(a1)
+
+      p0=obj.matrix_world @ p0
+      p1=obj.matrix_world @ p1
+      cv_view_verts += [p0,p1]
+      cv_view_colours += [colour,colour]
+
+      if x == 0:
+      #{
+         cv_view_verts += [p0,center]
+         cv_view_colours += [colour,colour]
+      #}
+      if x == 15:
+      #{
+         cv_view_verts += [p1,center]
+         cv_view_colours += [colour,colour]
+      #}
+   #}
+
+   cv_view_verts += [center+major*1.2*f,center+major*f*0.8]
+   cv_view_colours += [colour,colour]
+
+   cv_draw_lines()
+#}
+
+# Draws constraints and stuff for the skeleton. This isnt documented and wont be
+#
+def draw_skeleton_helpers( obj ):
+#{
+   global cv_view_verts, cv_view_colours
+
+   for bone in obj.data.bones:
+   #{
+      if bone.cv_data.collider and (obj.data.pose_position == 'REST'):
+      #{
+         c = bone.head_local
+         a = bone.cv_data.v0
+         b = bone.cv_data.v1
+         
+         vs = [None]*8
+         vs[0]=obj.matrix_world@Vector((c[0]+a[0],c[1]+a[1],c[2]+a[2]))
+         vs[1]=obj.matrix_world@Vector((c[0]+a[0],c[1]+b[1],c[2]+a[2]))
+         vs[2]=obj.matrix_world@Vector((c[0]+b[0],c[1]+b[1],c[2]+a[2]))
+         vs[3]=obj.matrix_world@Vector((c[0]+b[0],c[1]+a[1],c[2]+a[2]))
+         vs[4]=obj.matrix_world@Vector((c[0]+a[0],c[1]+a[1],c[2]+b[2]))
+         vs[5]=obj.matrix_world@Vector((c[0]+a[0],c[1]+b[1],c[2]+b[2]))
+         vs[6]=obj.matrix_world@Vector((c[0]+b[0],c[1]+b[1],c[2]+b[2]))
+         vs[7]=obj.matrix_world@Vector((c[0]+b[0],c[1]+a[1],c[2]+b[2]))
+
+         indices = [(0,1),(1,2),(2,3),(3,0),(4,5),(5,6),(6,7),(7,4),\
+                    (0,4),(1,5),(2,6),(3,7)]
+
+         for l in indices:
+         #{
+            v0 = vs[l[0]]
+            v1 = vs[l[1]]
+
+            cv_view_verts += [(v0[0],v0[1],v0[2])]
+            cv_view_verts += [(v1[0],v1[1],v1[2])]
+            cv_view_colours += [(0.5,0.5,0.5,0.5),(0.5,0.5,0.5,0.5)]
+         #}
+
+         center = obj.matrix_world @ c
+         if bone.cv_data.con0:
+         #{
+            draw_limit( obj, c, Vector((0,1,0)),Vector((0,0,1)), \
+                        bone.cv_data.mins[0], bone.cv_data.maxs[0], \
+                        (1,0,0,1))
+            draw_limit( obj, c, Vector((0,0,1)),Vector((1,0,0)), \
+                        bone.cv_data.mins[1], bone.cv_data.maxs[1], \
+                        (0,1,0,1))
+            draw_limit( obj, c, Vector((1,0,0)),Vector((0,1,0)), \
+                        bone.cv_data.mins[2], bone.cv_data.maxs[2], \
+                        (0,0,1,1))
+         #}
+      #}
+   #}
+#}
+
+def cv_draw():
+#{
+   global cv_view_shader
+   global cv_view_verts
+   global cv_view_colours
+   global cv_view_course_i
+
+   cv_view_course_i = 0
+   cv_view_verts = []
+   cv_view_colours = []
+
+   cv_view_shader.bind()
+   gpu.state.depth_mask_set(False)
+   gpu.state.line_width_set(2.0)
+   gpu.state.face_culling_set('BACK')
+   gpu.state.depth_test_set('LESS')
+   gpu.state.blend_set('NONE')
+
+   for obj in bpy.context.collection.objects:
+   #{
+      if obj.type == 'ARMATURE':
+      #{
+         if obj.data.pose_position == 'REST':
+            draw_skeleton_helpers( obj )
+      #}
+      else:
+      #{
+         classtype = obj.cv_data.classtype
+         if (classtype != 'classtype_none') and (classtype in globals()):
+         #{
+            cl = globals()[ classtype ]
+
+            if getattr( cl, "draw_scene_helpers", None ):
+            #{
+               cl.draw_scene_helpers( obj )
+            #}
+         #}
+      #}
+   #}
+
+   cv_draw_lines()
+   return
+#}
+               
+
+# ---------------------------------------------------------------------------- #
+#                                                                              #
+#                                 Blender                                      #
+#                                                                              #
+# ---------------------------------------------------------------------------- #
+
+# Checks whether this object has a classtype assigned. we can only target other
+# classes
 def cv_poll_target(scene, obj):
+#{
    if obj == bpy.context.active_object:
       return False
    if obj.cv_data.classtype == 'classtype_none':
       return False
+
    return True
+#}
 
 class CV_MESH_SETTINGS(bpy.types.PropertyGroup):
+#{
    v0: bpy.props.FloatVectorProperty(name="v0",size=3)
    v1: bpy.props.FloatVectorProperty(name="v1",size=3)
    v2: bpy.props.FloatVectorProperty(name="v2",size=3)
    v3: bpy.props.FloatVectorProperty(name="v3",size=3)
+#}
 
 class CV_OBJ_SETTINGS(bpy.types.PropertyGroup):
+#{
    uid: bpy.props.IntProperty( name="" )
 
    strp: bpy.props.StringProperty( name="strp" )
    intp: bpy.props.IntProperty( name="intp" )
    fltp: bpy.props.FloatProperty( name="fltp" )
+   bp0: bpy.props.BoolProperty( name="bp0" )
+   bp1: bpy.props.BoolProperty( name="bp1" )
+   bp2: bpy.props.BoolProperty( name="bp2" )
+   bp3: bpy.props.BoolProperty( name="bp3" )
 
    target: bpy.props.PointerProperty( type=bpy.types.Object, name="target", \
          poll=cv_poll_target )
    target1: bpy.props.PointerProperty( type=bpy.types.Object, name="target1", \
          poll=cv_poll_target )
+   target2: bpy.props.PointerProperty( type=bpy.types.Object, name="target2", \
+         poll=cv_poll_target )
+   target3: bpy.props.PointerProperty( type=bpy.types.Object, name="target3", \
+         poll=cv_poll_target )
 
-   colour: bpy.props.FloatVectorProperty(name="colour",subtype='COLOR',\
-                                         min=0.0,max=1.0)
+   colour: bpy.props.FloatVectorProperty( name="colour",subtype='COLOR',\
+                                          min=0.0,max=1.0)
 
    classtype: bpy.props.EnumProperty(
       name="Format", 
       items = [
       ('classtype_none', "classtype_none", "", 0),
       ('classtype_gate', "classtype_gate", "", 1),
-      ('classtype_block', "classtype_block", "", 2),
       ('classtype_spawn', "classtype_spawn", "", 3),
       ('classtype_water', "classtype_water", "", 4),
-      ('classtype_car_path', "classtype_car_path", "", 5),
-      ('classtype_INSTANCE', "","", 6 ),
-      ('classtype_capsule', "classtype_capsule", "", 7 ),
       ('classtype_route_node', "classtype_route_node", "", 8 ),
       ('classtype_route', "classtype_route", "", 9 ),
-      ('classtype_bone',"classtype_bone","",10),
-      ('classtype_SKELETON', "","", 11 ),
-      ('classtype_SKIN',"","",12),
-      ('classtype_achievement_box',"classtype_achievement_box","",13),
       ('classtype_audio',"classtype_audio","",14),
+      ('classtype_trigger',"classtype_trigger","",100),
+      ('classtype_logic_achievement',"classtype_logic_achievement","",101),
+      ('classtype_logic_relay',"classtype_logic_relay","",102),
       ])
+#}
 
 class CV_BONE_SETTINGS(bpy.types.PropertyGroup):
+#{
    collider: bpy.props.BoolProperty(name="Collider",default=False)
    v0: bpy.props.FloatVectorProperty(name="v0",size=3)
    v1: bpy.props.FloatVectorProperty(name="v1",size=3)
@@ -1739,8 +2105,10 @@ class CV_BONE_SETTINGS(bpy.types.PropertyGroup):
    con0: bpy.props.BoolProperty(name="Constriant 0",default=False)
    mins: bpy.props.FloatVectorProperty(name="mins",size=3)
    maxs: bpy.props.FloatVectorProperty(name="maxs",size=3)
+#}
 
 class CV_BONE_PANEL(bpy.types.Panel):
+#{
    bl_label="Bone Config"
    bl_idname="SCENE_PT_cv_bone"
    bl_space_type='PROPERTIES'
@@ -1748,6 +2116,7 @@ class CV_BONE_PANEL(bpy.types.Panel):
    bl_context='bone'
 
    def draw(_,context):
+   #{
       active_object = context.active_object
       if active_object == None: return
 
@@ -1762,11 +2131,16 @@ class CV_BONE_PANEL(bpy.types.Panel):
       _.layout.prop( bone.cv_data, "con0" )
       _.layout.prop( bone.cv_data, "mins" )
       _.layout.prop( bone.cv_data, "maxs" )
+   #}
+#}
 
 class CV_SCENE_SETTINGS(bpy.types.PropertyGroup):
+#{
    use_hidden: bpy.props.BoolProperty( name="use hidden", default=False )
+#}
 
 class CV_OBJ_PANEL(bpy.types.Panel):
+#{
    bl_label="Entity Config"
    bl_idname="SCENE_PT_cv_entity"
    bl_space_type='PROPERTIES'
@@ -1774,6 +2148,7 @@ class CV_OBJ_PANEL(bpy.types.Panel):
    bl_context="object"
    
    def draw(_,context):
+   #{
       active_object = bpy.context.active_object
       if active_object == None: return
       if active_object.type == 'ARMATURE':
@@ -1786,43 +2161,22 @@ class CV_OBJ_PANEL(bpy.types.Panel):
 
       _.layout.prop( active_object.cv_data, "classtype" )
 
-      if active_object.cv_data.classtype == 'classtype_gate':
-         _.layout.prop( active_object.cv_data, "target" )
-
-         mesh = active_object.data
-         _.layout.label( text=F"(i) Data is stored in {mesh.name}" )
-         _.layout.prop( mesh.cv_data, "v0" )
-
-      elif active_object.cv_data.classtype == 'classtype_car_path' or \
-           active_object.cv_data.classtype == 'classtype_route_node':
-         _.layout.prop( active_object.cv_data, "target" )
-         _.layout.prop( active_object.cv_data, "target1" )
-
-      elif active_object.cv_data.classtype == 'classtype_route':
-         _.layout.prop( active_object.cv_data, "target" )
-         _.layout.prop( active_object.cv_data, "colour" )
-
-      elif active_object.cv_data.classtype == 'classtype_block':
-         mesh = active_object.data
-
-         _.layout.label( text=F"(i) Data is stored in {mesh.name}" )
-         _.layout.prop( mesh.cv_data, "v0" )
-         _.layout.prop( mesh.cv_data, "v1" )
-         _.layout.prop( mesh.cv_data, "v2" )
-         _.layout.prop( mesh.cv_data, "v3" )
-      elif active_object.cv_data.classtype == 'classtype_capsule':
-         mesh = active_object.data
-         _.layout.label( text=F"(i) Data is stored in {mesh.name}" )
-         _.layout.prop( mesh.cv_data, "v0" )
-      elif active_object.cv_data.classtype == 'classtype_achievement_box':
-         _.layout.prop( active_object.cv_data, "strp" )
-         _.layout.prop( active_object.cv_data, "target" )
-      elif active_object.cv_data.classtype == 'classtype_audio':
-         _.layout.prop( active_object.cv_data, "strp" )
-         _.layout.prop( active_object.cv_data, "intp" )
-         _.layout.prop( active_object.cv_data, "fltp" )
+      classtype = active_object.cv_data.classtype
+
+      if (classtype != 'classtype_none') and (classtype in globals()):
+      #{
+         cl = globals()[ classtype ]
+
+         if getattr( cl, "editor_interface", None ):
+         #{
+            cl.editor_interface( _.layout, active_object )
+         #}
+      #}
+   #}
+#}
 
 class CV_INTERFACE(bpy.types.Panel):
+#{
    bl_idname = "VIEW3D_PT_carve"
    bl_label = "Carve"
    bl_space_type = 'VIEW_3D'
@@ -1830,33 +2184,43 @@ class CV_INTERFACE(bpy.types.Panel):
    bl_category = "Carve"
 
    def draw(_, context):
+   #{
       layout = _.layout
       layout.prop( context.scene.cv_data, "use_hidden")
       layout.operator( "carve.compile_all" )
+   #}
+#}
 
 def test_compile():
+#{
    view_layer = bpy.context.view_layer
    for col in view_layer.layer_collection.children["export"].children:
       if not col.hide_viewport or bpy.context.scene.cv_data.use_hidden:
          write_model( col.name )
+#}
 
 class CV_COMPILE(bpy.types.Operator):
+#{
    bl_idname="carve.compile_all"
    bl_label="Compile All"
 
    def execute(_,context):
+   #{
       test_compile()
       #cProfile.runctx("test_compile()",globals(),locals(),sort=1)
       #for col in bpy.data.collections["export"].children:
       #   write_model( col.name )
 
       return {'FINISHED'}
+   #}
+#}
 
 classes = [CV_OBJ_SETTINGS,CV_OBJ_PANEL,CV_COMPILE,CV_INTERFACE,\
            CV_MESH_SETTINGS, CV_SCENE_SETTINGS, CV_BONE_SETTINGS,\
            CV_BONE_PANEL]
 
 def register():
+#{
    global cv_view_draw_handler
 
    for c in classes:
@@ -1869,11 +2233,14 @@ def register():
 
    cv_view_draw_handler = bpy.types.SpaceView3D.draw_handler_add(\
       cv_draw,(),'WINDOW','POST_VIEW')
+#}
 
 def unregister():
+#{
    global cv_view_draw_handler
 
    for c in classes:
       bpy.utils.unregister_class(c)
 
    bpy.types.SpaceView3D.draw_handler_remove(cv_view_draw_handler,'WINDOW')
+#}