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;
102 vec4 pWorldPos = modelMatrix * vec4(aPos, 1.0);
103 vec3 worldPos = pWorldPos.xyz;
105 gl_Position = viewProjectionMatrix * pWorldPos;
106 lNormal = normalize(mat3(transpose(inverse(modelMatrix))) * aNormal);
112 uniform vec3 testLightDir;
119 float SoftenCosineTerm( float flDot )
121 return ( flDot + ( flDot * flDot ) ) * 0.5;
124 vec3 DiffuseTerm( vec3 worldNormal, vec3 lightDir )
127 float NDotL = dot( worldNormal, lightDir );
129 fResult = clamp( NDotL, 0.0, 1.0 );
130 fResult = SoftenCosineTerm( fResult );
132 vec3 fOut = vec3( fResult, fResult, fResult );
136 vec3 PixelShaderDoLightingLinear( vec3 worldPos, vec3 worldNormal )
138 vec3 linearColor = vec3(0.0,0.0,0.0);
139 linearColor += DiffuseTerm( worldNormal, testLightDir );
144 vec3 LinearToGamma( vec3 f3linear )
146 return pow( f3linear, vec3(1.0 / 2.2) );
151 vec3 tangentSpaceNormal = vec3( 0.0, 0.0, 1.0 );
152 vec4 normalTexel = vec4(1.0,1.0,1.0,1.0);
153 vec4 baseColor = colour;
155 //normalTexel = tex2D( BumpmapSampler, i.detailOrBumpTexCoord );
156 //tangentSpaceNormal = 2.0 * normalTexel - 1.0;
158 vec3 diffuseLighting = vec3( 1.0, 1.0, 1.0 );
160 vec3 staticLightingColor = vec3( 0.0, 0.0, 0.0 );
161 diffuseLighting = PixelShaderDoLightingLinear( lPos, lNormal );
163 // multiply by .5 since we want a 50% (in gamma space) reflective surface)
164 diffuseLighting *= pow( 0.5, 2.2 );
166 vec3 result = diffuseLighting * baseColor.xyz;
168 FragColor = vec4( LinearToGamma(result), 1.0 );
174 def cxr_ui(_
,context
):
175 global cxr_jobs_batch
, cxr_ui_shader
, cxr_jobs_inf
, cxr_error_inf
177 w
= gpu
.state
.viewport_get()[2]
179 cxr_ui_shader
.uniform_float( "scale", w
)
181 if cxr_error_inf
!= None:
184 if isinstance(cxr_error_inf
[1],list):
185 err_begin
+= 20*(len(cxr_error_inf
[1])-1)
187 blf
.position(0,2,err_begin
,0)
189 blf
.color(0, 1.0,0.2,0.2,0.9)
190 blf
.draw(0,cxr_error_inf
[0])
193 blf
.color(0, 1.0,1.0,1.0,1.0)
195 if isinstance(cxr_error_inf
[1],list):
196 for i
,inf
in enumerate(cxr_error_inf
[1]):
197 blf
.position(0,2,err_begin
-30-i
*20,0)
200 blf
.position(0,2,err_begin
-30,0)
201 blf
.draw(0,cxr_error_inf
[1])
203 elif cxr_jobs_batch
!= None:
204 gpu
.state
.blend_set('ALPHA')
205 cxr_jobs_batch
.draw(cxr_ui_shader
)
207 blf
.position(0,2,50,0)
209 blf
.color(0,1.0,1.0,1.0,1.0)
210 blf
.draw(0,"Compiling")
212 for ji
in cxr_jobs_inf
:
213 blf
.position(0,ji
[0]*w
,35,0)
219 for ln
in reversed(CXR_COMPILER_CHAIN
.LOG
[-25:]):
220 blf
.position(0,2,py
,0)
224 # Something is off with TIMER,
225 # this forces the viewport to redraw before we can continue with our
228 CXR_COMPILER_CHAIN
.WAIT_REDRAW
= False
231 global cxr_view_shader
, cxr_view_mesh
, cxr_view_lines
, cxr_mdl_shader
,\
234 cxr_view_shader
.bind()
236 gpu
.state
.depth_mask_set(False)
237 gpu
.state
.line_width_set(1.5)
238 gpu
.state
.face_culling_set('BACK')
239 gpu
.state
.depth_test_set('NONE')
240 gpu
.state
.blend_set('ALPHA')
242 if cxr_view_lines
!= None:
243 cxr_view_lines
.draw( cxr_view_shader
)
245 if cxr_view_mesh
!= None:
246 gpu
.state
.depth_test_set('LESS_EQUAL')
247 gpu
.state
.blend_set('ADDITIVE')
249 cxr_view_mesh
.draw( cxr_view_shader
)
251 if cxr_mdl_mesh
!= None:
252 gpu
.state
.depth_mask_set(True)
253 gpu
.state
.depth_test_set('LESS_EQUAL')
254 gpu
.state
.face_culling_set('FRONT')
255 gpu
.state
.blend_set('NONE')
256 cxr_mdl_shader
.bind()
257 cxr_mdl_shader
.uniform_float('colour',(0.5,0.5,0.5,1.0))
258 cxr_mdl_shader
.uniform_float("viewProjectionMatrix", \
259 bpy
.context
.region_data
.perspective_matrix
)
261 testmdl
= bpy
.context
.scene
.objects
['target']
262 light
= bpy
.context
.scene
.objects
['point']
263 relative
= light
.location
- testmdl
.location
266 cxr_mdl_shader
.uniform_float("modelMatrix", testmdl
.matrix_world
)
267 cxr_mdl_shader
.uniform_float("testLightDir", relative
)
270 cxr_mdl_mesh
.draw( cxr_mdl_shader
)
272 def cxr_jobs_update_graph(jobs
):
273 global cxr_jobs_batch
, cxr_ui_shader
, cxr_jobs_inf
283 total_width
+= sys
['w']
292 colour
= sys
['colour']
293 colourwait
= (colour
[0],colour
[1],colour
[2],0.4)
294 colourrun
= (colour
[0]*1.5,colour
[1]*1.5,colour
[2]*1.5,0.5)
295 colourdone
= (colour
[0],colour
[1],colour
[2],1.0)
298 sfsub
= (1.0/(len(jobs
)))*w
302 if j
== None: colour
= colourdone
303 else: colour
= colourwait
305 px
= (cur
+ (i
)*sfsub
) * sf
306 px1
= (cur
+ (i
+1.0)*sfsub
) * sf
309 verts
+= [(px
,0), (px
, h
), (px1
, 0.0), (px1
,h
)]
310 colours
+= [colour
,colour
,colour
,colour
]
311 indices
+= [(ci
+0,ci
+2,ci
+3),(ci
+0,ci
+3,ci
+1)]
314 cxr_jobs_inf
+= [((sf
*cur
), sys
['title'])]
317 cxr_jobs_batch
= batch_for_shader(
318 cxr_ui_shader
, 'TRIS',
319 { "aPos": verts
, "aColour": colours
},
323 # view_layer.update() doesnt seem to work,
324 # tag_redraw() seems to have broken
325 # therefore, change a property
327 ob
= bpy
.context
.scene
.objects
[0]
328 ob
.hide_render
= ob
.hide_render
330 # the 'real' way to refresh the scene
331 for area
in bpy
.context
.window
.screen
.areas
:
332 if area
.type == 'view_3d':
336 # ------------------------------------------------------------------------------
339 # dlclose for reloading modules manually
341 libc_dlclose
= cdll
.LoadLibrary(None).dlclose
342 libc_dlclose
.argtypes
= [c_void_p
]
344 # wrapper for ctypes binding
346 def __init__(_
,name
,argtypes
,restype
):
348 _
.argtypes
= argtypes
353 _
.call
= getattr(so
,_
.name
)
354 _
.call
.argtypes
= _
.argtypes
356 if _
.restype
!= None:
357 _
.call
.restype
= _
.restype
360 # ------------------------------------------------------------------------------
364 # Structure definitions
366 class cxr_edge(Structure
):
367 _fields_
= [("i0",c_int32
),
369 ("freestyle",c_int32
),
372 class cxr_static_loop(Structure
):
373 _fields_
= [("index",c_int32
),
374 ("edge_index",c_int32
),
378 class cxr_polygon(Structure
):
379 _fields_
= [("loop_start",c_int32
),
380 ("loop_total",c_int32
),
381 ("normal",c_double
* 3),
382 ("center",c_double
* 3),
383 ("material_id",c_int32
)]
385 class cxr_material(Structure
):
386 _fields_
= [("res",c_int32
* 2),
389 class cxr_static_mesh(Structure
):
390 _fields_
= [("vertices",POINTER(c_double
* 3)),
391 ("edges",POINTER(cxr_edge
)),
392 ("loops",POINTER(cxr_static_loop
)),
393 ("polys",POINTER(cxr_polygon
)),
394 ("materials",POINTER(cxr_material
)),
396 ("poly_count",c_int32
),
397 ("vertex_count",c_int32
),
398 ("edge_count",c_int32
),
399 ("loop_count",c_int32
),
400 ("material_count",c_int32
)]
402 class cxr_tri_mesh(Structure
):
403 _fields_
= [("vertices",POINTER(c_double
*3)),
404 ("normals",POINTER(c_double
*3)),
405 ("uvs",POINTER(c_double
*2)),
406 ("colours",POINTER(c_double
*4)),
407 ("indices",POINTER(c_int32
)),
408 ("indices_count",c_int32
),
409 ("vertex_count",c_int32
)]
411 class cxr_visgroup(Structure
):
412 _fields_
= [("name",c_char_p
)]
414 class cxr_vmf_context(Structure
):
415 _fields_
= [("mapversion",c_int32
),
416 ("skyname",c_char_p
),
417 ("detailvbsp",c_char_p
),
418 ("detailmaterial",c_char_p
),
419 ("visgroups",POINTER(cxr_visgroup
)),
420 ("visgroup_count",c_int32
),
422 ("offset",c_double
*3),
423 ("lightmap_scale",c_int32
),
424 ("visgroupid",c_int32
),
425 ("brush_count",c_int32
),
426 ("entity_count",c_int32
),
427 ("face_count",c_int32
)]
429 # Valve wrapper types
430 class fs_locator(Structure
):
431 _fields_
= [("vpk_entry",c_void_p
),
432 ("path",c_char_p
*1024)]
434 class valve_material(Structure
):
435 _fields_
= [("basetexture",c_char_p
),
436 ("bumpmap",c_char_p
)]
438 class valve_model_batch(Structure
):
439 _fields_
= [("material",c_uint32
),
440 ("ibstart",c_uint32
),
441 ("ibcount",c_uint32
)]
443 class valve_model(Structure
):
444 _fields_
= [("vertex_data",POINTER(c_float
)),
445 ("indices",POINTER(c_uint32
)),
446 ("indices_count",c_uint32
),
447 ("vertex_count",c_uint32
),
448 ("part_count",c_uint32
),
449 ("material_count",c_uint32
),
450 ("materials",POINTER(c_char_p
)),
451 ("parts",POINTER(valve_model_batch
)),
452 ("studiohdr",c_void_p
),
456 # Convert blenders mesh format into CXR's static format (they are very similar)
458 def mesh_cxr_format(obj
):
461 if bpy
.context
.active_object
!= None:
462 orig_state
= obj
.mode
463 if orig_state
!= 'OBJECT':
464 bpy
.ops
.object.mode_set(mode
='OBJECT')
466 dgraph
= bpy
.context
.evaluated_depsgraph_get()
467 data
= obj
.evaluated_get(dgraph
).data
469 _
,mtx_rot
,_
= obj
.matrix_world
.decompose()
471 mesh
= cxr_static_mesh()
473 vertex_data
= ((c_double
*3)*len(data
.vertices
))()
474 for i
, vert
in enumerate(data
.vertices
):
475 v
= obj
.matrix_world
@ vert
.co
476 vertex_data
[i
][0] = c_double(v
[0])
477 vertex_data
[i
][1] = c_double(v
[1])
478 vertex_data
[i
][2] = c_double(v
[2])
480 loop_data
= (cxr_static_loop
*len(data
.loops
))()
481 polygon_data
= (cxr_polygon
*len(data
.polygons
))()
483 for i
, poly
in enumerate(data
.polygons
):
484 loop_start
= poly
.loop_start
485 loop_end
= poly
.loop_start
+ poly
.loop_total
486 for loop_index
in range(loop_start
, loop_end
):
487 loop
= data
.loops
[loop_index
]
488 loop_data
[loop_index
].index
= loop
.vertex_index
489 loop_data
[loop_index
].edge_index
= loop
.edge_index
492 uv
= data
.uv_layers
.active
.data
[loop_index
].uv
493 loop_data
[loop_index
].uv
[0] = c_double(uv
[0])
494 loop_data
[loop_index
].uv
[1] = c_double(uv
[1])
496 loop_data
[loop_index
].uv
[0] = c_double(0.0)
497 loop_data
[loop_index
].uv
[1] = c_double(0.0)
499 if data
.vertex_colors
:
500 alpha
= data
.vertex_colors
.active
.data
[loop_index
].color
[0]
504 loop_data
[loop_index
].alpha
= alpha
506 center
= obj
.matrix_world
@ poly
.center
507 normal
= mtx_rot
@ poly
.normal
509 polygon_data
[i
].loop_start
= poly
.loop_start
510 polygon_data
[i
].loop_total
= poly
.loop_total
511 polygon_data
[i
].normal
[0] = normal
[0]
512 polygon_data
[i
].normal
[1] = normal
[1]
513 polygon_data
[i
].normal
[2] = normal
[2]
514 polygon_data
[i
].center
[0] = center
[0]
515 polygon_data
[i
].center
[1] = center
[1]
516 polygon_data
[i
].center
[2] = center
[2]
517 polygon_data
[i
].material_id
= poly
.material_index
519 edge_data
= (cxr_edge
*len(data
.edges
))()
521 for i
, edge
in enumerate(data
.edges
):
522 edge_data
[i
].i0
= edge
.vertices
[0]
523 edge_data
[i
].i1
= edge
.vertices
[1]
524 edge_data
[i
].freestyle
= edge
.use_freestyle_mark
525 edge_data
[i
].sharp
= edge
.use_edge_sharp
527 material_data
= (cxr_material
*len(obj
.material_slots
))()
529 for i
, ms
in enumerate(obj
.material_slots
):
530 inf
= material_info(ms
.material
)
531 material_data
[i
].res
[0] = inf
['res'][0]
532 material_data
[i
].res
[1] = inf
['res'][1]
533 material_data
[i
].name
= inf
['name'].encode('utf-8')
535 mesh
.edges
= cast(edge_data
, POINTER(cxr_edge
))
536 mesh
.vertices
= cast(vertex_data
, POINTER(c_double
*3))
537 mesh
.loops
= cast(loop_data
,POINTER(cxr_static_loop
))
538 mesh
.polys
= cast(polygon_data
, POINTER(cxr_polygon
))
539 mesh
.materials
= cast(material_data
, POINTER(cxr_material
))
541 mesh
.poly_count
= len(data
.polygons
)
542 mesh
.vertex_count
= len(data
.vertices
)
543 mesh
.edge_count
= len(data
.edges
)
544 mesh
.loop_count
= len(data
.loops
)
545 mesh
.material_count
= len(obj
.material_slots
)
547 if orig_state
!= None:
548 bpy
.ops
.object.mode_set(mode
=orig_state
)
552 # Callback ctypes indirection things.. not really sure.
553 c_libcxr_log_callback
= None
554 c_libcxr_line_callback
= None
557 # -------------------------------------------------------------
558 libcxr_decompose
= extern( "cxr_decompose",
559 [POINTER(cxr_static_mesh
), POINTER(c_int32
)],
562 libcxr_free_world
= extern( "cxr_free_world",
566 libcxr_write_test_data
= extern( "cxr_write_test_data",
567 [POINTER(cxr_static_mesh
)],
570 libcxr_world_preview
= extern( "cxr_world_preview",
572 POINTER(cxr_tri_mesh
)
574 libcxr_free_tri_mesh
= extern( "cxr_free_tri_mesh",
578 libcxr_begin_vmf
= extern( "cxr_begin_vmf",
579 [POINTER(cxr_vmf_context
), c_void_p
],
582 libcxr_vmf_begin_entities
= extern( "cxr_vmf_begin_entities",
583 [POINTER(cxr_vmf_context
), c_void_p
],
586 libcxr_push_world_vmf
= extern("cxr_push_world_vmf",
587 [c_void_p
,POINTER(cxr_vmf_context
),c_void_p
],
590 libcxr_end_vmf
= extern( "cxr_end_vmf",
591 [POINTER(cxr_vmf_context
),c_void_p
],
595 # VDF + with open wrapper
596 libcxr_vdf_open
= extern( "cxr_vdf_open", [c_char_p
], c_void_p
)
597 libcxr_vdf_close
= extern( "cxr_vdf_close", [c_void_p
], None )
598 libcxr_vdf_put
= extern( "cxr_vdf_put", [c_void_p
,c_char_p
], None )
599 libcxr_vdf_node
= extern( "cxr_vdf_node", [c_void_p
,c_char_p
], None )
600 libcxr_vdf_edon
= extern( "cxr_vdf_edon", [c_void_p
], None )
601 libcxr_vdf_kv
= extern( "cxr_vdf_kv", [c_void_p
,c_char_p
,c_char_p
], None )
603 class vdf_structure():
604 def __init__(_
,path
):
607 _
.fp
= libcxr_vdf_open
.call( _
.path
.encode('utf-8') )
609 print( F
"Could not open file {_.path}" )
612 def __exit__(_
,type,value
,traceback
):
614 libcxr_vdf_close
.call(_
.fp
)
616 libcxr_vdf_put
.call(_
.fp
, s
.encode('utf-8') )
618 libcxr_vdf_node
.call(_
.fp
, name
.encode('utf-8') )
620 libcxr_vdf_edon
.call(_
.fp
)
622 libcxr_vdf_kv
.call(_
.fp
, k
.encode('utf-8'), v
.encode('utf-8'))
625 libcxr_lightpatch_bsp
= extern( "cxr_lightpatch_bsp", [c_char_p
], None )
627 # Binary file formats and FS
628 libcxr_fs_set_gameinfo
= extern( "cxr_fs_set_gameinfo", [c_char_p
], c_int32
)
629 libcxr_fs_exit
= extern( "cxr_fs_exit", [], None )
630 libcxr_fs_get
= extern( "cxr_fs_get", [c_char_p
, c_int32
], c_char_p
)
631 libcxr_fs_find
= extern( "cxr_fs_find", [c_char_p
, POINTER(fs_locator
)],\
634 libcxr_valve_load_model
= extern( "valve_load_model", [c_char_p
], \
635 POINTER(valve_model
) )
636 libcxr_valve_free_model
= extern( "valve_free_model", [POINTER(valve_model
)],\
639 libcxr_valve_load_material
= extern( "valve_load_material", [c_char_p
], \
640 POINTER(valve_material
) )
641 libcxr_valve_free_material
= extern( "valve_free_material", \
642 [POINTER(valve_material
)], None )
644 libcxr_funcs
= [ libcxr_decompose
, libcxr_free_world
, libcxr_begin_vmf
, \
645 libcxr_vmf_begin_entities
, libcxr_push_world_vmf
, \
646 libcxr_end_vmf
, libcxr_vdf_open
, libcxr_vdf_close
, \
647 libcxr_vdf_put
, libcxr_vdf_node
, libcxr_vdf_edon
, \
648 libcxr_vdf_kv
, libcxr_lightpatch_bsp
, libcxr_write_test_data
,\
649 libcxr_world_preview
, libcxr_free_tri_mesh
, \
650 libcxr_fs_set_gameinfo
, libcxr_fs_exit
, libcxr_fs_get
, \
652 libcxr_valve_load_model
, libcxr_valve_free_model
,\
653 libcxr_valve_load_material
, libcxr_valve_free_material
]
656 def libcxr_log_callback(logStr
):
657 print( F
"{logStr.decode('utf-8')}",end
='' )
659 cxr_line_positions
= None
660 cxr_line_colours
= None
662 def cxr_reset_lines():
663 global cxr_line_positions
, cxr_line_colours
665 cxr_line_positions
= []
666 cxr_line_colours
= []
668 def cxr_batch_lines():
669 global cxr_line_positions
, cxr_line_colours
, cxr_view_shader
, cxr_view_lines
671 cxr_view_lines
= batch_for_shader(\
672 cxr_view_shader
, 'LINES',\
673 { "pos": cxr_line_positions
, "color": cxr_line_colours
})
675 def libcxr_line_callback( p0
,p1
,colour
):
676 global cxr_line_colours
, cxr_line_positions
678 cxr_line_positions
+= [(p0
[0],p0
[1],p0
[2])]
679 cxr_line_positions
+= [(p1
[0],p1
[1],p1
[2])]
680 cxr_line_colours
+= [(colour
[0],colour
[1],colour
[2],colour
[3])]
681 cxr_line_colours
+= [(colour
[0],colour
[1],colour
[2],colour
[3])]
684 global cxr_jobs_inf
, cxr_jobs_batch
, cxr_error_inf
, cxr_view_mesh
, \
687 cxr_jobs_batch
= None
694 cxr_asset_lib
['models'] = {}
695 cxr_asset_lib
['materials'] = {}
696 cxr_asset_lib
['textures'] = {}
701 # ------------------------------------------------------------------------------
706 NBVTF_IMAGE_FORMAT_ABGR8888
= 1
707 NBVTF_IMAGE_FORMAT_BGR888
= 3
708 NBVTF_IMAGE_FORMAT_DXT1
= 13
709 NBVTF_IMAGE_FORMAT_DXT5
= 15
710 NBVTF_TEXTUREFLAGS_CLAMPS
= 0x00000004
711 NBVTF_TEXTUREFLAGS_CLAMPT
= 0x00000008
712 NBVTF_TEXTUREFLAGS_NORMAL
= 0x00000080
713 NBVTF_TEXTUREFLAGS_NOMIP
= 0x00000100
714 NBVTF_TEXTUREFLAGS_NOLOD
= 0x00000200
716 libnbvtf_convert
= extern( "nbvtf_convert", \
717 [c_char_p
,c_int32
,c_int32
,c_int32
,c_int32
,c_int32
,c_uint32
,c_char_p
], \
720 libnbvtf_init
= extern( "nbvtf_init", [], None )
721 libnbvtf_funcs
= [ libnbvtf_convert
, libnbvtf_init
]
724 # --------------------------
727 global libcxr
, libnbvtf
, libcxr_funcs
, libnbvtf_funcs
729 # Unload libraries if existing
730 def _reload( lib
, path
):
733 _handle
= lib
._handle
734 for i
in range(10): libc_dlclose( _handle
)
738 libpath
= F
'{os.path.dirname(__file__)}/{path}{CXR_SHARED_EXT}'
739 return cdll
.LoadLibrary( libpath
)
741 libnbvtf
= _reload( libnbvtf
, "libnbvtf" )
742 libcxr
= _reload( libcxr
, "libcxr" )
744 for fd
in libnbvtf_funcs
:
745 fd
.loadfrom( libnbvtf
)
748 for fd
in libcxr_funcs
:
749 fd
.loadfrom( libcxr
)
752 global c_libcxr_log_callback
, c_libcxr_line_callback
754 LOG_FUNCTION_TYPE
= CFUNCTYPE(None,c_char_p
)
755 c_libcxr_log_callback
= LOG_FUNCTION_TYPE(libcxr_log_callback
)
757 LINE_FUNCTION_TYPE
= CFUNCTYPE(None,\
758 POINTER(c_double
), POINTER(c_double
), POINTER(c_double
))
759 c_libcxr_line_callback
= LINE_FUNCTION_TYPE(libcxr_line_callback
)
761 libcxr
.cxr_set_log_function(cast(c_libcxr_log_callback
,c_void_p
))
762 libcxr
.cxr_set_line_function(cast(c_libcxr_line_callback
,c_void_p
))
764 build_time
= c_char_p
.in_dll(libcxr
,'cxr_build_time')
765 print( F
"libcxr build time: {build_time.value}" )
770 # ------------------------------------------------------------------------------
772 # Standard entity functions, think of like base.fgd
774 def cxr_get_origin(context
):
775 return context
['object'].location
* context
['transform']['scale'] + \
776 mathutils
.Vector(context
['transform']['offset'])
778 def cxr_get_angles(context
):
779 obj
= context
['object']
780 euler
= [ a
*57.295779513 for a
in obj
.rotation_euler
]
787 def cxr_baseclass(classes
, other
):
790 base
.update(x
.copy())
793 def ent_soundscape(context
):
794 obj
= context
['object']
795 kvs
= cxr_baseclass([ent_origin
],\
797 "radius": obj
.scale
.x
* bpy
.context
.scene
.cxr_data
.scale_factor
,
798 "soundscape": {"type":"string","default":""}
803 # EEVEE Light component converter -> Source 1
805 def ent_lights(context
):
806 obj
= context
['object']
807 kvs
= cxr_baseclass([ent_origin
],\
809 "_distance": (0.0 if obj
.data
.cxr_data
.realtime
else -1.0),
810 "_lightHDR": '-1 -1 -1 1',
814 light_base
= [(pow(obj
.data
.color
[i
],1.0/2.2)*255.0) for i
in range(3)] +\
815 [obj
.data
.energy
* bpy
.context
.scene
.cxr_data
.light_scale
]
817 if obj
.data
.type == 'SPOT' or obj
.data
.type == 'SUN':
818 # Blenders directional lights are -z forward
819 # Source is +x, however, it seems to use a completely different system.
820 # Since we dont care about roll for spotlights, we just take the
821 # pitch and yaw via trig
823 _
,mtx_rot
,_
= obj
.matrix_world
.decompose()
824 fwd
= mtx_rot
@ mathutils
.Vector((0,0,-1))
825 dir_pitch
= math
.asin(fwd
[2]) * 57.295779513
826 dir_yaw
= math
.atan2(fwd
[1],fwd
[0]) * 57.295779513
828 if obj
.data
.type == 'SPOT':
829 kvs
['_light'] = [ int(x
) for x
in light_base
]
830 kvs
['_cone'] = obj
.data
.spot_size
*(57.295779513/2.0)
831 kvs
['_inner_cone'] = (1.0-obj
.data
.spot_blend
)*kvs
['_cone']
833 kvs
['pitch'] = dir_pitch
834 kvs
['angles'] = [ 0, dir_yaw
, 0 ]
835 kvs
['_quadratic_attn'] = 0.0 # Source spotlights + quadratic falloff look
838 # Blender's default has a much more 'nice'
840 kvs
['_linear_attn'] = 1.0
842 elif obj
.data
.type == 'POINT':
843 kvs
['_light'] = [ int(x
) for x
in light_base
]
844 kvs
['_quadratic_attn'] = 1.0
845 kvs
['_linear_attn'] = 1.0
847 elif obj
.data
.type == 'SUN':
848 light_base
[3] *= 300.0 * 5
849 kvs
['_light'] = [ int(x
) for x
in light_base
]
851 ambient
= bpy
.context
.scene
.world
.color
852 kvs
['_ambient'] = [int(pow(ambient
[i
],1.0/2.2)*255.0) for i
in range(3)] +\
854 kvs
['_ambientHDR'] = [-1,-1,-1,1]
855 kvs
['_AmbientScaleHDR'] = 1
856 kvs
['pitch'] = dir_pitch
857 kvs
['angles'] = [ dir_pitch
, dir_yaw
, 0.0 ]
858 kvs
['SunSpreadAngle'] = 0
862 def ent_prop(context
):
863 if isinstance( context
['object'], bpy
.types
.Collection
):
865 target
= context
['object']
866 pos
= mathutils
.Vector(context
['origin'])
867 pos
+= mathutils
.Vector(context
['transform']['offset'])
869 kvs
['origin'] = [pos
[1],-pos
[0],pos
[2]]
870 kvs
['angles'] = [0,180,0]
871 kvs
['uniformscale'] = 1.0
873 kvs
= cxr_baseclass([ent_origin
],{})
874 target
= context
['object'].instance_collection
876 obj
= context
['object']
877 euler
= [ a
*57.295779513 for a
in obj
.rotation_euler
]
880 angle
[1] = euler
[2] + 180.0 # Dunno...
883 kvs
['angles'] = angle
884 kvs
['uniformscale'] = obj
.scale
[0]
886 if target
.cxr_data
.shadow_caster
:
887 kvs
['enablelightbounce'] = 1
888 kvs
['disableshadows'] = 0
890 kvs
['enablelightbounce'] = 0
891 kvs
['disableshadows'] = 1
893 kvs
['fademindist'] = -1
895 kvs
['model'] = F
"{asset_path('models',target)}.mdl".lower()
896 kvs
['renderamt'] = 255
897 kvs
['rendercolor'] = [255, 255, 255]
903 def ent_sky_camera(context
):
904 settings
= bpy
.context
.scene
.cxr_data
905 scale
= settings
.scale_factor
/ settings
.skybox_scale_factor
908 "origin": [_
for _
in context
['transform']['offset']],
909 "angles": [ 0, 0, 0 ],
910 "fogcolor": [255, 255, 255],
911 "fogcolor2": [255, 255, 255],
916 "HDRColorScale": 1.0,
921 def ent_cubemap(context
):
922 obj
= context
['object']
923 return cxr_baseclass([ent_origin
], {"cubemapsize": obj
.data
.cxr_data
.size
})
925 ent_origin
= { "origin": cxr_get_origin
}
926 ent_angles
= { "angles": cxr_get_angles
}
927 ent_transform
= cxr_baseclass( [ent_origin
], ent_angles
)
929 #include the user config
930 exec(open(F
'{os.path.dirname(__file__)}/config.py').read())
932 # Blender state callbacks
933 # ------------------------------------------------------------------------------
936 def cxr_on_load(dummy
):
937 global cxr_view_lines
, cxr_view_mesh
939 cxr_view_lines
= None
943 def cxr_dgraph_update(scene
,dgraph
):
945 print( F
"Hallo {time.time()}" )
947 # Convexer compilation functions
948 # ------------------------------------------------------------------------------
950 # Asset path management
952 def asset_uid(asset
):
953 if isinstance(asset
,str):
956 # Create a unique ID string
958 v
= asset
.cxr_data
.asset_id
967 dig
.append( int( v
% len(base
) ) )
973 if bpy
.context
.scene
.cxr_data
.include_names
:
974 name
+= asset
.name
.replace('.','_')
978 # -> <project_name>/<asset_name>
979 def asset_name(asset
):
980 return F
"{bpy.context.scene.cxr_data.project_name}/{asset_uid(asset)}"
982 # -> <subdir>/<project_name>/<asset_name>
983 def asset_path(subdir
, asset
):
984 return F
"{subdir}/{asset_name(asset_uid(asset))}"
986 # -> <csgo>/<subdir>/<project_name>/<asset_name>
987 def asset_full_path(sdir
,asset
):
988 return F
"{bpy.context.scene.cxr_data.subdir}/"+\
989 F
"{asset_path(sdir,asset_uid(asset))}"
991 # Decomposes mesh, and sets global error information if failed.
992 # - returns None on fail
993 # - returns world on success
994 def cxr_decompose_globalerr( mesh_src
):
998 world
= libcxr_decompose
.call( mesh_src
, pointer(err
) )
1001 cxr_view_mesh
= None
1005 ("No Error", "There is no error?"),\
1006 ("Bad input", "Non manifold geometry is present in the input mesh"),\
1007 ("Bad result","An invalid manifold was generated, try to simplify"),\
1008 ("Bad result","Make sure there is a clear starting point"),\
1009 ("Bad result","Implicit vertex was invalid, try to simplify"),\
1010 ("Bad input","Non coplanar vertices are in the source mesh"),\
1011 ("Bad input","Non convex polygon is in the source mesh"),\
1012 ("Bad result","Undefined failure"),\
1013 ("Invalid Input", "Undefined failure"),\
1020 # Entity functions / infos
1021 # ------------------------
1023 def cxr_collection_purpose(collection
):
1024 if collection
.name
.startswith('.'): return None
1025 if collection
.hide_render
: return None
1026 if collection
.name
.startswith('mdl_'): return 'model'
1029 def cxr_object_purpose(obj
):
1033 def _search(collection
):
1034 nonlocal objpurpose
, group
, obj
1036 purpose
= cxr_collection_purpose( collection
)
1037 if purpose
== None: return
1038 if purpose
== 'model':
1039 for o
in collection
.objects
:
1041 if o
.type != 'EMPTY':
1042 objpurpose
= 'model'
1046 for o
in collection
.objects
:
1048 classname
= cxr_classname(o
)
1049 if classname
!= None:
1050 objpurpose
= 'entity'
1051 if o
.type == 'MESH':
1052 objpurpose
= 'brush_entity'
1055 if o
.type == 'MESH':
1056 objpurpose
= 'brush'
1059 for c
in collection
.children
:
1062 if 'main' in bpy
.data
.collections
:
1063 _search( bpy
.data
.collections
['main'] )
1065 if objpurpose
== None and 'skybox' in bpy
.data
.collections
:
1066 _search( bpy
.data
.collections
['skybox'] )
1068 return (group
,objpurpose
)
1070 def cxr_intrinsic_classname(obj
):
1071 if obj
.type == 'LIGHT':
1073 'SPOT': "light_spot",
1075 'SUN': "light_environment" }[ obj
.data
.type ]
1077 elif obj
.type == 'LIGHT_PROBE':
1078 return "env_cubemap"
1079 elif obj
.type == 'EMPTY':
1080 if obj
.is_instancer
:
1081 return "prop_static"
1085 def cxr_custom_class(obj
):
1086 if obj
.type == 'MESH': custom_class
= obj
.cxr_data
.brushclass
1087 else: custom_class
= obj
.cxr_data
.classname
1091 def cxr_classname(obj
):
1092 intr
= cxr_intrinsic_classname(obj
)
1093 if intr
!= None: return intr
1095 custom_class
= cxr_custom_class(obj
)
1096 if custom_class
!= 'NONE':
1102 # intinsic: (k, False, value)
1103 # property: (k, True, value or default)
1107 def cxr_entity_keyvalues(context
):
1108 classname
= context
['classname']
1109 obj
= context
['object']
1110 if classname
not in cxr_entities
: return None
1114 entdef
= cxr_entities
[classname
]
1115 kvs
= entdef
['keyvalues']
1117 if callable(kvs
): kvs
= kvs(context
)
1124 if isinstance(kv
,dict):
1126 value
= obj
[ F
"cxrkv_{k}" ]
1131 if isinstance(value
,mathutils
.Vector
):
1132 value
= [_
for _
in value
]
1134 result
+= [(k
, isprop
, value
)]
1138 # Extract material information from shader graph data
1140 def material_info(mat
):
1142 info
['res'] = (512,512)
1143 info
['name'] = 'tools/toolsnodraw'
1145 if mat
== None or mat
.use_nodes
== False:
1149 if mat
.cxr_data
.shader
== 'Builtin':
1150 info
['name'] = mat
.name
1154 info
['name'] = asset_name(mat
)
1156 # Using the cxr_graph_mapping as a reference, go through the shader
1157 # graph and gather all $props from it.
1159 def _graph_read( node_def
, node
=None, depth
=0 ):
1163 def _variant_apply( val
):
1166 if isinstance( val
, list ):
1167 for shader_variant
in val
:
1168 if shader_variant
[0] == mat
.cxr_data
.shader
:
1169 return shader_variant
[1]
1176 _graph_read
.extracted
= []
1178 for node_idname
in node_def
:
1179 for n
in mat
.node_tree
.nodes
:
1180 if n
.name
== node_idname
:
1181 node_def
= node_def
[node_idname
]
1185 for link
in node_def
:
1186 link_def
= _variant_apply( node_def
[link
] )
1188 if isinstance( link_def
, dict ):
1189 node_link
= node
.inputs
[link
]
1191 if node_link
.is_linked
:
1193 # look for definitions for the connected node type
1194 from_node
= node_link
.links
[0].from_node
1196 node_name
= from_node
.name
.split('.')[0]
1197 if node_name
in link_def
:
1198 from_node_def
= link_def
[ node_name
]
1200 _graph_read( from_node_def
, from_node
, depth
+1 )
1203 # TODO: Make a warning for this?
1206 if "default" in link_def
:
1207 prop
= _variant_apply( link_def
['default'] )
1208 info
[prop
] = node_link
.default_value
1210 prop
= _variant_apply( link_def
)
1211 info
[prop
] = getattr( node
, link
)
1213 _graph_read(cxr_graph_mapping
)
1215 if "$basetexture" in info
:
1216 export_res
= info
['$basetexture'].cxr_data
.export_res
1217 info
['res'] = (export_res
[0], export_res
[1])
1221 def vec3_min( a
, b
):
1222 return mathutils
.Vector((min(a
[0],b
[0]),min(a
[1],b
[1]),min(a
[2],b
[2])))
1223 def vec3_max( a
, b
):
1224 return mathutils
.Vector((max(a
[0],b
[0]),max(a
[1],b
[1]),max(a
[2],b
[2])))
1226 def cxr_collection_center(collection
, transform
):
1228 bounds_min
= mathutils
.Vector((BIG
,BIG
,BIG
))
1229 bounds_max
= mathutils
.Vector((-BIG
,-BIG
,-BIG
))
1231 for obj
in collection
.objects
:
1232 if obj
.type == 'MESH':
1233 corners
= [ mathutils
.Vector(c
) for c
in obj
.bound_box
]
1235 for corner
in [ obj
.matrix_world
@c for c
in corners
]:
1236 bounds_min
= vec3_min( bounds_min
, corner
)
1237 bounds_max
= vec3_max( bounds_max
, corner
)
1239 center
= (bounds_min
+ bounds_max
) / 2.0
1241 origin
= mathutils
.Vector((-center
[1],center
[0],center
[2]))
1242 origin
*= transform
['scale']
1246 # Prepares Scene into dictionary format
1248 def cxr_scene_collect():
1249 context
= bpy
.context
1251 # Make sure all of our asset types have a unique ID
1252 def _uid_prepare(objtype
):
1258 if vs
.asset_id
in used_ids
:
1261 id_max
= max(id_max
,vs
.asset_id
)
1262 used_ids
+=[vs
.asset_id
]
1263 for vs
in to_generate
:
1265 vs
.asset_id
= id_max
1266 _uid_prepare(bpy
.data
.materials
)
1267 _uid_prepare(bpy
.data
.images
)
1268 _uid_prepare(bpy
.data
.collections
)
1271 "entities": [], # Everything with a classname
1272 "geo": [], # All meshes without a classname
1273 "heros": [] # Collections prefixed with mdl_
1276 def _collect(collection
,transform
):
1279 purpose
= cxr_collection_purpose( collection
)
1280 if purpose
== None: return
1281 if purpose
== 'model':
1282 sceneinfo
['entities'] += [{
1283 "object": collection
,
1284 "classname": "prop_static",
1285 "transform": transform
,
1286 "origin": cxr_collection_center( collection
, transform
)
1289 sceneinfo
['heros'] += [{
1290 "collection": collection
,
1291 "transform": transform
,
1292 "origin": cxr_collection_center( collection
, transform
)
1296 for obj
in collection
.objects
:
1297 if obj
.hide_get(): continue
1299 classname
= cxr_classname( obj
)
1301 if classname
!= None:
1302 sceneinfo
['entities'] += [{
1304 "classname": classname
,
1305 "transform": transform
1307 elif obj
.type == 'MESH':
1308 sceneinfo
['geo'] += [{
1310 "transform": transform
1313 for c
in collection
.children
:
1314 _collect( c
, transform
)
1317 "scale": context
.scene
.cxr_data
.scale_factor
,
1322 "scale": context
.scene
.cxr_data
.skybox_scale_factor
,
1323 "offset": (0,0,context
.scene
.cxr_data
.skybox_offset
)
1326 if 'main' in bpy
.data
.collections
:
1327 _collect( bpy
.data
.collections
['main'], transform_main
)
1329 if 'skybox' in bpy
.data
.collections
:
1330 _collect( bpy
.data
.collections
['skybox'], transform_sky
)
1332 sceneinfo
['entities'] += [{
1334 "transform": transform_sky
,
1335 "classname": "sky_camera"
1340 # Write VMF out to file (JOB HANDLER)
1342 def cxr_export_vmf(sceneinfo
, output_vmf
):
1345 with
vdf_structure(output_vmf
) as m
:
1346 print( F
"Write: {output_vmf}" )
1348 vmfinfo
= cxr_vmf_context()
1349 vmfinfo
.mapversion
= 4
1351 #TODO: These need to be in options...
1352 vmfinfo
.skyname
= bpy
.context
.scene
.cxr_data
.skyname
.encode('utf-8')
1353 vmfinfo
.detailvbsp
= b
"detail.vbsp"
1354 vmfinfo
.detailmaterial
= b
"detail/detailsprites"
1355 vmfinfo
.lightmap_scale
= 12
1357 vmfinfo
.brush_count
= 0
1358 vmfinfo
.entity_count
= 0
1359 vmfinfo
.face_count
= 0
1361 visgroups
= (cxr_visgroup
*len(cxr_visgroups
))()
1362 for i
, vg
in enumerate(cxr_visgroups
):
1363 visgroups
[i
].name
= vg
.encode('utf-8')
1364 vmfinfo
.visgroups
= cast(visgroups
, POINTER(cxr_visgroup
))
1365 vmfinfo
.visgroup_count
= len(cxr_visgroups
)
1367 libcxr_begin_vmf
.call( pointer(vmfinfo
), m
.fp
)
1369 def _buildsolid( cmd
):
1372 print( F
"{vmfinfo.brush_count} :: {cmd['object'].name}" )
1374 baked
= mesh_cxr_format( cmd
['object'] )
1375 world
= cxr_decompose_globalerr( baked
)
1380 vmfinfo
.scale
= cmd
['transform']['scale']
1382 offset
= cmd
['transform']['offset']
1383 vmfinfo
.offset
[0] = offset
[0]
1384 vmfinfo
.offset
[1] = offset
[1]
1385 vmfinfo
.offset
[2] = offset
[2]
1387 if cmd
['object'].cxr_data
.lightmap_override
> 0:
1388 vmfinfo
.lightmap_scale
= cmd
['object'].cxr_data
.lightmap_override
1390 vmfinfo
.lightmap_scale
= bpy
.context
.scene
.cxr_data
.lightmap_scale
1392 libcxr_push_world_vmf
.call( world
, pointer(vmfinfo
), m
.fp
)
1393 libcxr_free_world
.call( world
)
1398 for brush
in sceneinfo
['geo']:
1399 vmfinfo
.visgroupid
= int(brush
['object'].cxr_data
.visgroup
)
1400 if not _buildsolid( brush
):
1404 vmfinfo
.visgroupid
= 0
1406 libcxr_vmf_begin_entities
.call(pointer(vmfinfo
), m
.fp
)
1409 for ent
in sceneinfo
['entities']:
1411 ctx
= ent
['transform']
1412 cls
= ent
['classname']
1415 m
.kv( 'classname', cls
)
1417 kvs
= cxr_entity_keyvalues( ent
)
1420 if isinstance(kv
[2], list):
1421 m
.kv( kv
[0], ' '.join([str(_
) for _
in kv
[2]]) )
1422 else: m
.kv( kv
[0], str(kv
[2]) )
1426 elif not isinstance( obj
, bpy
.types
.Collection
):
1427 if obj
.type == 'MESH':
1428 vmfinfo
.visgroupid
= int(obj
.cxr_data
.visgroup
)
1429 if not _buildsolid( ent
):
1436 m
.kv( 'visgroupid', str(obj
.cxr_data
.visgroup
) )
1437 m
.kv( 'visgroupshown', '1' )
1438 m
.kv( 'visgroupautoshown', '1' )
1442 vmfinfo
.visgroupid
= 0
1447 # COmpile image using NBVTF and hash it (JOB HANDLER)
1449 def compile_image(img
):
1453 name
= asset_name(img
)
1454 src_path
= bpy
.path
.abspath(img
.filepath
)
1456 dims
= img
.cxr_data
.export_res
1458 'RGBA': NBVTF_IMAGE_FORMAT_ABGR8888
,
1459 'DXT1': NBVTF_IMAGE_FORMAT_DXT1
,
1460 'DXT5': NBVTF_IMAGE_FORMAT_DXT5
,
1461 'RGB': NBVTF_IMAGE_FORMAT_BGR888
1462 }[ img
.cxr_data
.fmt
]
1464 mipmap
= img
.cxr_data
.mipmap
1465 lod
= img
.cxr_data
.lod
1466 clamp
= img
.cxr_data
.clamp
1467 flags
= img
.cxr_data
.flags
1469 q
=bpy
.context
.scene
.cxr_data
.image_quality
1471 userflag_hash
= F
"{mipmap}.{lod}.{clamp}.{flags}"
1472 file_hash
= F
"{name}.{os.path.getmtime(src_path)}"
1473 comphash
= F
"{file_hash}.{dims[0]}.{dims[1]}.{fmt}.{userflag_hash}.{q}"
1475 if img
.cxr_data
.last_hash
!= comphash
:
1476 print( F
"Texture update: {img.filepath}" )
1478 src
= src_path
.encode('utf-8')
1479 dst
= (asset_full_path('materials',img
)+'.vtf').encode('utf-8')
1483 # texture setting flags
1484 if not lod
: flags_full |
= NBVTF_TEXTUREFLAGS_NOLOD
1486 flags_full |
= NBVTF_TEXTUREFLAGS_CLAMPS
1487 flags_full |
= NBVTF_TEXTUREFLAGS_CLAMPT
1489 if libnbvtf_convert
.call(src
,dims
[0],dims
[1],mipmap
,fmt
,q
,flags_full
,dst
):
1490 img
.cxr_data
.last_hash
= comphash
1495 # Compile a material to VMT format. This is quick to run, doesnt need to be a
1498 def compile_material(mat
):
1499 info
= material_info(mat
)
1500 properties
= mat
.cxr_data
1502 print( F
"Compile {asset_full_path('materials',mat)}.vmt" )
1503 if properties
.shader
== 'Builtin':
1508 # Walk the property tree
1509 def _mlayer( layer
):
1510 nonlocal properties
, props
1513 if isinstance(layer
[decl
],dict): # $property definition
1515 ptype
= pdef
['type']
1521 if 'shaders' in pdef
and properties
.shader
not in pdef
['shaders']:
1524 # Group expansion (does it have subdefinitions?)
1526 if isinstance(pdef
[ch
],dict):
1535 if ptype
== 'intrinsic':
1539 prop
= getattr(properties
,decl
)
1540 default
= pdef
['default']
1542 if not isinstance(prop
,str) and \
1543 not isinstance(prop
,bpy
.types
.Image
) and \
1544 hasattr(prop
,'__getitem__'):
1545 prop
= tuple([p
for p
in prop
])
1549 props
+= [(decl
,pdef
,prop
)]
1554 if expandview
: _mlayer(pdef
)
1556 _mlayer( cxr_shader_params
)
1559 with
vdf_structure( F
"{asset_full_path('materials',mat)}.vmt" ) as vmt
:
1560 vmt
.node( properties
.shader
)
1561 vmt
.put( "// Convexer export\n" )
1570 if 'exponent' in pdef
: return str(pow( v
, pdef
['exponent'] ))
1573 if isinstance(prop
,bpy
.types
.Image
):
1574 vmt
.kv( decl
, asset_name(prop
))
1575 elif isinstance(prop
,bool):
1576 vmt
.kv( decl
, '1' if prop
else '0' )
1577 elif isinstance(prop
,str):
1578 vmt
.kv( decl
, prop
)
1579 elif isinstance(prop
,float) or isinstance(prop
,int):
1580 vmt
.kv( decl
, _numeric(prop
) )
1581 elif isinstance(prop
,tuple):
1582 vmt
.kv( decl
, F
"[{' '.join([_numeric(_) for _ in prop])}]" )
1584 vmt
.put( F
"// (cxr) unkown shader value type'{type(prop)}'" )
1589 def cxr_modelsrc_vphys( mdl
):
1590 for obj
in mdl
.objects
:
1591 if obj
.name
== F
"{mdl.name}_phy":
1595 def cxr_export_modelsrc( mdl
, origin
, asset_dir
, project_name
, transform
):
1596 dgraph
= bpy
.context
.evaluated_depsgraph_get()
1598 # Compute hash value
1599 chash
= asset_uid(mdl
)+str(origin
)+str(transform
)
1601 #for obj in mdl.objects:
1602 # if obj.type != 'MESH':
1605 # ev = obj.evaluated_get(dgraph).data
1606 # srcverts=[(v.co[0],v.co[1],v.co[2]) for v in ev.vertices]
1607 # srcloops=[(l.normal[0],l.normal[1],l.normal[2]) for l in ev.loops]
1609 # chash=hashlib.sha224((str(srcverts)+chash).encode()).hexdigest()
1610 # chash=hashlib.sha224((str(srcloops)+chash).encode()).hexdigest()
1612 # if ev.uv_layers.active != None:
1613 # uv_layer = ev.uv_layers.active.data
1614 # srcuv=[(uv.uv[0],uv.uv[1]) for uv in uv_layer]
1618 # chash=hashlib.sha224((str(srcuv)+chash).encode()).hexdigest()
1619 # srcmats=[ ms.material.name for ms in obj.material_slots ]
1620 # chash=hashlib.sha224((str(srcmats)+chash).encode()).hexdigest()
1621 # transforms=[ obj.location, obj.rotation_euler, obj.scale ]
1622 # srctr=[(v[0],v[1],v[2]) for v in transforms]
1623 # chash=hashlib.sha224((str(srctr)+chash).encode()).hexdigest()
1625 #if chash != mdl.cxr_data.last_hash:
1626 # mdl.cxr_data.last_hash = chash
1627 # print( F"Compile: {mdl.name}" )
1631 bpy
.ops
.object.select_all(action
='DESELECT')
1634 def _get_layer(col
,name
):
1635 for c
in col
.children
:
1638 sub
= _get_layer(c
,name
)
1642 layer
= _get_layer(bpy
.context
.view_layer
.layer_collection
,mdl
.name
)
1644 prev_state
= layer
.hide_viewport
1645 layer
.hide_viewport
=False
1647 # Collect materials to be compiled, and temp rename for export
1651 for obj
in mdl
.objects
:
1652 if obj
.name
== F
"{mdl.name}_phy":
1656 obj
.select_set(state
=True)
1657 for ms
in obj
.material_slots
:
1658 if ms
.material
!= None:
1659 if ms
.material
not in mat_dict
:
1660 mat_dict
[ms
.material
] = ms
.material
.name
1661 ms
.material
.name
= asset_uid(ms
.material
)
1662 ms
.material
.use_nodes
= False
1665 bpy
.ops
.export_scene
.fbx( filepath
=F
'{asset_dir}/{uid}_ref.fbx',\
1666 check_existing
=False,
1668 apply_unit_scale
=False,
1669 bake_space_transform
=False
1672 bpy
.ops
.object.select_all(action
='DESELECT')
1675 vphys
.select_set(state
=True)
1676 bpy
.ops
.export_scene
.fbx( filepath
=F
'{asset_dir}/{uid}_phy.fbx',\
1677 check_existing
=False,
1679 apply_unit_scale
=False,
1680 bake_space_transform
=False
1682 bpy
.ops
.object.select_all(action
='DESELECT')
1684 # Fix material names back to original
1685 for mat
in mat_dict
:
1686 mat
.name
= mat_dict
[mat
]
1687 mat
.use_nodes
= True
1689 layer
.hide_viewport
=prev_state
1692 with
open(F
'{asset_dir}/{uid}.qc','w') as o
:
1693 o
.write(F
'$modelname "{project_name}/{uid}"\n')
1694 #o.write(F'$scale .32\n')
1695 o
.write(F
'$scale {transform["scale"]/100.0}\n')
1696 o
.write(F
'$body _ "{uid}_ref.fbx"\n')
1697 o
.write(F
'$staticprop\n')
1698 o
.write(F
'$origin {origin[0]:.6f} {origin[1]:.6f} {origin[2]:.6f}\n')
1700 if mdl
.cxr_data
.preserve_order
:
1701 o
.write(F
"$preservetriangleorder\n")
1703 if mdl
.cxr_data
.texture_shadows
:
1704 o
.write(F
"$casttextureshadows\n")
1706 o
.write(F
"$surfaceprop {mdl.cxr_data.surfaceprop}\n")
1709 o
.write(F
'$collisionmodel "{uid}_phy.fbx"\n')
1711 o
.write(" $concave\n")
1714 o
.write(F
'$cdmaterials {project_name}\n')
1715 o
.write(F
'$sequence idle {uid}_ref.fbx\n')
1719 # Copy bsp file (and also lightpatch it)
1721 def cxr_patchmap( src
, dst
):
1722 libcxr_lightpatch_bsp
.call( src
.encode('utf-8') )
1723 shutil
.copyfile( src
, dst
)
1726 # Convexer operators
1727 # ------------------------------------------------------------------------------
1729 # Force reload of shared libraries
1731 class CXR_RELOAD(bpy
.types
.Operator
):
1732 bl_idname
="convexer.reload"
1734 def execute(_
,context
):
1738 # Reset all debugging/ui information
1740 class CXR_RESET(bpy
.types
.Operator
):
1741 bl_idname
="convexer.reset"
1742 bl_label
="Reset Convexer"
1743 def execute(_
,context
):
1747 # Used for exporting data to use with ASAN builds
1749 class CXR_DEV_OPERATOR(bpy
.types
.Operator
):
1750 bl_idname
="convexer.dev_test"
1751 bl_label
="Export development data"
1753 def execute(_
,context
):
1754 # Prepare input data
1755 mesh_src
= mesh_cxr_format(context
.active_object
)
1756 libcxr_write_test_data
.call( pointer(mesh_src
) )
1759 class CXR_INIT_FS_OPERATOR(bpy
.types
.Operator
):
1760 bl_idname
="convexer.fs_init"
1761 bl_label
="Initialize filesystem"
1763 def execute(_
,context
):
1764 gameinfo
= F
'{bpy.context.scene.cxr_data.subdir}/gameinfo.txt'
1766 if libcxr_fs_set_gameinfo
.call( gameinfo
.encode('utf-8') ) == 1:
1767 print( "File system ready" )
1769 print( "File system failed to initialize" )
1773 def cxr_load_texture( path
):
1774 global cxr_asset_lib
1776 if path
in cxr_asset_lib
['textures']:
1777 return cxr_asset_lib
['textures'][path
]
1779 print( F
"cxr_load_texture( '{path}' )" )
1783 tex
= cxr_asset_lib
['textures'][path
] = None
1786 def cxr_load_material( path
):
1787 global cxr_asset_lib
1789 if path
in cxr_asset_lib
['materials']:
1790 return cxr_asset_lib
['materials'][path
]
1792 print( F
"cxr_load_material( '{path}' )" )
1794 pvmt
= libcxr_valve_load_material
.call( path
.encode( 'utf-8') )
1797 mat
= cxr_asset_lib
['materials'][path
] = {}
1800 mat
['basetexture'] = cxr_load_texture( vmt
.basetexture
.decode('utf-8') )
1803 mat
['bumpmap'] = cxr_load_texture( vmt
.bumpmap
.decode('utf-8') )
1805 libcxr_valve_free_material
.call( pvmt
)
1809 def cxr_load_model_full( path
):
1810 global cxr_asset_lib
, cxr_mdl_shader
1812 if path
in cxr_asset_lib
['models']:
1813 return cxr_asset_lib
['models'][path
]
1815 pmdl
= libcxr_valve_load_model
.call( path
.encode( 'utf-8' ) )
1817 print( F
"cxr_load_model_full( '{path}' )" )
1820 print( "Failed to load model" )
1821 cxr_asset_lib
['models'][path
] = None
1826 # Convert our lovely interleaved vertex stream into, whatever this is.
1827 positions
= [ (mdl
.vertex_data
[i
*8+0], \
1828 mdl
.vertex_data
[i
*8+1], \
1829 mdl
.vertex_data
[i
*8+2]) for i
in range(mdl
.vertex_count
) ]
1831 normals
= [ (mdl
.vertex_data
[i
*8+3], \
1832 mdl
.vertex_data
[i
*8+4], \
1833 mdl
.vertex_data
[i
*8+5]) for i
in range(mdl
.vertex_count
) ]
1835 uvs
= [ (mdl
.vertex_data
[i
*8+6], \
1836 mdl
.vertex_data
[i
*8+7]) for i
in range(mdl
.vertex_count
) ]
1838 fmt
= gpu
.types
.GPUVertFormat()
1839 fmt
.attr_add(id="aPos", comp_type
='F32', len=3, fetch_mode
='FLOAT')
1840 fmt
.attr_add(id="aNormal", comp_type
='F32', len=3, fetch_mode
='FLOAT')
1841 fmt
.attr_add(id="aUv", comp_type
='F32', len=2, fetch_mode
='FLOAT')
1843 vbo
= gpu
.types
.GPUVertBuf(len=mdl
.vertex_count
, format
=fmt
)
1844 vbo
.attr_fill(id="aPos", data
=positions
)
1845 vbo
.attr_fill(id="aNormal", data
=normals
)
1846 vbo
.attr_fill(id="aUv", data
=uvs
)
1848 batches
= cxr_asset_lib
['models'][path
] = []
1850 for p
in range(mdl
.part_count
):
1852 indices
= mdl
.indices
[part
.ibstart
:part
.ibstart
+part
.ibcount
]
1853 indices
= [ (indices
[i
*3+0],indices
[i
*3+1],indices
[i
*3+2]) \
1854 for i
in range(part
.ibcount
//3) ]
1856 ibo
= gpu
.types
.GPUIndexBuf( type='TRIS', seq
=indices
)
1858 batch
= gpu
.types
.GPUBatch( type='TRIS', buf
=vbo
, elem
=ibo
)
1859 batch
.program_set( cxr_mdl_shader
)
1861 mat_str
= cast( mdl
.materials
[ part
.material
], c_char_p
)
1862 batches
+= [( cxr_load_material( mat_str
.value
.decode('utf-8') ), batch
)]
1864 libcxr_valve_free_model
.call( pmdl
)
1868 class CXR_LOAD_MODEL_OPERATOR(bpy
.types
.Operator
):
1869 bl_idname
="convexer.model_load"
1870 bl_label
="Load model"
1872 def execute(_
,context
):
1873 global cxr_mdl_mesh
, cxr_mdl_shader
, cxr_asset_lib
1875 test_mdl
= cxr_load_model_full( bpy
.context
.scene
.cxr_data
.dev_mdl
)
1877 if test_mdl
!= None:
1878 # just draw first batch part for now
1879 cxr_mdl_mesh
= test_mdl
[0][1]
1886 # UI: Preview how the brushes will looks in 3D view
1888 class CXR_PREVIEW_OPERATOR(bpy
.types
.Operator
):
1889 bl_idname
="convexer.preview"
1890 bl_label
="Preview Brushes"
1894 def execute(_
,context
):
1895 global cxr_view_mesh
1896 global cxr_view_shader
, cxr_view_mesh
, cxr_error_inf
1900 static
= _
.__class
__
1902 mesh_src
= mesh_cxr_format(context
.active_object
)
1903 world
= cxr_decompose_globalerr( mesh_src
)
1908 # Generate preview using cxr
1910 ptrpreview
= libcxr_world_preview
.call( world
)
1911 preview
= ptrpreview
[0]
1913 vertices
= preview
.vertices
[:preview
.vertex_count
]
1914 vertices
= [(_
[0],_
[1],_
[2]) for _
in vertices
]
1916 colours
= preview
.colours
[:preview
.vertex_count
]
1917 colours
= [(_
[0],_
[1],_
[2],_
[3]) for _
in colours
]
1919 indices
= preview
.indices
[:preview
.indices_count
]
1920 indices
= [ (indices
[i
*3+0],indices
[i
*3+1],indices
[i
*3+2]) \
1921 for i
in range(int(preview
.indices_count
/3)) ]
1923 cxr_view_mesh
= batch_for_shader(
1924 cxr_view_shader
, 'TRIS',
1925 { "pos": vertices
, "color": colours
},
1929 libcxr_free_tri_mesh
.call( ptrpreview
)
1930 libcxr_free_world
.call( world
)
1936 # Search for VMF compiler executables in subdirectory
1938 class CXR_DETECT_COMPILERS(bpy
.types
.Operator
):
1939 bl_idname
="convexer.detect_compilers"
1940 bl_label
="Find compilers"
1942 def execute(self
,context
):
1943 scene
= context
.scene
1944 settings
= scene
.cxr_data
1945 subdir
= settings
.subdir
1947 for exename
in ['studiomdl','vbsp','vvis','vrad']:
1948 searchpath
= os
.path
.normpath(F
'{subdir}/../bin/{exename}.exe')
1949 if os
.path
.exists(searchpath
):
1950 settings
[F
'exe_{exename}'] = searchpath
1954 def cxr_compiler_path( compiler
):
1955 settings
= bpy
.context
.scene
.cxr_data
1956 subdir
= settings
.subdir
1957 path
= os
.path
.normpath(F
'{subdir}/../bin/{compiler}.exe')
1959 if os
.path
.exists( path
): return path
1962 # Compatibility layer
1964 def cxr_temp_file( fn
):
1965 if CXR_GNU_LINUX
== 1:
1968 filepath
= bpy
.data
.filepath
1969 directory
= os
.path
.dirname(filepath
)
1970 return F
"{directory}/{fn}"
1972 def cxr_winepath( path
):
1973 if CXR_GNU_LINUX
== 1:
1974 return 'z:'+path
.replace('/','\\')
1978 # Main compile function
1980 class CXR_COMPILER_CHAIN(bpy
.types
.Operator
):
1981 bl_idname
="convexer.chain"
1982 bl_label
="Compile Chain"
1997 def cancel(_
,context
):
1998 #global cxr_jobs_batch
1999 static
= _
.__class
__
2000 wm
= context
.window_manager
2002 if static
.SUBPROC
!= None:
2003 static
.SUBPROC
.terminate()
2004 static
.SUBPROC
= None
2006 if static
.TIMER
!= None:
2007 wm
.event_timer_remove( static
.TIMER
)
2012 #cxr_jobs_batch = None
2016 def modal(_
,context
,ev
):
2017 static
= _
.__class
__
2019 if ev
.type == 'TIMER':
2020 global cxr_jobs_batch
, cxr_error_inf
2022 if static
.WAIT_REDRAW
:
2024 return {'PASS_THROUGH'}
2025 static
.WAIT_REDRAW
= True
2027 if static
.USER_EXIT
:
2028 print( "Chain USER_EXIT" )
2029 return _
.cancel(context
)
2031 if static
.SUBPROC
!= None:
2032 # Deal with async modes
2033 status
= static
.SUBPROC
.poll()
2035 # Cannot redirect STDOUT through here without causing
2036 # undefined behaviour due to the Blender Python specification.
2038 # Have to write it out to a file and read it back in.
2041 with
open(cxr_temp_file("convexer_compile_log.txt"),"r") as log
:
2042 static
.LOG
= log
.readlines()
2044 return {'PASS_THROUGH'}
2046 #for l in static.SUBPROC.stdout:
2047 # print( F'-> {l.decode("utf-8")}',end='' )
2048 static
.SUBPROC
= None
2051 print(F
'Compiler () error: {status}')
2053 jobn
= static
.JOBSYS
['jobs'][static
.JOBID
]
2054 cxr_error_inf
= ( F
"{static.JOBSYS['title']} error {status}", jobn
)
2056 return _
.cancel(context
)
2058 static
.JOBSYS
['jobs'][static
.JOBID
] = None
2059 cxr_jobs_update_graph( static
.JOBINFO
)
2061 return {'PASS_THROUGH'}
2063 # Compile syncronous thing
2064 for sys
in static
.JOBINFO
:
2065 for i
,target
in enumerate(sys
['jobs']):
2068 if callable(sys
['exec']):
2069 print( F
"Run (sync): {static.JOBID} @{time.time()}" )
2071 if not sys
['exec'](*target
):
2072 print( "Job failed" )
2073 return _
.cancel(context
)
2075 sys
['jobs'][i
] = None
2078 # Run external executable (wine)
2079 static
.SUBPROC
= subprocess
.Popen( target
,
2080 stdout
=static
.FILE
,\
2081 stderr
=subprocess
.PIPE
,\
2086 cxr_jobs_update_graph( static
.JOBINFO
)
2088 return {'PASS_THROUGH'}
2091 print( "All jobs completed!" )
2092 #cxr_jobs_batch = None
2094 return _
.cancel(context
)
2096 return {'PASS_THROUGH'}
2098 def invoke(_
,context
,event
):
2099 global cxr_error_inf
2101 static
= _
.__class
__
2102 wm
= context
.window_manager
2104 if static
.TIMER
!= None:
2105 print("Chain exiting...")
2106 static
.USER_EXIT
=True
2107 return {'RUNNING_MODAL'}
2109 print("Launching compiler toolchain")
2112 # Run static compilation units now (collect, vmt..)
2113 filepath
= bpy
.data
.filepath
2114 directory
= os
.path
.dirname(filepath
)
2115 settings
= bpy
.context
.scene
.cxr_data
2117 asset_dir
= F
"{directory}/modelsrc"
2118 material_dir
= F
"{settings.subdir}/materials/{settings.project_name}"
2119 model_dir
= F
"{settings.subdir}/models/{settings.project_name}"
2120 output_vmf
= F
"{directory}/{settings.project_name}.vmf"
2122 bsp_local
= F
"{directory}/{settings.project_name}.bsp"
2123 bsp_remote
= F
"{settings.subdir}/maps/{settings.project_name}.bsp"
2124 bsp_packed
= F
"{settings.subdir}/maps/{settings.project_name}_pack.bsp"
2125 packlist
= F
"{directory}/{settings.project_name}_assets.txt"
2127 os
.makedirs( asset_dir
, exist_ok
=True )
2128 os
.makedirs( material_dir
, exist_ok
=True )
2129 os
.makedirs( model_dir
, exist_ok
=True )
2131 static
.FILE
= open(cxr_temp_file("convexer_compile_log.txt"),"w")
2134 sceneinfo
= cxr_scene_collect()
2140 for brush
in sceneinfo
['geo']:
2141 for ms
in brush
['object'].material_slots
:
2142 a_materials
.add( ms
.material
)
2143 if ms
.material
.cxr_data
.shader
== 'VertexLitGeneric':
2144 errmat
= ms
.material
.name
2145 errnam
= brush
['object'].name
2147 cxr_error_inf
= ( "Shader error", \
2148 F
"Vertex shader ({errmat}) used on model ({errnam})" )
2150 print( F
"Vertex shader {errmat} used on {errnam}")
2152 return {'CANCELLED'}
2156 for ent
in sceneinfo
['entities']:
2157 if ent
['object'] == None: continue
2159 if ent
['classname'] == 'prop_static':
2161 if isinstance(obj
,bpy
.types
.Collection
):
2163 a_models
.add( target
)
2164 model_jobs
+= [(target
, ent
['origin'], asset_dir
, \
2165 settings
.project_name
, ent
['transform'])]
2167 target
= obj
.instance_collection
2168 if target
in a_models
:
2170 a_models
.add( target
)
2172 # TODO: Should take into account collection instancing offset
2173 model_jobs
+= [(target
, [0,0,0], asset_dir
, \
2174 settings
.project_name
, ent
['transform'])]
2176 elif ent
['object'].type == 'MESH':
2177 for ms
in ent
['object'].material_slots
:
2178 a_materials
.add( ms
.material
)
2180 for mdl
in a_models
:
2181 uid
= asset_uid(mdl
)
2182 qc_jobs
+= [F
'{uid}.qc']
2184 for obj
in mdl
.objects
:
2185 for ms
in obj
.material_slots
:
2186 a_materials
.add( ms
.material
)
2187 if ms
.material
.cxr_data
.shader
== 'LightMappedGeneric' or \
2188 ms
.material
.cxr_data
.shader
== 'WorldVertexTransition':
2190 errmat
= ms
.material
.name
2193 cxr_error_inf
= ( "Shader error", \
2194 F
"Lightmapped shader ({errmat}) used on model ({errnam})" )
2196 print( F
"Lightmapped shader {errmat} used on {errnam}")
2198 return {'CANCELLED'}
2201 for mat
in a_materials
:
2202 for pair
in compile_material(mat
):
2207 if isinstance(prop
,bpy
.types
.Image
):
2209 if 'flags' in pdef
: flags
= pdef
['flags']
2210 if prop
not in image_jobs
:
2211 image_jobs
+= [(prop
,)]
2212 prop
.cxr_data
.flags
= flags
2215 with
open( packlist
, "w" ) as fp
:
2217 for mat
in a_materials
:
2218 if mat
.cxr_data
.shader
== 'Builtin': continue
2219 fp
.write(F
"{asset_path('materials',mat)}.vmt\n")
2220 fp
.write(F
"{cxr_winepath(asset_full_path('materials',mat))}.vmt\n")
2222 for img_job
in image_jobs
:
2224 fp
.write(F
"{asset_path('materials',img)}.vtf\n")
2225 fp
.write(F
"{cxr_winepath(asset_full_path('materials',img))}.vtf\n")
2227 for mdl
in a_models
:
2228 local
= asset_path('models',mdl
)
2229 winep
= cxr_winepath(asset_full_path('models',mdl
))
2231 fp
.write(F
"{local}.vvd\n")
2232 fp
.write(F
"{winep}.vvd\n")
2233 fp
.write(F
"{local}.dx90.vtx\n")
2234 fp
.write(F
"{winep}.dx90.vtx\n")
2235 fp
.write(F
"{local}.mdl\n")
2236 fp
.write(F
"{winep}.mdl\n")
2237 fp
.write(F
"{local}.vvd\n")
2238 fp
.write(F
"{winep}.vvd\n")
2240 if cxr_modelsrc_vphys(mdl
):
2241 fp
.write(F
"{local}.phy\n")
2242 fp
.write(F
"{winep}.phy\n")
2248 if settings
.comp_vmf
:
2249 static
.JOBINFO
+= [{
2250 "title": "Convexer",
2252 "colour": (0.863, 0.078, 0.235,1.0),
2253 "exec": cxr_export_vmf
,
2254 "jobs": [(sceneinfo
,output_vmf
)]
2257 if settings
.comp_textures
:
2258 if len(image_jobs
) > 0:
2259 static
.JOBINFO
+= [{
2260 "title": "Textures",
2262 "colour": (1.000, 0.271, 0.000,1.0),
2263 "exec": compile_image
,
2267 game
= cxr_winepath( settings
.subdir
)
2269 '-game', game
, settings
.project_name
2273 if settings
.comp_models
:
2274 if len(model_jobs
) > 0:
2275 static
.JOBINFO
+= [{
2278 "colour": (1.000, 0.647, 0.000,1.0),
2279 "exec": cxr_export_modelsrc
,
2283 if len(qc_jobs
) > 0:
2284 static
.JOBINFO
+= [{
2285 "title": "StudioMDL",
2287 "colour": (1.000, 0.843, 0.000, 1.0),
2288 "exec": "studiomdl",
2289 "jobs": [[settings
[F
'exe_studiomdl']] + [\
2290 '-nop4', '-game', game
, qc
] for qc
in qc_jobs
],
2295 if settings
.comp_compile
:
2296 if not settings
.opt_vbsp
.startswith( 'disable' ):
2297 vbsp_opt
= settings
.opt_vbsp
.split()
2298 static
.JOBINFO
+= [{
2301 "colour": (0.678, 1.000, 0.184,1.0),
2303 "jobs": [[settings
[F
'exe_vbsp']] + vbsp_opt
+ args
],
2307 if not settings
.opt_vvis
.startswith( 'disable' ):
2308 vvis_opt
= settings
.opt_vvis
.split()
2309 static
.JOBINFO
+= [{
2312 "colour": (0.000, 1.000, 0.498,1.0),
2314 "jobs": [[settings
[F
'exe_vvis']] + vvis_opt
+ args
],
2318 if not settings
.opt_vrad
.startswith( 'disable' ):
2319 vrad_opt
= settings
.opt_vrad
.split()
2320 static
.JOBINFO
+= [{
2323 "colour": (0.125, 0.698, 0.667,1.0),
2325 "jobs": [[settings
[F
'exe_vrad']] + vrad_opt
+ args
],
2329 static
.JOBINFO
+= [{
2332 "colour": (0.118, 0.565, 1.000,1.0),
2333 "exec": cxr_patchmap
,
2334 "jobs": [(bsp_local
,bsp_remote
)]
2337 if settings
.comp_pack
:
2338 static
.JOBINFO
+= [{
2341 "colour": (0.541, 0.169, 0.886,1.0),
2343 "jobs": [[cxr_compiler_path("bspzip"), '-addlist', \
2344 cxr_winepath(bsp_remote
),
2345 cxr_winepath(packlist
),
2346 cxr_winepath(bsp_packed
) ]],
2350 if len(static
.JOBINFO
) == 0:
2351 return {'CANCELLED'}
2353 static
.USER_EXIT
=False
2354 static
.TIMER
=wm
.event_timer_add(0.1,window
=context
.window
)
2355 wm
.modal_handler_add(_
)
2357 cxr_jobs_update_graph( static
.JOBINFO
)
2359 return {'RUNNING_MODAL'}
2361 class CXR_RESET_HASHES(bpy
.types
.Operator
):
2362 bl_idname
="convexer.hash_reset"
2363 bl_label
="Reset asset hashes"
2365 def execute(_
,context
):
2366 for c
in bpy
.data
.collections
:
2367 c
.cxr_data
.last_hash
= F
"<RESET>{time.time()}"
2368 c
.cxr_data
.asset_id
=0
2370 for t
in bpy
.data
.images
:
2371 t
.cxr_data
.last_hash
= F
"<RESET>{time.time()}"
2372 t
.cxr_data
.asset_id
=0
2376 class CXR_COMPILE_MATERIAL(bpy
.types
.Operator
):
2377 bl_idname
="convexer.matcomp"
2378 bl_label
="Recompile Material"
2380 def execute(_
,context
):
2381 active_obj
= bpy
.context
.active_object
2382 active_mat
= active_obj
.active_material
2384 #TODO: reduce code dupe (L1663)
2385 for pair
in compile_material(active_mat
):
2390 if isinstance(prop
,bpy
.types
.Image
):
2392 if 'flags' in pdef
: flags
= pdef
['flags']
2393 prop
.cxr_data
.flags
= flags
2395 compile_image( prop
)
2397 settings
= bpy
.context
.scene
.cxr_data
2398 with
open(F
'{settings.subdir}/cfg/convexer_mat_update.cfg','w') as o
:
2399 o
.write(F
'mat_reloadmaterial {asset_name(active_mat)}')
2402 with
open(F
'{settings.subdir}/cfg/convexer.cfg','w') as o
:
2403 o
.write('sv_cheats 1\n')
2404 o
.write('mp_warmup_pausetimer 1\n')
2405 o
.write('bot_kick\n')
2406 o
.write('alias cxr_reload "exec convexer_mat_update"\n')
2411 # ------------------------------------------------------------------------------
2413 # Helper buttons for 3d toolbox view
2415 class CXR_VIEW3D( bpy
.types
.Panel
):
2416 bl_idname
= "VIEW3D_PT_convexer"
2417 bl_label
= "Convexer"
2418 bl_space_type
= 'VIEW_3D'
2419 bl_region_type
= 'UI'
2420 bl_category
= "Convexer"
2422 def draw(_
, context
):
2425 active_object
= context
.object
2426 if active_object
== None: return
2428 purpose
= cxr_object_purpose( active_object
)
2430 if purpose
[0] == None or purpose
[1] == None:
2431 usage_str
= "No purpose"
2433 if purpose
[1] == 'model':
2434 usage_str
= F
'mesh in {asset_name( purpose[0] )}.mdl'
2436 usage_str
= F
'{purpose[1]} in {purpose[0].name}'
2438 layout
.label(text
=F
"Currently editing:")
2440 box
.label(text
=usage_str
)
2442 if purpose
[1] == 'brush' or purpose
[1] == 'brush_entity':
2445 row
.operator("convexer.preview")
2449 row
.operator("convexer.reset")
2451 layout
.prop( bpy
.context
.scene
.cxr_data
, "dev_mdl" )
2452 layout
.operator( "convexer.model_load" )
2454 # Main scene properties interface, where all the settings go
2456 class CXR_INTERFACE(bpy
.types
.Panel
):
2458 bl_idname
="SCENE_PT_convexer"
2459 bl_space_type
='PROPERTIES'
2460 bl_region_type
='WINDOW'
2463 def draw(_
,context
):
2464 if CXR_GNU_LINUX
==1:
2465 _
.layout
.operator("convexer.reload")
2466 _
.layout
.operator("convexer.dev_test")
2467 _
.layout
.operator("convexer.fs_init")
2469 _
.layout
.operator("convexer.hash_reset")
2470 settings
= context
.scene
.cxr_data
2472 _
.layout
.prop(settings
, "scale_factor")
2473 _
.layout
.prop(settings
, "skybox_scale_factor")
2474 _
.layout
.prop(settings
, "skyname" )
2475 _
.layout
.prop(settings
, "lightmap_scale")
2476 _
.layout
.prop(settings
, "light_scale" )
2477 _
.layout
.prop(settings
, "image_quality" )
2479 box
= _
.layout
.box()
2481 box
.prop(settings
, "project_name")
2482 box
.prop(settings
, "subdir")
2484 box
= _
.layout
.box()
2485 box
.operator("convexer.detect_compilers")
2486 box
.prop(settings
, "exe_studiomdl")
2487 box
.prop(settings
, "exe_vbsp")
2488 box
.prop(settings
, "opt_vbsp")
2490 box
.prop(settings
, "exe_vvis")
2491 box
.prop(settings
, "opt_vvis")
2493 box
.prop(settings
, "exe_vrad")
2494 box
.prop(settings
, "opt_vrad")
2498 row
.prop(settings
,"comp_vmf")
2499 row
.prop(settings
,"comp_textures")
2500 row
.prop(settings
,"comp_models")
2501 row
.prop(settings
,"comp_compile")
2502 row
.prop(settings
,"comp_pack")
2504 text
= "Compile" if CXR_COMPILER_CHAIN
.TIMER
== None else "Cancel"
2507 row
.operator("convexer.chain", text
=text
)
2511 row
.operator("convexer.reset")
2512 if CXR_COMPILER_CHAIN
.TIMER
!= None:
2515 class CXR_MATERIAL_PANEL(bpy
.types
.Panel
):
2516 bl_label
="VMT Properties"
2517 bl_idname
="SCENE_PT_convexer_vmt"
2518 bl_space_type
='PROPERTIES'
2519 bl_region_type
='WINDOW'
2520 bl_context
="material"
2522 def draw(_
,context
):
2523 active_object
= bpy
.context
.active_object
2524 if active_object
== None: return
2526 active_material
= active_object
.active_material
2527 if active_material
== None: return
2529 properties
= active_material
.cxr_data
2530 info
= material_info( active_material
)
2532 _
.layout
.label(text
=F
"{info['name']} @{info['res'][0]}x{info['res'][1]}")
2533 row
= _
.layout
.row()
2534 row
.prop( properties
, "shader" )
2535 row
.operator( "convexer.matcomp" )
2537 #for xk in info: _.layout.label(text=F"{xk}:={info[xk]}")
2539 def _mtex( name
, img
, uiParent
):
2542 box
= uiParent
.box()
2543 box
.label( text
=F
'{name} "{img.filepath}"' )
2545 if ((x
& (x
- 1)) == 0):
2548 closest_diff
= 10000000
2550 dist
= abs((1 << i
)-x
)
2551 if dist
< closest_diff
:
2556 return 1 << (closest
+1)
2558 return 1 << (closest
-1)
2563 row
.prop( img
.cxr_data
, "export_res" )
2564 row
.prop( img
.cxr_data
, "fmt" )
2567 row
.prop( img
.cxr_data
, "mipmap" )
2568 row
.prop( img
.cxr_data
, "lod" )
2569 row
.prop( img
.cxr_data
, "clamp" )
2571 img
.cxr_data
.export_res
[0] = _p2( img
.cxr_data
.export_res
[0] )
2572 img
.cxr_data
.export_res
[1] = _p2( img
.cxr_data
.export_res
[1] )
2574 def _mview( layer
, uiParent
):
2578 if isinstance(layer
[decl
],dict): # $property definition
2580 ptype
= pdef
['type']
2586 if ('shaders' in pdef
) and \
2587 (properties
.shader
not in pdef
['shaders']):
2590 if ptype
== 'intrinsic':
2591 if decl
not in info
:
2596 if isinstance(pdef
[ch
],dict):
2597 if ptype
== 'ui' or ptype
== 'intrinsic':
2599 elif getattr(properties
,decl
) == pdef
['default']:
2602 thisnode
= uiParent
.box()
2606 thisnode
.label( text
=decl
)
2607 elif ptype
== 'intrinsic':
2608 if isinstance(info
[decl
], bpy
.types
.Image
):
2609 _mtex( decl
, info
[decl
], thisnode
)
2611 # hidden intrinsic value.
2612 # Means its a float array or something not an image
2613 thisnode
.label(text
=F
"-- hidden intrinsic '{decl}' --")
2615 thisnode
.prop(properties
,decl
)
2616 if expandview
: _mview(pdef
,thisnode
)
2618 _mview( cxr_shader_params
, _
.layout
)
2620 def cxr_entity_changeclass(_
,context
):
2621 active_object
= context
.active_object
2623 # Create ID properties
2625 classname
= cxr_custom_class(active_object
)
2627 if classname
in cxr_entities
:
2628 entdef
= cxr_entities
[classname
]
2630 kvs
= entdef
['keyvalues']
2631 if callable(kvs
): kvs
= kvs( {'object': active_object
} )
2637 if callable(kv
) or not isinstance(kv
,dict): continue
2639 if key
not in active_object
:
2640 active_object
[key
] = kv
['default']
2641 id_prop
= active_object
.id_properties_ui(key
)
2642 id_prop
.update(default
=kv
['default'])
2644 class CXR_ENTITY_PANEL(bpy
.types
.Panel
):
2645 bl_label
="Entity Config"
2646 bl_idname
="SCENE_PT_convexer_entity"
2647 bl_space_type
='PROPERTIES'
2648 bl_region_type
='WINDOW'
2651 def draw(_
,context
):
2652 active_object
= bpy
.context
.active_object
2654 if active_object
== None: return
2657 "scale": bpy
.context
.scene
.cxr_data
.scale_factor
,
2661 ecn
= cxr_intrinsic_classname( active_object
)
2662 classname
= cxr_custom_class( active_object
)
2665 if active_object
.type == 'MESH':
2666 _
.layout
.prop( active_object
.cxr_data
, 'brushclass' )
2667 else: _
.layout
.prop( active_object
.cxr_data
, 'classname' )
2669 _
.layout
.prop( active_object
.cxr_data
, 'visgroup' )
2670 _
.layout
.prop( active_object
.cxr_data
, 'lightmap_override' )
2672 if classname
== 'NONE':
2675 _
.layout
.label(text
=F
"<implementation defined ({ecn})>")
2676 _
.layout
.enabled
=False
2679 kvs
= cxr_entity_keyvalues( {
2680 "object": active_object
,
2681 "transform": default_context
,
2682 "classname": classname
2688 _
.layout
.prop( active_object
, F
'["cxrkv_{kv[0]}"]', text
=kv
[0])
2690 row
= _
.layout
.row()
2692 row
.label( text
=F
'{kv[0]}: {repr(kv[2])}' )
2694 _
.layout
.label( text
=F
"ERROR: NO CLASS DEFINITION" )
2696 class CXR_LIGHT_PANEL(bpy
.types
.Panel
):
2697 bl_label
= "Source Settings"
2698 bl_idname
= "LIGHT_PT_cxr"
2699 bl_space_type
= 'PROPERTIES'
2700 bl_region_type
= 'WINDOW'
2703 def draw(self
, context
):
2704 layout
= self
.layout
2705 scene
= context
.scene
2707 active_object
= bpy
.context
.active_object
2708 if active_object
== None: return
2710 if active_object
.type == 'LIGHT' or \
2711 active_object
.type == 'LIGHT_PROBE':
2713 properties
= active_object
.data
.cxr_data
2715 if active_object
.type == 'LIGHT':
2716 layout
.prop( properties
, "realtime" )
2717 elif active_object
.type == 'LIGHT_PROBE':
2718 layout
.prop( properties
, "size" )
2720 class CXR_COLLECTION_PANEL(bpy
.types
.Panel
):
2721 bl_label
= "Source Settings"
2722 bl_idname
= "COL_PT_cxr"
2723 bl_space_type
= 'PROPERTIES'
2724 bl_region_type
= 'WINDOW'
2725 bl_context
= "collection"
2727 def draw(self
, context
):
2728 layout
= self
.layout
2729 scene
= context
.scene
2731 active_collection
= bpy
.context
.collection
2733 if active_collection
!= None:
2734 layout
.prop( active_collection
.cxr_data
, "shadow_caster" )
2735 layout
.prop( active_collection
.cxr_data
, "texture_shadows" )
2736 layout
.prop( active_collection
.cxr_data
, "preserve_order" )
2737 layout
.prop( active_collection
.cxr_data
, "surfaceprop" )
2738 layout
.prop( active_collection
.cxr_data
, "visgroup" )
2741 # ------------------------------------------------------------------------------
2743 class CXR_IMAGE_SETTINGS(bpy
.types
.PropertyGroup
):
2744 export_res
: bpy
.props
.IntVectorProperty(
2746 description
="Texture Export Resolution",
2752 fmt
: bpy
.props
.EnumProperty(
2755 ('DXT1', "DXT1", "BC1 compressed", '', 0),
2756 ('DXT5', "DXT5", "BC3 compressed", '', 1),
2757 ('RGB', "RGB", "Uncompressed", '', 2),
2758 ('RGBA', "RGBA", "Uncompressed (with alpha)", '', 3)
2760 description
="Image format",
2763 last_hash
: bpy
.props
.StringProperty( name
="" )
2764 asset_id
: bpy
.props
.IntProperty(name
="intl_assetid",default
=0)
2766 mipmap
: bpy
.props
.BoolProperty(name
="MIP",default
=True)
2767 lod
: bpy
.props
.BoolProperty(name
="LOD",default
=True)
2768 clamp
: bpy
.props
.BoolProperty(name
="CLAMP",default
=False)
2769 flags
: bpy
.props
.IntProperty(name
="flags",default
=0)
2771 class CXR_LIGHT_SETTINGS(bpy
.types
.PropertyGroup
):
2772 realtime
: bpy
.props
.BoolProperty(name
="Realtime Light", default
=True)
2774 class CXR_CUBEMAP_SETTINGS(bpy
.types
.PropertyGroup
):
2775 size
: bpy
.props
.EnumProperty(
2778 ('1',"1x1",'','',0),
2779 ('2',"2x2",'','',1),
2780 ('3',"4x4",'','',2),
2781 ('4',"8x8",'','',3),
2782 ('5',"16x16",'','',4),
2783 ('6',"32x32",'','',5),
2784 ('7',"64x64",'','',6),
2785 ('8',"128x128",'','',7),
2786 ('9',"256x256",'','',8)
2788 description
="Texture resolution",
2791 class CXR_ENTITY_SETTINGS(bpy
.types
.PropertyGroup
):
2792 entity
: bpy
.props
.BoolProperty(name
="")
2794 enum_pointents
= [('NONE',"None","")]
2795 enum_brushents
= [('NONE',"None","")]
2797 for classname
in cxr_entities
:
2798 entdef
= cxr_entities
[classname
]
2799 if 'allow' in entdef
:
2800 itm
= [(classname
, classname
, "")]
2801 if 'EMPTY' in entdef
['allow']: enum_pointents
+= itm
2802 else: enum_brushents
+= itm
2804 classname
: bpy
.props
.EnumProperty(items
=enum_pointents
, name
="Class", \
2805 update
=cxr_entity_changeclass
, default
='NONE' )
2807 brushclass
: bpy
.props
.EnumProperty(items
=enum_brushents
, name
="Class", \
2808 update
=cxr_entity_changeclass
, default
='NONE' )
2810 enum_classes
= [('0',"None","")]
2811 for i
, vg
in enumerate(cxr_visgroups
):
2812 enum_classes
+= [(str(i
+1),vg
,"")]
2813 visgroup
: bpy
.props
.EnumProperty(name
="visgroup",items
=enum_classes
,default
=0)
2814 lightmap_override
: bpy
.props
.IntProperty(name
="Lightmap Override",default
=0)
2816 class CXR_MODEL_SETTINGS(bpy
.types
.PropertyGroup
):
2817 last_hash
: bpy
.props
.StringProperty( name
="" )
2818 asset_id
: bpy
.props
.IntProperty(name
="vmf_settings",default
=0)
2819 shadow_caster
: bpy
.props
.BoolProperty( name
="Shadow caster", default
=True )
2820 texture_shadows
: bpy
.props
.BoolProperty( name
="Texture Shadows", default
=False )
2821 preserve_order
: bpy
.props
.BoolProperty( name
="Preserve Order", default
=False )
2822 surfaceprop
: bpy
.props
.StringProperty( name
="Suface prop",default
="default" )
2824 enum_classes
= [('0',"None","")]
2825 for i
, vg
in enumerate(cxr_visgroups
):
2826 enum_classes
+= [(str(i
+1),vg
,"")]
2827 visgroup
: bpy
.props
.EnumProperty(name
="visgroup",items
=enum_classes
,default
=0)
2829 class CXR_SCENE_SETTINGS(bpy
.types
.PropertyGroup
):
2830 project_name
: bpy
.props
.StringProperty( name
="Project Name" )
2831 subdir
: bpy
.props
.StringProperty( name
="../csgo/ folder" )
2833 exe_studiomdl
: bpy
.props
.StringProperty( name
="studiomdl" )
2834 exe_vbsp
: bpy
.props
.StringProperty( name
="vbsp" )
2835 opt_vbsp
: bpy
.props
.StringProperty( name
="args" )
2836 exe_vvis
: bpy
.props
.StringProperty( name
="vvis" )
2837 opt_vvis
: bpy
.props
.StringProperty( name
="args" )
2838 exe_vrad
: bpy
.props
.StringProperty( name
="vrad" )
2839 opt_vrad
: bpy
.props
.StringProperty( name
="args", \
2840 default
="-reflectivityScale 0.35 -aoscale 1.4 -final -textureshadows -hdr -StaticPropLighting -StaticPropPolys" )
2842 scale_factor
: bpy
.props
.FloatProperty( name
="VMF Scale factor", \
2843 default
=32.0,min=1.0)
2844 skybox_scale_factor
: bpy
.props
.FloatProperty( name
="Sky Scale factor", \
2845 default
=1.0,min=0.01)
2846 skyname
: bpy
.props
.StringProperty(name
="Skyname",default
="sky_csgo_night02b")
2847 skybox_offset
: bpy
.props
.FloatProperty(name
="Sky offset",default
=-4096.0)
2848 light_scale
: bpy
.props
.FloatProperty(name
="Light Scale",default
=1.0/5.0)
2849 include_names
: bpy
.props
.BoolProperty(name
="Append original file names",\
2851 lightmap_scale
: bpy
.props
.IntProperty(name
="Global Lightmap Scale",\
2853 image_quality
: bpy
.props
.IntProperty(name
="Texture Quality (0-18)",\
2854 default
=8, min=0, max=18 )
2856 comp_vmf
: bpy
.props
.BoolProperty(name
="VMF",default
=True)
2857 comp_models
: bpy
.props
.BoolProperty(name
="Models",default
=True)
2858 comp_textures
: bpy
.props
.BoolProperty(name
="Textures",default
=True)
2859 comp_compile
: bpy
.props
.BoolProperty(name
="Compile",default
=True)
2860 comp_pack
: bpy
.props
.BoolProperty(name
="Pack",default
=False)
2862 dev_mdl
: bpy
.props
.StringProperty(name
="Model",default
="")
2864 classes
= [ CXR_RELOAD
, CXR_DEV_OPERATOR
, CXR_INTERFACE
, \
2865 CXR_MATERIAL_PANEL
, CXR_IMAGE_SETTINGS
,\
2866 CXR_MODEL_SETTINGS
, CXR_ENTITY_SETTINGS
, CXR_CUBEMAP_SETTINGS
,\
2867 CXR_LIGHT_SETTINGS
, CXR_SCENE_SETTINGS
, CXR_DETECT_COMPILERS
,\
2868 CXR_ENTITY_PANEL
, CXR_LIGHT_PANEL
, CXR_PREVIEW_OPERATOR
,\
2869 CXR_VIEW3D
, CXR_COMPILER_CHAIN
, CXR_RESET_HASHES
,\
2870 CXR_COMPILE_MATERIAL
, CXR_COLLECTION_PANEL
, CXR_RESET
, \
2871 CXR_INIT_FS_OPERATOR
, CXR_LOAD_MODEL_OPERATOR
]
2873 vmt_param_dynamic_class
= None
2876 global cxr_view_draw_handler
, vmt_param_dynamic_class
, cxr_ui_handler
2879 bpy
.utils
.register_class(c
)
2881 # Build dynamic VMT properties class defined by cxr_shader_params
2882 annotations_dict
= {}
2884 def _dvmt_propogate(layer
):
2885 nonlocal annotations_dict
2888 if isinstance(layer
[decl
],dict): # $property definition
2892 if pdef
['type'] == 'bool':
2893 prop
= bpy
.props
.BoolProperty(\
2894 name
= pdef
['name'],\
2895 default
= pdef
['default'])
2897 elif pdef
['type'] == 'float':
2898 prop
= bpy
.props
.FloatProperty(\
2899 name
= pdef
['name'],\
2900 default
= pdef
['default'])
2902 elif pdef
['type'] == 'vector':
2903 if 'subtype' in pdef
:
2904 prop
= bpy
.props
.FloatVectorProperty(\
2905 name
= pdef
['name'],\
2906 subtype
= pdef
['subtype'],\
2907 default
= pdef
['default'],\
2908 size
= len(pdef
['default']))
2910 prop
= bpy
.props
.FloatVectorProperty(\
2911 name
= pdef
['name'],\
2912 default
= pdef
['default'],\
2913 size
= len(pdef
['default']))
2915 elif pdef
['type'] == 'string':
2916 prop
= bpy
.props
.StringProperty(\
2917 name
= pdef
['name'],\
2918 default
= pdef
['default'])
2920 elif pdef
['type'] == 'enum':
2921 prop
= bpy
.props
.EnumProperty(\
2922 name
= pdef
['name'],\
2923 items
= pdef
['items'],\
2924 default
= pdef
['default'])
2927 annotations_dict
[decl
] = prop
2929 # Recurse into sub-definitions
2930 _dvmt_propogate(pdef
)
2932 annotations_dict
["shader"] = bpy
.props
.EnumProperty(\
2935 cxr_shaders
[_
]["name"],\
2936 '') for _
in cxr_shaders
],\
2937 default
= next(iter(cxr_shaders
)))
2939 annotations_dict
["asset_id"] = bpy
.props
.IntProperty(name
="intl_assetid",\
2942 _dvmt_propogate( cxr_shader_params
)
2943 vmt_param_dynamic_class
= type(
2945 (bpy
.types
.PropertyGroup
,),{
2946 "__annotations__": annotations_dict
2950 bpy
.utils
.register_class( vmt_param_dynamic_class
)
2953 bpy
.types
.Material
.cxr_data
= \
2954 bpy
.props
.PointerProperty(type=vmt_param_dynamic_class
)
2955 bpy
.types
.Image
.cxr_data
= \
2956 bpy
.props
.PointerProperty(type=CXR_IMAGE_SETTINGS
)
2957 bpy
.types
.Object
.cxr_data
= \
2958 bpy
.props
.PointerProperty(type=CXR_ENTITY_SETTINGS
)
2959 bpy
.types
.Collection
.cxr_data
= \
2960 bpy
.props
.PointerProperty(type=CXR_MODEL_SETTINGS
)
2961 bpy
.types
.Light
.cxr_data
= \
2962 bpy
.props
.PointerProperty(type=CXR_LIGHT_SETTINGS
)
2963 bpy
.types
.LightProbe
.cxr_data
= \
2964 bpy
.props
.PointerProperty(type=CXR_CUBEMAP_SETTINGS
)
2965 bpy
.types
.Scene
.cxr_data
= \
2966 bpy
.props
.PointerProperty(type=CXR_SCENE_SETTINGS
)
2968 # CXR Scene settings
2971 cxr_view_draw_handler
= bpy
.types
.SpaceView3D
.draw_handler_add(\
2972 cxr_draw
,(),'WINDOW','POST_VIEW')
2974 cxr_ui_handler
= bpy
.types
.SpaceView3D
.draw_handler_add(\
2975 cxr_ui
,(None,None),'WINDOW','POST_PIXEL')
2977 bpy
.app
.handlers
.load_post
.append(cxr_on_load
)
2978 bpy
.app
.handlers
.depsgraph_update_post
.append(cxr_dgraph_update
)
2981 global cxr_view_draw_handler
, vmt_param_dynamic_class
, cxr_ui_handler
2983 bpy
.utils
.unregister_class( vmt_param_dynamic_class
)
2985 bpy
.utils
.unregister_class(c
)
2987 bpy
.app
.handlers
.depsgraph_update_post
.remove(cxr_dgraph_update
)
2988 bpy
.app
.handlers
.load_post
.remove(cxr_on_load
)
2990 bpy
.types
.SpaceView3D
.draw_handler_remove(cxr_view_draw_handler
,'WINDOW')
2991 bpy
.types
.SpaceView3D
.draw_handler_remove(cxr_ui_handler
,'WINDOW')