3 # A GNU/Linux-first Source1 Hammer replacement
4 # built with Blender, for mapmakers
6 # Copyright (C) 2022 Harry Godden (hgn)
8 # LICENSE: GPLv3.0, please see COPYING and LICENSE for more information
13 "author": "Harry Godden (hgn)",
20 "category":"Import/Export",
23 print( "Convexer reload" )
25 #from mathutils import *
26 import bpy
, gpu
, math
, os
, time
, mathutils
, blf
, subprocess
, shutil
, hashlib
28 from gpu_extras
.batch
import batch_for_shader
29 from bpy
.app
.handlers
import persistent
31 # Setup platform dependent variables
33 exec(open(F
'{os.path.dirname(__file__)}/platform.py').read())
41 # GPU and viewport drawing
42 # ------------------------------------------------------------------------------
45 cxr_view_draw_handler
= None
46 cxr_ui_draw_handler
= None
64 cxr_view_shader
= gpu
.shader
.from_builtin('3D_SMOOTH_COLOR')
66 cxr_ui_shader
= gpu
.types
.GPUShader("""
67 uniform mat4 ModelViewProjectionMatrix;
77 gl_Position = ModelViewProjectionMatrix * vec4(aPos.x*scale,aPos.y, 0.0, 1.0);
90 cxr_mdl_shader
= gpu
.types
.GPUShader("""
91 uniform mat4 modelMatrix;
92 uniform mat4 viewProjectionMatrix;
104 vec4 pWorldPos = modelMatrix * vec4(aPos, 1.0);
105 vec3 worldPos = pWorldPos.xyz;
107 gl_Position = viewProjectionMatrix * pWorldPos;
108 lNormal = normalize(mat3(transpose(inverse(modelMatrix))) * aNormal);
115 uniform vec3 testLightDir;
116 uniform sampler2D uBasetexture;
124 float SoftenCosineTerm( float flDot )
126 return ( flDot + ( flDot * flDot ) ) * 0.5;
129 vec3 DiffuseTerm( vec3 worldNormal, vec3 lightDir )
132 float NDotL = dot( worldNormal, lightDir );
134 fResult = clamp( NDotL, 0.0, 1.0 );
135 fResult = SoftenCosineTerm( fResult );
137 vec3 fOut = vec3( fResult, fResult, fResult );
141 vec3 PixelShaderDoLightingLinear( vec3 worldPos, vec3 worldNormal )
143 vec3 linearColor = vec3(0.0,0.0,0.0);
144 linearColor += DiffuseTerm( worldNormal, testLightDir );
149 vec3 LinearToGamma( vec3 f3linear )
151 return pow( f3linear, vec3(1.0 / 2.2) );
154 vec3 GammaToLinear( vec3 f3gamma )
156 return pow( f3gamma, vec3(2.2) );
161 vec3 tangentSpaceNormal = vec3( 0.0, 0.0, 1.0 );
162 vec4 normalTexel = vec4(1.0,1.0,1.0,1.0);
163 vec3 colorInput = GammaToLinear( texture( uBasetexture, lUv ).rgb );
165 vec4 baseColor = vec4( colorInput * colour.rgb, 1.0 );
167 //normalTexel = tex2D( BumpmapSampler, i.detailOrBumpTexCoord );
168 //tangentSpaceNormal = 2.0 * normalTexel - 1.0;
170 vec3 diffuseLighting = vec3( 1.0, 1.0, 1.0 );
172 vec3 staticLightingColor = vec3( 0.0, 0.0, 0.0 );
173 diffuseLighting = PixelShaderDoLightingLinear( lPos, lNormal );
175 // multiply by .5 since we want a 50% (in gamma space) reflective surface)
176 diffuseLighting *= pow( 0.5, 2.2 );
178 vec3 result = diffuseLighting * baseColor.xyz;
180 FragColor = vec4( LinearToGamma(result), 1.0 );
186 def cxr_ui(_
,context
):
187 global cxr_jobs_batch
, cxr_ui_shader
, cxr_jobs_inf
, cxr_error_inf
189 w
= gpu
.state
.viewport_get()[2]
191 cxr_ui_shader
.uniform_float( "scale", w
)
193 if cxr_error_inf
!= None:
196 if isinstance(cxr_error_inf
[1],list):
197 err_begin
+= 20*(len(cxr_error_inf
[1])-1)
199 blf
.position(0,2,err_begin
,0)
201 blf
.color(0, 1.0,0.2,0.2,0.9)
202 blf
.draw(0,cxr_error_inf
[0])
205 blf
.color(0, 1.0,1.0,1.0,1.0)
207 if isinstance(cxr_error_inf
[1],list):
208 for i
,inf
in enumerate(cxr_error_inf
[1]):
209 blf
.position(0,2,err_begin
-30-i
*20,0)
212 blf
.position(0,2,err_begin
-30,0)
213 blf
.draw(0,cxr_error_inf
[1])
215 elif cxr_jobs_batch
!= None:
216 gpu
.state
.blend_set('ALPHA')
217 cxr_jobs_batch
.draw(cxr_ui_shader
)
219 blf
.position(0,2,50,0)
221 blf
.color(0,1.0,1.0,1.0,1.0)
222 blf
.draw(0,"Compiling")
224 for ji
in cxr_jobs_inf
:
225 blf
.position(0,ji
[0]*w
,35,0)
231 for ln
in reversed(CXR_COMPILER_CHAIN
.LOG
[-25:]):
232 blf
.position(0,2,py
,0)
236 # Something is off with TIMER,
237 # this forces the viewport to redraw before we can continue with our
240 CXR_COMPILER_CHAIN
.WAIT_REDRAW
= False
243 global cxr_view_shader
, cxr_view_mesh
, cxr_view_lines
, cxr_mdl_shader
,\
244 cxr_mdl_mesh
, cxr_test_mdl
246 cxr_view_shader
.bind()
248 gpu
.state
.depth_mask_set(False)
249 gpu
.state
.line_width_set(1.5)
250 gpu
.state
.face_culling_set('BACK')
251 gpu
.state
.depth_test_set('NONE')
252 gpu
.state
.blend_set('ALPHA')
254 if cxr_view_lines
!= None:
255 cxr_view_lines
.draw( cxr_view_shader
)
257 if cxr_view_mesh
!= None:
258 gpu
.state
.depth_test_set('LESS_EQUAL')
259 gpu
.state
.blend_set('ADDITIVE')
261 cxr_view_mesh
.draw( cxr_view_shader
)
264 gpu
.state
.depth_mask_set(True)
265 gpu
.state
.depth_test_set('LESS_EQUAL')
266 gpu
.state
.face_culling_set('FRONT')
267 gpu
.state
.blend_set('NONE')
269 cxr_mdl_shader
.bind()
270 cxr_mdl_shader
.uniform_float("viewProjectionMatrix", \
271 bpy
.context
.region_data
.perspective_matrix
)
273 if cxr_test_mdl
!= None:
274 cxr_mdl_shader
.uniform_float('colour',(1.0,1.0,1.0,1.0))
277 testmdl
= bpy
.context
.scene
.objects
['target']
278 light
= bpy
.context
.scene
.objects
['point']
279 relative
= light
.location
- testmdl
.location
281 cxr_mdl_shader
.uniform_float("modelMatrix", testmdl
.matrix_world
)
282 cxr_mdl_shader
.uniform_float("testLightDir", relative
)
284 for part
in cxr_test_mdl
:
285 cxr_mdl_shader
.uniform_sampler("uBasetexture", part
[0]['basetexture'])
286 part
[1].draw( cxr_mdl_shader
)
288 def cxr_jobs_update_graph(jobs
):
289 global cxr_jobs_batch
, cxr_ui_shader
, cxr_jobs_inf
299 total_width
+= sys
['w']
308 colour
= sys
['colour']
309 colourwait
= (colour
[0],colour
[1],colour
[2],0.4)
310 colourrun
= (colour
[0]*1.5,colour
[1]*1.5,colour
[2]*1.5,0.5)
311 colourdone
= (colour
[0],colour
[1],colour
[2],1.0)
314 sfsub
= (1.0/(len(jobs
)))*w
318 if j
== None: colour
= colourdone
319 else: colour
= colourwait
321 px
= (cur
+ (i
)*sfsub
) * sf
322 px1
= (cur
+ (i
+1.0)*sfsub
) * sf
325 verts
+= [(px
,0), (px
, h
), (px1
, 0.0), (px1
,h
)]
326 colours
+= [colour
,colour
,colour
,colour
]
327 indices
+= [(ci
+0,ci
+2,ci
+3),(ci
+0,ci
+3,ci
+1)]
330 cxr_jobs_inf
+= [((sf
*cur
), sys
['title'])]
333 cxr_jobs_batch
= batch_for_shader(
334 cxr_ui_shader
, 'TRIS',
335 { "aPos": verts
, "aColour": colours
},
339 # view_layer.update() doesnt seem to work,
340 # tag_redraw() seems to have broken
341 # therefore, change a property
343 ob
= bpy
.context
.scene
.objects
[0]
344 ob
.hide_render
= ob
.hide_render
346 # the 'real' way to refresh the scene
347 for area
in bpy
.context
.window
.screen
.areas
:
348 if area
.type == 'view_3d':
352 # ------------------------------------------------------------------------------
355 # dlclose for reloading modules manually
357 libc_dlclose
= cdll
.LoadLibrary(None).dlclose
358 libc_dlclose
.argtypes
= [c_void_p
]
360 # wrapper for ctypes binding
362 def __init__(_
,name
,argtypes
,restype
):
364 _
.argtypes
= argtypes
369 _
.call
= getattr(so
,_
.name
)
370 _
.call
.argtypes
= _
.argtypes
372 if _
.restype
!= None:
373 _
.call
.restype
= _
.restype
376 # ------------------------------------------------------------------------------
380 # Structure definitions
382 class cxr_edge(Structure
):
383 _fields_
= [("i0",c_int32
),
385 ("freestyle",c_int32
),
388 class cxr_static_loop(Structure
):
389 _fields_
= [("index",c_int32
),
390 ("edge_index",c_int32
),
394 class cxr_polygon(Structure
):
395 _fields_
= [("loop_start",c_int32
),
396 ("loop_total",c_int32
),
397 ("normal",c_double
* 3),
398 ("center",c_double
* 3),
399 ("material_id",c_int32
)]
401 class cxr_material(Structure
):
402 _fields_
= [("res",c_int32
* 2),
405 class cxr_static_mesh(Structure
):
406 _fields_
= [("vertices",POINTER(c_double
* 3)),
407 ("edges",POINTER(cxr_edge
)),
408 ("loops",POINTER(cxr_static_loop
)),
409 ("polys",POINTER(cxr_polygon
)),
410 ("materials",POINTER(cxr_material
)),
412 ("poly_count",c_int32
),
413 ("vertex_count",c_int32
),
414 ("edge_count",c_int32
),
415 ("loop_count",c_int32
),
416 ("material_count",c_int32
)]
418 class cxr_tri_mesh(Structure
):
419 _fields_
= [("vertices",POINTER(c_double
*3)),
420 ("normals",POINTER(c_double
*3)),
421 ("uvs",POINTER(c_double
*2)),
422 ("colours",POINTER(c_double
*4)),
423 ("indices",POINTER(c_int32
)),
424 ("indices_count",c_int32
),
425 ("vertex_count",c_int32
)]
427 class cxr_visgroup(Structure
):
428 _fields_
= [("name",c_char_p
)]
430 class cxr_vmf_context(Structure
):
431 _fields_
= [("mapversion",c_int32
),
432 ("skyname",c_char_p
),
433 ("detailvbsp",c_char_p
),
434 ("detailmaterial",c_char_p
),
435 ("visgroups",POINTER(cxr_visgroup
)),
436 ("visgroup_count",c_int32
),
438 ("offset",c_double
*3),
439 ("lightmap_scale",c_int32
),
440 ("visgroupid",c_int32
),
441 ("brush_count",c_int32
),
442 ("entity_count",c_int32
),
443 ("face_count",c_int32
)]
445 # Valve wrapper types
446 class fs_locator(Structure
):
447 _fields_
= [("vpk_entry",c_void_p
),
448 ("path",c_char_p
*1024)]
450 class valve_material(Structure
):
451 _fields_
= [("basetexture",c_char_p
),
452 ("bumpmap",c_char_p
)]
454 class valve_model_batch(Structure
):
455 _fields_
= [("material",c_uint32
),
456 ("ibstart",c_uint32
),
457 ("ibcount",c_uint32
)]
459 class valve_model(Structure
):
460 _fields_
= [("vertex_data",POINTER(c_float
)),
461 ("indices",POINTER(c_uint32
)),
462 ("indices_count",c_uint32
),
463 ("vertex_count",c_uint32
),
464 ("part_count",c_uint32
),
465 ("material_count",c_uint32
),
466 ("materials",POINTER(c_char_p
)),
467 ("parts",POINTER(valve_model_batch
)),
468 ("studiohdr",c_void_p
),
472 # Convert blenders mesh format into CXR's static format (they are very similar)
474 def mesh_cxr_format(obj
):
477 if bpy
.context
.active_object
!= None:
478 orig_state
= obj
.mode
479 if orig_state
!= 'OBJECT':
480 bpy
.ops
.object.mode_set(mode
='OBJECT')
482 dgraph
= bpy
.context
.evaluated_depsgraph_get()
483 data
= obj
.evaluated_get(dgraph
).data
485 _
,mtx_rot
,_
= obj
.matrix_world
.decompose()
487 mesh
= cxr_static_mesh()
489 vertex_data
= ((c_double
*3)*len(data
.vertices
))()
490 for i
, vert
in enumerate(data
.vertices
):
491 v
= obj
.matrix_world
@ vert
.co
492 vertex_data
[i
][0] = c_double(v
[0])
493 vertex_data
[i
][1] = c_double(v
[1])
494 vertex_data
[i
][2] = c_double(v
[2])
496 loop_data
= (cxr_static_loop
*len(data
.loops
))()
497 polygon_data
= (cxr_polygon
*len(data
.polygons
))()
499 for i
, poly
in enumerate(data
.polygons
):
500 loop_start
= poly
.loop_start
501 loop_end
= poly
.loop_start
+ poly
.loop_total
502 for loop_index
in range(loop_start
, loop_end
):
503 loop
= data
.loops
[loop_index
]
504 loop_data
[loop_index
].index
= loop
.vertex_index
505 loop_data
[loop_index
].edge_index
= loop
.edge_index
508 uv
= data
.uv_layers
.active
.data
[loop_index
].uv
509 loop_data
[loop_index
].uv
[0] = c_double(uv
[0])
510 loop_data
[loop_index
].uv
[1] = c_double(uv
[1])
512 loop_data
[loop_index
].uv
[0] = c_double(0.0)
513 loop_data
[loop_index
].uv
[1] = c_double(0.0)
515 if data
.vertex_colors
:
516 alpha
= data
.vertex_colors
.active
.data
[loop_index
].color
[0]
520 loop_data
[loop_index
].alpha
= alpha
522 center
= obj
.matrix_world
@ poly
.center
523 normal
= mtx_rot
@ poly
.normal
525 polygon_data
[i
].loop_start
= poly
.loop_start
526 polygon_data
[i
].loop_total
= poly
.loop_total
527 polygon_data
[i
].normal
[0] = normal
[0]
528 polygon_data
[i
].normal
[1] = normal
[1]
529 polygon_data
[i
].normal
[2] = normal
[2]
530 polygon_data
[i
].center
[0] = center
[0]
531 polygon_data
[i
].center
[1] = center
[1]
532 polygon_data
[i
].center
[2] = center
[2]
533 polygon_data
[i
].material_id
= poly
.material_index
535 edge_data
= (cxr_edge
*len(data
.edges
))()
537 for i
, edge
in enumerate(data
.edges
):
538 edge_data
[i
].i0
= edge
.vertices
[0]
539 edge_data
[i
].i1
= edge
.vertices
[1]
540 edge_data
[i
].freestyle
= edge
.use_freestyle_mark
541 edge_data
[i
].sharp
= edge
.use_edge_sharp
543 material_data
= (cxr_material
*len(obj
.material_slots
))()
545 for i
, ms
in enumerate(obj
.material_slots
):
546 inf
= material_info(ms
.material
)
547 material_data
[i
].res
[0] = inf
['res'][0]
548 material_data
[i
].res
[1] = inf
['res'][1]
549 material_data
[i
].name
= inf
['name'].encode('utf-8')
551 mesh
.edges
= cast(edge_data
, POINTER(cxr_edge
))
552 mesh
.vertices
= cast(vertex_data
, POINTER(c_double
*3))
553 mesh
.loops
= cast(loop_data
,POINTER(cxr_static_loop
))
554 mesh
.polys
= cast(polygon_data
, POINTER(cxr_polygon
))
555 mesh
.materials
= cast(material_data
, POINTER(cxr_material
))
557 mesh
.poly_count
= len(data
.polygons
)
558 mesh
.vertex_count
= len(data
.vertices
)
559 mesh
.edge_count
= len(data
.edges
)
560 mesh
.loop_count
= len(data
.loops
)
561 mesh
.material_count
= len(obj
.material_slots
)
563 if orig_state
!= None:
564 bpy
.ops
.object.mode_set(mode
=orig_state
)
568 # Callback ctypes indirection things.. not really sure.
569 c_libcxr_log_callback
= None
570 c_libcxr_line_callback
= None
573 # -------------------------------------------------------------
574 libcxr_decompose
= extern( "cxr_decompose",
575 [POINTER(cxr_static_mesh
), POINTER(c_int32
)],
578 libcxr_free_world
= extern( "cxr_free_world",
582 libcxr_write_test_data
= extern( "cxr_write_test_data",
583 [POINTER(cxr_static_mesh
)],
586 libcxr_world_preview
= extern( "cxr_world_preview",
588 POINTER(cxr_tri_mesh
)
590 libcxr_free_tri_mesh
= extern( "cxr_free_tri_mesh",
594 libcxr_begin_vmf
= extern( "cxr_begin_vmf",
595 [POINTER(cxr_vmf_context
), c_void_p
],
598 libcxr_vmf_begin_entities
= extern( "cxr_vmf_begin_entities",
599 [POINTER(cxr_vmf_context
), c_void_p
],
602 libcxr_push_world_vmf
= extern("cxr_push_world_vmf",
603 [c_void_p
,POINTER(cxr_vmf_context
),c_void_p
],
606 libcxr_end_vmf
= extern( "cxr_end_vmf",
607 [POINTER(cxr_vmf_context
),c_void_p
],
611 # VDF + with open wrapper
612 libcxr_vdf_open
= extern( "cxr_vdf_open", [c_char_p
], c_void_p
)
613 libcxr_vdf_close
= extern( "cxr_vdf_close", [c_void_p
], None )
614 libcxr_vdf_put
= extern( "cxr_vdf_put", [c_void_p
,c_char_p
], None )
615 libcxr_vdf_node
= extern( "cxr_vdf_node", [c_void_p
,c_char_p
], None )
616 libcxr_vdf_edon
= extern( "cxr_vdf_edon", [c_void_p
], None )
617 libcxr_vdf_kv
= extern( "cxr_vdf_kv", [c_void_p
,c_char_p
,c_char_p
], None )
619 class vdf_structure():
620 def __init__(_
,path
):
623 _
.fp
= libcxr_vdf_open
.call( _
.path
.encode('utf-8') )
625 print( F
"Could not open file {_.path}" )
628 def __exit__(_
,type,value
,traceback
):
630 libcxr_vdf_close
.call(_
.fp
)
632 libcxr_vdf_put
.call(_
.fp
, s
.encode('utf-8') )
634 libcxr_vdf_node
.call(_
.fp
, name
.encode('utf-8') )
636 libcxr_vdf_edon
.call(_
.fp
)
638 libcxr_vdf_kv
.call(_
.fp
, k
.encode('utf-8'), v
.encode('utf-8'))
641 libcxr_lightpatch_bsp
= extern( "cxr_lightpatch_bsp", [c_char_p
], None )
643 # Binary file formats and FS
644 libcxr_fs_set_gameinfo
= extern( "cxr_fs_set_gameinfo", [c_char_p
], c_int32
)
645 libcxr_fs_exit
= extern( "cxr_fs_exit", [], None )
646 libcxr_fs_get
= extern( "cxr_fs_get", [c_char_p
, c_int32
], c_void_p
)
647 libcxr_fs_free
= extern( "cxr_fs_free", [c_void_p
], None )
648 libcxr_fs_find
= extern( "cxr_fs_find", [c_char_p
, POINTER(fs_locator
)],\
651 libcxr_valve_load_model
= extern( "valve_load_model", [c_char_p
], \
652 POINTER(valve_model
) )
653 libcxr_valve_free_model
= extern( "valve_free_model", [POINTER(valve_model
)],\
656 libcxr_valve_load_material
= extern( "valve_load_material", [c_char_p
], \
657 POINTER(valve_material
) )
658 libcxr_valve_free_material
= extern( "valve_free_material", \
659 [POINTER(valve_material
)], None )
661 libcxr_funcs
= [ libcxr_decompose
, libcxr_free_world
, libcxr_begin_vmf
, \
662 libcxr_vmf_begin_entities
, libcxr_push_world_vmf
, \
663 libcxr_end_vmf
, libcxr_vdf_open
, libcxr_vdf_close
, \
664 libcxr_vdf_put
, libcxr_vdf_node
, libcxr_vdf_edon
, \
665 libcxr_vdf_kv
, libcxr_lightpatch_bsp
, libcxr_write_test_data
,\
666 libcxr_world_preview
, libcxr_free_tri_mesh
, \
667 libcxr_fs_set_gameinfo
, libcxr_fs_exit
, libcxr_fs_get
, \
668 libcxr_fs_find
, libcxr_fs_free
, \
669 libcxr_valve_load_model
, libcxr_valve_free_model
,\
670 libcxr_valve_load_material
, libcxr_valve_free_material
]
673 def libcxr_log_callback(logStr
):
674 print( F
"{logStr.decode('utf-8')}",end
='' )
676 cxr_line_positions
= None
677 cxr_line_colours
= None
679 def cxr_reset_lines():
680 global cxr_line_positions
, cxr_line_colours
682 cxr_line_positions
= []
683 cxr_line_colours
= []
685 def cxr_batch_lines():
686 global cxr_line_positions
, cxr_line_colours
, cxr_view_shader
, cxr_view_lines
688 cxr_view_lines
= batch_for_shader(\
689 cxr_view_shader
, 'LINES',\
690 { "pos": cxr_line_positions
, "color": cxr_line_colours
})
692 def libcxr_line_callback( p0
,p1
,colour
):
693 global cxr_line_colours
, cxr_line_positions
695 cxr_line_positions
+= [(p0
[0],p0
[1],p0
[2])]
696 cxr_line_positions
+= [(p1
[0],p1
[1],p1
[2])]
697 cxr_line_colours
+= [(colour
[0],colour
[1],colour
[2],colour
[3])]
698 cxr_line_colours
+= [(colour
[0],colour
[1],colour
[2],colour
[3])]
701 global cxr_jobs_inf
, cxr_jobs_batch
, cxr_error_inf
, cxr_view_mesh
, \
704 cxr_jobs_batch
= None
711 cxr_asset_lib
['models'] = {}
712 cxr_asset_lib
['materials'] = {}
713 cxr_asset_lib
['textures'] = {}
718 # ------------------------------------------------------------------------------
723 NBVTF_IMAGE_FORMAT_ABGR8888
= 1
724 NBVTF_IMAGE_FORMAT_BGR888
= 3
725 NBVTF_IMAGE_FORMAT_DXT1
= 13
726 NBVTF_IMAGE_FORMAT_DXT5
= 15
727 NBVTF_TEXTUREFLAGS_CLAMPS
= 0x00000004
728 NBVTF_TEXTUREFLAGS_CLAMPT
= 0x00000008
729 NBVTF_TEXTUREFLAGS_NORMAL
= 0x00000080
730 NBVTF_TEXTUREFLAGS_NOMIP
= 0x00000100
731 NBVTF_TEXTUREFLAGS_NOLOD
= 0x00000200
733 libnbvtf_convert
= extern( "nbvtf_convert", \
734 [c_char_p
,c_int32
,c_int32
,c_int32
,c_int32
,c_int32
,c_uint32
,c_char_p
], \
737 libnbvtf_read
= extern( "nbvtf_read", \
738 [c_void_p
,POINTER(c_int32
),POINTER(c_int32
), c_int32
], \
741 libnbvtf_free
= extern( "nbvtf_free", [POINTER(c_uint8
)], None )
743 libnbvtf_init
= extern( "nbvtf_init", [], None )
744 libnbvtf_funcs
= [ libnbvtf_convert
, libnbvtf_init
, libnbvtf_read
, \
748 # --------------------------
751 global libcxr
, libnbvtf
, libcxr_funcs
, libnbvtf_funcs
753 # Unload libraries if existing
754 def _reload( lib
, path
):
757 _handle
= lib
._handle
758 for i
in range(10): libc_dlclose( _handle
)
762 libpath
= F
'{os.path.dirname(__file__)}/{path}{CXR_SHARED_EXT}'
763 return cdll
.LoadLibrary( libpath
)
765 libnbvtf
= _reload( libnbvtf
, "libnbvtf" )
766 libcxr
= _reload( libcxr
, "libcxr" )
768 for fd
in libnbvtf_funcs
:
769 fd
.loadfrom( libnbvtf
)
772 for fd
in libcxr_funcs
:
773 fd
.loadfrom( libcxr
)
776 global c_libcxr_log_callback
, c_libcxr_line_callback
778 LOG_FUNCTION_TYPE
= CFUNCTYPE(None,c_char_p
)
779 c_libcxr_log_callback
= LOG_FUNCTION_TYPE(libcxr_log_callback
)
781 LINE_FUNCTION_TYPE
= CFUNCTYPE(None,\
782 POINTER(c_double
), POINTER(c_double
), POINTER(c_double
))
783 c_libcxr_line_callback
= LINE_FUNCTION_TYPE(libcxr_line_callback
)
785 libcxr
.cxr_set_log_function(cast(c_libcxr_log_callback
,c_void_p
))
786 libcxr
.cxr_set_line_function(cast(c_libcxr_line_callback
,c_void_p
))
788 build_time
= c_char_p
.in_dll(libcxr
,'cxr_build_time')
789 print( F
"libcxr build time: {build_time.value}" )
794 # ------------------------------------------------------------------------------
796 # Standard entity functions, think of like base.fgd
798 def cxr_get_origin(context
):
799 return context
['object'].location
* context
['transform']['scale'] + \
800 mathutils
.Vector(context
['transform']['offset'])
802 def cxr_get_angles(context
):
803 obj
= context
['object']
804 euler
= [ a
*57.295779513 for a
in obj
.rotation_euler
]
811 def cxr_baseclass(classes
, other
):
814 base
.update(x
.copy())
817 def ent_soundscape(context
):
818 obj
= context
['object']
819 kvs
= cxr_baseclass([ent_origin
],\
821 "radius": obj
.scale
.x
* bpy
.context
.scene
.cxr_data
.scale_factor
,
822 "soundscape": {"type":"string","default":""}
827 # EEVEE Light component converter -> Source 1
829 def ent_lights(context
):
830 obj
= context
['object']
831 kvs
= cxr_baseclass([ent_origin
],\
833 "_distance": (0.0 if obj
.data
.cxr_data
.realtime
else -1.0),
834 "_lightHDR": '-1 -1 -1 1',
838 light_base
= [(pow(obj
.data
.color
[i
],1.0/2.2)*255.0) for i
in range(3)] +\
839 [obj
.data
.energy
* bpy
.context
.scene
.cxr_data
.light_scale
]
841 if obj
.data
.type == 'SPOT' or obj
.data
.type == 'SUN':
842 # Blenders directional lights are -z forward
843 # Source is +x, however, it seems to use a completely different system.
844 # Since we dont care about roll for spotlights, we just take the
845 # pitch and yaw via trig
847 _
,mtx_rot
,_
= obj
.matrix_world
.decompose()
848 fwd
= mtx_rot
@ mathutils
.Vector((0,0,-1))
849 dir_pitch
= math
.asin(fwd
[2]) * 57.295779513
850 dir_yaw
= math
.atan2(fwd
[1],fwd
[0]) * 57.295779513
852 if obj
.data
.type == 'SPOT':
853 kvs
['_light'] = [ int(x
) for x
in light_base
]
854 kvs
['_cone'] = obj
.data
.spot_size
*(57.295779513/2.0)
855 kvs
['_inner_cone'] = (1.0-obj
.data
.spot_blend
)*kvs
['_cone']
857 kvs
['pitch'] = dir_pitch
858 kvs
['angles'] = [ 0, dir_yaw
, 0 ]
859 kvs
['_quadratic_attn'] = 0.0 # Source spotlights + quadratic falloff look
862 # Blender's default has a much more 'nice'
864 kvs
['_linear_attn'] = 1.0
866 elif obj
.data
.type == 'POINT':
867 kvs
['_light'] = [ int(x
) for x
in light_base
]
868 kvs
['_quadratic_attn'] = 1.0
869 kvs
['_linear_attn'] = 1.0
871 elif obj
.data
.type == 'SUN':
872 light_base
[3] *= 300.0 * 5
873 kvs
['_light'] = [ int(x
) for x
in light_base
]
875 ambient
= bpy
.context
.scene
.world
.color
876 kvs
['_ambient'] = [int(pow(ambient
[i
],1.0/2.2)*255.0) for i
in range(3)] +\
878 kvs
['_ambientHDR'] = [-1,-1,-1,1]
879 kvs
['_AmbientScaleHDR'] = 1
880 kvs
['pitch'] = dir_pitch
881 kvs
['angles'] = [ dir_pitch
, dir_yaw
, 0.0 ]
882 kvs
['SunSpreadAngle'] = 0
886 def ent_prop(context
):
887 if isinstance( context
['object'], bpy
.types
.Collection
):
889 target
= context
['object']
890 pos
= mathutils
.Vector(context
['origin'])
891 pos
+= mathutils
.Vector(context
['transform']['offset'])
893 kvs
['origin'] = [pos
[1],-pos
[0],pos
[2]]
894 kvs
['angles'] = [0,180,0]
895 kvs
['uniformscale'] = 1.0
897 kvs
= cxr_baseclass([ent_origin
],{})
898 target
= context
['object'].instance_collection
900 obj
= context
['object']
901 euler
= [ a
*57.295779513 for a
in obj
.rotation_euler
]
904 angle
[1] = euler
[2] + 180.0 # Dunno...
907 kvs
['angles'] = angle
908 kvs
['uniformscale'] = obj
.scale
[0]
910 if target
.cxr_data
.shadow_caster
:
911 kvs
['enablelightbounce'] = 1
912 kvs
['disableshadows'] = 0
914 kvs
['enablelightbounce'] = 0
915 kvs
['disableshadows'] = 1
917 kvs
['fademindist'] = -1
919 kvs
['model'] = F
"{asset_path('models',target)}.mdl".lower()
920 kvs
['renderamt'] = 255
921 kvs
['rendercolor'] = [255, 255, 255]
927 def ent_sky_camera(context
):
928 settings
= bpy
.context
.scene
.cxr_data
929 scale
= settings
.scale_factor
/ settings
.skybox_scale_factor
932 "origin": [_
for _
in context
['transform']['offset']],
933 "angles": [ 0, 0, 0 ],
934 "fogcolor": [255, 255, 255],
935 "fogcolor2": [255, 255, 255],
940 "HDRColorScale": 1.0,
945 def ent_cubemap(context
):
946 obj
= context
['object']
947 return cxr_baseclass([ent_origin
], {"cubemapsize": obj
.data
.cxr_data
.size
})
949 ent_origin
= { "origin": cxr_get_origin
}
950 ent_angles
= { "angles": cxr_get_angles
}
951 ent_transform
= cxr_baseclass( [ent_origin
], ent_angles
)
953 #include the user config
954 exec(open(F
'{os.path.dirname(__file__)}/config.py').read())
956 # Blender state callbacks
957 # ------------------------------------------------------------------------------
960 def cxr_on_load(dummy
):
961 global cxr_view_lines
, cxr_view_mesh
963 cxr_view_lines
= None
967 def cxr_dgraph_update(scene
,dgraph
):
969 print( F
"Hallo {time.time()}" )
971 # Convexer compilation functions
972 # ------------------------------------------------------------------------------
974 # Asset path management
976 def asset_uid(asset
):
977 if isinstance(asset
,str):
980 # Create a unique ID string
982 v
= asset
.cxr_data
.asset_id
991 dig
.append( int( v
% len(base
) ) )
997 if bpy
.context
.scene
.cxr_data
.include_names
:
998 name
+= asset
.name
.replace('.','_')
1002 # -> <project_name>/<asset_name>
1003 def asset_name(asset
):
1004 return F
"{bpy.context.scene.cxr_data.project_name}/{asset_uid(asset)}"
1006 # -> <subdir>/<project_name>/<asset_name>
1007 def asset_path(subdir
, asset
):
1008 return F
"{subdir}/{asset_name(asset_uid(asset))}"
1010 # -> <csgo>/<subdir>/<project_name>/<asset_name>
1011 def asset_full_path(sdir
,asset
):
1012 return F
"{bpy.context.scene.cxr_data.subdir}/"+\
1013 F
"{asset_path(sdir,asset_uid(asset))}"
1015 # Decomposes mesh, and sets global error information if failed.
1016 # - returns None on fail
1017 # - returns world on success
1018 def cxr_decompose_globalerr( mesh_src
):
1019 global cxr_error_inf
1022 world
= libcxr_decompose
.call( mesh_src
, pointer(err
) )
1025 cxr_view_mesh
= None
1029 ("No Error", "There is no error?"),\
1030 ("Bad input", "Non manifold geometry is present in the input mesh"),\
1031 ("Bad result","An invalid manifold was generated, try to simplify"),\
1032 ("Bad result","Make sure there is a clear starting point"),\
1033 ("Bad result","Implicit vertex was invalid, try to simplify"),\
1034 ("Bad input","Non coplanar vertices are in the source mesh"),\
1035 ("Bad input","Non convex polygon is in the source mesh"),\
1036 ("Bad result","Undefined failure"),\
1037 ("Invalid Input", "Undefined failure"),\
1044 # Entity functions / infos
1045 # ------------------------
1047 def cxr_collection_purpose(collection
):
1048 if collection
.name
.startswith('.'): return None
1049 if collection
.hide_render
: return None
1050 if collection
.name
.startswith('mdl_'): return 'model'
1053 def cxr_object_purpose(obj
):
1057 def _search(collection
):
1058 nonlocal objpurpose
, group
, obj
1060 purpose
= cxr_collection_purpose( collection
)
1061 if purpose
== None: return
1062 if purpose
== 'model':
1063 for o
in collection
.objects
:
1065 if o
.type != 'EMPTY':
1066 objpurpose
= 'model'
1070 for o
in collection
.objects
:
1072 classname
= cxr_classname(o
)
1073 if classname
!= None:
1074 objpurpose
= 'entity'
1075 if o
.type == 'MESH':
1076 objpurpose
= 'brush_entity'
1079 if o
.type == 'MESH':
1080 objpurpose
= 'brush'
1083 for c
in collection
.children
:
1086 if 'main' in bpy
.data
.collections
:
1087 _search( bpy
.data
.collections
['main'] )
1089 if objpurpose
== None and 'skybox' in bpy
.data
.collections
:
1090 _search( bpy
.data
.collections
['skybox'] )
1092 return (group
,objpurpose
)
1094 def cxr_intrinsic_classname(obj
):
1095 if obj
.type == 'LIGHT':
1097 'SPOT': "light_spot",
1099 'SUN': "light_environment" }[ obj
.data
.type ]
1101 elif obj
.type == 'LIGHT_PROBE':
1102 return "env_cubemap"
1103 elif obj
.type == 'EMPTY':
1104 if obj
.is_instancer
:
1105 return "prop_static"
1109 def cxr_custom_class(obj
):
1110 if obj
.type == 'MESH': custom_class
= obj
.cxr_data
.brushclass
1111 else: custom_class
= obj
.cxr_data
.classname
1115 def cxr_classname(obj
):
1116 intr
= cxr_intrinsic_classname(obj
)
1117 if intr
!= None: return intr
1119 custom_class
= cxr_custom_class(obj
)
1120 if custom_class
!= 'NONE':
1126 # intinsic: (k, False, value)
1127 # property: (k, True, value or default)
1131 def cxr_entity_keyvalues(context
):
1132 classname
= context
['classname']
1133 obj
= context
['object']
1134 if classname
not in cxr_entities
: return None
1138 entdef
= cxr_entities
[classname
]
1139 kvs
= entdef
['keyvalues']
1141 if callable(kvs
): kvs
= kvs(context
)
1148 if isinstance(kv
,dict):
1150 value
= obj
[ F
"cxrkv_{k}" ]
1155 if isinstance(value
,mathutils
.Vector
):
1156 value
= [_
for _
in value
]
1158 result
+= [(k
, isprop
, value
)]
1162 # Extract material information from shader graph data
1164 def material_info(mat
):
1166 info
['res'] = (512,512)
1167 info
['name'] = 'tools/toolsnodraw'
1169 if mat
== None or mat
.use_nodes
== False:
1173 if mat
.cxr_data
.shader
== 'Builtin':
1174 info
['name'] = mat
.name
1178 info
['name'] = asset_name(mat
)
1180 # Using the cxr_graph_mapping as a reference, go through the shader
1181 # graph and gather all $props from it.
1183 def _graph_read( node_def
, node
=None, depth
=0 ):
1187 def _variant_apply( val
):
1190 if isinstance( val
, list ):
1191 for shader_variant
in val
:
1192 if shader_variant
[0] == mat
.cxr_data
.shader
:
1193 return shader_variant
[1]
1200 _graph_read
.extracted
= []
1202 for node_idname
in node_def
:
1203 for n
in mat
.node_tree
.nodes
:
1204 if n
.name
== node_idname
:
1205 node_def
= node_def
[node_idname
]
1209 for link
in node_def
:
1210 link_def
= _variant_apply( node_def
[link
] )
1212 if isinstance( link_def
, dict ):
1213 node_link
= node
.inputs
[link
]
1215 if node_link
.is_linked
:
1217 # look for definitions for the connected node type
1218 from_node
= node_link
.links
[0].from_node
1220 node_name
= from_node
.name
.split('.')[0]
1221 if node_name
in link_def
:
1222 from_node_def
= link_def
[ node_name
]
1224 _graph_read( from_node_def
, from_node
, depth
+1 )
1227 # TODO: Make a warning for this?
1230 if "default" in link_def
:
1231 prop
= _variant_apply( link_def
['default'] )
1232 info
[prop
] = node_link
.default_value
1234 prop
= _variant_apply( link_def
)
1235 info
[prop
] = getattr( node
, link
)
1237 _graph_read(cxr_graph_mapping
)
1239 if "$basetexture" in info
:
1240 export_res
= info
['$basetexture'].cxr_data
.export_res
1241 info
['res'] = (export_res
[0], export_res
[1])
1245 def vec3_min( a
, b
):
1246 return mathutils
.Vector((min(a
[0],b
[0]),min(a
[1],b
[1]),min(a
[2],b
[2])))
1247 def vec3_max( a
, b
):
1248 return mathutils
.Vector((max(a
[0],b
[0]),max(a
[1],b
[1]),max(a
[2],b
[2])))
1250 def cxr_collection_center(collection
, transform
):
1252 bounds_min
= mathutils
.Vector((BIG
,BIG
,BIG
))
1253 bounds_max
= mathutils
.Vector((-BIG
,-BIG
,-BIG
))
1255 for obj
in collection
.objects
:
1256 if obj
.type == 'MESH':
1257 corners
= [ mathutils
.Vector(c
) for c
in obj
.bound_box
]
1259 for corner
in [ obj
.matrix_world
@c for c
in corners
]:
1260 bounds_min
= vec3_min( bounds_min
, corner
)
1261 bounds_max
= vec3_max( bounds_max
, corner
)
1263 center
= (bounds_min
+ bounds_max
) / 2.0
1265 origin
= mathutils
.Vector((-center
[1],center
[0],center
[2]))
1266 origin
*= transform
['scale']
1270 # Prepares Scene into dictionary format
1272 def cxr_scene_collect():
1273 context
= bpy
.context
1275 # Make sure all of our asset types have a unique ID
1276 def _uid_prepare(objtype
):
1282 if vs
.asset_id
in used_ids
:
1285 id_max
= max(id_max
,vs
.asset_id
)
1286 used_ids
+=[vs
.asset_id
]
1287 for vs
in to_generate
:
1289 vs
.asset_id
= id_max
1290 _uid_prepare(bpy
.data
.materials
)
1291 _uid_prepare(bpy
.data
.images
)
1292 _uid_prepare(bpy
.data
.collections
)
1295 "entities": [], # Everything with a classname
1296 "geo": [], # All meshes without a classname
1297 "heros": [] # Collections prefixed with mdl_
1300 def _collect(collection
,transform
):
1303 purpose
= cxr_collection_purpose( collection
)
1304 if purpose
== None: return
1305 if purpose
== 'model':
1306 sceneinfo
['entities'] += [{
1307 "object": collection
,
1308 "classname": "prop_static",
1309 "transform": transform
,
1310 "origin": cxr_collection_center( collection
, transform
)
1313 sceneinfo
['heros'] += [{
1314 "collection": collection
,
1315 "transform": transform
,
1316 "origin": cxr_collection_center( collection
, transform
)
1320 for obj
in collection
.objects
:
1321 if obj
.hide_get(): continue
1323 classname
= cxr_classname( obj
)
1325 if classname
!= None:
1326 sceneinfo
['entities'] += [{
1328 "classname": classname
,
1329 "transform": transform
1331 elif obj
.type == 'MESH':
1332 sceneinfo
['geo'] += [{
1334 "transform": transform
1337 for c
in collection
.children
:
1338 _collect( c
, transform
)
1341 "scale": context
.scene
.cxr_data
.scale_factor
,
1346 "scale": context
.scene
.cxr_data
.skybox_scale_factor
,
1347 "offset": (0,0,context
.scene
.cxr_data
.skybox_offset
)
1350 if 'main' in bpy
.data
.collections
:
1351 _collect( bpy
.data
.collections
['main'], transform_main
)
1353 if 'skybox' in bpy
.data
.collections
:
1354 _collect( bpy
.data
.collections
['skybox'], transform_sky
)
1356 sceneinfo
['entities'] += [{
1358 "transform": transform_sky
,
1359 "classname": "sky_camera"
1364 # Write VMF out to file (JOB HANDLER)
1366 def cxr_export_vmf(sceneinfo
, output_vmf
):
1369 with
vdf_structure(output_vmf
) as m
:
1370 print( F
"Write: {output_vmf}" )
1372 vmfinfo
= cxr_vmf_context()
1373 vmfinfo
.mapversion
= 4
1375 #TODO: These need to be in options...
1376 vmfinfo
.skyname
= bpy
.context
.scene
.cxr_data
.skyname
.encode('utf-8')
1377 vmfinfo
.detailvbsp
= b
"detail.vbsp"
1378 vmfinfo
.detailmaterial
= b
"detail/detailsprites"
1379 vmfinfo
.lightmap_scale
= 12
1381 vmfinfo
.brush_count
= 0
1382 vmfinfo
.entity_count
= 0
1383 vmfinfo
.face_count
= 0
1385 visgroups
= (cxr_visgroup
*len(cxr_visgroups
))()
1386 for i
, vg
in enumerate(cxr_visgroups
):
1387 visgroups
[i
].name
= vg
.encode('utf-8')
1388 vmfinfo
.visgroups
= cast(visgroups
, POINTER(cxr_visgroup
))
1389 vmfinfo
.visgroup_count
= len(cxr_visgroups
)
1391 libcxr_begin_vmf
.call( pointer(vmfinfo
), m
.fp
)
1393 def _buildsolid( cmd
):
1396 print( F
"{vmfinfo.brush_count} :: {cmd['object'].name}" )
1398 baked
= mesh_cxr_format( cmd
['object'] )
1399 world
= cxr_decompose_globalerr( baked
)
1404 vmfinfo
.scale
= cmd
['transform']['scale']
1406 offset
= cmd
['transform']['offset']
1407 vmfinfo
.offset
[0] = offset
[0]
1408 vmfinfo
.offset
[1] = offset
[1]
1409 vmfinfo
.offset
[2] = offset
[2]
1411 if cmd
['object'].cxr_data
.lightmap_override
> 0:
1412 vmfinfo
.lightmap_scale
= cmd
['object'].cxr_data
.lightmap_override
1414 vmfinfo
.lightmap_scale
= bpy
.context
.scene
.cxr_data
.lightmap_scale
1416 libcxr_push_world_vmf
.call( world
, pointer(vmfinfo
), m
.fp
)
1417 libcxr_free_world
.call( world
)
1422 for brush
in sceneinfo
['geo']:
1423 vmfinfo
.visgroupid
= int(brush
['object'].cxr_data
.visgroup
)
1424 if not _buildsolid( brush
):
1428 vmfinfo
.visgroupid
= 0
1430 libcxr_vmf_begin_entities
.call(pointer(vmfinfo
), m
.fp
)
1433 for ent
in sceneinfo
['entities']:
1435 ctx
= ent
['transform']
1436 cls
= ent
['classname']
1439 m
.kv( 'classname', cls
)
1441 kvs
= cxr_entity_keyvalues( ent
)
1444 if isinstance(kv
[2], list):
1445 m
.kv( kv
[0], ' '.join([str(_
) for _
in kv
[2]]) )
1446 else: m
.kv( kv
[0], str(kv
[2]) )
1450 elif not isinstance( obj
, bpy
.types
.Collection
):
1451 if obj
.type == 'MESH':
1452 vmfinfo
.visgroupid
= int(obj
.cxr_data
.visgroup
)
1453 if not _buildsolid( ent
):
1460 m
.kv( 'visgroupid', str(obj
.cxr_data
.visgroup
) )
1461 m
.kv( 'visgroupshown', '1' )
1462 m
.kv( 'visgroupautoshown', '1' )
1466 vmfinfo
.visgroupid
= 0
1471 # COmpile image using NBVTF and hash it (JOB HANDLER)
1473 def compile_image(img
):
1477 name
= asset_name(img
)
1478 src_path
= bpy
.path
.abspath(img
.filepath
)
1480 dims
= img
.cxr_data
.export_res
1482 'RGBA': NBVTF_IMAGE_FORMAT_ABGR8888
,
1483 'DXT1': NBVTF_IMAGE_FORMAT_DXT1
,
1484 'DXT5': NBVTF_IMAGE_FORMAT_DXT5
,
1485 'RGB': NBVTF_IMAGE_FORMAT_BGR888
1486 }[ img
.cxr_data
.fmt
]
1488 mipmap
= img
.cxr_data
.mipmap
1489 lod
= img
.cxr_data
.lod
1490 clamp
= img
.cxr_data
.clamp
1491 flags
= img
.cxr_data
.flags
1493 q
=bpy
.context
.scene
.cxr_data
.image_quality
1495 userflag_hash
= F
"{mipmap}.{lod}.{clamp}.{flags}"
1496 file_hash
= F
"{name}.{os.path.getmtime(src_path)}"
1497 comphash
= F
"{file_hash}.{dims[0]}.{dims[1]}.{fmt}.{userflag_hash}.{q}"
1499 if img
.cxr_data
.last_hash
!= comphash
:
1500 print( F
"Texture update: {img.filepath}" )
1502 src
= src_path
.encode('utf-8')
1503 dst
= (asset_full_path('materials',img
)+'.vtf').encode('utf-8')
1507 # texture setting flags
1508 if not lod
: flags_full |
= NBVTF_TEXTUREFLAGS_NOLOD
1510 flags_full |
= NBVTF_TEXTUREFLAGS_CLAMPS
1511 flags_full |
= NBVTF_TEXTUREFLAGS_CLAMPT
1513 if libnbvtf_convert
.call(src
,dims
[0],dims
[1],mipmap
,fmt
,q
,flags_full
,dst
):
1514 img
.cxr_data
.last_hash
= comphash
1519 # Compile a material to VMT format. This is quick to run, doesnt need to be a
1522 def compile_material(mat
):
1523 info
= material_info(mat
)
1524 properties
= mat
.cxr_data
1526 print( F
"Compile {asset_full_path('materials',mat)}.vmt" )
1527 if properties
.shader
== 'Builtin':
1532 # Walk the property tree
1533 def _mlayer( layer
):
1534 nonlocal properties
, props
1537 if isinstance(layer
[decl
],dict): # $property definition
1539 ptype
= pdef
['type']
1545 if 'shaders' in pdef
and properties
.shader
not in pdef
['shaders']:
1548 # Group expansion (does it have subdefinitions?)
1550 if isinstance(pdef
[ch
],dict):
1559 if ptype
== 'intrinsic':
1563 prop
= getattr(properties
,decl
)
1564 default
= pdef
['default']
1566 if not isinstance(prop
,str) and \
1567 not isinstance(prop
,bpy
.types
.Image
) and \
1568 hasattr(prop
,'__getitem__'):
1569 prop
= tuple([p
for p
in prop
])
1573 props
+= [(decl
,pdef
,prop
)]
1578 if expandview
: _mlayer(pdef
)
1580 _mlayer( cxr_shader_params
)
1583 with
vdf_structure( F
"{asset_full_path('materials',mat)}.vmt" ) as vmt
:
1584 vmt
.node( properties
.shader
)
1585 vmt
.put( "// Convexer export\n" )
1594 if 'exponent' in pdef
: return str(pow( v
, pdef
['exponent'] ))
1597 if isinstance(prop
,bpy
.types
.Image
):
1598 vmt
.kv( decl
, asset_name(prop
))
1599 elif isinstance(prop
,bool):
1600 vmt
.kv( decl
, '1' if prop
else '0' )
1601 elif isinstance(prop
,str):
1602 vmt
.kv( decl
, prop
)
1603 elif isinstance(prop
,float) or isinstance(prop
,int):
1604 vmt
.kv( decl
, _numeric(prop
) )
1605 elif isinstance(prop
,tuple):
1606 vmt
.kv( decl
, F
"[{' '.join([_numeric(_) for _ in prop])}]" )
1608 vmt
.put( F
"// (cxr) unkown shader value type'{type(prop)}'" )
1613 def cxr_modelsrc_vphys( mdl
):
1614 for obj
in mdl
.objects
:
1615 if obj
.name
== F
"{mdl.name}_phy":
1619 def cxr_export_modelsrc( mdl
, origin
, asset_dir
, project_name
, transform
):
1620 dgraph
= bpy
.context
.evaluated_depsgraph_get()
1622 # Compute hash value
1623 chash
= asset_uid(mdl
)+str(origin
)+str(transform
)
1625 #for obj in mdl.objects:
1626 # if obj.type != 'MESH':
1629 # ev = obj.evaluated_get(dgraph).data
1630 # srcverts=[(v.co[0],v.co[1],v.co[2]) for v in ev.vertices]
1631 # srcloops=[(l.normal[0],l.normal[1],l.normal[2]) for l in ev.loops]
1633 # chash=hashlib.sha224((str(srcverts)+chash).encode()).hexdigest()
1634 # chash=hashlib.sha224((str(srcloops)+chash).encode()).hexdigest()
1636 # if ev.uv_layers.active != None:
1637 # uv_layer = ev.uv_layers.active.data
1638 # srcuv=[(uv.uv[0],uv.uv[1]) for uv in uv_layer]
1642 # chash=hashlib.sha224((str(srcuv)+chash).encode()).hexdigest()
1643 # srcmats=[ ms.material.name for ms in obj.material_slots ]
1644 # chash=hashlib.sha224((str(srcmats)+chash).encode()).hexdigest()
1645 # transforms=[ obj.location, obj.rotation_euler, obj.scale ]
1646 # srctr=[(v[0],v[1],v[2]) for v in transforms]
1647 # chash=hashlib.sha224((str(srctr)+chash).encode()).hexdigest()
1649 #if chash != mdl.cxr_data.last_hash:
1650 # mdl.cxr_data.last_hash = chash
1651 # print( F"Compile: {mdl.name}" )
1655 bpy
.ops
.object.select_all(action
='DESELECT')
1658 def _get_layer(col
,name
):
1659 for c
in col
.children
:
1662 sub
= _get_layer(c
,name
)
1666 layer
= _get_layer(bpy
.context
.view_layer
.layer_collection
,mdl
.name
)
1668 prev_state
= layer
.hide_viewport
1669 layer
.hide_viewport
=False
1671 # Collect materials to be compiled, and temp rename for export
1675 for obj
in mdl
.objects
:
1676 if obj
.name
== F
"{mdl.name}_phy":
1680 obj
.select_set(state
=True)
1681 for ms
in obj
.material_slots
:
1682 if ms
.material
!= None:
1683 if ms
.material
not in mat_dict
:
1684 mat_dict
[ms
.material
] = ms
.material
.name
1685 ms
.material
.name
= asset_uid(ms
.material
)
1686 ms
.material
.use_nodes
= False
1689 bpy
.ops
.export_scene
.fbx( filepath
=F
'{asset_dir}/{uid}_ref.fbx',\
1690 check_existing
=False,
1692 apply_unit_scale
=False,
1693 bake_space_transform
=False
1696 bpy
.ops
.object.select_all(action
='DESELECT')
1699 vphys
.select_set(state
=True)
1700 bpy
.ops
.export_scene
.fbx( filepath
=F
'{asset_dir}/{uid}_phy.fbx',\
1701 check_existing
=False,
1703 apply_unit_scale
=False,
1704 bake_space_transform
=False
1706 bpy
.ops
.object.select_all(action
='DESELECT')
1708 # Fix material names back to original
1709 for mat
in mat_dict
:
1710 mat
.name
= mat_dict
[mat
]
1711 mat
.use_nodes
= True
1713 layer
.hide_viewport
=prev_state
1716 with
open(F
'{asset_dir}/{uid}.qc','w') as o
:
1717 o
.write(F
'$modelname "{project_name}/{uid}"\n')
1718 #o.write(F'$scale .32\n')
1719 o
.write(F
'$scale {transform["scale"]/100.0}\n')
1720 o
.write(F
'$body _ "{uid}_ref.fbx"\n')
1721 o
.write(F
'$staticprop\n')
1722 o
.write(F
'$origin {origin[0]:.6f} {origin[1]:.6f} {origin[2]:.6f}\n')
1724 if mdl
.cxr_data
.preserve_order
:
1725 o
.write(F
"$preservetriangleorder\n")
1727 if mdl
.cxr_data
.texture_shadows
:
1728 o
.write(F
"$casttextureshadows\n")
1730 o
.write(F
"$surfaceprop {mdl.cxr_data.surfaceprop}\n")
1733 o
.write(F
'$collisionmodel "{uid}_phy.fbx"\n')
1735 o
.write(" $concave\n")
1738 o
.write(F
'$cdmaterials {project_name}\n')
1739 o
.write(F
'$sequence idle {uid}_ref.fbx\n')
1743 # Copy bsp file (and also lightpatch it)
1745 def cxr_patchmap( src
, dst
):
1746 libcxr_lightpatch_bsp
.call( src
.encode('utf-8') )
1747 shutil
.copyfile( src
, dst
)
1750 # Convexer operators
1751 # ------------------------------------------------------------------------------
1753 # Force reload of shared libraries
1755 class CXR_RELOAD(bpy
.types
.Operator
):
1756 bl_idname
="convexer.reload"
1758 def execute(_
,context
):
1762 # Reset all debugging/ui information
1764 class CXR_RESET(bpy
.types
.Operator
):
1765 bl_idname
="convexer.reset"
1766 bl_label
="Reset Convexer"
1767 def execute(_
,context
):
1771 # Used for exporting data to use with ASAN builds
1773 class CXR_DEV_OPERATOR(bpy
.types
.Operator
):
1774 bl_idname
="convexer.dev_test"
1775 bl_label
="Export development data"
1777 def execute(_
,context
):
1778 # Prepare input data
1779 mesh_src
= mesh_cxr_format(context
.active_object
)
1780 libcxr_write_test_data
.call( pointer(mesh_src
) )
1783 class CXR_INIT_FS_OPERATOR(bpy
.types
.Operator
):
1784 bl_idname
="convexer.fs_init"
1785 bl_label
="Initialize filesystem"
1787 def execute(_
,context
):
1788 gameinfo
= F
'{bpy.context.scene.cxr_data.subdir}/gameinfo.txt'
1790 if libcxr_fs_set_gameinfo
.call( gameinfo
.encode('utf-8') ) == 1:
1791 print( "File system ready" )
1793 print( "File system failed to initialize" )
1797 def cxr_load_texture( path
, is_normal
):
1798 global cxr_asset_lib
1800 if path
in cxr_asset_lib
['textures']:
1801 return cxr_asset_lib
['textures'][path
]
1803 print( F
"cxr_load_texture( '{path}' )" )
1805 pvtf
= libcxr_fs_get
.call( path
.encode('utf-8'), 0 )
1808 print( "vtf failed to load" )
1809 cxr_asset_lib
['textures'][path
] = None
1815 img_data
= libnbvtf_read
.call( pvtf
, pointer(x
), pointer(y
), \
1816 c_int32(is_normal
) )
1822 print( "vtf failed to decode" )
1823 libcxr_fs_free
.call( pvtf
)
1824 cxr_asset_lib
['textures'][path
] = None
1827 img_buf
= gpu
.types
.Buffer('FLOAT', [x
*y
*4], [_
/255.0 for _
in img_data
[:x
*y
*4]])
1829 tex
= cxr_asset_lib
['textures'][path
] = \
1830 gpu
.types
.GPUTexture( size
=(x
,y
), layers
=0, is_cubemap
=False,\
1831 format
='RGBA8', data
=img_buf
)
1833 libnbvtf_free
.call( img_data
)
1834 libcxr_fs_free
.call( pvtf
)
1837 def cxr_load_material( path
):
1838 global cxr_asset_lib
1840 if path
in cxr_asset_lib
['materials']:
1841 return cxr_asset_lib
['materials'][path
]
1843 print( F
"cxr_load_material( '{path}' )" )
1845 pvmt
= libcxr_valve_load_material
.call( path
.encode( 'utf-8') )
1848 cxr_asset_lib
['materials'][path
] = None
1852 mat
= cxr_asset_lib
['materials'][path
] = {}
1855 mat
['basetexture'] = cxr_load_texture( vmt
.basetexture
.decode('utf-8'), 0)
1858 mat
['bumpmap'] = cxr_load_texture( vmt
.bumpmap
.decode('utf-8'), 1)
1860 libcxr_valve_free_material
.call( pvmt
)
1864 def cxr_load_model_full( path
):
1865 global cxr_asset_lib
, cxr_mdl_shader
1867 if path
in cxr_asset_lib
['models']:
1868 return cxr_asset_lib
['models'][path
]
1870 pmdl
= libcxr_valve_load_model
.call( path
.encode( 'utf-8' ) )
1872 print( F
"cxr_load_model_full( '{path}' )" )
1875 print( "Failed to load model" )
1876 cxr_asset_lib
['models'][path
] = None
1881 # Convert our lovely interleaved vertex stream into, whatever this is.
1882 positions
= [ (mdl
.vertex_data
[i
*8+0], \
1883 mdl
.vertex_data
[i
*8+1], \
1884 mdl
.vertex_data
[i
*8+2]) for i
in range(mdl
.vertex_count
) ]
1886 normals
= [ (mdl
.vertex_data
[i
*8+3], \
1887 mdl
.vertex_data
[i
*8+4], \
1888 mdl
.vertex_data
[i
*8+5]) for i
in range(mdl
.vertex_count
) ]
1890 uvs
= [ (mdl
.vertex_data
[i
*8+6], \
1891 mdl
.vertex_data
[i
*8+7]) for i
in range(mdl
.vertex_count
) ]
1893 fmt
= gpu
.types
.GPUVertFormat()
1894 fmt
.attr_add(id="aPos", comp_type
='F32', len=3, fetch_mode
='FLOAT')
1895 fmt
.attr_add(id="aNormal", comp_type
='F32', len=3, fetch_mode
='FLOAT')
1896 fmt
.attr_add(id="aUv", comp_type
='F32', len=2, fetch_mode
='FLOAT')
1898 vbo
= gpu
.types
.GPUVertBuf(len=mdl
.vertex_count
, format
=fmt
)
1899 vbo
.attr_fill(id="aPos", data
=positions
)
1900 vbo
.attr_fill(id="aNormal", data
=normals
)
1901 vbo
.attr_fill(id="aUv", data
=uvs
)
1903 batches
= cxr_asset_lib
['models'][path
] = []
1905 for p
in range(mdl
.part_count
):
1907 indices
= mdl
.indices
[part
.ibstart
:part
.ibstart
+part
.ibcount
]
1908 indices
= [ (indices
[i
*3+0],indices
[i
*3+1],indices
[i
*3+2]) \
1909 for i
in range(part
.ibcount
//3) ]
1911 ibo
= gpu
.types
.GPUIndexBuf( type='TRIS', seq
=indices
)
1913 batch
= gpu
.types
.GPUBatch( type='TRIS', buf
=vbo
, elem
=ibo
)
1914 batch
.program_set( cxr_mdl_shader
)
1916 mat_str
= cast( mdl
.materials
[ part
.material
], c_char_p
)
1917 batches
+= [( cxr_load_material( mat_str
.value
.decode('utf-8') ), batch
)]
1919 libcxr_valve_free_model
.call( pmdl
)
1923 class CXR_LOAD_MODEL_OPERATOR(bpy
.types
.Operator
):
1924 bl_idname
="convexer.model_load"
1925 bl_label
="Load model"
1927 def execute(_
,context
):
1928 global cxr_test_mdl
, cxr_mdl_shader
, cxr_asset_lib
1930 cxr_test_mdl
= cxr_load_model_full( bpy
.context
.scene
.cxr_data
.dev_mdl
)
1935 # UI: Preview how the brushes will looks in 3D view
1937 class CXR_PREVIEW_OPERATOR(bpy
.types
.Operator
):
1938 bl_idname
="convexer.preview"
1939 bl_label
="Preview Brushes"
1943 def execute(_
,context
):
1944 global cxr_view_mesh
1945 global cxr_view_shader
, cxr_view_mesh
, cxr_error_inf
1949 static
= _
.__class
__
1951 mesh_src
= mesh_cxr_format(context
.active_object
)
1952 world
= cxr_decompose_globalerr( mesh_src
)
1957 # Generate preview using cxr
1959 ptrpreview
= libcxr_world_preview
.call( world
)
1960 preview
= ptrpreview
[0]
1962 vertices
= preview
.vertices
[:preview
.vertex_count
]
1963 vertices
= [(_
[0],_
[1],_
[2]) for _
in vertices
]
1965 colours
= preview
.colours
[:preview
.vertex_count
]
1966 colours
= [(_
[0],_
[1],_
[2],_
[3]) for _
in colours
]
1968 indices
= preview
.indices
[:preview
.indices_count
]
1969 indices
= [ (indices
[i
*3+0],indices
[i
*3+1],indices
[i
*3+2]) \
1970 for i
in range(int(preview
.indices_count
/3)) ]
1972 cxr_view_mesh
= batch_for_shader(
1973 cxr_view_shader
, 'TRIS',
1974 { "pos": vertices
, "color": colours
},
1978 libcxr_free_tri_mesh
.call( ptrpreview
)
1979 libcxr_free_world
.call( world
)
1985 # Search for VMF compiler executables in subdirectory
1987 class CXR_DETECT_COMPILERS(bpy
.types
.Operator
):
1988 bl_idname
="convexer.detect_compilers"
1989 bl_label
="Find compilers"
1991 def execute(self
,context
):
1992 scene
= context
.scene
1993 settings
= scene
.cxr_data
1994 subdir
= settings
.subdir
1996 for exename
in ['studiomdl','vbsp','vvis','vrad']:
1997 searchpath
= os
.path
.normpath(F
'{subdir}/../bin/{exename}.exe')
1998 if os
.path
.exists(searchpath
):
1999 settings
[F
'exe_{exename}'] = searchpath
2003 def cxr_compiler_path( compiler
):
2004 settings
= bpy
.context
.scene
.cxr_data
2005 subdir
= settings
.subdir
2006 path
= os
.path
.normpath(F
'{subdir}/../bin/{compiler}.exe')
2008 if os
.path
.exists( path
): return path
2011 # Compatibility layer
2013 def cxr_temp_file( fn
):
2014 if CXR_GNU_LINUX
== 1:
2017 filepath
= bpy
.data
.filepath
2018 directory
= os
.path
.dirname(filepath
)
2019 return F
"{directory}/{fn}"
2021 def cxr_winepath( path
):
2022 if CXR_GNU_LINUX
== 1:
2023 return 'z:'+path
.replace('/','\\')
2027 # Main compile function
2029 class CXR_COMPILER_CHAIN(bpy
.types
.Operator
):
2030 bl_idname
="convexer.chain"
2031 bl_label
="Compile Chain"
2046 def cancel(_
,context
):
2047 #global cxr_jobs_batch
2048 static
= _
.__class
__
2049 wm
= context
.window_manager
2051 if static
.SUBPROC
!= None:
2052 static
.SUBPROC
.terminate()
2053 static
.SUBPROC
= None
2055 if static
.TIMER
!= None:
2056 wm
.event_timer_remove( static
.TIMER
)
2061 #cxr_jobs_batch = None
2065 def modal(_
,context
,ev
):
2066 static
= _
.__class
__
2068 if ev
.type == 'TIMER':
2069 global cxr_jobs_batch
, cxr_error_inf
2071 if static
.WAIT_REDRAW
:
2073 return {'PASS_THROUGH'}
2074 static
.WAIT_REDRAW
= True
2076 if static
.USER_EXIT
:
2077 print( "Chain USER_EXIT" )
2078 return _
.cancel(context
)
2080 if static
.SUBPROC
!= None:
2081 # Deal with async modes
2082 status
= static
.SUBPROC
.poll()
2084 # Cannot redirect STDOUT through here without causing
2085 # undefined behaviour due to the Blender Python specification.
2087 # Have to write it out to a file and read it back in.
2090 with
open(cxr_temp_file("convexer_compile_log.txt"),"r") as log
:
2091 static
.LOG
= log
.readlines()
2093 return {'PASS_THROUGH'}
2095 #for l in static.SUBPROC.stdout:
2096 # print( F'-> {l.decode("utf-8")}',end='' )
2097 static
.SUBPROC
= None
2100 print(F
'Compiler () error: {status}')
2102 jobn
= static
.JOBSYS
['jobs'][static
.JOBID
]
2103 cxr_error_inf
= ( F
"{static.JOBSYS['title']} error {status}", jobn
)
2105 return _
.cancel(context
)
2107 static
.JOBSYS
['jobs'][static
.JOBID
] = None
2108 cxr_jobs_update_graph( static
.JOBINFO
)
2110 return {'PASS_THROUGH'}
2112 # Compile syncronous thing
2113 for sys
in static
.JOBINFO
:
2114 for i
,target
in enumerate(sys
['jobs']):
2117 if callable(sys
['exec']):
2118 print( F
"Run (sync): {static.JOBID} @{time.time()}" )
2120 if not sys
['exec'](*target
):
2121 print( "Job failed" )
2122 return _
.cancel(context
)
2124 sys
['jobs'][i
] = None
2127 # Run external executable (wine)
2128 static
.SUBPROC
= subprocess
.Popen( target
,
2129 stdout
=static
.FILE
,\
2130 stderr
=subprocess
.PIPE
,\
2135 cxr_jobs_update_graph( static
.JOBINFO
)
2137 return {'PASS_THROUGH'}
2140 print( "All jobs completed!" )
2141 #cxr_jobs_batch = None
2143 return _
.cancel(context
)
2145 return {'PASS_THROUGH'}
2147 def invoke(_
,context
,event
):
2148 global cxr_error_inf
2150 static
= _
.__class
__
2151 wm
= context
.window_manager
2153 if static
.TIMER
!= None:
2154 print("Chain exiting...")
2155 static
.USER_EXIT
=True
2156 return {'RUNNING_MODAL'}
2158 print("Launching compiler toolchain")
2161 # Run static compilation units now (collect, vmt..)
2162 filepath
= bpy
.data
.filepath
2163 directory
= os
.path
.dirname(filepath
)
2164 settings
= bpy
.context
.scene
.cxr_data
2166 asset_dir
= F
"{directory}/modelsrc"
2167 material_dir
= F
"{settings.subdir}/materials/{settings.project_name}"
2168 model_dir
= F
"{settings.subdir}/models/{settings.project_name}"
2169 output_vmf
= F
"{directory}/{settings.project_name}.vmf"
2171 bsp_local
= F
"{directory}/{settings.project_name}.bsp"
2172 bsp_remote
= F
"{settings.subdir}/maps/{settings.project_name}.bsp"
2173 bsp_packed
= F
"{settings.subdir}/maps/{settings.project_name}_pack.bsp"
2174 packlist
= F
"{directory}/{settings.project_name}_assets.txt"
2176 os
.makedirs( asset_dir
, exist_ok
=True )
2177 os
.makedirs( material_dir
, exist_ok
=True )
2178 os
.makedirs( model_dir
, exist_ok
=True )
2180 static
.FILE
= open(cxr_temp_file("convexer_compile_log.txt"),"w")
2183 sceneinfo
= cxr_scene_collect()
2189 for brush
in sceneinfo
['geo']:
2190 for ms
in brush
['object'].material_slots
:
2191 a_materials
.add( ms
.material
)
2192 if ms
.material
.cxr_data
.shader
== 'VertexLitGeneric':
2193 errmat
= ms
.material
.name
2194 errnam
= brush
['object'].name
2196 cxr_error_inf
= ( "Shader error", \
2197 F
"Vertex shader ({errmat}) used on model ({errnam})" )
2199 print( F
"Vertex shader {errmat} used on {errnam}")
2201 return {'CANCELLED'}
2205 for ent
in sceneinfo
['entities']:
2206 if ent
['object'] == None: continue
2208 if ent
['classname'] == 'prop_static':
2210 if isinstance(obj
,bpy
.types
.Collection
):
2212 a_models
.add( target
)
2213 model_jobs
+= [(target
, ent
['origin'], asset_dir
, \
2214 settings
.project_name
, ent
['transform'])]
2216 target
= obj
.instance_collection
2217 if target
in a_models
:
2219 a_models
.add( target
)
2221 # TODO: Should take into account collection instancing offset
2222 model_jobs
+= [(target
, [0,0,0], asset_dir
, \
2223 settings
.project_name
, ent
['transform'])]
2225 elif ent
['object'].type == 'MESH':
2226 for ms
in ent
['object'].material_slots
:
2227 a_materials
.add( ms
.material
)
2229 for mdl
in a_models
:
2230 uid
= asset_uid(mdl
)
2231 qc_jobs
+= [F
'{uid}.qc']
2233 for obj
in mdl
.objects
:
2234 for ms
in obj
.material_slots
:
2235 a_materials
.add( ms
.material
)
2236 if ms
.material
.cxr_data
.shader
== 'LightMappedGeneric' or \
2237 ms
.material
.cxr_data
.shader
== 'WorldVertexTransition':
2239 errmat
= ms
.material
.name
2242 cxr_error_inf
= ( "Shader error", \
2243 F
"Lightmapped shader ({errmat}) used on model ({errnam})" )
2245 print( F
"Lightmapped shader {errmat} used on {errnam}")
2247 return {'CANCELLED'}
2250 for mat
in a_materials
:
2251 for pair
in compile_material(mat
):
2256 if isinstance(prop
,bpy
.types
.Image
):
2258 if 'flags' in pdef
: flags
= pdef
['flags']
2259 if prop
not in image_jobs
:
2260 image_jobs
+= [(prop
,)]
2261 prop
.cxr_data
.flags
= flags
2264 with
open( packlist
, "w" ) as fp
:
2266 for mat
in a_materials
:
2267 if mat
.cxr_data
.shader
== 'Builtin': continue
2268 fp
.write(F
"{asset_path('materials',mat)}.vmt\n")
2269 fp
.write(F
"{cxr_winepath(asset_full_path('materials',mat))}.vmt\n")
2271 for img_job
in image_jobs
:
2273 fp
.write(F
"{asset_path('materials',img)}.vtf\n")
2274 fp
.write(F
"{cxr_winepath(asset_full_path('materials',img))}.vtf\n")
2276 for mdl
in a_models
:
2277 local
= asset_path('models',mdl
)
2278 winep
= cxr_winepath(asset_full_path('models',mdl
))
2280 fp
.write(F
"{local}.vvd\n")
2281 fp
.write(F
"{winep}.vvd\n")
2282 fp
.write(F
"{local}.dx90.vtx\n")
2283 fp
.write(F
"{winep}.dx90.vtx\n")
2284 fp
.write(F
"{local}.mdl\n")
2285 fp
.write(F
"{winep}.mdl\n")
2286 fp
.write(F
"{local}.vvd\n")
2287 fp
.write(F
"{winep}.vvd\n")
2289 if cxr_modelsrc_vphys(mdl
):
2290 fp
.write(F
"{local}.phy\n")
2291 fp
.write(F
"{winep}.phy\n")
2297 if settings
.comp_vmf
:
2298 static
.JOBINFO
+= [{
2299 "title": "Convexer",
2301 "colour": (0.863, 0.078, 0.235,1.0),
2302 "exec": cxr_export_vmf
,
2303 "jobs": [(sceneinfo
,output_vmf
)]
2306 if settings
.comp_textures
:
2307 if len(image_jobs
) > 0:
2308 static
.JOBINFO
+= [{
2309 "title": "Textures",
2311 "colour": (1.000, 0.271, 0.000,1.0),
2312 "exec": compile_image
,
2316 game
= cxr_winepath( settings
.subdir
)
2318 '-game', game
, settings
.project_name
2322 if settings
.comp_models
:
2323 if len(model_jobs
) > 0:
2324 static
.JOBINFO
+= [{
2327 "colour": (1.000, 0.647, 0.000,1.0),
2328 "exec": cxr_export_modelsrc
,
2332 if len(qc_jobs
) > 0:
2333 static
.JOBINFO
+= [{
2334 "title": "StudioMDL",
2336 "colour": (1.000, 0.843, 0.000, 1.0),
2337 "exec": "studiomdl",
2338 "jobs": [[settings
[F
'exe_studiomdl']] + [\
2339 '-nop4', '-game', game
, qc
] for qc
in qc_jobs
],
2344 if settings
.comp_compile
:
2345 if not settings
.opt_vbsp
.startswith( 'disable' ):
2346 vbsp_opt
= settings
.opt_vbsp
.split()
2347 static
.JOBINFO
+= [{
2350 "colour": (0.678, 1.000, 0.184,1.0),
2352 "jobs": [[settings
[F
'exe_vbsp']] + vbsp_opt
+ args
],
2356 if not settings
.opt_vvis
.startswith( 'disable' ):
2357 vvis_opt
= settings
.opt_vvis
.split()
2358 static
.JOBINFO
+= [{
2361 "colour": (0.000, 1.000, 0.498,1.0),
2363 "jobs": [[settings
[F
'exe_vvis']] + vvis_opt
+ args
],
2367 if not settings
.opt_vrad
.startswith( 'disable' ):
2368 vrad_opt
= settings
.opt_vrad
.split()
2369 static
.JOBINFO
+= [{
2372 "colour": (0.125, 0.698, 0.667,1.0),
2374 "jobs": [[settings
[F
'exe_vrad']] + vrad_opt
+ args
],
2378 static
.JOBINFO
+= [{
2381 "colour": (0.118, 0.565, 1.000,1.0),
2382 "exec": cxr_patchmap
,
2383 "jobs": [(bsp_local
,bsp_remote
)]
2386 if settings
.comp_pack
:
2387 static
.JOBINFO
+= [{
2390 "colour": (0.541, 0.169, 0.886,1.0),
2392 "jobs": [[cxr_compiler_path("bspzip"), '-addlist', \
2393 cxr_winepath(bsp_remote
),
2394 cxr_winepath(packlist
),
2395 cxr_winepath(bsp_packed
) ]],
2399 if len(static
.JOBINFO
) == 0:
2400 return {'CANCELLED'}
2402 static
.USER_EXIT
=False
2403 static
.TIMER
=wm
.event_timer_add(0.1,window
=context
.window
)
2404 wm
.modal_handler_add(_
)
2406 cxr_jobs_update_graph( static
.JOBINFO
)
2408 return {'RUNNING_MODAL'}
2410 class CXR_RESET_HASHES(bpy
.types
.Operator
):
2411 bl_idname
="convexer.hash_reset"
2412 bl_label
="Reset asset hashes"
2414 def execute(_
,context
):
2415 for c
in bpy
.data
.collections
:
2416 c
.cxr_data
.last_hash
= F
"<RESET>{time.time()}"
2417 c
.cxr_data
.asset_id
=0
2419 for t
in bpy
.data
.images
:
2420 t
.cxr_data
.last_hash
= F
"<RESET>{time.time()}"
2421 t
.cxr_data
.asset_id
=0
2425 class CXR_COMPILE_MATERIAL(bpy
.types
.Operator
):
2426 bl_idname
="convexer.matcomp"
2427 bl_label
="Recompile Material"
2429 def execute(_
,context
):
2430 active_obj
= bpy
.context
.active_object
2431 active_mat
= active_obj
.active_material
2433 #TODO: reduce code dupe (L1663)
2434 for pair
in compile_material(active_mat
):
2439 if isinstance(prop
,bpy
.types
.Image
):
2441 if 'flags' in pdef
: flags
= pdef
['flags']
2442 prop
.cxr_data
.flags
= flags
2444 compile_image( prop
)
2446 settings
= bpy
.context
.scene
.cxr_data
2447 with
open(F
'{settings.subdir}/cfg/convexer_mat_update.cfg','w') as o
:
2448 o
.write(F
'mat_reloadmaterial {asset_name(active_mat)}')
2451 with
open(F
'{settings.subdir}/cfg/convexer.cfg','w') as o
:
2452 o
.write('sv_cheats 1\n')
2453 o
.write('mp_warmup_pausetimer 1\n')
2454 o
.write('bot_kick\n')
2455 o
.write('alias cxr_reload "exec convexer_mat_update"\n')
2460 # ------------------------------------------------------------------------------
2462 # Helper buttons for 3d toolbox view
2464 class CXR_VIEW3D( bpy
.types
.Panel
):
2465 bl_idname
= "VIEW3D_PT_convexer"
2466 bl_label
= "Convexer"
2467 bl_space_type
= 'VIEW_3D'
2468 bl_region_type
= 'UI'
2469 bl_category
= "Convexer"
2471 def draw(_
, context
):
2474 active_object
= context
.object
2475 if active_object
== None: return
2477 purpose
= cxr_object_purpose( active_object
)
2479 if purpose
[0] == None or purpose
[1] == None:
2480 usage_str
= "No purpose"
2482 if purpose
[1] == 'model':
2483 usage_str
= F
'mesh in {asset_name( purpose[0] )}.mdl'
2485 usage_str
= F
'{purpose[1]} in {purpose[0].name}'
2487 layout
.label(text
=F
"Currently editing:")
2489 box
.label(text
=usage_str
)
2491 if purpose
[1] == 'brush' or purpose
[1] == 'brush_entity':
2494 row
.operator("convexer.preview")
2498 row
.operator("convexer.reset")
2500 layout
.prop( bpy
.context
.scene
.cxr_data
, "dev_mdl" )
2501 layout
.operator( "convexer.model_load" )
2503 # Main scene properties interface, where all the settings go
2505 class CXR_INTERFACE(bpy
.types
.Panel
):
2507 bl_idname
="SCENE_PT_convexer"
2508 bl_space_type
='PROPERTIES'
2509 bl_region_type
='WINDOW'
2512 def draw(_
,context
):
2513 if CXR_GNU_LINUX
==1:
2514 _
.layout
.operator("convexer.reload")
2515 _
.layout
.operator("convexer.dev_test")
2516 _
.layout
.operator("convexer.fs_init")
2518 _
.layout
.operator("convexer.hash_reset")
2519 settings
= context
.scene
.cxr_data
2521 _
.layout
.prop(settings
, "scale_factor")
2522 _
.layout
.prop(settings
, "skybox_scale_factor")
2523 _
.layout
.prop(settings
, "skyname" )
2524 _
.layout
.prop(settings
, "lightmap_scale")
2525 _
.layout
.prop(settings
, "light_scale" )
2526 _
.layout
.prop(settings
, "image_quality" )
2528 box
= _
.layout
.box()
2530 box
.prop(settings
, "project_name")
2531 box
.prop(settings
, "subdir")
2533 box
= _
.layout
.box()
2534 box
.operator("convexer.detect_compilers")
2535 box
.prop(settings
, "exe_studiomdl")
2536 box
.prop(settings
, "exe_vbsp")
2537 box
.prop(settings
, "opt_vbsp")
2539 box
.prop(settings
, "exe_vvis")
2540 box
.prop(settings
, "opt_vvis")
2542 box
.prop(settings
, "exe_vrad")
2543 box
.prop(settings
, "opt_vrad")
2547 row
.prop(settings
,"comp_vmf")
2548 row
.prop(settings
,"comp_textures")
2549 row
.prop(settings
,"comp_models")
2550 row
.prop(settings
,"comp_compile")
2551 row
.prop(settings
,"comp_pack")
2553 text
= "Compile" if CXR_COMPILER_CHAIN
.TIMER
== None else "Cancel"
2556 row
.operator("convexer.chain", text
=text
)
2560 row
.operator("convexer.reset")
2561 if CXR_COMPILER_CHAIN
.TIMER
!= None:
2564 class CXR_MATERIAL_PANEL(bpy
.types
.Panel
):
2565 bl_label
="VMT Properties"
2566 bl_idname
="SCENE_PT_convexer_vmt"
2567 bl_space_type
='PROPERTIES'
2568 bl_region_type
='WINDOW'
2569 bl_context
="material"
2571 def draw(_
,context
):
2572 active_object
= bpy
.context
.active_object
2573 if active_object
== None: return
2575 active_material
= active_object
.active_material
2576 if active_material
== None: return
2578 properties
= active_material
.cxr_data
2579 info
= material_info( active_material
)
2581 _
.layout
.label(text
=F
"{info['name']} @{info['res'][0]}x{info['res'][1]}")
2582 row
= _
.layout
.row()
2583 row
.prop( properties
, "shader" )
2584 row
.operator( "convexer.matcomp" )
2586 #for xk in info: _.layout.label(text=F"{xk}:={info[xk]}")
2588 def _mtex( name
, img
, uiParent
):
2591 box
= uiParent
.box()
2592 box
.label( text
=F
'{name} "{img.filepath}"' )
2594 if ((x
& (x
- 1)) == 0):
2597 closest_diff
= 10000000
2599 dist
= abs((1 << i
)-x
)
2600 if dist
< closest_diff
:
2605 return 1 << (closest
+1)
2607 return 1 << (closest
-1)
2612 row
.prop( img
.cxr_data
, "export_res" )
2613 row
.prop( img
.cxr_data
, "fmt" )
2616 row
.prop( img
.cxr_data
, "mipmap" )
2617 row
.prop( img
.cxr_data
, "lod" )
2618 row
.prop( img
.cxr_data
, "clamp" )
2620 img
.cxr_data
.export_res
[0] = _p2( img
.cxr_data
.export_res
[0] )
2621 img
.cxr_data
.export_res
[1] = _p2( img
.cxr_data
.export_res
[1] )
2623 def _mview( layer
, uiParent
):
2627 if isinstance(layer
[decl
],dict): # $property definition
2629 ptype
= pdef
['type']
2635 if ('shaders' in pdef
) and \
2636 (properties
.shader
not in pdef
['shaders']):
2639 if ptype
== 'intrinsic':
2640 if decl
not in info
:
2645 if isinstance(pdef
[ch
],dict):
2646 if ptype
== 'ui' or ptype
== 'intrinsic':
2648 elif getattr(properties
,decl
) == pdef
['default']:
2651 thisnode
= uiParent
.box()
2655 thisnode
.label( text
=decl
)
2656 elif ptype
== 'intrinsic':
2657 if isinstance(info
[decl
], bpy
.types
.Image
):
2658 _mtex( decl
, info
[decl
], thisnode
)
2660 # hidden intrinsic value.
2661 # Means its a float array or something not an image
2662 thisnode
.label(text
=F
"-- hidden intrinsic '{decl}' --")
2664 thisnode
.prop(properties
,decl
)
2665 if expandview
: _mview(pdef
,thisnode
)
2667 _mview( cxr_shader_params
, _
.layout
)
2669 def cxr_entity_changeclass(_
,context
):
2670 active_object
= context
.active_object
2672 # Create ID properties
2674 classname
= cxr_custom_class(active_object
)
2676 if classname
in cxr_entities
:
2677 entdef
= cxr_entities
[classname
]
2679 kvs
= entdef
['keyvalues']
2680 if callable(kvs
): kvs
= kvs( {'object': active_object
} )
2686 if callable(kv
) or not isinstance(kv
,dict): continue
2688 if key
not in active_object
:
2689 active_object
[key
] = kv
['default']
2690 id_prop
= active_object
.id_properties_ui(key
)
2691 id_prop
.update(default
=kv
['default'])
2693 class CXR_ENTITY_PANEL(bpy
.types
.Panel
):
2694 bl_label
="Entity Config"
2695 bl_idname
="SCENE_PT_convexer_entity"
2696 bl_space_type
='PROPERTIES'
2697 bl_region_type
='WINDOW'
2700 def draw(_
,context
):
2701 active_object
= bpy
.context
.active_object
2703 if active_object
== None: return
2706 "scale": bpy
.context
.scene
.cxr_data
.scale_factor
,
2710 ecn
= cxr_intrinsic_classname( active_object
)
2711 classname
= cxr_custom_class( active_object
)
2714 if active_object
.type == 'MESH':
2715 _
.layout
.prop( active_object
.cxr_data
, 'brushclass' )
2716 else: _
.layout
.prop( active_object
.cxr_data
, 'classname' )
2718 _
.layout
.prop( active_object
.cxr_data
, 'visgroup' )
2719 _
.layout
.prop( active_object
.cxr_data
, 'lightmap_override' )
2721 if classname
== 'NONE':
2724 _
.layout
.label(text
=F
"<implementation defined ({ecn})>")
2725 _
.layout
.enabled
=False
2728 kvs
= cxr_entity_keyvalues( {
2729 "object": active_object
,
2730 "transform": default_context
,
2731 "classname": classname
2737 _
.layout
.prop( active_object
, F
'["cxrkv_{kv[0]}"]', text
=kv
[0])
2739 row
= _
.layout
.row()
2741 row
.label( text
=F
'{kv[0]}: {repr(kv[2])}' )
2743 _
.layout
.label( text
=F
"ERROR: NO CLASS DEFINITION" )
2745 class CXR_LIGHT_PANEL(bpy
.types
.Panel
):
2746 bl_label
= "Source Settings"
2747 bl_idname
= "LIGHT_PT_cxr"
2748 bl_space_type
= 'PROPERTIES'
2749 bl_region_type
= 'WINDOW'
2752 def draw(self
, context
):
2753 layout
= self
.layout
2754 scene
= context
.scene
2756 active_object
= bpy
.context
.active_object
2757 if active_object
== None: return
2759 if active_object
.type == 'LIGHT' or \
2760 active_object
.type == 'LIGHT_PROBE':
2762 properties
= active_object
.data
.cxr_data
2764 if active_object
.type == 'LIGHT':
2765 layout
.prop( properties
, "realtime" )
2766 elif active_object
.type == 'LIGHT_PROBE':
2767 layout
.prop( properties
, "size" )
2769 class CXR_COLLECTION_PANEL(bpy
.types
.Panel
):
2770 bl_label
= "Source Settings"
2771 bl_idname
= "COL_PT_cxr"
2772 bl_space_type
= 'PROPERTIES'
2773 bl_region_type
= 'WINDOW'
2774 bl_context
= "collection"
2776 def draw(self
, context
):
2777 layout
= self
.layout
2778 scene
= context
.scene
2780 active_collection
= bpy
.context
.collection
2782 if active_collection
!= None:
2783 layout
.prop( active_collection
.cxr_data
, "shadow_caster" )
2784 layout
.prop( active_collection
.cxr_data
, "texture_shadows" )
2785 layout
.prop( active_collection
.cxr_data
, "preserve_order" )
2786 layout
.prop( active_collection
.cxr_data
, "surfaceprop" )
2787 layout
.prop( active_collection
.cxr_data
, "visgroup" )
2790 # ------------------------------------------------------------------------------
2792 class CXR_IMAGE_SETTINGS(bpy
.types
.PropertyGroup
):
2793 export_res
: bpy
.props
.IntVectorProperty(
2795 description
="Texture Export Resolution",
2801 fmt
: bpy
.props
.EnumProperty(
2804 ('DXT1', "DXT1", "BC1 compressed", '', 0),
2805 ('DXT5', "DXT5", "BC3 compressed", '', 1),
2806 ('RGB', "RGB", "Uncompressed", '', 2),
2807 ('RGBA', "RGBA", "Uncompressed (with alpha)", '', 3)
2809 description
="Image format",
2812 last_hash
: bpy
.props
.StringProperty( name
="" )
2813 asset_id
: bpy
.props
.IntProperty(name
="intl_assetid",default
=0)
2815 mipmap
: bpy
.props
.BoolProperty(name
="MIP",default
=True)
2816 lod
: bpy
.props
.BoolProperty(name
="LOD",default
=True)
2817 clamp
: bpy
.props
.BoolProperty(name
="CLAMP",default
=False)
2818 flags
: bpy
.props
.IntProperty(name
="flags",default
=0)
2820 class CXR_LIGHT_SETTINGS(bpy
.types
.PropertyGroup
):
2821 realtime
: bpy
.props
.BoolProperty(name
="Realtime Light", default
=True)
2823 class CXR_CUBEMAP_SETTINGS(bpy
.types
.PropertyGroup
):
2824 size
: bpy
.props
.EnumProperty(
2827 ('1',"1x1",'','',0),
2828 ('2',"2x2",'','',1),
2829 ('3',"4x4",'','',2),
2830 ('4',"8x8",'','',3),
2831 ('5',"16x16",'','',4),
2832 ('6',"32x32",'','',5),
2833 ('7',"64x64",'','',6),
2834 ('8',"128x128",'','',7),
2835 ('9',"256x256",'','',8)
2837 description
="Texture resolution",
2840 class CXR_ENTITY_SETTINGS(bpy
.types
.PropertyGroup
):
2841 entity
: bpy
.props
.BoolProperty(name
="")
2843 enum_pointents
= [('NONE',"None","")]
2844 enum_brushents
= [('NONE',"None","")]
2846 for classname
in cxr_entities
:
2847 entdef
= cxr_entities
[classname
]
2848 if 'allow' in entdef
:
2849 itm
= [(classname
, classname
, "")]
2850 if 'EMPTY' in entdef
['allow']: enum_pointents
+= itm
2851 else: enum_brushents
+= itm
2853 classname
: bpy
.props
.EnumProperty(items
=enum_pointents
, name
="Class", \
2854 update
=cxr_entity_changeclass
, default
='NONE' )
2856 brushclass
: bpy
.props
.EnumProperty(items
=enum_brushents
, name
="Class", \
2857 update
=cxr_entity_changeclass
, default
='NONE' )
2859 enum_classes
= [('0',"None","")]
2860 for i
, vg
in enumerate(cxr_visgroups
):
2861 enum_classes
+= [(str(i
+1),vg
,"")]
2862 visgroup
: bpy
.props
.EnumProperty(name
="visgroup",items
=enum_classes
,default
=0)
2863 lightmap_override
: bpy
.props
.IntProperty(name
="Lightmap Override",default
=0)
2865 class CXR_MODEL_SETTINGS(bpy
.types
.PropertyGroup
):
2866 last_hash
: bpy
.props
.StringProperty( name
="" )
2867 asset_id
: bpy
.props
.IntProperty(name
="vmf_settings",default
=0)
2868 shadow_caster
: bpy
.props
.BoolProperty( name
="Shadow caster", default
=True )
2869 texture_shadows
: bpy
.props
.BoolProperty( name
="Texture Shadows", default
=False )
2870 preserve_order
: bpy
.props
.BoolProperty( name
="Preserve Order", default
=False )
2871 surfaceprop
: bpy
.props
.StringProperty( name
="Suface prop",default
="default" )
2873 enum_classes
= [('0',"None","")]
2874 for i
, vg
in enumerate(cxr_visgroups
):
2875 enum_classes
+= [(str(i
+1),vg
,"")]
2876 visgroup
: bpy
.props
.EnumProperty(name
="visgroup",items
=enum_classes
,default
=0)
2878 class CXR_SCENE_SETTINGS(bpy
.types
.PropertyGroup
):
2879 project_name
: bpy
.props
.StringProperty( name
="Project Name" )
2880 subdir
: bpy
.props
.StringProperty( name
="../csgo/ folder" )
2882 exe_studiomdl
: bpy
.props
.StringProperty( name
="studiomdl" )
2883 exe_vbsp
: bpy
.props
.StringProperty( name
="vbsp" )
2884 opt_vbsp
: bpy
.props
.StringProperty( name
="args" )
2885 exe_vvis
: bpy
.props
.StringProperty( name
="vvis" )
2886 opt_vvis
: bpy
.props
.StringProperty( name
="args" )
2887 exe_vrad
: bpy
.props
.StringProperty( name
="vrad" )
2888 opt_vrad
: bpy
.props
.StringProperty( name
="args", \
2889 default
="-reflectivityScale 0.35 -aoscale 1.4 -final -textureshadows -hdr -StaticPropLighting -StaticPropPolys" )
2891 scale_factor
: bpy
.props
.FloatProperty( name
="VMF Scale factor", \
2892 default
=32.0,min=1.0)
2893 skybox_scale_factor
: bpy
.props
.FloatProperty( name
="Sky Scale factor", \
2894 default
=1.0,min=0.01)
2895 skyname
: bpy
.props
.StringProperty(name
="Skyname",default
="sky_csgo_night02b")
2896 skybox_offset
: bpy
.props
.FloatProperty(name
="Sky offset",default
=-4096.0)
2897 light_scale
: bpy
.props
.FloatProperty(name
="Light Scale",default
=1.0/5.0)
2898 include_names
: bpy
.props
.BoolProperty(name
="Append original file names",\
2900 lightmap_scale
: bpy
.props
.IntProperty(name
="Global Lightmap Scale",\
2902 image_quality
: bpy
.props
.IntProperty(name
="Texture Quality (0-18)",\
2903 default
=8, min=0, max=18 )
2905 comp_vmf
: bpy
.props
.BoolProperty(name
="VMF",default
=True)
2906 comp_models
: bpy
.props
.BoolProperty(name
="Models",default
=True)
2907 comp_textures
: bpy
.props
.BoolProperty(name
="Textures",default
=True)
2908 comp_compile
: bpy
.props
.BoolProperty(name
="Compile",default
=True)
2909 comp_pack
: bpy
.props
.BoolProperty(name
="Pack",default
=False)
2911 dev_mdl
: bpy
.props
.StringProperty(name
="Model",default
="")
2913 classes
= [ CXR_RELOAD
, CXR_DEV_OPERATOR
, CXR_INTERFACE
, \
2914 CXR_MATERIAL_PANEL
, CXR_IMAGE_SETTINGS
,\
2915 CXR_MODEL_SETTINGS
, CXR_ENTITY_SETTINGS
, CXR_CUBEMAP_SETTINGS
,\
2916 CXR_LIGHT_SETTINGS
, CXR_SCENE_SETTINGS
, CXR_DETECT_COMPILERS
,\
2917 CXR_ENTITY_PANEL
, CXR_LIGHT_PANEL
, CXR_PREVIEW_OPERATOR
,\
2918 CXR_VIEW3D
, CXR_COMPILER_CHAIN
, CXR_RESET_HASHES
,\
2919 CXR_COMPILE_MATERIAL
, CXR_COLLECTION_PANEL
, CXR_RESET
, \
2920 CXR_INIT_FS_OPERATOR
, CXR_LOAD_MODEL_OPERATOR
]
2922 vmt_param_dynamic_class
= None
2925 global cxr_view_draw_handler
, vmt_param_dynamic_class
, cxr_ui_handler
2928 bpy
.utils
.register_class(c
)
2930 # Build dynamic VMT properties class defined by cxr_shader_params
2931 annotations_dict
= {}
2933 def _dvmt_propogate(layer
):
2934 nonlocal annotations_dict
2937 if isinstance(layer
[decl
],dict): # $property definition
2941 if pdef
['type'] == 'bool':
2942 prop
= bpy
.props
.BoolProperty(\
2943 name
= pdef
['name'],\
2944 default
= pdef
['default'])
2946 elif pdef
['type'] == 'float':
2947 prop
= bpy
.props
.FloatProperty(\
2948 name
= pdef
['name'],\
2949 default
= pdef
['default'])
2951 elif pdef
['type'] == 'vector':
2952 if 'subtype' in pdef
:
2953 prop
= bpy
.props
.FloatVectorProperty(\
2954 name
= pdef
['name'],\
2955 subtype
= pdef
['subtype'],\
2956 default
= pdef
['default'],\
2957 size
= len(pdef
['default']))
2959 prop
= bpy
.props
.FloatVectorProperty(\
2960 name
= pdef
['name'],\
2961 default
= pdef
['default'],\
2962 size
= len(pdef
['default']))
2964 elif pdef
['type'] == 'string':
2965 prop
= bpy
.props
.StringProperty(\
2966 name
= pdef
['name'],\
2967 default
= pdef
['default'])
2969 elif pdef
['type'] == 'enum':
2970 prop
= bpy
.props
.EnumProperty(\
2971 name
= pdef
['name'],\
2972 items
= pdef
['items'],\
2973 default
= pdef
['default'])
2976 annotations_dict
[decl
] = prop
2978 # Recurse into sub-definitions
2979 _dvmt_propogate(pdef
)
2981 annotations_dict
["shader"] = bpy
.props
.EnumProperty(\
2984 cxr_shaders
[_
]["name"],\
2985 '') for _
in cxr_shaders
],\
2986 default
= next(iter(cxr_shaders
)))
2988 annotations_dict
["asset_id"] = bpy
.props
.IntProperty(name
="intl_assetid",\
2991 _dvmt_propogate( cxr_shader_params
)
2992 vmt_param_dynamic_class
= type(
2994 (bpy
.types
.PropertyGroup
,),{
2995 "__annotations__": annotations_dict
2999 bpy
.utils
.register_class( vmt_param_dynamic_class
)
3002 bpy
.types
.Material
.cxr_data
= \
3003 bpy
.props
.PointerProperty(type=vmt_param_dynamic_class
)
3004 bpy
.types
.Image
.cxr_data
= \
3005 bpy
.props
.PointerProperty(type=CXR_IMAGE_SETTINGS
)
3006 bpy
.types
.Object
.cxr_data
= \
3007 bpy
.props
.PointerProperty(type=CXR_ENTITY_SETTINGS
)
3008 bpy
.types
.Collection
.cxr_data
= \
3009 bpy
.props
.PointerProperty(type=CXR_MODEL_SETTINGS
)
3010 bpy
.types
.Light
.cxr_data
= \
3011 bpy
.props
.PointerProperty(type=CXR_LIGHT_SETTINGS
)
3012 bpy
.types
.LightProbe
.cxr_data
= \
3013 bpy
.props
.PointerProperty(type=CXR_CUBEMAP_SETTINGS
)
3014 bpy
.types
.Scene
.cxr_data
= \
3015 bpy
.props
.PointerProperty(type=CXR_SCENE_SETTINGS
)
3017 # CXR Scene settings
3020 cxr_view_draw_handler
= bpy
.types
.SpaceView3D
.draw_handler_add(\
3021 cxr_draw
,(),'WINDOW','POST_VIEW')
3023 cxr_ui_handler
= bpy
.types
.SpaceView3D
.draw_handler_add(\
3024 cxr_ui
,(None,None),'WINDOW','POST_PIXEL')
3026 bpy
.app
.handlers
.load_post
.append(cxr_on_load
)
3027 bpy
.app
.handlers
.depsgraph_update_post
.append(cxr_dgraph_update
)
3030 global cxr_view_draw_handler
, vmt_param_dynamic_class
, cxr_ui_handler
3032 bpy
.utils
.unregister_class( vmt_param_dynamic_class
)
3034 bpy
.utils
.unregister_class(c
)
3036 bpy
.app
.handlers
.depsgraph_update_post
.remove(cxr_dgraph_update
)
3037 bpy
.app
.handlers
.load_post
.remove(cxr_on_load
)
3039 bpy
.types
.SpaceView3D
.draw_handler_remove(cxr_view_draw_handler
,'WINDOW')
3040 bpy
.types
.SpaceView3D
.draw_handler_remove(cxr_ui_handler
,'WINDOW')