python interface revision
[convexer.git] / __init__.py
index 0e4ddd283edf8fbbdf125f4f38b01793ee1dc2fe..fc20f056977ebaa93d2d186f258cf311167a64ca 100644 (file)
@@ -25,31 +25,135 @@ vmt_param_dynamic_class = None
 
 # libcxr interface (TODO: We can probably automate this)
 # ======================================================
-libcxr = None
+
+class extern():
+   def __init__(_,name,argtypes,restype):
+      _.name = name
+      _.argtypes = argtypes
+      _.restype = restype
+      _.call = None
+
+   def loadfrom(_,so):
+      _.call = getattr(so,_.name)
+      _.call.argtypes = _.argtypes
+
+      if _.restype != None:
+         _.call.restype = _.restype
+
 libc_dlclose = None
 libc_dlclose = cdll.LoadLibrary(None).dlclose
 libc_dlclose.argtypes = [c_void_p]
 
+# Callback ctypes wrapper...
+libcxr = None
 c_libcxr_log_callback = None
 c_libcxr_line_callback = None
 
-libcxr_decompose = None
-libcxr_context_reset = None
-libcxr_set_offset = None
-libcxr_set_scale_factor = None
+# Structure definitions
+class cxr_edge(Structure):
+   _fields_ = [("i0",c_int32),
+               ("i1",c_int32),
+               ("freestyle",c_int32)]
+
+class cxr_static_loop(Structure):
+   _fields_ = [("index",c_int32),
+               ("edge_index",c_int32),
+               ("uv",c_double * 2)]
+
+class cxr_polygon(Structure):
+   _fields_ =  [("loop_start",c_int32),
+                ("loop_total",c_int32),
+                ("normal",c_double * 3),
+                ("center",c_double * 3),
+                ("material_id",c_int32)]
+
+class cxr_material(Structure):
+   _fields_ = [("res",c_int32 * 2),
+               ("name",c_char_p)]
 
-# Vdf writing interface
-libcxr_vdf_open = None
-libcxr_vdf_close = None
-libcxr_vdf_put = None
-libcxr_vdf_node = None
-libcxr_vdf_edon = None
-libcxr_vdf_kv = None
+class cxr_static_mesh(Structure):
+   _fields_ = [("vertices",POINTER(c_double * 3)),
+               ("edges",POINTER(cxr_edge)),
+               ("loops",POINTER(cxr_static_loop)),
+               ("polys",POINTER(cxr_polygon)),
+               ("materials",POINTER(cxr_material)),
+
+               ("poly_count",c_int32),
+               ("vertex_count",c_int32),
+               ("edge_count",c_int32),
+               ("loop_count",c_int32),
+               ("material_count",c_int32)]
+
+class cxr_tri_mesh(Structure):
+   _fields_ = [("vertices",POINTER(c_double *3)),
+               ("colours",POINTER(c_double *4)),
+               ("indices",POINTER(c_int32)),
+               ("indices_count",c_int32),
+               ("vertex_count",c_int32)]
+
+class cxr_vmf_context(Structure):
+   _fields_ = [("mapversion",c_int32),
+               ("skyname",c_char_p),
+               ("detailvbsp",c_char_p),
+               ("detailmaterial",c_char_p),
+               ("scale",c_double),
+               ("offset",c_double *3),
+               ("lightmap_scale",c_int32),
+               ("brush_count",c_int32),
+               ("entity_count",c_int32),
+               ("face_count",c_int32)]
+
+# Public API
+libcxr_decompose = extern( "cxr_decompose", \
+      [POINTER(cxr_static_mesh), POINTER(c_int32)], c_void_p )
+
+libcxr_free_world = extern( "cxr_free_world", [c_void_p], None )
+libcxr_write_test_data = extern( "cxr_write_test_data", \
+      [POINTER(cxr_static_mesh)], None )
+libcxr_world_preview = extern( "cxr_world_preview", [c_void_p], \
+        POINTER(cxr_tri_mesh))
+libcxr_free_tri_mesh = extern( "cxr_free_tri_mesh", [c_void_p], None )
+
+# VMF
+libcxr_begin_vmf = extern( "cxr_begin_vmf", \
+      [POINTER(cxr_vmf_context), c_void_p], None )
+
+libcxr_vmf_begin_entities = extern( "cxr_vmf_begin_entities", \
+      [POINTER(cxr_vmf_context), c_void_p], None )
+
+libcxr_push_world_vmf = extern("cxr_push_world_vmf", \
+      [c_void_p,POINTER(cxr_vmf_context),c_void_p], None )
+
+libcxr_end_vmf = extern( "cxr_end_vmf", \
+      [POINTER(cxr_vmf_context),c_void_p], None )
+
+# VDF
+libcxr_vdf_open = extern( "cxr_vdf_open", [c_char_p], c_void_p )
+libcxr_vdf_close = extern( "cxr_vdf_close", [c_void_p], None )
+libcxr_vdf_put = extern( "cxr_vdf_put", [c_void_p,c_char_p], None )
+libcxr_vdf_node = extern( "cxr_vdf_node", [c_void_p,c_char_p], None )
+libcxr_vdf_edon = extern( "cxr_vdf_edon", [c_void_p], None )
+libcxr_vdf_kv = extern( "cxr_vdf_kv", [c_void_p,c_char_p,c_char_p], None )
+
+# Other
+libcxr_lightpatch_bsp = extern( "cxr_lightpatch_bsp", [c_char_p], None )
+
+libcxr_funcs = [ libcxr_decompose, libcxr_free_world, libcxr_begin_vmf, \
+                 libcxr_vmf_begin_entities, libcxr_push_world_vmf, \
+                 libcxr_end_vmf, libcxr_vdf_open, libcxr_vdf_close, \
+                 libcxr_vdf_put, libcxr_vdf_node, libcxr_vdf_edon, 
+                 libcxr_vdf_kv, libcxr_lightpatch_bsp, libcxr_write_test_data,\
+                 libcxr_world_preview, libcxr_free_tri_mesh ]
 
 # libnbvtf interface
 # ==================
 libnbvtf = None
