1 # Copyright (C) 2022 Harry Godden (hgn)
5 "author": "Harry Godden (hgn)",
12 "category":"Import/Export",
15 print( "Convexer reload" )
17 #from mathutils import *
18 import bpy
, gpu
, math
, os
, time
, mathutils
, blf
, subprocess
, shutil
, hashlib
20 from gpu_extras
.batch
import batch_for_shader
21 from bpy
.app
.handlers
import persistent
23 # GPU and viewport drawing
24 # ------------------------------------------------------------------------------
27 cxr_view_draw_handler
= None
28 cxr_ui_draw_handler
= None
37 cxr_view_shader
= gpu
.shader
.from_builtin('3D_SMOOTH_COLOR')
38 cxr_ui_shader
= gpu
.types
.GPUShader("""
39 uniform mat4 ModelViewProjectionMatrix;
49 gl_Position = ModelViewProjectionMatrix * vec4(aPos.x*scale,aPos.y, 0.0, 1.0);
64 def cxr_ui(_
,context
):
65 global cxr_jobs_batch
, cxr_ui_shader
, cxr_jobs_inf
67 w
= gpu
.state
.viewport_get()[2]
69 cxr_ui_shader
.uniform_float( "scale", w
)
71 if cxr_jobs_batch
!= None:
72 gpu
.state
.blend_set('ALPHA')
73 cxr_jobs_batch
.draw(cxr_ui_shader
)
75 blf
.position(0,2,50,0)
77 blf
.color(0,1.0,1.0,1.0,1.0)
78 blf
.draw(0,"Compiling")
80 for ji
in cxr_jobs_inf
:
81 blf
.position(0,ji
[0]*w
,35,0)
87 for ln
in reversed(CXR_COMPILER_CHAIN
.LOG
[-25:]):
88 blf
.position(0,2,py
,0)
92 #if CXR_PREVIEW_OPERATOR.LASTERR != None:
93 # blf.position(0,2,80,0)
95 # blf.color(0,1.0,0.2,0.2,0.9)
96 # blf.draw(0,"Invalid geometry")
98 # Something is off with TIMER,
99 # this forces the viewport to redraw before we can continue with our
102 CXR_COMPILER_CHAIN
.WAIT_REDRAW
= False
105 global cxr_view_shader
, cxr_view_mesh
, cxr_view_lines
107 cxr_view_shader
.bind()
109 gpu
.state
.depth_mask_set(False)
110 gpu
.state
.line_width_set(1.5)
111 gpu
.state
.face_culling_set('BACK')
112 gpu
.state
.depth_test_set('NONE')
113 gpu
.state
.blend_set('ALPHA')
115 if cxr_view_lines
!= None:
116 cxr_view_lines
.draw( cxr_view_shader
)
118 gpu
.state
.depth_test_set('LESS_EQUAL')
119 gpu
.state
.blend_set('ADDITIVE')
120 if cxr_view_mesh
!= None:
121 cxr_view_mesh
.draw( cxr_view_shader
)
123 def cxr_jobs_update_graph(jobs
):
124 global cxr_jobs_batch
, cxr_ui_shader
, cxr_jobs_inf
134 total_width
+= sys
['w']
143 colour
= sys
['colour']
144 colourwait
= (colour
[0],colour
[1],colour
[2],0.4)
145 colourrun
= (colour
[0]*1.5,colour
[1]*1.5,colour
[2]*1.5,0.5)
146 colourdone
= (colour
[0],colour
[1],colour
[2],1.0)
149 sfsub
= (1.0/(len(jobs
)))*w
153 if j
== None: colour
= colourdone
154 else: colour
= colourwait
156 px
= (cur
+ (i
)*sfsub
) * sf
157 px1
= (cur
+ (i
+1.0)*sfsub
) * sf
160 verts
+= [(px
,0), (px
, h
), (px1
, 0.0), (px1
,h
)]
161 colours
+= [colour
,colour
,colour
,colour
]
162 indices
+= [(ci
+0,ci
+2,ci
+3),(ci
+0,ci
+3,ci
+1)]
165 cxr_jobs_inf
+= [((sf
*cur
), sys
['title'])]
168 cxr_jobs_batch
= batch_for_shader(
169 cxr_ui_shader
, 'TRIS',
170 { "aPos": verts
, "aColour": colours
},
174 # view_layer.update() doesnt seem to work,
175 # tag_redraw() seems to have broken
176 # therefore, change a property
178 ob
= bpy
.context
.scene
.objects
[0]
179 ob
.hide_render
= ob
.hide_render
181 # the 'real' way to refresh the scene
182 for area
in bpy
.context
.window
.screen
.areas
:
183 if area
.type == 'view_3d':
187 # ------------------------------------------------------------------------------
189 # dlclose for reloading modules manually
191 libc_dlclose
= cdll
.LoadLibrary(None).dlclose
192 libc_dlclose
.argtypes
= [c_void_p
]
194 # wrapper for ctypes binding
196 def __init__(_
,name
,argtypes
,restype
):
198 _
.argtypes
= argtypes
203 _
.call
= getattr(so
,_
.name
)
204 _
.call
.argtypes
= _
.argtypes
206 if _
.restype
!= None:
207 _
.call
.restype
= _
.restype
210 # ------------------------------------------------------------------------------
214 # Structure definitions
216 class cxr_edge(Structure
):
217 _fields_
= [("i0",c_int32
),
219 ("freestyle",c_int32
)]
221 class cxr_static_loop(Structure
):
222 _fields_
= [("index",c_int32
),
223 ("edge_index",c_int32
),
226 class cxr_polygon(Structure
):
227 _fields_
= [("loop_start",c_int32
),
228 ("loop_total",c_int32
),
229 ("normal",c_double
* 3),
230 ("center",c_double
* 3),
231 ("material_id",c_int32
)]
233 class cxr_material(Structure
):
234 _fields_
= [("res",c_int32
* 2),
237 class cxr_static_mesh(Structure
):
238 _fields_
= [("vertices",POINTER(c_double
* 3)),
239 ("edges",POINTER(cxr_edge
)),
240 ("loops",POINTER(cxr_static_loop
)),
241 ("polys",POINTER(cxr_polygon
)),
242 ("materials",POINTER(cxr_material
)),
244 ("poly_count",c_int32
),
245 ("vertex_count",c_int32
),
246 ("edge_count",c_int32
),
247 ("loop_count",c_int32
),
248 ("material_count",c_int32
)]
250 class cxr_tri_mesh(Structure
):
251 _fields_
= [("vertices",POINTER(c_double
*3)),
252 ("colours",POINTER(c_double
*4)),
253 ("indices",POINTER(c_int32
)),
254 ("indices_count",c_int32
),
255 ("vertex_count",c_int32
)]
257 class cxr_vmf_context(Structure
):
258 _fields_
= [("mapversion",c_int32
),
259 ("skyname",c_char_p
),
260 ("detailvbsp",c_char_p
),
261 ("detailmaterial",c_char_p
),
263 ("offset",c_double
*3),
264 ("lightmap_scale",c_int32
),
265 ("brush_count",c_int32
),
266 ("entity_count",c_int32
),
267 ("face_count",c_int32
)]
269 # Convert blenders mesh format into CXR's static format (they are very similar)
271 def mesh_cxr_format(obj
):
274 if bpy
.context
.active_object
!= None:
275 orig_state
= obj
.mode
276 if orig_state
!= 'OBJECT':
277 bpy
.ops
.object.mode_set(mode
='OBJECT')
279 dgraph
= bpy
.context
.evaluated_depsgraph_get()
280 data
= obj
.evaluated_get(dgraph
).data
282 _
,mtx_rot
,_
= obj
.matrix_world
.decompose()
284 mesh
= cxr_static_mesh()
286 vertex_data
= ((c_double
*3)*len(data
.vertices
))()
287 for i
, vert
in enumerate(data
.vertices
):
288 v
= obj
.matrix_world
@ vert
.co
289 vertex_data
[i
][0] = c_double(v
[0])
290 vertex_data
[i
][1] = c_double(v
[1])
291 vertex_data
[i
][2] = c_double(v
[2])
293 loop_data
= (cxr_static_loop
*len(data
.loops
))()
294 polygon_data
= (cxr_polygon
*len(data
.polygons
))()
296 for i
, poly
in enumerate(data
.polygons
):
297 loop_start
= poly
.loop_start
298 loop_end
= poly
.loop_start
+ poly
.loop_total
299 for loop_index
in range(loop_start
, loop_end
):
300 loop
= data
.loops
[loop_index
]
301 loop_data
[loop_index
].index
= loop
.vertex_index
302 loop_data
[loop_index
].edge_index
= loop
.edge_index
305 uv
= data
.uv_layers
.active
.data
[loop_index
].uv
306 loop_data
[loop_index
].uv
[0] = c_double(uv
[0])
307 loop_data
[loop_index
].uv
[1] = c_double(uv
[1])
309 loop_data
[loop_index
].uv
[0] = c_double(0.0)
310 loop_data
[loop_index
].uv
[1] = c_double(0.0)
311 center
= obj
.matrix_world
@ poly
.center
312 normal
= mtx_rot
@ poly
.normal
314 polygon_data
[i
].loop_start
= poly
.loop_start
315 polygon_data
[i
].loop_total
= poly
.loop_total
316 polygon_data
[i
].normal
[0] = normal
[0]
317 polygon_data
[i
].normal
[1] = normal
[1]
318 polygon_data
[i
].normal
[2] = normal
[2]
319 polygon_data
[i
].center
[0] = center
[0]
320 polygon_data
[i
].center
[1] = center
[1]
321 polygon_data
[i
].center
[2] = center
[2]
322 polygon_data
[i
].material_id
= poly
.material_index
324 edge_data
= (cxr_edge
*len(data
.edges
))()
326 for i
, edge
in enumerate(data
.edges
):
327 edge_data
[i
].i0
= edge
.vertices
[0]
328 edge_data
[i
].i1
= edge
.vertices
[1]
329 edge_data
[i
].freestyle
= edge
.use_freestyle_mark
331 material_data
= (cxr_material
*len(obj
.material_slots
))()
333 for i
, ms
in enumerate(obj
.material_slots
):
334 inf
= material_info(ms
.material
)
335 material_data
[i
].res
[0] = inf
['res'][0]
336 material_data
[i
].res
[1] = inf
['res'][1]
337 material_data
[i
].name
= inf
['name'].encode('utf-8')
339 mesh
.edges
= cast(edge_data
, POINTER(cxr_edge
))
340 mesh
.vertices
= cast(vertex_data
, POINTER(c_double
*3))
341 mesh
.loops
= cast(loop_data
,POINTER(cxr_static_loop
))
342 mesh
.polys
= cast(polygon_data
, POINTER(cxr_polygon
))
343 mesh
.materials
= cast(material_data
, POINTER(cxr_material
))
345 mesh
.poly_count
= len(data
.polygons
)
346 mesh
.vertex_count
= len(data
.vertices
)
347 mesh
.edge_count
= len(data
.edges
)
348 mesh
.loop_count
= len(data
.loops
)
349 mesh
.material_count
= len(obj
.material_slots
)
351 if orig_state
!= None:
352 bpy
.ops
.object.mode_set(mode
=orig_state
)
356 # Callback ctypes indirection things.. not really sure.
357 c_libcxr_log_callback
= None
358 c_libcxr_line_callback
= None
361 # -------------------------------------------------------------
362 libcxr_decompose
= extern( "cxr_decompose",
363 [POINTER(cxr_static_mesh
), POINTER(c_int32
)],
366 libcxr_free_world
= extern( "cxr_free_world",
370 libcxr_write_test_data
= extern( "cxr_write_test_data",
371 [POINTER(cxr_static_mesh
)],
374 libcxr_world_preview
= extern( "cxr_world_preview",
376 POINTER(cxr_tri_mesh
)
378 libcxr_free_tri_mesh
= extern( "cxr_free_tri_mesh",
382 libcxr_begin_vmf
= extern( "cxr_begin_vmf",
383 [POINTER(cxr_vmf_context
), c_void_p
],
386 libcxr_vmf_begin_entities
= extern( "cxr_vmf_begin_entities",
387 [POINTER(cxr_vmf_context
), c_void_p
],
390 libcxr_push_world_vmf
= extern("cxr_push_world_vmf",
391 [c_void_p
,POINTER(cxr_vmf_context
),c_void_p
],
394 libcxr_end_vmf
= extern( "cxr_end_vmf",
395 [POINTER(cxr_vmf_context
),c_void_p
],
399 # VDF + with open wrapper
400 libcxr_vdf_open
= extern( "cxr_vdf_open", [c_char_p
], c_void_p
)
401 libcxr_vdf_close
= extern( "cxr_vdf_close", [c_void_p
], None )
402 libcxr_vdf_put
= extern( "cxr_vdf_put", [c_void_p
,c_char_p
], None )
403 libcxr_vdf_node
= extern( "cxr_vdf_node", [c_void_p
,c_char_p
], None )
404 libcxr_vdf_edon
= extern( "cxr_vdf_edon", [c_void_p
], None )
405 libcxr_vdf_kv
= extern( "cxr_vdf_kv", [c_void_p
,c_char_p
,c_char_p
], None )
407 class vdf_structure():
408 def __init__(_
,path
):
411 _
.fp
= libcxr_vdf_open
.call( _
.path
.encode('utf-8') )
413 print( F
"Could not open file {_.path}" )
416 def __exit__(_
,type,value
,traceback
):
418 libcxr_vdf_close
.call(_
.fp
)
420 libcxr_vdf_put
.call(_
.fp
, s
.encode('utf-8') )
422 libcxr_vdf_node
.call(_
.fp
, name
.encode('utf-8') )
424 libcxr_vdf_edon
.call(_
.fp
)
426 libcxr_vdf_kv
.call(_
.fp
, k
.encode('utf-8'), v
.encode('utf-8'))
429 libcxr_lightpatch_bsp
= extern( "cxr_lightpatch_bsp", [c_char_p
], None )
431 libcxr_funcs
= [ libcxr_decompose
, libcxr_free_world
, libcxr_begin_vmf
, \
432 libcxr_vmf_begin_entities
, libcxr_push_world_vmf
, \
433 libcxr_end_vmf
, libcxr_vdf_open
, libcxr_vdf_close
, \
434 libcxr_vdf_put
, libcxr_vdf_node
, libcxr_vdf_edon
,
435 libcxr_vdf_kv
, libcxr_lightpatch_bsp
, libcxr_write_test_data
,\
436 libcxr_world_preview
, libcxr_free_tri_mesh
]
440 def libcxr_log_callback(logStr
):
441 print( F
"{logStr.decode('utf-8')}",end
='' )
443 cxr_line_positions
= None
444 cxr_line_colours
= None
446 def cxr_reset_lines():
447 global cxr_line_positions
, cxr_line_colours
449 cxr_line_positions
= []
450 cxr_line_colours
= []
452 def cxr_batch_lines():
453 global cxr_line_positions
, cxr_line_colours
, cxr_view_shader
, cxr_view_lines
455 cxr_view_lines
= batch_for_shader(\
456 cxr_view_shader
, 'LINES',\
457 { "pos": cxr_line_positions
, "color": cxr_line_colours
})
459 def libcxr_line_callback( p0
,p1
,colour
):
460 global cxr_line_colours
, cxr_line_positions
462 cxr_line_positions
+= [(p0
[0],p0
[1],p0
[2])]
463 cxr_line_positions
+= [(p1
[0],p1
[1],p1
[2])]
464 cxr_line_colours
+= [(colour
[0],colour
[1],colour
[2],colour
[3])]
465 cxr_line_colours
+= [(colour
[0],colour
[1],colour
[2],colour
[3])]
468 # ------------------------------------------------------------------------------
473 NBVTF_IMAGE_FORMAT_ABGR8888
= 1
474 NBVTF_IMAGE_FORMAT_BGR888
= 3
475 NBVTF_IMAGE_FORMAT_DXT1
= 13
476 NBVTF_IMAGE_FORMAT_DXT5
= 15
477 NBVTF_TEXTUREFLAGS_CLAMPS
= 0x00000004
478 NBVTF_TEXTUREFLAGS_CLAMPT
= 0x00000008
479 NBVTF_TEXTUREFLAGS_NORMAL
= 0x00000080
480 NBVTF_TEXTUREFLAGS_NOMIP
= 0x00000100
481 NBVTF_TEXTUREFLAGS_NOLOD
= 0x00000200
483 libnbvtf_convert
= extern( "nbvtf_convert", \
484 [c_char_p
,c_int32
,c_int32
,c_int32
,c_int32
,c_int32
,c_uint32
,c_char_p
], \
487 libnbvtf_init
= extern( "nbvtf_init", [], None )
488 libnbvtf_funcs
= [ libnbvtf_convert
, libnbvtf_init
]
491 # --------------------------
494 global libcxr
, libnbvtf
, libcxr_funcs
, libnbvtf_funcs
496 # Unload libraries if existing
497 def _reload( lib
, path
):
499 _handle
= lib
._handle
500 for i
in range(10): libc_dlclose( _handle
)
503 return cdll
.LoadLibrary( F
'{os.path.dirname(__file__)}/{path}.so' )
505 libnbvtf
= _reload( libnbvtf
, "libnbvtf" )
506 libcxr
= _reload( libcxr
, "libcxr" )
508 for fd
in libnbvtf_funcs
:
509 fd
.loadfrom( libnbvtf
)
512 for fd
in libcxr_funcs
:
513 fd
.loadfrom( libcxr
)
516 global c_libcxr_log_callback
, c_libcxr_line_callback
518 LOG_FUNCTION_TYPE
= CFUNCTYPE(None,c_char_p
)
519 c_libcxr_log_callback
= LOG_FUNCTION_TYPE(libcxr_log_callback
)
521 LINE_FUNCTION_TYPE
= CFUNCTYPE(None,\
522 POINTER(c_double
), POINTER(c_double
), POINTER(c_double
))
523 c_libcxr_line_callback
= LINE_FUNCTION_TYPE(libcxr_line_callback
)
525 libcxr
.cxr_set_log_function(cast(c_libcxr_log_callback
,c_void_p
))
526 libcxr
.cxr_set_line_function(cast(c_libcxr_line_callback
,c_void_p
))
528 build_time
= c_char_p
.in_dll(libcxr
,'cxr_build_time')
529 print( F
"libcxr build time: {build_time.value}" )
534 # ------------------------------------------------------------------------------
536 # Standard entity functions, think of like base.fgd
538 def cxr_get_origin(context
):
539 return context
['object'].location
* context
['transform']['scale'] + \
540 mathutils
.Vector(context
['transform']['offset'])
542 def cxr_get_angles(context
):
543 obj
= context
['object']
544 euler
= [ a
*57.295779513 for a
in obj
.rotation_euler
]
551 def cxr_baseclass(classes
, other
):
554 base
.update(x
.copy())
557 # EEVEE Light component converter -> Source 1
559 def ent_lights(context
):
560 obj
= context
['object']
561 kvs
= cxr_baseclass([ent_origin
],\
563 "_distance": (0.0 if obj
.data
.cxr_data
.realtime
else -1.0),
564 "_lightHDR": '-1 -1 -1 1',
568 light_base
= [(pow(obj
.data
.color
[i
],1.0/2.2)*255.0) for i
in range(3)] +\
569 [obj
.data
.energy
* bpy
.context
.scene
.cxr_data
.light_scale
]
571 if obj
.data
.type == 'SPOT' or obj
.data
.type == 'SUN':
572 # Blenders directional lights are -z forward
573 # Source is +x, however, it seems to use a completely different system.
574 # Since we dont care about roll for spotlights, we just take the
575 # pitch and yaw via trig
577 _
,mtx_rot
,_
= obj
.matrix_world
.decompose()
578 fwd
= mtx_rot
@ mathutils
.Vector((0,0,-1))
579 dir_pitch
= math
.asin(fwd
[2]) * 57.295779513
580 dir_yaw
= math
.atan2(fwd
[1],fwd
[0]) * 57.295779513
582 if obj
.data
.type == 'SPOT':
583 kvs
['_light'] = [ int(x
) for x
in light_base
]
584 kvs
['_cone'] = obj
.data
.spot_size
*(57.295779513/2.0)
585 kvs
['_inner_cone'] = (1.0-obj
.data
.spot_blend
)*kvs
['_cone']
587 kvs
['pitch'] = dir_pitch
588 kvs
['angles'] = [ 0, dir_yaw
, 0 ]
589 kvs
['_quadratic_attn'] = 0.0 # Source spotlights + quadratic falloff look
592 # Blender's default has a much more 'nice'
594 kvs
['_linear_attn'] = 1.0
596 elif obj
.data
.type == 'POINT':
597 kvs
['_light'] = [ int(x
) for x
in light_base
]
598 kvs
['_quadratic_attn'] = 1.0
599 kvs
['_linear_attn'] = 0.0
601 elif obj
.data
.type == 'SUN':
602 light_base
[3] *= 300.0 * 5
603 kvs
['_light'] = [ int(x
) for x
in light_base
]
605 ambient
= bpy
.context
.scene
.world
.color
606 kvs
['_ambient'] = [int(pow(ambient
[i
],1.0/2.2)*255.0) for i
in range(3)] +\
608 kvs
['_ambientHDR'] = [-1,-1,-1,1]
609 kvs
['_AmbientScaleHDR'] = 1
610 kvs
['pitch'] = dir_pitch
611 kvs
['angles'] = [ dir_pitch
, dir_yaw
, 0.0 ]
612 kvs
['SunSpreadAngle'] = 0
616 def ent_prop(context
):
618 if isinstance( context
['object'], bpy
.types
.Collection
):
619 kvs
['angles'] = [0,180,0]
620 kvs
['enablelightbounce'] = 1
621 kvs
['disableshadows'] = 0
622 kvs
['fademindist'] = -1
624 kvs
['model'] = F
"{asset_path('models',context['object'])}.mdl".lower()
625 kvs
['renderamt'] = 255
626 kvs
['rendercolor'] = [255, 255, 255]
629 kvs
['uniformscale'] = 1.0
631 pos
= mathutils
.Vector(context
['origin'])
632 pos
+= mathutils
.Vector(context
['transform']['offset'])
634 kvs
['origin'] = [pos
[1],-pos
[0],pos
[2]]
638 def ent_sky_camera(context
):
639 settings
= bpy
.context
.scene
.cxr_data
640 scale
= settings
.scale_factor
/ settings
.skybox_scale_factor
643 "origin": [_
for _
in context
['transform']['offset']],
644 "angles": [ 0, 0, 0 ],
645 "fogcolor": [255, 255, 255],
646 "fogcolor2": [255, 255, 255],
651 "HDRColorScale": 1.0,
656 def ent_cubemap(context
):
657 obj
= context
['object']
658 return cxr_baseclass([ent_origin
], {"cubemapsize": obj
.data
.cxr_data
.size
})
660 ent_origin
= { "origin": cxr_get_origin
}
661 ent_angles
= { "angles": cxr_get_angles
}
662 ent_transform
= cxr_baseclass( [ent_origin
], ent_angles
)
664 #include the user config
665 exec(open(F
'{os.path.dirname(__file__)}/config.py').read())
667 # Blender state callbacks
668 # ------------------------------------------------------------------------------
671 def cxr_on_load(dummy
):
672 global cxr_view_lines
, cxr_view_mesh
674 cxr_view_lines
= None
678 def cxr_dgraph_update(scene
,dgraph
):
680 print( F
"Hallo {time.time()}" )
682 # Convexer compilation functions
683 # ------------------------------------------------------------------------------
685 # Asset path management
687 def asset_uid(asset
):
688 if isinstance(asset
,str):
691 # Create a unique ID string
693 v
= asset
.cxr_data
.asset_id
702 dig
.append( int( v
% len(base
) ) )
708 if bpy
.context
.scene
.cxr_data
.include_names
:
709 name
+= asset
.name
.replace('.','_')
713 # -> <project_name>/<asset_name>
714 def asset_name(asset
):
715 return F
"{bpy.context.scene.cxr_data.project_name}/{asset_uid(asset)}"
717 # -> <subdir>/<project_name>/<asset_name>
718 def asset_path(subdir
, asset
):
719 return F
"{subdir}/{asset_name(asset_uid(asset))}"
721 # -> <csgo>/<subdir>/<project_name>/<asset_name>
722 def asset_full_path(sdir
,asset
):
723 return F
"{bpy.context.scene.cxr_data.subdir}/"+\
724 F
"{asset_path(sdir,asset_uid(asset))}"
726 # Entity functions / infos
727 # ------------------------
729 def cxr_intrinsic_classname(obj
):
730 if obj
.type == 'LIGHT':
732 'SPOT': "light_spot",
734 'SUN': "light_environment" }[ obj
.data
.type ]
736 elif obj
.type == 'LIGHT_PROBE':
738 elif obj
.type == 'EMPTY':
744 def cxr_custom_class(obj
):
745 if obj
.type == 'MESH': custom_class
= obj
.cxr_data
.brushclass
746 else: custom_class
= obj
.cxr_data
.classname
750 def cxr_classname(obj
):
751 intr
= cxr_intrinsic_classname(obj
)
752 if intr
!= None: return intr
754 custom_class
= cxr_custom_class(obj
)
755 if custom_class
!= 'NONE':
761 # intinsic: (k, False, value)
762 # property: (k, True, value or default)
766 def cxr_entity_keyvalues(context
):
767 classname
= context
['classname']
768 obj
= context
['object']
769 if classname
not in cxr_entities
: return None
773 entdef
= cxr_entities
[classname
]
774 kvs
= entdef
['keyvalues']
776 if callable(kvs
): kvs
= kvs(context
)
783 if isinstance(kv
,dict):
785 value
= obj
[ F
"cxrkv_{k}" ]
790 if isinstance(value
,mathutils
.Vector
):
791 value
= [_
for _
in value
]
793 result
+= [(k
, isprop
, value
)]
797 # Extract material information from shader graph data
799 def material_info(mat
):
801 info
['res'] = (512,512)
802 info
['name'] = 'tools/toolsnodraw'
804 if mat
== None or mat
.use_nodes
== False:
808 if mat
.cxr_data
.shader
== 'Builtin':
809 info
['name'] = mat
.name
813 info
['name'] = asset_name(mat
)
815 # Using the cxr_graph_mapping as a reference, go through the shader
816 # graph and gather all $props from it.
818 def _graph_read( node_def
, node
=None, depth
=0 ):
822 def _variant_apply( val
):
825 if isinstance( val
, str ):
828 for shader_variant
in val
:
829 if shader_variant
[0] == mat
.cxr_data
.shader
:
830 return shader_variant
[1]
834 _graph_read
.extracted
= []
836 for node_idname
in node_def
:
837 for n
in mat
.node_tree
.nodes
:
838 if n
.bl_idname
== node_idname
:
839 node_def
= node_def
[node_idname
]
843 for link
in node_def
:
844 if isinstance( node_def
[link
], dict ):
845 inputt
= node
.inputs
[link
]
846 inputt_def
= node_def
[link
]
850 # look for definitions for the connected node type
851 con
= inputt
.links
[0].from_node
853 for node_idname
in inputt_def
:
854 if con
.bl_idname
== node_idname
:
855 con_def
= inputt_def
[ node_idname
]
856 _graph_read( con_def
, con
, depth
+1 )
858 # No definition found! :(
859 # TODO: Make a warning for this?
862 if "default" in inputt_def
:
863 prop
= _variant_apply( inputt_def
['default'] )
864 info
[prop
] = inputt
.default_value
866 prop
= _variant_apply( node_def
[link
] )
867 info
[prop
] = getattr(node
,link
)
869 _graph_read(cxr_graph_mapping
)
871 if "$basetexture" in info
:
872 export_res
= info
['$basetexture'].cxr_data
.export_res
873 info
['res'] = (export_res
[0], export_res
[1])
877 def vec3_min( a
, b
):
878 return mathutils
.Vector((min(a
[0],b
[0]),min(a
[1],b
[1]),min(a
[2],b
[2])))
879 def vec3_max( a
, b
):
880 return mathutils
.Vector((max(a
[0],b
[0]),max(a
[1],b
[1]),max(a
[2],b
[2])))
882 def cxr_collection_center(collection
, transform
):
884 bounds_min
= mathutils
.Vector((BIG
,BIG
,BIG
))
885 bounds_max
= mathutils
.Vector((-BIG
,-BIG
,-BIG
))
887 for obj
in collection
.objects
:
888 if obj
.type == 'MESH':
889 corners
= [ mathutils
.Vector(c
) for c
in obj
.bound_box
]
891 for corner
in [ obj
.matrix_world
@c for c
in corners
]:
892 bounds_min
= vec3_min( bounds_min
, corner
)
893 bounds_max
= vec3_max( bounds_max
, corner
)
895 center
= (bounds_min
+ bounds_max
) / 2.0
897 origin
= mathutils
.Vector((-center
[1],center
[0],center
[2]))
898 origin
*= transform
['scale']
902 # Prepares Scene into dictionary format
904 def cxr_scene_collect():
905 context
= bpy
.context
907 # Make sure all of our asset types have a unique ID
908 def _uid_prepare(objtype
):
914 if vs
.asset_id
in used_ids
:
917 id_max
= max(id_max
,vs
.asset_id
)
918 used_ids
+=[vs
.asset_id
]
919 for vs
in to_generate
:
922 _uid_prepare(bpy
.data
.materials
)
923 _uid_prepare(bpy
.data
.images
)
924 _uid_prepare(bpy
.data
.collections
)
927 "entities": [], # Everything with a classname
928 "geo": [], # All meshes without a classname
929 "heros": [] # Collections prefixed with mdl_
932 def _collect(collection
,transform
):
935 if collection
.name
.startswith('.'): return
936 if collection
.hide_render
: return
938 if collection
.name
.startswith('mdl_'):
939 sceneinfo
['entities'] += [{
940 "object": collection
,
941 "classname": "prop_static",
942 "transform": transform
,
943 "origin": cxr_collection_center( collection
, transform
)
946 sceneinfo
['heros'] += [{
947 "collection": collection
,
948 "transform": transform
,
949 "origin": cxr_collection_center( collection
, transform
)
953 for obj
in collection
.objects
:
954 if obj
.hide_get(): continue
956 classname
= cxr_classname( obj
)
958 if classname
!= None:
959 sceneinfo
['entities'] += [{
961 "classname": classname
,
962 "transform": transform
964 elif obj
.type == 'MESH':
965 sceneinfo
['geo'] += [{
967 "transform": transform
970 for c
in collection
.children
:
971 _collect( c
, transform
)
974 "scale": context
.scene
.cxr_data
.scale_factor
,
979 "scale": context
.scene
.cxr_data
.skybox_scale_factor
,
980 "offset": (0,0,context
.scene
.cxr_data
.skybox_offset
)
983 if 'main' in bpy
.data
.collections
:
984 _collect( bpy
.data
.collections
['main'], transform_main
)
986 if 'skybox' in bpy
.data
.collections
:
987 _collect( bpy
.data
.collections
['skybox'], transform_sky
)
989 sceneinfo
['entities'] += [{
991 "transform": transform_sky
,
992 "classname": "sky_camera"
997 # Write VMF out to file (JOB HANDLER)
999 def cxr_export_vmf(sceneinfo
, output_vmf
):
1002 with
vdf_structure(output_vmf
) as m
:
1003 print( F
"Write: {output_vmf}" )
1005 vmfinfo
= cxr_vmf_context()
1006 vmfinfo
.mapversion
= 4
1008 #TODO: These need to be in options...
1009 vmfinfo
.skyname
= bpy
.context
.scene
.cxr_data
.skyname
.encode('utf-8')
1010 vmfinfo
.detailvbsp
= b
"detail.vbsp"
1011 vmfinfo
.detailmaterial
= b
"detail/detailsprites"
1012 vmfinfo
.lightmap_scale
= 12
1014 vmfinfo
.brush_count
= 0
1015 vmfinfo
.entity_count
= 0
1016 vmfinfo
.face_count
= 0
1018 libcxr_begin_vmf
.call( pointer(vmfinfo
), m
.fp
)
1020 def _buildsolid( cmd
):
1023 print( F
"{vmfinfo.brush_count} :: {cmd['object'].name}" )
1025 baked
= mesh_cxr_format( cmd
['object'] )
1026 world
= libcxr_decompose
.call( baked
, None )
1031 vmfinfo
.scale
= cmd
['transform']['scale']
1033 offset
= cmd
['transform']['offset']
1034 vmfinfo
.offset
[0] = offset
[0]
1035 vmfinfo
.offset
[1] = offset
[1]
1036 vmfinfo
.offset
[2] = offset
[2]
1038 libcxr_push_world_vmf
.call( world
, pointer(vmfinfo
), m
.fp
)
1039 libcxr_free_world
.call( world
)
1044 for brush
in sceneinfo
['geo']:
1045 if not _buildsolid( brush
):
1050 libcxr_vmf_begin_entities
.call(pointer(vmfinfo
), m
.fp
)
1053 for ent
in sceneinfo
['entities']:
1055 ctx
= ent
['transform']
1056 cls
= ent
['classname']
1059 m
.kv( 'classname', cls
)
1061 kvs
= cxr_entity_keyvalues( ent
)
1064 if isinstance(kv
[2], list):
1065 m
.kv( kv
[0], ' '.join([str(_
) for _
in kv
[2]]) )
1066 else: m
.kv( kv
[0], str(kv
[2]) )
1070 elif not isinstance( obj
, bpy
.types
.Collection
):
1071 if obj
.type == 'MESH':
1072 if not _buildsolid( ent
):
1082 # COmpile image using NBVTF and hash it (JOB HANDLER)
1084 def compile_image(img
):
1088 name
= asset_name(img
)
1089 src_path
= bpy
.path
.abspath(img
.filepath
)
1091 dims
= img
.cxr_data
.export_res
1093 'RGBA': NBVTF_IMAGE_FORMAT_ABGR8888
,
1094 'DXT1': NBVTF_IMAGE_FORMAT_DXT1
,
1095 'DXT5': NBVTF_IMAGE_FORMAT_DXT5
,
1096 'RGB': NBVTF_IMAGE_FORMAT_BGR888
1097 }[ img
.cxr_data
.fmt
]
1099 mipmap
= img
.cxr_data
.mipmap
1100 lod
= img
.cxr_data
.lod
1101 clamp
= img
.cxr_data
.clamp
1102 flags
= img
.cxr_data
.flags
1104 q
=bpy
.context
.scene
.cxr_data
.image_quality
1106 userflag_hash
= F
"{mipmap}.{lod}.{clamp}.{flags}"
1107 file_hash
= F
"{name}.{os.path.getmtime(src_path)}"
1108 comphash
= F
"{file_hash}.{dims[0]}.{dims[1]}.{fmt}.{userflag_hash}.{q}"
1110 if img
.cxr_data
.last_hash
!= comphash
:
1111 print( F
"Texture update: {img.filepath}" )
1113 src
= src_path
.encode('utf-8')
1114 dst
= (asset_full_path('materials',img
)+'.vtf').encode('utf-8')
1118 # texture setting flags
1119 if not lod
: flags_full |
= NBVTF_TEXTUREFLAGS_NOLOD
1121 flags_full |
= NBVTF_TEXTUREFLAGS_CLAMPS
1122 flags_full |
= NBVTF_TEXTUREFLAGS_CLAMPT
1124 if libnbvtf_convert
.call(src
,dims
[0],dims
[1],mipmap
,fmt
,q
,flags_full
,dst
):
1125 img
.cxr_data
.last_hash
= comphash
1130 # Compile a material to VMT format. This is quick to run, doesnt need to be a
1133 def compile_material(mat
):
1134 info
= material_info(mat
)
1135 properties
= mat
.cxr_data
1137 print( F
"Compile {asset_full_path('materials',mat)}.vmt" )
1138 if properties
.shader
== 'Builtin':
1143 # Walk the property tree
1144 def _mlayer( layer
):
1145 nonlocal properties
, props
1148 if isinstance(layer
[decl
],dict): # $property definition
1150 ptype
= pdef
['type']
1156 if 'shaders' in pdef
and properties
.shader
not in pdef
['shaders']:
1159 # Group expansion (does it have subdefinitions?)
1161 if isinstance(pdef
[ch
],dict):
1170 if ptype
== 'intrinsic':
1174 prop
= getattr(properties
,decl
)
1175 default
= pdef
['default']
1177 if not isinstance(prop
,str) and \
1178 not isinstance(prop
,bpy
.types
.Image
) and \
1179 hasattr(prop
,'__getitem__'):
1180 prop
= tuple([p
for p
in prop
])
1184 props
+= [(decl
,pdef
,prop
)]
1189 if expandview
: _mlayer(pdef
)
1191 _mlayer( cxr_shader_params
)
1194 with
vdf_structure( F
"{asset_full_path('materials',mat)}.vmt" ) as vmt
:
1195 vmt
.node( properties
.shader
)
1196 vmt
.put( "// Convexer export\n" )
1205 if 'exponent' in pdef
: return str(pow( v
, pdef
['exponent'] ))
1208 if isinstance(prop
,bpy
.types
.Image
):
1209 vmt
.kv( decl
, asset_name(prop
))
1210 elif isinstance(prop
,bool):
1211 vmt
.kv( decl
, '1' if prop
else '0' )
1212 elif isinstance(prop
,str):
1213 vmt
.kv( decl
, prop
)
1214 elif isinstance(prop
,float) or isinstance(prop
,int):
1215 vmt
.kv( decl
, _numeric(prop
) )
1216 elif isinstance(prop
,tuple):
1217 vmt
.kv( decl
, F
"[{' '.join([_numeric(_) for _ in prop])}]" )
1219 vmt
.put( F
"// (cxr) unkown shader value type'{type(prop)}'" )
1224 def cxr_export_modelsrc( mdl
, origin
, asset_dir
, project_name
, transform
):
1225 dgraph
= bpy
.context
.evaluated_depsgraph_get()
1227 # Compute hash value
1228 chash
= asset_uid(mdl
)+str(origin
)+str(transform
)
1230 #for obj in mdl.objects:
1231 # if obj.type != 'MESH':
1234 # ev = obj.evaluated_get(dgraph).data
1235 # srcverts=[(v.co[0],v.co[1],v.co[2]) for v in ev.vertices]
1236 # srcloops=[(l.normal[0],l.normal[1],l.normal[2]) for l in ev.loops]
1238 # chash=hashlib.sha224((str(srcverts)+chash).encode()).hexdigest()
1239 # chash=hashlib.sha224((str(srcloops)+chash).encode()).hexdigest()
1241 # if ev.uv_layers.active != None:
1242 # uv_layer = ev.uv_layers.active.data
1243 # srcuv=[(uv.uv[0],uv.uv[1]) for uv in uv_layer]
1247 # chash=hashlib.sha224((str(srcuv)+chash).encode()).hexdigest()
1248 # srcmats=[ ms.material.name for ms in obj.material_slots ]
1249 # chash=hashlib.sha224((str(srcmats)+chash).encode()).hexdigest()
1250 # transforms=[ obj.location, obj.rotation_euler, obj.scale ]
1251 # srctr=[(v[0],v[1],v[2]) for v in transforms]
1252 # chash=hashlib.sha224((str(srctr)+chash).encode()).hexdigest()
1254 #if chash != mdl.cxr_data.last_hash:
1255 # mdl.cxr_data.last_hash = chash
1256 # print( F"Compile: {mdl.name}" )
1260 bpy
.ops
.object.select_all(action
='DESELECT')
1263 def _get_layer(col
,name
):
1264 for c
in col
.children
:
1267 sub
= _get_layer(c
,name
)
1271 layer
= _get_layer(bpy
.context
.view_layer
.layer_collection
,mdl
.name
)
1273 prev_state
= layer
.hide_viewport
1274 layer
.hide_viewport
=False
1276 # Collect materials to be compiled, and temp rename for export
1280 for obj
in mdl
.objects
:
1281 if obj
.name
== F
"{mdl.name}_phy":
1285 obj
.select_set(state
=True)
1286 for ms
in obj
.material_slots
:
1287 if ms
.material
!= None:
1288 if ms
.material
not in mat_dict
:
1289 mat_dict
[ms
.material
] = ms
.material
.name
1290 ms
.material
.name
= asset_uid(ms
.material
)
1291 ms
.material
.use_nodes
= False
1294 bpy
.ops
.export_scene
.fbx( filepath
=F
'{asset_dir}/{uid}_ref.fbx',\
1295 check_existing
=False,
1297 apply_unit_scale
=False,
1298 bake_space_transform
=False
1301 bpy
.ops
.object.select_all(action
='DESELECT')
1304 vphys
.select_set(state
=True)
1305 bpy
.ops
.export_scene
.fbx( filepath
=F
'{asset_dir}/{uid}_phy.fbx',\
1306 check_existing
=False,
1308 apply_unit_scale
=False,
1309 bake_space_transform
=False
1311 bpy
.ops
.object.select_all(action
='DESELECT')
1313 # Fix material names back to original
1314 for mat
in mat_dict
:
1315 mat
.name
= mat_dict
[mat
]
1316 mat
.use_nodes
= True
1318 layer
.hide_viewport
=prev_state
1321 with
open(F
'{asset_dir}/{uid}.qc','w') as o
:
1322 o
.write(F
'$modelname "{project_name}/{uid}"\n')
1323 #o.write(F'$scale .32\n')
1324 o
.write(F
'$scale {transform["scale"]/100.0}\n')
1325 o
.write(F
'$body _ "{uid}_ref.fbx"\n')
1326 o
.write(F
'$staticprop\n')
1327 o
.write(F
'$origin {origin[0]} {origin[1]} {origin[2]}\n')
1330 o
.write(F
'$collisionmodel "{uid}_phy.fbx"\n')
1332 o
.write(" $concave\n")
1335 o
.write(F
'$cdmaterials {project_name}\n')
1336 o
.write(F
'$sequence idle {uid}_ref.fbx\n')
1340 # Copy bsp file (and also lightpatch it)
1342 def cxr_patchmap( src
, dst
):
1343 libcxr_lightpatch_bsp
.call( src
.encode('utf-8') )
1344 shutil
.copyfile( src
, dst
)
1347 # Convexer operators
1348 # ------------------------------------------------------------------------------
1350 # Force reload of shared libraries
1352 class CXR_RELOAD(bpy
.types
.Operator
):
1353 bl_idname
="convexer.reload"
1355 def execute(_
,context
):
1359 # Used for exporting data to use with ASAN builds
1361 class CXR_DEV_OPERATOR(bpy
.types
.Operator
):
1362 bl_idname
="convexer.dev_test"
1363 bl_label
="Export development data"
1365 def execute(_
,context
):
1366 # Prepare input data
1367 mesh_src
= mesh_cxr_format(context
.active_object
)
1368 libcxr_write_test_data
.call( pointer(mesh_src
) )
1371 # UI: Preview how the brushes will looks in 3D view
1373 class CXR_PREVIEW_OPERATOR(bpy
.types
.Operator
):
1374 bl_idname
="convexer.preview"
1375 bl_label
="Preview Brushes"
1380 def execute(_
,context
):
1383 def modal(_
,context
,event
):
1384 global cxr_view_mesh
1385 static
= _
.__class
__
1387 if event
.type == 'ESC':
1390 cxr_view_mesh
= None
1391 static
.RUNNING
= False
1396 return {'PASS_THROUGH'}
1398 def invoke(_
,context
,event
):
1399 global cxr_view_shader
, cxr_view_mesh
1400 static
= _
.__class
__
1401 static
.LASTERR
= None
1405 mesh_src
= mesh_cxr_format(context
.active_object
)
1408 world
= libcxr_decompose
.call( mesh_src
, pointer(err
) )
1411 cxr_view_mesh
= None
1415 static
.LASTERR
= ["There is no error", \
1421 "Non-Convex Polygon",\
1426 return {'CANCELLED'}
1428 context
.window_manager
.modal_handler_add(_
)
1429 return {'RUNNING_MODAL'}
1431 # Generate preview using cxr
1433 ptrpreview
= libcxr_world_preview
.call( world
)
1434 preview
= ptrpreview
[0]
1436 vertices
= preview
.vertices
[:preview
.vertex_count
]
1437 vertices
= [(_
[0],_
[1],_
[2]) for _
in vertices
]
1439 colours
= preview
.colours
[:preview
.vertex_count
]
1440 colours
= [(_
[0],_
[1],_
[2],_
[3]) for _
in colours
]
1442 indices
= preview
.indices
[:preview
.indices_count
]
1443 indices
= [ (indices
[i
*3+0],indices
[i
*3+1],indices
[i
*3+2]) \
1444 for i
in range(int(preview
.indices_count
/3)) ]
1446 cxr_view_mesh
= batch_for_shader(
1447 cxr_view_shader
, 'TRIS',
1448 { "pos": vertices
, "color": colours
},
1452 libcxr_free_tri_mesh
.call( ptrpreview
)
1453 libcxr_free_world
.call( world
)
1457 # Allow user to spam the operator
1459 return {'CANCELLED'}
1461 if not static
.RUNNING
:
1462 static
.RUNNING
= True
1463 context
.window_manager
.modal_handler_add(_
)
1464 return {'RUNNING_MODAL'}
1466 # Search for VMF compiler executables in subdirectory
1468 class CXR_DETECT_COMPILERS(bpy
.types
.Operator
):
1469 bl_idname
="convexer.detect_compilers"
1470 bl_label
="Find compilers"
1472 def execute(self
,context
):
1473 scene
= context
.scene
1474 settings
= scene
.cxr_data
1475 subdir
= settings
.subdir
1477 for exename
in ['studiomdl','vbsp','vvis','vrad']:
1478 searchpath
= os
.path
.normpath(F
'{subdir}/../bin/{exename}.exe')
1479 if os
.path
.exists(searchpath
):
1480 settings
[F
'exe_{exename}'] = searchpath
1484 # Main compile function
1486 class CXR_COMPILER_CHAIN(bpy
.types
.Operator
):
1487 bl_idname
="convexer.chain"
1488 bl_label
="Compile Chain"
1503 def cancel(_
,context
):
1504 global cxr_jobs_batch
1505 static
= _
.__class
__
1506 wm
= context
.window_manager
1508 if static
.SUBPROC
!= None:
1509 static
.SUBPROC
.terminate()
1510 static
.SUBPROC
= None
1512 if static
.TIMER
!= None:
1513 wm
.event_timer_remove( static
.TIMER
)
1518 cxr_jobs_batch
= None
1522 def modal(_
,context
,ev
):
1523 static
= _
.__class
__
1525 if ev
.type == 'TIMER':
1526 global cxr_jobs_batch
1528 if static
.WAIT_REDRAW
:
1530 return {'PASS_THROUGH'}
1531 static
.WAIT_REDRAW
= True
1533 if static
.USER_EXIT
:
1534 print( "Chain USER_EXIT" )
1535 return _
.cancel(context
)
1537 if static
.SUBPROC
!= None:
1538 # Deal with async modes
1539 status
= static
.SUBPROC
.poll()
1542 # Cannot redirect STDOUT through here without causing
1543 # undefined behaviour due to the Blender Python specification.
1545 # Have to write it out to a file and read it back in.
1547 with
open("/tmp/convexer_compile_log.txt","r") as log
:
1548 static
.LOG
= log
.readlines()
1549 return {'PASS_THROUGH'}
1551 #for l in static.SUBPROC.stdout:
1552 # print( F'-> {l.decode("utf-8")}',end='' )
1553 static
.SUBPROC
= None
1556 print(F
'Compiler () error: {status}')
1557 return _
.cancel(context
)
1559 static
.JOBSYS
['jobs'][static
.JOBID
] = None
1560 cxr_jobs_update_graph( static
.JOBINFO
)
1562 return {'PASS_THROUGH'}
1564 # Compile syncronous thing
1565 for sys
in static
.JOBINFO
:
1566 for i
,target
in enumerate(sys
['jobs']):
1569 if callable(sys
['exec']):
1570 print( F
"Run (sync): {static.JOBID} @{time.time()}" )
1572 if not sys
['exec'](*target
):
1573 print( "Job failed" )
1574 return _
.cancel(context
)
1576 sys
['jobs'][i
] = None
1579 # Run external executable (wine)
1580 static
.SUBPROC
= subprocess
.Popen( target
,
1581 stdout
=static
.FILE
,\
1582 stderr
=subprocess
.PIPE
,\
1587 cxr_jobs_update_graph( static
.JOBINFO
)
1589 return {'PASS_THROUGH'}
1592 print( "All jobs completed!" )
1593 cxr_jobs_batch
= None
1596 return _
.cancel(context
)
1598 return {'PASS_THROUGH'}
1600 def invoke(_
,context
,event
):
1601 static
= _
.__class
__
1602 wm
= context
.window_manager
1604 if static
.TIMER
== None:
1605 print("Launching compiler toolchain")
1607 # Run static compilation units now (collect, vmt..)
1608 filepath
= bpy
.data
.filepath
1609 directory
= os
.path
.dirname(filepath
)
1610 settings
= bpy
.context
.scene
.cxr_data
1612 asset_dir
= F
"{directory}/modelsrc"
1613 material_dir
= F
"{settings.subdir}/materials/{settings.project_name}"
1614 model_dir
= F
"{settings.subdir}/models/{settings.project_name}"
1615 output_vmf
= F
"{directory}/{settings.project_name}.vmf"
1617 os
.makedirs( asset_dir
, exist_ok
=True )
1618 os
.makedirs( material_dir
, exist_ok
=True )
1619 os
.makedirs( model_dir
, exist_ok
=True )
1621 static
.FILE
= open(F
"/tmp/convexer_compile_log.txt","w")
1624 sceneinfo
= cxr_scene_collect()
1630 for brush
in sceneinfo
['geo']:
1631 for ms
in brush
['object'].material_slots
:
1632 a_materials
.add( ms
.material
)
1633 if ms
.material
.cxr_data
.shader
== 'VertexLitGeneric':
1634 errmat
= ms
.material
.name
1635 errnam
= brush
['object'].name
1636 print( F
"Vertex shader {errmat} used on {errnam}")
1637 return {'CANCELLED'}
1639 for ent
in sceneinfo
['entities']:
1640 if ent
['object'] == None: continue
1641 if isinstance(ent
['object'],bpy
.types
.Collection
): continue
1643 if ent
['object'].type == 'MESH':
1644 for ms
in ent
['object'].material_slots
:
1645 a_materials
.add( ms
.material
)
1647 # TODO.. this should just be in the entity loop
1648 for hero
in sceneinfo
['heros']:
1649 uid
= asset_uid(hero
['collection'])
1650 qc_jobs
+= [F
'{uid}.qc']
1651 for obj
in hero
['collection'].objects
:
1652 for ms
in obj
.material_slots
:
1653 a_materials
.add( ms
.material
)
1654 if ms
.material
.cxr_data
.shader
== 'LightMappedGeneric' or \
1655 ms
.material
.cxr_data
.shader
== 'WorldVertexTransition':
1657 errmat
= ms
.material
.name
1659 print( F
"Lightmapped shader {errmat} used on {errnam}")
1660 return {'CANCELLED'}
1663 for mat
in a_materials
:
1664 for pair
in compile_material(mat
):
1669 if isinstance(prop
,bpy
.types
.Image
):
1671 if 'flags' in pdef
: flags
= pdef
['flags']
1672 if prop
not in image_jobs
:
1673 image_jobs
+= [(prop
,)]
1674 prop
.cxr_data
.flags
= flags
1680 if settings
.comp_vmf
:
1681 static
.JOBINFO
+= [{
1682 "title": "Convexer",
1684 "colour": (1.0,0.3,0.1,1.0),
1685 "exec": cxr_export_vmf
,
1686 "jobs": [(sceneinfo
,output_vmf
)]
1689 if settings
.comp_textures
:
1690 if len(image_jobs
) > 0:
1691 static
.JOBINFO
+= [{
1692 "title": "Textures",
1694 "colour": (0.1,1.0,0.3,1.0),
1695 "exec": compile_image
,
1699 game
= 'z:'+settings
.subdir
.replace('/','\\')
1701 '-game', game
, settings
.project_name
1705 if settings
.comp_models
:
1706 if len(sceneinfo
['heros']) > 0:
1707 static
.JOBINFO
+= [{
1710 "colour": (0.5,0.5,1.0,1.0),
1711 "exec": cxr_export_modelsrc
,
1712 "jobs": [(h
['collection'], h
['origin'], asset_dir
, \
1713 settings
.project_name
, h
['transform']) for h
in \
1717 if len(qc_jobs
) > 0:
1718 static
.JOBINFO
+= [{
1719 "title": "StudioMDL",
1721 "colour": (0.8,0.1,0.1,1.0),
1722 "exec": "studiomdl",
1723 "jobs": [[settings
[F
'exe_studiomdl']] + [\
1724 '-nop4', '-game', game
, qc
] for qc
in qc_jobs
],
1729 if settings
.comp_compile
:
1730 static
.JOBINFO
+= [{
1733 "colour": (0.1,0.2,1.0,1.0),
1735 "jobs": [[settings
[F
'exe_vbsp']] + args
],
1739 static
.JOBINFO
+= [{
1742 "colour": (0.9,0.5,0.5,1.0),
1744 "jobs": [[settings
[F
'exe_vvis']] + ['-fast'] + args
],
1748 vrad_opt
= settings
.opt_vrad
.split()
1749 static
.JOBINFO
+= [{
1752 "colour": (0.9,0.2,0.3,1.0),
1754 "jobs": [[settings
[F
'exe_vrad']] + vrad_opt
+ args
],
1758 static
.JOBINFO
+= [{
1761 "colour": (0.0,1.0,0.4,1.0),
1762 "exec": cxr_patchmap
,
1763 "jobs": [(F
"{directory}/{settings.project_name}.bsp",\
1764 F
"{settings.subdir}/maps/{settings.project_name}.bsp")]
1767 static
.USER_EXIT
=False
1768 static
.TIMER
=wm
.event_timer_add(0.1,window
=context
.window
)
1769 wm
.modal_handler_add(_
)
1771 cxr_jobs_update_graph( static
.JOBINFO
)
1773 return {'RUNNING_MODAL'}
1775 print("Chain exiting...")
1776 static
.USER_EXIT
=True
1777 return {'RUNNING_MODAL'}
1779 class CXR_RESET_HASHES(bpy
.types
.Operator
):
1780 bl_idname
="convexer.hash_reset"
1781 bl_label
="Reset asset hashes"
1783 def execute(_
,context
):
1784 for c
in bpy
.data
.collections
:
1785 c
.cxr_data
.last_hash
= F
"<RESET>{time.time()}"
1786 c
.cxr_data
.asset_id
=0
1788 for t
in bpy
.data
.images
:
1789 t
.cxr_data
.last_hash
= F
"<RESET>{time.time()}"
1790 t
.cxr_data
.asset_id
=0
1794 class CXR_COMPILE_MATERIAL(bpy
.types
.Operator
):
1795 bl_idname
="convexer.matcomp"
1796 bl_label
="Recompile Material"
1798 def execute(_
,context
):
1799 active_obj
= bpy
.context
.active_object
1800 active_mat
= active_obj
.active_material
1802 #TODO: reduce code dupe (L1663)
1803 for pair
in compile_material(active_mat
):
1808 if isinstance(prop
,bpy
.types
.Image
):
1810 if 'flags' in pdef
: flags
= pdef
['flags']
1811 prop
.cxr_data
.flags
= flags
1813 compile_image( prop
)
1815 settings
= bpy
.context
.scene
.cxr_data
1816 with
open(F
'{settings.subdir}/cfg/convexer_mat_update.cfg','w') as o
:
1817 o
.write(F
'mat_reloadmaterial {asset_name(active_mat)}')
1820 with
open(F
'{settings.subdir}/cfg/convexer.cfg','w') as o
:
1821 o
.write('sv_cheats 1\n')
1822 o
.write('mp_warmup_pausetimer 1\n')
1823 o
.write('bot_kick\n')
1824 o
.write('alias cxr_reload "exec convexer_mat_update"\n')
1829 # ------------------------------------------------------------------------------
1831 # Helper buttons for 3d toolbox view
1833 class CXR_VIEW3D( bpy
.types
.Panel
):
1834 bl_idname
= "VIEW3D_PT_convexer"
1835 bl_label
= "Convexer"
1836 bl_space_type
= 'VIEW_3D'
1837 bl_region_type
= 'UI'
1838 bl_category
= "Convexer"
1841 def poll(cls
, context
):
1842 return (context
.object is not None)
1844 def draw(_
, context
):
1848 row
.operator("convexer.preview")
1850 if CXR_PREVIEW_OPERATOR
.LASTERR
!= None:
1852 box
.label(text
=CXR_PREVIEW_OPERATOR
.LASTERR
, icon
='ERROR')
1854 # Main scene properties interface, where all the settings go
1856 class CXR_INTERFACE(bpy
.types
.Panel
):
1858 bl_idname
="SCENE_PT_convexer"
1859 bl_space_type
='PROPERTIES'
1860 bl_region_type
='WINDOW'
1863 def draw(_
,context
):
1864 _
.layout
.operator("convexer.reload")
1865 _
.layout
.operator("convexer.dev_test")
1866 _
.layout
.operator("convexer.preview")
1867 _
.layout
.operator("convexer.hash_reset")
1869 settings
= context
.scene
.cxr_data
1871 _
.layout
.prop(settings
, "debug")
1872 _
.layout
.prop(settings
, "scale_factor")
1873 _
.layout
.prop(settings
, "skybox_scale_factor")
1874 _
.layout
.prop(settings
, "skyname" )
1875 _
.layout
.prop(settings
, "lightmap_scale")
1876 _
.layout
.prop(settings
, "light_scale" )
1877 _
.layout
.prop(settings
, "image_quality" )
1879 box
= _
.layout
.box()
1881 box
.prop(settings
, "project_name")
1882 box
.prop(settings
, "subdir")
1884 box
= _
.layout
.box()
1885 box
.operator("convexer.detect_compilers")
1886 box
.prop(settings
, "exe_studiomdl")
1887 box
.prop(settings
, "exe_vbsp")
1888 box
.prop(settings
, "exe_vvis")
1889 box
.prop(settings
, "exe_vrad")
1890 box
.prop(settings
, "opt_vrad")
1894 row
.prop(settings
,"comp_vmf")
1895 row
.prop(settings
,"comp_textures")
1896 row
.prop(settings
,"comp_models")
1897 row
.prop(settings
,"comp_compile")
1899 text
= "Compile" if CXR_COMPILER_CHAIN
.TIMER
== None else "Cancel"
1902 row
.operator("convexer.chain", text
=text
)
1905 class CXR_MATERIAL_PANEL(bpy
.types
.Panel
):
1906 bl_label
="VMT Properties"
1907 bl_idname
="SCENE_PT_convexer_vmt"
1908 bl_space_type
='PROPERTIES'
1909 bl_region_type
='WINDOW'
1910 bl_context
="material"
1912 def draw(_
,context
):
1913 active_object
= bpy
.context
.active_object
1914 if active_object
== None: return
1916 active_material
= active_object
.active_material
1917 if active_material
== None: return
1919 properties
= active_material
.cxr_data
1920 info
= material_info( active_material
)
1922 _
.layout
.label(text
=F
"{info['name']} @{info['res'][0]}x{info['res'][1]}")
1923 row
= _
.layout
.row()
1924 row
.prop( properties
, "shader" )
1925 row
.operator( "convexer.matcomp" )
1927 #for xk in info: _.layout.label(text=F"{xk}:={info[xk]}")
1929 def _mtex( name
, img
, uiParent
):
1932 box
= uiParent
.box()
1933 box
.label( text
=F
'{name} "{img.filepath}"' )
1935 if ((x
& (x
- 1)) == 0):
1938 closest_diff
= 10000000
1940 dist
= abs((1 << i
)-x
)
1941 if dist
< closest_diff
:
1946 return 1 << (closest
+1)
1948 return 1 << (closest
-1)
1953 row
.prop( img
.cxr_data
, "export_res" )
1954 row
.prop( img
.cxr_data
, "fmt" )
1957 row
.prop( img
.cxr_data
, "mipmap" )
1958 row
.prop( img
.cxr_data
, "lod" )
1959 row
.prop( img
.cxr_data
, "clamp" )
1961 img
.cxr_data
.export_res
[0] = _p2( img
.cxr_data
.export_res
[0] )
1962 img
.cxr_data
.export_res
[1] = _p2( img
.cxr_data
.export_res
[1] )
1964 def _mview( layer
, uiParent
):
1968 if isinstance(layer
[decl
],dict): # $property definition
1970 ptype
= pdef
['type']
1976 if ('shaders' in pdef
) and \
1977 (properties
.shader
not in pdef
['shaders']):
1980 if ptype
== 'intrinsic':
1981 if decl
not in info
:
1986 if isinstance(pdef
[ch
],dict):
1987 if ptype
== 'ui' or ptype
== 'intrinsic':
1989 elif getattr(properties
,decl
) == pdef
['default']:
1992 thisnode
= uiParent
.box()
1996 thisnode
.label( text
=decl
)
1997 elif ptype
== 'intrinsic':
1998 if isinstance(info
[decl
], bpy
.types
.Image
):
1999 _mtex( decl
, info
[decl
], thisnode
)
2001 # hidden intrinsic value.
2002 # Means its a float array or something not an image
2003 thisnode
.label(text
=F
"-- hidden intrinsic '{decl}' --")
2005 thisnode
.prop(properties
,decl
)
2006 if expandview
: _mview(pdef
,thisnode
)
2008 _mview( cxr_shader_params
, _
.layout
)
2010 def cxr_entity_changeclass(_
,context
):
2011 active_object
= context
.active_object
2013 # Create ID properties
2015 classname
= cxr_custom_class(active_object
)
2017 if classname
in cxr_entities
:
2018 entdef
= cxr_entities
[classname
]
2020 kvs
= entdef
['keyvalues']
2021 if callable(kvs
): kvs
= kvs(active_object
)
2027 if callable(kv
) or not isinstance(kv
,dict): continue
2029 if key
not in active_object
:
2030 active_object
[key
] = kv
['default']
2031 id_prop
= active_object
.id_properties_ui(key
)
2032 id_prop
.update(default
=kv
['default'])
2034 class CXR_ENTITY_PANEL(bpy
.types
.Panel
):
2035 bl_label
="Entity Config"
2036 bl_idname
="SCENE_PT_convexer_entity"
2037 bl_space_type
='PROPERTIES'
2038 bl_region_type
='WINDOW'
2041 def draw(_
,context
):
2042 active_object
= bpy
.context
.active_object
2044 if active_object
== None: return
2047 "scale": bpy
.context
.scene
.cxr_data
.scale_factor
,
2051 ecn
= cxr_intrinsic_classname( active_object
)
2052 classname
= cxr_custom_class( active_object
)
2055 if active_object
.type == 'MESH':
2056 _
.layout
.prop( active_object
.cxr_data
, 'brushclass' )
2057 else: _
.layout
.prop( active_object
.cxr_data
, 'classname' )
2059 if classname
== 'NONE':
2062 _
.layout
.label(text
=F
"<implementation defined ({ecn})>")
2063 _
.layout
.enabled
=False
2066 kvs
= cxr_entity_keyvalues( {
2067 "object": active_object
,
2068 "transform": default_context
,
2069 "classname": classname
2075 _
.layout
.prop( active_object
, F
'["cxrkv_{kv[0]}"]', text
=kv
[0])
2077 row
= _
.layout
.row()
2079 row
.label( text
=F
'{kv[0]}: {repr(kv[2])}' )
2081 _
.layout
.label( text
=F
"ERROR: NO CLASS DEFINITION" )
2083 class CXR_LIGHT_PANEL(bpy
.types
.Panel
):
2084 bl_label
= "Source Settings"
2085 bl_idname
= "LIGHT_PT_cxr"
2086 bl_space_type
= 'PROPERTIES'
2087 bl_region_type
= 'WINDOW'
2090 def draw(self
, context
):
2091 layout
= self
.layout
2092 scene
= context
.scene
2094 active_object
= bpy
.context
.active_object
2095 if active_object
== None: return
2097 if active_object
.type == 'LIGHT' or \
2098 active_object
.type == 'LIGHT_PROBE':
2100 properties
= active_object
.data
.cxr_data
2102 if active_object
.type == 'LIGHT':
2103 layout
.prop( properties
, "realtime" )
2104 elif active_object
.type == 'LIGHT_PROBE':
2105 layout
.prop( properties
, "size" )
2108 # ------------------------------------------------------------------------------
2110 class CXR_IMAGE_SETTINGS(bpy
.types
.PropertyGroup
):
2111 export_res
: bpy
.props
.IntVectorProperty(
2113 description
="Texture Export Resolution",
2119 fmt
: bpy
.props
.EnumProperty(
2122 ('DXT1', "DXT1", "BC1 compressed", '', 0),
2123 ('DXT5', "DXT5", "BC3 compressed", '', 1),
2124 ('RGB', "RGB", "Uncompressed", '', 2),
2125 ('RGBA', "RGBA", "Uncompressed (with alpha)", '', 3)
2127 description
="Image format",
2130 last_hash
: bpy
.props
.StringProperty( name
="" )
2131 asset_id
: bpy
.props
.IntProperty(name
="intl_assetid",default
=0)
2133 mipmap
: bpy
.props
.BoolProperty(name
="MIP",default
=True)
2134 lod
: bpy
.props
.BoolProperty(name
="LOD",default
=True)
2135 clamp
: bpy
.props
.BoolProperty(name
="CLAMP",default
=False)
2136 flags
: bpy
.props
.IntProperty(name
="flags",default
=0)
2138 class CXR_LIGHT_SETTINGS(bpy
.types
.PropertyGroup
):
2139 realtime
: bpy
.props
.BoolProperty(name
="Realtime Light", default
=True)
2141 class CXR_CUBEMAP_SETTINGS(bpy
.types
.PropertyGroup
):
2142 size
: bpy
.props
.EnumProperty(
2145 ('1',"1x1",'','',0),
2146 ('2',"2x2",'','',1),
2147 ('3',"4x4",'','',2),
2148 ('4',"8x8",'','',3),
2149 ('5',"16x16",'','',4),
2150 ('6',"32x32",'','',5),
2151 ('7',"64x64",'','',6),
2152 ('8',"128x128",'','',7),
2153 ('9',"256x256",'','',8)
2155 description
="Texture resolution",
2158 class CXR_ENTITY_SETTINGS(bpy
.types
.PropertyGroup
):
2159 entity
: bpy
.props
.BoolProperty(name
="")
2161 enum_pointents
= [('NONE',"None","")]
2162 enum_brushents
= [('NONE',"None","")]
2164 for classname
in cxr_entities
:
2165 entdef
= cxr_entities
[classname
]
2166 if 'allow' in entdef
:
2167 itm
= [(classname
, classname
, "")]
2168 if 'EMPTY' in entdef
['allow']: enum_pointents
+= itm
2169 else: enum_brushents
+= itm
2171 classname
: bpy
.props
.EnumProperty(items
=enum_pointents
, name
="Class", \
2172 update
=cxr_entity_changeclass
, default
='NONE' )
2174 brushclass
: bpy
.props
.EnumProperty(items
=enum_brushents
, name
="Class", \
2175 update
=cxr_entity_changeclass
, default
='NONE' )
2177 class CXR_MODEL_SETTINGS(bpy
.types
.PropertyGroup
):
2178 last_hash
: bpy
.props
.StringProperty( name
="" )
2179 asset_id
: bpy
.props
.IntProperty(name
="vmf_settings",default
=0)
2181 class CXR_SCENE_SETTINGS(bpy
.types
.PropertyGroup
):
2182 project_name
: bpy
.props
.StringProperty( name
="Project Name" )
2183 subdir
: bpy
.props
.StringProperty( name
="Subdirectory" )
2185 exe_studiomdl
: bpy
.props
.StringProperty( name
="studiomdl" )
2186 exe_vbsp
: bpy
.props
.StringProperty( name
="vbsp" )
2187 opt_vbsp
: bpy
.props
.StringProperty( name
="args" )
2188 exe_vvis
: bpy
.props
.StringProperty( name
="vvis" )
2189 opt_vvis
: bpy
.props
.StringProperty( name
="args" )
2190 exe_vrad
: bpy
.props
.StringProperty( name
="vrad" )
2191 opt_vrad
: bpy
.props
.StringProperty( name
="args", \
2192 default
="-reflectivityScale 0.35 -aoscale 1.4 -final -textureshadows -hdr -StaticPropLighting -StaticPropPolys" )
2194 debug
: bpy
.props
.BoolProperty(name
="Debug",default
=False)
2195 scale_factor
: bpy
.props
.FloatProperty( name
="VMF Scale factor", \
2196 default
=32.0,min=1.0)
2197 skybox_scale_factor
: bpy
.props
.FloatProperty( name
="Sky Scale factor", \
2198 default
=1.0,min=0.01)
2199 skyname
: bpy
.props
.StringProperty(name
="Skyname",default
="sky_csgo_night02b")
2200 skybox_offset
: bpy
.props
.FloatProperty(name
="Sky offset",default
=-4096.0)
2201 light_scale
: bpy
.props
.FloatProperty(name
="Light Scale",default
=1.0/5.0)
2202 include_names
: bpy
.props
.BoolProperty(name
="Append original file names",\
2204 lightmap_scale
: bpy
.props
.IntProperty(name
="Global Lightmap Scale",\
2206 image_quality
: bpy
.props
.IntProperty(name
="Texture Quality (0-18)",\
2207 default
=8, min=0, max=18 )
2209 comp_vmf
: bpy
.props
.BoolProperty(name
="VMF",default
=True)
2210 comp_models
: bpy
.props
.BoolProperty(name
="Models",default
=True)
2211 comp_textures
: bpy
.props
.BoolProperty(name
="Textures",default
=True)
2212 comp_compile
: bpy
.props
.BoolProperty(name
="Compile",default
=True)
2214 classes
= [ CXR_RELOAD
, CXR_DEV_OPERATOR
, CXR_INTERFACE
, \
2215 CXR_MATERIAL_PANEL
, CXR_IMAGE_SETTINGS
,\
2216 CXR_MODEL_SETTINGS
, CXR_ENTITY_SETTINGS
, CXR_CUBEMAP_SETTINGS
,\
2217 CXR_LIGHT_SETTINGS
, CXR_SCENE_SETTINGS
, CXR_DETECT_COMPILERS
,\
2218 CXR_ENTITY_PANEL
, CXR_LIGHT_PANEL
, CXR_PREVIEW_OPERATOR
,\
2219 CXR_VIEW3D
, CXR_COMPILER_CHAIN
, CXR_RESET_HASHES
,\
2220 CXR_COMPILE_MATERIAL
]
2222 vmt_param_dynamic_class
= None
2225 global cxr_view_draw_handler
, vmt_param_dynamic_class
, cxr_ui_handler
2228 bpy
.utils
.register_class(c
)
2230 # Build dynamic VMT properties class defined by cxr_shader_params
2231 annotations_dict
= {}
2233 def _dvmt_propogate(layer
):
2234 nonlocal annotations_dict
2237 if isinstance(layer
[decl
],dict): # $property definition
2241 if pdef
['type'] == 'bool':
2242 prop
= bpy
.props
.BoolProperty(\
2243 name
= pdef
['name'],\
2244 default
= pdef
['default'])
2246 elif pdef
['type'] == 'float':
2247 prop
= bpy
.props
.FloatProperty(\
2248 name
= pdef
['name'],\
2249 default
= pdef
['default'])
2251 elif pdef
['type'] == 'vector':
2252 if 'subtype' in pdef
:
2253 prop
= bpy
.props
.FloatVectorProperty(\
2254 name
= pdef
['name'],\
2255 subtype
= pdef
['subtype'],\
2256 default
= pdef
['default'],\
2257 size
= len(pdef
['default']))
2259 prop
= bpy
.props
.FloatVectorProperty(\
2260 name
= pdef
['name'],\
2261 default
= pdef
['default'],\
2262 size
= len(pdef
['default']))
2264 elif pdef
['type'] == 'string':
2265 prop
= bpy
.props
.StringProperty(\
2266 name
= pdef
['name'],\
2267 default
= pdef
['default'])
2269 elif pdef
['type'] == 'enum':
2270 prop
= bpy
.props
.EnumProperty(\
2271 name
= pdef
['name'],\
2272 items
= pdef
['items'],\
2273 default
= pdef
['default'])
2276 annotations_dict
[decl
] = prop
2278 # Recurse into sub-definitions
2279 _dvmt_propogate(pdef
)
2281 annotations_dict
["shader"] = bpy
.props
.EnumProperty(\
2284 cxr_shaders
[_
]["name"],\
2285 '') for _
in cxr_shaders
],\
2286 default
= next(iter(cxr_shaders
)))
2288 annotations_dict
["asset_id"] = bpy
.props
.IntProperty(name
="intl_assetid",\
2291 _dvmt_propogate( cxr_shader_params
)
2292 vmt_param_dynamic_class
= type(
2294 (bpy
.types
.PropertyGroup
,),{
2295 "__annotations__": annotations_dict
2299 bpy
.utils
.register_class( vmt_param_dynamic_class
)
2302 bpy
.types
.Material
.cxr_data
= \
2303 bpy
.props
.PointerProperty(type=vmt_param_dynamic_class
)
2304 bpy
.types
.Image
.cxr_data
= \
2305 bpy
.props
.PointerProperty(type=CXR_IMAGE_SETTINGS
)
2306 bpy
.types
.Object
.cxr_data
= \
2307 bpy
.props
.PointerProperty(type=CXR_ENTITY_SETTINGS
)
2308 bpy
.types
.Collection
.cxr_data
= \
2309 bpy
.props
.PointerProperty(type=CXR_MODEL_SETTINGS
)
2310 bpy
.types
.Light
.cxr_data
= \
2311 bpy
.props
.PointerProperty(type=CXR_LIGHT_SETTINGS
)
2312 bpy
.types
.LightProbe
.cxr_data
= \
2313 bpy
.props
.PointerProperty(type=CXR_CUBEMAP_SETTINGS
)
2314 bpy
.types
.Scene
.cxr_data
= \
2315 bpy
.props
.PointerProperty(type=CXR_SCENE_SETTINGS
)
2317 # CXR Scene settings
2320 cxr_view_draw_handler
= bpy
.types
.SpaceView3D
.draw_handler_add(\
2321 cxr_draw
,(),'WINDOW','POST_VIEW')
2323 cxr_ui_handler
= bpy
.types
.SpaceView3D
.draw_handler_add(\
2324 cxr_ui
,(None,None),'WINDOW','POST_PIXEL')
2326 bpy
.app
.handlers
.load_post
.append(cxr_on_load
)
2327 bpy
.app
.handlers
.depsgraph_update_post
.append(cxr_dgraph_update
)
2330 global cxr_view_draw_handler
, vmt_param_dynamic_class
, cxr_ui_handler
2332 bpy
.utils
.unregister_class( vmt_param_dynamic_class
)
2334 bpy
.utils
.unregister_class(c
)
2336 bpy
.app
.handlers
.depsgraph_update_post
.remove(cxr_dgraph_update
)
2337 bpy
.app
.handlers
.load_post
.remove(cxr_on_load
)
2339 bpy
.types
.SpaceView3D
.draw_handler_remove(cxr_view_draw_handler
,'WINDOW')
2340 bpy
.types
.SpaceView3D
.draw_handler_remove(cxr_ui_handler
,'WINDOW')