-libnbvtf_convert = None
+
+libnbvtf_convert = extern( "nbvtf_convert", \
+      [c_char_p,c_int32,c_int32,c_int32,c_int32,c_int32,c_uint32,c_char_p],
+      c_int32 )
+
+libnbvtf_funcs = [ libnbvtf_convert ]
 
 # NBVTF constants
 # ===============
@@ -69,76 +173,34 @@ class vdf_structure():
    def __init__(_,path):
       _.path = path
    def __enter__(_):
-      _.fp = libcxr_vdf_open( _.path.encode('utf-8') )
+      _.fp = libcxr_vdf_open.call( _.path.encode('utf-8') )
       if _.fp == None:
          print( F"Could not open file {_.path}" )
          return None
       return _
    def __exit__(_,type,value,traceback):
       if _.fp != None:
-         libcxr_vdf_close(_.fp)
+         libcxr_vdf_close.call(_.fp)
 
    def put(_,s):
-      libcxr_vdf_put(_.fp, s.encode('utf-8') )
+      libcxr_vdf_put.call(_.fp, s.encode('utf-8') )
    def node(_,name):
-      libcxr_vdf_node(_.fp, name.encode('utf-8') )
+      libcxr_vdf_node.call(_.fp, name.encode('utf-8') )
    def edon(_):
-      libcxr_vdf_edon(_.fp)
+      libcxr_vdf_edon.call(_.fp)
    def kv(_,k,v):
-      libcxr_vdf_kv(_.fp, k.encode('utf-8'), v.encode('utf-8'))
+      libcxr_vdf_kv.call(_.fp, k.encode('utf-8'), v.encode('utf-8'))
 
 class cxr_object_context():
    def __init__(_,scale,offset_z):
       _.scale=scale
       _.offset_z=offset_z
 
-libcxr_convert_mesh_to_vmf = None
-
 debug_gpu_lines = None
+debug_gpu_mesh = None
 debug_gpu_shader = gpu.shader.from_builtin('3D_SMOOTH_COLOR')
 debug_draw_handler = None
 
-class cxr_settings(Structure):
-   _fields_ = [("debug",c_int32),
-               ("lightmap_scale",c_int32),
-               ("light_scale",c_double)]
-
-class cxr_input_loop(Structure):
-   _fields_ = [("index",c_int32),
-               ("edge_index",c_int32),
-               ("uv",c_double * 2)]
-
-class cxr_polygon(Structure):
-   _fields_ =  [("loop_start",c_int32),
-                ("loop_total",c_int32),
-                ("normal",c_double * 3),
-                ("center",c_double * 3),
-                ("material_id",c_int32)]
-
-class cxr_edge(Structure):
-   _fields_ = [("i0",c_int32),
-               ("i1",c_int32),
-               ("freestyle",c_int32)]
-
-class cxr_material(Structure):
-   _fields_ = [("res",c_int32 * 2),
-               ("vmt_path",c_char_p)]
-
-class cxr_input_mesh(Structure):
-   _fields_ = [("vertices",POINTER(c_double * 3)),
-               ("edges",POINTER(cxr_edge)),
-               ("loops",POINTER(cxr_input_loop)),
-               ("polys",POINTER(cxr_polygon)),
-               ("materials",POINTER(cxr_material)),
-
-               ("poly_count",c_int32),
-               ("vertex_count",c_int32),
-               ("edge_count",c_int32),
-               ("loop_count",c_int32),
-               ("material_count",c_int32)]
-
-class cxr_output_mesh(Structure):
-   _fields_ = []
 
 def libcxr_log_callback(logStr):
    print( F"{logStr.decode('utf-8')}",end='' )
@@ -184,35 +246,37 @@ def libcxr_line_callback(p0,p1,colour):
 def cxr_draw():
    global debug_gpu_lines
    global debug_gpu_shader
-   
+   global debug_gpu_mesh
+
+   debug_gpu_shader.bind()
+
+   gpu.state.depth_mask_set(False)
+   gpu.state.line_width_set(1.5)
+   gpu.state.face_culling_set('BACK')
+
+   gpu.state.depth_test_set('NONE')
    gpu.state.blend_set('ALPHA')
    if debug_gpu_lines != None:
       debug_gpu_lines.draw(debug_gpu_shader)
 
+   gpu.state.depth_test_set('LESS_EQUAL')
+   gpu.state.blend_set('ADDITIVE')
+   if debug_gpu_mesh != None:
+      debug_gpu_mesh.draw(debug_gpu_shader)
+
 class CXR_RELOAD(bpy.types.Operator):
    bl_idname="convexer.reload"
    bl_label="Reload convexer"
 
    def execute(_,context):
-      global libcxr, libnbvtf, libnbvtf_convert
+      global libcxr, libnbvtf, libcxr_funcs, libnbvtf_funcs
 
       # Load vtf library
       libnbvtf = cdll.LoadLibrary( os.path.dirname(__file__)+'/libnbvtf.so')
-      libnbvtf_convert = libnbvtf.nbvtf_convert
-      libnbvtf_convert.argtypes = [\
-        c_char_p, \
-        c_int32, \
-        c_int32, \
-        c_int32, \
-        c_int32, \
-        c_uint32, \
-        c_char_p ]
-      libnbvtf_convert.restype = c_int32
 
       if libcxr != None:
          _handle = libcxr._handle
          
-         # TODO: Find a propper way to do this
          for i in range(10): libc_dlclose( _handle )
          del libcxr
 
@@ -222,63 +286,14 @@ class CXR_RELOAD(bpy.types.Operator):
       build_time = c_char_p.in_dll(libcxr,'cxr_build_time')
       print( F"libcxr build time: {build_time.value}" )
    
-      # Public API
-      global libcxr_decompose
-      global libcxr_convert_mesh_to_vmf
-
-      libcxr_decompose = libcxr.cxr_decompose
-      libcxr_decompose.argtypes = [\
-         POINTER(cxr_input_mesh)
-      ]
-      libcxr_decompose.restype = c_int32
-
-      libcxr_convert_mesh_to_vmf = libcxr.cxr_convert_mesh_to_vmf
-      libcxr_convert_mesh_to_vmf.argtypes = [\
-         POINTER(cxr_input_mesh),\
-         c_void_p,\
-      ]
-      libcxr_convert_mesh_to_vmf.restype = c_int32
-      
-      global libcxr_context_reset, libcxr_set_offset,\
-             libcxr_set_scale_factor
-      libcxr_context_reset = libcxr.cxr_context_reset
-      
-      libcxr_set_offset = libcxr.cxr_set_offset
-      libcxr_set_offset.argtypes = [ c_double ]
-      
-      libcxr_set_scale_factor = libcxr.cxr_set_scale_factor
-      libcxr_set_scale_factor.argtypes = [ c_double ]
-
-      # VDF
-      global libcxr_vdf_open, \
-            libcxr_vdf_close, \
-            libcxr_vdf_put, \
-            libcxr_vdf_node, \
-            libcxr_vdf_edon, \
-            libcxr_vdf_kv
-      
-      libcxr_vdf_open = libcxr.cxr_vdf_open
-      libcxr_vdf_open.argtypes = [ c_char_p ]
-      libcxr_vdf_open.restype = c_void_p
+      for fd in libnbvtf_funcs:
+         fd.loadfrom( libnbvtf )
 
-      libcxr_vdf_close = libcxr.cxr_vdf_close
-      libcxr_vdf_close.argtypes = [ c_void_p ]
-
-      libcxr_vdf_put = libcxr.cxr_vdf_put
-      libcxr_vdf_put.argtypes = [ c_void_p, c_char_p ]
-
-      libcxr_vdf_node = libcxr.cxr_vdf_node
-      libcxr_vdf_node.argtypes = [ c_void_p, c_char_p ]
-
-      libcxr_vdf_edon = libcxr.cxr_vdf_edon
-      libcxr_vdf_edon.argtypes = [ c_void_p ]
-
-      libcxr_vdf_kv = libcxr.cxr_vdf_kv
-      libcxr_vdf_kv.argtypes = [ c_void_p, c_char_p, c_char_p ]
+      for fd in libcxr_funcs:
+         fd.loadfrom( libcxr )
 
       # Callbacks
-      global c_libcxr_log_callback
-      global c_libcxr_line_callback
+      global c_libcxr_log_callback, c_libcxr_line_callback
 
       LOG_FUNCTION_TYPE = CFUNCTYPE(None,c_char_p)
       c_libcxr_log_callback = LOG_FUNCTION_TYPE(libcxr_log_callback)
@@ -296,20 +311,6 @@ def libcxr_use():
 
    if libcxr == None:
       bpy.ops.convexer.reload()
-   
-   def _bool_int(b):
-      return 1 if b else 0
-   
-   scene = bpy.context.scene
-   cxr = scene.cxr_data
-
-   settings=cxr_settings()
-   settings.debug=_bool_int(cxr.debug)
-
-   settings.lightmap_scale=cxr.lightmap_scale
-   settings.light_scale=cxr.light_scale
-
-   libcxr.cxr_settings_update(pointer(settings))
 
 def to_aeiou( v ):
    ret = ""
@@ -396,8 +397,8 @@ cxr_graph_mapping = {
       {
          "ShaderNodeMixRGB":
          {
-            "Color1": material_tex_image("$basetexture"),
-            "Color2": material_tex_image("$decaltexture")
+            "Color1": material_tex_image("basetexture"),
+            "Color2": material_tex_image("decaltexture")
          },
          "ShaderNodeTexImage":
          {
@@ -412,7 +413,7 @@ cxr_graph_mapping = {
       {
          "ShaderNodeNormalMap":
          {
-            "Color": material_tex_image("$bumpmap")
+            "Color": material_tex_image("bumpmap")
          }
       }
    }
@@ -587,8 +588,8 @@ def ent_lights(obj,context):
    kvs = ent_baseclass([ent_origin],\
    {
       "_distance": (0.0 if obj.data.cxr_data.realtime else -1.0),
-      "_light": [int(pow(obj.data.color[i],1.0/2.2)*255.0) for i in range(3)] + \
-                [int(obj.data.energy * bpy.context.scene.cxr_data.light_scale) ],
+      "_light": [int(pow(obj.data.color[i],1.0/2.2)*255.0) for i in range(3)] +\
+                [int(obj.data.energy * bpy.context.scene.cxr_data.light_scale)],
       "_lightHDR": '-1 -1 -1 1',
       "_lightscaleHDR": 1
    })
@@ -607,9 +608,11 @@ def ent_lights(obj,context):
       
       kvs['pitch'] = math.asin(fwd[2]) * 57.295779513
       kvs['angles'] = [ 0.0, math.atan2(fwd[1],fwd[0]) * 57.295779513, 0.0 ]
-      kvs['_quadratic_attn'] = 0.0  # Source spotlights + quadratic falloff look awful.
-                                    # Blender's default has a much more 'accurate' look
-                                    # They appear correct when using linear scale.
+      kvs['_quadratic_attn'] = 0.0  # Source spotlights + quadratic falloff look
+                                    # Really bad...
+                                    #
+                                    # Blender's default has a much more 'nice'
+                                    # look.
       kvs['_linear_attn'] = 1.0
    
    elif obj.data.type == 'POINT':
@@ -636,6 +639,16 @@ cxr_entities = {
          "enabled": {"type": "int", "default": 1 },
       })
    },
+   "info_player_terrorist":
+   {
+      "gizmo": [],
+      "allow": ('EMPTY',),
+      "keyvalues": ent_baseclass([ent_transform],\
+      {
+         "priority": {"type": "int", "default": 0 },
+         "enabled": {"type": "int", "default": 1 },
+      })
+   },
    "light": { "keyvalues": ent_lights },
    "light_spot": { "keyvalues": ent_lights },
    # SUN
@@ -801,12 +814,16 @@ def material_info(mat):
    return info
 
 def mesh_cxr_format(obj):
+   orig_state = obj.mode
+   if orig_state != 'OBJECT':
+      bpy.ops.object.mode_set(mode='OBJECT')
+
    dgraph = bpy.context.evaluated_depsgraph_get()
    data = obj.evaluated_get(dgraph).data
    
    _,mtx_rot,_ = obj.matrix_world.decompose()
 
-   mesh = cxr_input_mesh()
+   mesh = cxr_static_mesh()
 
    vertex_data = ((c_double*3)*len(data.vertices))()
    for i, vert in enumerate(data.vertices):
@@ -815,7 +832,7 @@ def mesh_cxr_format(obj):
       vertex_data[i][1] = c_double(v[1])
       vertex_data[i][2] = c_double(v[2])
 
-   loop_data = (cxr_input_loop*len(data.loops))()
+   loop_data = (cxr_static_loop*len(data.loops))()
    polygon_data = (cxr_polygon*len(data.polygons))()
 
    for i, poly in enumerate(data.polygons):
@@ -863,7 +880,7 @@ def mesh_cxr_format(obj):
    
    mesh.edges = cast(edge_data, POINTER(cxr_edge))
    mesh.vertices = cast(vertex_data, POINTER(c_double*3))
-   mesh.loops = cast(loop_data,POINTER(cxr_input_loop))
+   mesh.loops = cast(loop_data,POINTER(cxr_static_loop))
    mesh.polys = cast(polygon_data, POINTER(cxr_polygon))
    mesh.materials = cast(material_data, POINTER(cxr_material))
    
@@ -873,6 +890,7 @@ def mesh_cxr_format(obj):
    mesh.loop_count =  len(data.loops)
    mesh.material_count = len(obj.material_slots)
 
+   bpy.ops.object.mode_set(mode=orig_state)
    return mesh
 
 class CXR_WRITE_VMF(bpy.types.Operator):
@@ -897,42 +915,25 @@ class CXR_WRITE_VMF(bpy.types.Operator):
       os.makedirs( model_dir, exist_ok=True )
       
       # States
+      libcxr_reset_debug_lines()
       material_info.references = set()
-      libcxr_context_reset()
-      
       output_vmf = F"{directory}/{settings.project_name}.vmf"
 
       with vdf_structure(output_vmf) as m:
          print( F"Write: {output_vmf}" )
 
-         m.node('versioninfo')
-         m.kv('editorversion','400')
-         m.kv('editorbuild','8456')
-         m.kv('mapversion','4')
-         m.kv('formatversion','100')
-         m.kv('prefab','0')
-         m.edon()
-
-         m.node('visgroups')
-         m.edon()
-
-         m.node('viewsettings')
-         m.kv('bSnapToGrid','1')
-         m.kv('bShowGrid','1')
-         m.kv('bShowLogicalGrid','0')
-         m.kv('nGridSpacing','64')
-         m.kv('bShow3DGrid','0')
-         m.edon()
-
-         m.node('world')
-         m.kv('id','1')
-         m.kv('mapversion','1')
-         m.kv('classname','worldspawn')
-         m.kv('skyname','sky_csgo_night02b')
-         m.kv('maxpropscreenwidth','-1')
-         m.kv('detailvbsp','detail.vbsp')
-         m.kv('detailmaterial','detail/detailsprites')
+         vmfinfo = cxr_vmf_context()
+         vmfinfo.mapversion = 4
+         vmfinfo.skyname = b"sky_csgo_night02b"
+         vmfinfo.detailvbsp = b"detail.vbsp"
+         vmfinfo.detailmaterial = b"detail/detailsprites"
+         vmfinfo.lightmap_scale = 12
+         vmfinfo.brush_count = 0
+         vmfinfo.entity_count = 0
+         vmfinfo.face_count = 0
          
+         libcxr_begin_vmf.call( pointer(vmfinfo), m.fp )
+
          # Make sure all of our asset types have a unique ID
          def _uid_prepare(objtype):
             used_ids = [0]
@@ -957,12 +958,17 @@ class CXR_WRITE_VMF(bpy.types.Operator):
          def _collect(collection,transform):
             if collection.name.startswith('.'):
                return
+            
+            if collection.hide_render:
+               return
 
             if collection.name.startswith('mdl_'):
                _collect.heros += [(collection,transform)]
                return
 
             for obj in collection.objects:
+               if obj.hide_get(): continue
+
                classname = cxr_classname( obj )
 
                if classname != None:
@@ -976,10 +982,14 @@ class CXR_WRITE_VMF(bpy.types.Operator):
          _collect.a_models = set()
          _collect.entities = []
          _collect.geo = []
+         _collect.heros = []
 
-         transform_main = cxr_object_context( context.scene.cxr_data.scale_factor, 0.0 )
-         transform_sky = cxr_object_context( context.scene.cxr_data.skybox_scale_factor, \
-                                             context.scene.cxr_data.skybox_offset )
+         transform_main = cxr_object_context( \
+               context.scene.cxr_data.scale_factor, 0.0 )
+
+         transform_sky = cxr_object_context( \
+               context.scene.cxr_data.skybox_scale_factor, \
+               context.scene.cxr_data.skybox_offset )
          
          if 'main' in bpy.data.collections:
             _collect( bpy.data.collections['main'], transform_main )
@@ -987,13 +997,32 @@ class CXR_WRITE_VMF(bpy.types.Operator):
          if 'skybox' in bpy.data.collections:
             _collect( bpy.data.collections['skybox'], transform_sky )
          
+         def _buildsolid( obj, ctx ):
+            nonlocal m
+
+            baked = mesh_cxr_format( brush[0] )
+            world = libcxr_decompose.call( baked, None )
+            
+            if world == None:
+               return False
+
+            vmfinfo.scale = brush[1].scale
+            vmfinfo.offset[0] = 0.0
+            vmfinfo.offset[1] = 0.0
+            vmfinfo.offset[2] = brush[1].offset_z
+
+            libcxr_push_world_vmf.call( world, pointer(vmfinfo), m.fp )
+            libcxr_free_world.call( world )
+
+            return True
+
          # World geometry
          for brush in _collect.geo:
-            baked = mesh_cxr_format( brush[0] )
-            libcxr_set_scale_factor( brush[1].scale )
-            libcxr_set_offset( brush[1].offset_z )
-            libcxr_convert_mesh_to_vmf(baked,m.fp)
-         
+            if not _buildsolid( brush[0], brush[1] ):
+               libcxr_batch_debug_lines()
+               scene_redraw()
+               return {'CANCELLED'}
+
          m.edon()
          
          # Entities
@@ -1011,11 +1040,10 @@ class CXR_WRITE_VMF(bpy.types.Operator):
                   m.kv( kv[0], ' '.join([str(_) for _ in kv[2]]) )
                else: m.kv( kv[0], str(kv[2]) )
 
-            if obj.type == 'MESH':
-               baked = mesh_cxr_format( obj )
-               libcxr_set_scale_factor( ctx.scale )
-               libcxr_set_offset( ctx.offset_z )
-               libcxr_convert_mesh_to_vmf(baked,m.fp)
+            if not _buildsolid( obj, ctx ):
+               libcxr_batch_debug_lines()
+               scene_redraw()
+               return {'CANCELLED'}
 
             m.edon()
          
@@ -1031,9 +1059,9 @@ class CXR_WRITE_VMF(bpy.types.Operator):
 
       return {'FINISHED'}
 
-class CXR_DECOMPOSE_SOLID(bpy.types.Operator):
-   bl_idname="convexer.decompose_solid"
-   bl_label="Decompose Solid"
+class CXR_DEV_OPERATOR(bpy.types.Operator):
+   bl_idname="convexer.dev_test"
+   bl_label="Export development data"
 
    def execute(_,context):
       libcxr_use()
@@ -1042,12 +1070,122 @@ class CXR_DECOMPOSE_SOLID(bpy.types.Operator):
       mesh_src = mesh_cxr_format(context.active_object)
       
       libcxr_reset_debug_lines()
-      libcxr_decompose( pointer(mesh_src) )
+      libcxr_write_test_data.call( pointer(mesh_src) )
       libcxr_batch_debug_lines()
          
       scene_redraw()
       return {'FINISHED'}
 
+class CXR_PREVIEW_OPERATOR(bpy.types.Operator):
+   bl_idname="convexer.preview"
+   bl_label="Preview Brushes"
+
+   LASTERR = None
+   RUNNING = False
+
+   def execute(_,context):
+      return {'FINISHED'}
+
+   def modal(_,context,event):
+      global debug_gpu_mesh
+      static = _.__class__
+
+      if event.type == 'ESC':
+         libcxr_reset_debug_lines()
+         libcxr_batch_debug_lines()
+         debug_gpu_mesh = None
+         scene_redraw()
+         
+         static.RUNNING = False
+         return {'FINISHED'}
+
+      return {'PASS_THROUGH'}
+
+   def invoke(_,context,event):
+      global debug_gpu_shader, debug_gpu_mesh
+      static = _.__class__
+      static.LASTERR = None
+
+      libcxr_use()
+      libcxr_reset_debug_lines()
+
+      mesh_src = mesh_cxr_format(context.active_object)
+
+      err = c_int32(0)
+      world = libcxr_decompose.call( mesh_src, pointer(err) )
+
+      if world == None:
+         debug_gpu_mesh = None
+         libcxr_batch_debug_lines()
+         scene_redraw()
+
+         static.LASTERR = ["There is no error", \
+               "Non-Manifold",\
+               "Bad-Manifold",\
+               "No-Candidate",\
+               "Internal-Fail",\
+               "Non-Coplanar",\
+               "Non-Convex Polygon"]\
+               [err.value]
+
+         if static.RUNNING:
+            return {'CANCELLED'}
+         else:
+            context.window_manager.modal_handler_add(_)
+            return {'RUNNING_MODAL'}
+      
+      ptrpreview = libcxr_world_preview.call( world )
+      preview = ptrpreview[0]
+
+      vertices = preview.vertices[:preview.vertex_count]
+      vertices = [(_[0],_[1],_[2]) for _ in vertices]
+
+      colours = preview.colours[:preview.vertex_count]
+      colours = [(_[0],_[1],_[2],_[3]) for _ in colours]
+
+      indices = preview.indices[:preview.indices_count]
+      indices = [ (indices[i*3+0],indices[i*3+1],indices[i*3+2]) \
+                  for i in range(int(preview.indices_count/3)) ]
+
+      debug_gpu_mesh = batch_for_shader(
+         debug_gpu_shader, 'TRIS',
+         { "pos": vertices, "color": colours },
+         indices = indices,
+      )
+
+      libcxr_free_tri_mesh.call( ptrpreview )
+      libcxr_free_world.call( world )
+      libcxr_batch_debug_lines()
+      scene_redraw()
+
+      if static.RUNNING:
+         return {'CANCELLED'}
+      if not static.RUNNING:
+         static.RUNNING = True
+         context.window_manager.modal_handler_add(_)
+         return {'RUNNING_MODAL'}
+
+class CXR_VIEW3D( bpy.types.Panel ):
+   bl_idname = "VIEW3D_PT_convexer"
+   bl_label = "Convexer"
+   bl_space_type = 'VIEW_3D'
+   bl_region_type = 'UI'
+   bl_category = "Convexer"
+
+   @classmethod
+   def poll(cls, context):
+      return (context.object is not None)
+
+   def draw(_, context):
+      layout = _.layout
+      row = layout.row()
+      row.scale_y = 2
+      row.operator("convexer.preview")
+
+      if CXR_PREVIEW_OPERATOR.LASTERR != None:
+         box = layout.box()
+         box.label(text=CXR_PREVIEW_OPERATOR.LASTERR, icon='ERROR')
+
 class CXR_INTERFACE(bpy.types.Panel):
    bl_label="Convexer"
    bl_idname="SCENE_PT_convexer"
@@ -1057,7 +1195,8 @@ class CXR_INTERFACE(bpy.types.Panel):
 
    def draw(_,context):
       _.layout.operator("convexer.reload")
-      _.layout.operator("convexer.decompose_solid")
+      _.layout.operator("convexer.dev_test")
+      _.layout.operator("convexer.preview")
       _.layout.operator("convexer.write_vmf")
 
       settings = context.scene.cxr_data
@@ -1117,7 +1256,7 @@ def compile_image(img,flags):
          flags_full |= NBVTF_TEXTUREFLAGS_CLAMPS
          flags_full |= NBVTF_TEXTUREFLAGS_CLAMPT
 
-      if libnbvtf_convert(src,dims[0],dims[1],mipmap,fmt,flags_full,dst):
+      if libnbvtf_convert.call(src,dims[0],dims[1],mipmap,fmt,0,flags_full,dst):
          img.cxr_data.last_hash = comphash
 
    return name
@@ -1283,7 +1422,8 @@ class CXR_MATERIAL_PANEL(bpy.types.Panel):
                expandview = True
                drawthis = True
 
-               if 'shaders' in pdef and properties.shader not in pdef['shaders']:
+               if ('shaders' in pdef) and \
+                  (properties.shader not in pdef['shaders']):
                   continue
 
                if ptype == 'intrinsic':
@@ -1309,7 +1449,7 @@ class CXR_MATERIAL_PANEL(bpy.types.Panel):
                      else:
                         # hidden intrinsic value.
                         # Means its a float array or something not an image
-                        thisnode.label( text=F"-- hidden intrinsic '{decl}' --" )
+                        thisnode.label(text=F"-- hidden intrinsic '{decl}' --")
                   else:
                      thisnode.prop(properties,decl)
                   if expandview: _mview(pdef,thisnode)
@@ -1352,12 +1492,15 @@ class CXR_ENTITY_PANEL(bpy.types.Panel):
 
       if active_object == None: return
       
-      default_context = cxr_object_context( bpy.context.scene.cxr_data.scale_factor, 0.0 )
+      default_context = cxr_object_context( \
+            bpy.context.scene.cxr_data.scale_factor, 0.0 )
+
       ecn = cxr_intrinsic_classname( active_object )
       classname = cxr_custom_class( active_object )
 
       if ecn == None: 
-         if active_object.type == 'MESH': _.layout.prop( active_object.cxr_data, 'brushclass' )
+         if active_object.type == 'MESH': 
+            _.layout.prop( active_object.cxr_data, 'brushclass' )
          else: _.layout.prop( active_object.cxr_data, 'classname' )
 
          if classname == 'NONE':
@@ -1486,13 +1629,17 @@ class CXR_SCENE_SETTINGS(bpy.types.PropertyGroup):
    opt_vrad: bpy.props.StringProperty( name="args" )
 
    debug: bpy.props.BoolProperty(name="Debug",default=False)
-   scale_factor: bpy.props.FloatProperty(name="VMF Scale factor",default=32.0,min=1.0)
-   skybox_scale_factor: bpy.props.FloatProperty(name="Sky Scale factor",default=1.0,min=0.01)
+   scale_factor: bpy.props.FloatProperty( name="VMF Scale factor", \
+         default=32.0,min=1.0)
+   skybox_scale_factor: bpy.props.FloatProperty( name="Sky Scale factor", \
+         default=1.0,min=0.01)
+
    skybox_offset: bpy.props.FloatProperty(name="Sky offset",default=-4096.0)
    light_scale: bpy.props.FloatProperty(name="Light Scale",default=1.0/5.0)
-   displacement_cardinal: bpy.props.BoolProperty(name="Cardinal displacements",default=True)
-   include_names: bpy.props.BoolProperty(name="Append original file names",default=True)
-   lightmap_scale: bpy.props.IntProperty(name="Global Lightmap Scale",default=12)
+   include_names: bpy.props.BoolProperty(name="Append original file names",\
+         default=True)
+   lightmap_scale: bpy.props.IntProperty(name="Global Lightmap Scale",\
+         default=12)
 
 class CXR_DETECT_COMPILERS(bpy.types.Operator):
    bl_idname="convexer.detect_compilers"
@@ -1510,11 +1657,12 @@ class CXR_DETECT_COMPILERS(bpy.types.Operator):
 
       return {'FINISHED'}
 
-classes = [ CXR_RELOAD, CXR_DECOMPOSE_SOLID, CXR_INTERFACE, \
+classes = [ CXR_RELOAD, CXR_DEV_OPERATOR, CXR_INTERFACE, \
             CXR_WRITE_VMF, CXR_MATERIAL_PANEL, CXR_IMAGE_SETTINGS,\
             CXR_MODEL_SETTINGS, CXR_ENTITY_SETTINGS, CXR_CUBEMAP_SETTINGS,\
             CXR_LIGHT_SETTINGS, CXR_SCENE_SETTINGS, CXR_DETECT_COMPILERS,\
-            CXR_ENTITY_PANEL, CXR_LIGHT_PANEL ]
+            CXR_ENTITY_PANEL, CXR_LIGHT_PANEL, CXR_PREVIEW_OPERATOR,\
+            CXR_VIEW3D ]
 
 def register():
    global debug_draw_handler, vmt_param_dynamic_class
@@ -1580,7 +1728,8 @@ def register():
                  '') for _ in cxr_shaders],\
       default = next(iter(cxr_shaders)))
    
-   annotations_dict["asset_id"] = bpy.props.IntProperty(name="intl_assetid",default=0)
+   annotations_dict["asset_id"] = bpy.props.IntProperty(name="intl_assetid",\
+         default=0)
 
    _dvmt_propogate( cxr_shader_params )
    vmt_param_dynamic_class = type(