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
57 cxr_view_shader
= gpu
.shader
.from_builtin('3D_SMOOTH_COLOR')
59 cxr_ui_shader
= gpu
.types
.GPUShader("""
60 uniform mat4 ModelViewProjectionMatrix;
70 gl_Position = ModelViewProjectionMatrix * vec4(aPos.x*scale,aPos.y, 0.0, 1.0);
83 cxr_mdl_shader
= gpu
.types
.GPUShader("""
84 uniform mat4 modelMatrix;
85 uniform mat4 viewProjectionMatrix;
95 vec4 pWorldPos = modelMatrix * vec4(aPos, 1.0);
96 vec3 worldPos = pWorldPos.xyz;
98 gl_Position = viewProjectionMatrix * pWorldPos;
99 lNormal = aNormal; //mat3(transpose(inverse(modelMatrix))) * aNormal;
105 uniform vec3 testLightDir;
112 float SoftenCosineTerm( float flDot )
114 return ( flDot + ( flDot * flDot ) ) * 0.5;
117 vec3 DiffuseTerm( vec3 worldNormal, vec3 lightDir )
120 float NDotL = dot( worldNormal, lightDir );
122 fResult = clamp( NDotL, 0.0, 1.0 );
123 fResult = SoftenCosineTerm( fResult );
125 vec3 fOut = vec3( fResult, fResult, fResult );
129 vec3 PixelShaderDoLightingLinear( vec3 worldPos, vec3 worldNormal )
131 vec3 linearColor = vec3(0.0,0.0,0.0);
132 linearColor += DiffuseTerm( worldNormal, testLightDir );
137 vec3 LinearToGamma( vec3 f3linear )
139 return pow( f3linear, vec3(1.0 / 2.2) );
144 vec3 tangentSpaceNormal = vec3( 0.0, 0.0, 1.0 );
145 vec4 normalTexel = vec4(1.0,1.0,1.0,1.0);
146 vec4 baseColor = colour;
148 //normalTexel = tex2D( BumpmapSampler, i.detailOrBumpTexCoord );
149 //tangentSpaceNormal = 2.0 * normalTexel - 1.0;
151 vec3 diffuseLighting = vec3( 1.0, 1.0, 1.0 );
153 vec3 staticLightingColor = vec3( 0.0, 0.0, 0.0 );
154 diffuseLighting = PixelShaderDoLightingLinear( lPos, lNormal );
156 // multiply by .5 since we want a 50% (in gamma space) reflective surface)
157 diffuseLighting *= pow( 0.5, 2.2 );
159 vec3 result = diffuseLighting * baseColor.xyz;
161 FragColor = vec4( LinearToGamma(result), 1.0 );
167 def cxr_ui(_
,context
):
168 global cxr_jobs_batch
, cxr_ui_shader
, cxr_jobs_inf
, cxr_error_inf
170 w
= gpu
.state
.viewport_get()[2]
172 cxr_ui_shader
.uniform_float( "scale", w
)
174 if cxr_error_inf
!= None:
177 if isinstance(cxr_error_inf
[1],list):
178 err_begin
+= 20*(len(cxr_error_inf
[1])-1)
180 blf
.position(0,2,err_begin
,0)
182 blf
.color(0, 1.0,0.2,0.2,0.9)
183 blf
.draw(0,cxr_error_inf
[0])
186 blf
.color(0, 1.0,1.0,1.0,1.0)
188 if isinstance(cxr_error_inf
[1],list):
189 for i
,inf
in enumerate(cxr_error_inf
[1]):
190 blf
.position(0,2,err_begin
-30-i
*20,0)
193 blf
.position(0,2,err_begin
-30,0)
194 blf
.draw(0,cxr_error_inf
[1])
196 elif cxr_jobs_batch
!= None:
197 gpu
.state
.blend_set('ALPHA')
198 cxr_jobs_batch
.draw(cxr_ui_shader
)
200 blf
.position(0,2,50,0)
202 blf
.color(0,1.0,1.0,1.0,1.0)
203 blf
.draw(0,"Compiling")
205 for ji
in cxr_jobs_inf
:
206 blf
.position(0,ji
[0]*w
,35,0)
212 for ln
in reversed(CXR_COMPILER_CHAIN
.LOG
[-25:]):
213 blf
.position(0,2,py
,0)
217 # Something is off with TIMER,
218 # this forces the viewport to redraw before we can continue with our
221 CXR_COMPILER_CHAIN
.WAIT_REDRAW
= False
224 global cxr_view_shader
, cxr_view_mesh
, cxr_view_lines
, cxr_mdl_shader
,\
227 cxr_view_shader
.bind()
229 gpu
.state
.depth_mask_set(False)
230 gpu
.state
.line_width_set(1.5)
231 gpu
.state
.face_culling_set('BACK')
232 gpu
.state
.depth_test_set('NONE')
233 gpu
.state
.blend_set('ALPHA')
235 if cxr_view_lines
!= None:
236 cxr_view_lines
.draw( cxr_view_shader
)
238 if cxr_view_mesh
!= None:
239 gpu
.state
.depth_test_set('LESS_EQUAL')
240 gpu
.state
.blend_set('ADDITIVE')
242 cxr_view_mesh
.draw( cxr_view_shader
)
244 if cxr_mdl_mesh
!= None:
245 gpu
.state
.depth_mask_set(True)
246 gpu
.state
.depth_test_set('LESS_EQUAL')
247 gpu
.state
.face_culling_set('FRONT')
248 gpu
.state
.blend_set('NONE')
249 cxr_mdl_shader
.bind()
250 cxr_mdl_shader
.uniform_float('colour',(0.5,0.5,0.5,1.0))
251 cxr_mdl_shader
.uniform_float("viewProjectionMatrix", \
252 bpy
.context
.region_data
.perspective_matrix
)
254 testmdl
= bpy
.context
.scene
.objects
['target']
255 light
= bpy
.context
.scene
.objects
['point']
256 relative
= light
.location
- testmdl
.location
259 cxr_mdl_shader
.uniform_float("modelMatrix", testmdl
.matrix_world
)
260 cxr_mdl_shader
.uniform_float("testLightDir", relative
)
263 cxr_mdl_mesh
.draw( cxr_mdl_shader
)
265 def cxr_jobs_update_graph(jobs
):
266 global cxr_jobs_batch
, cxr_ui_shader
, cxr_jobs_inf
276 total_width
+= sys
['w']
285 colour
= sys
['colour']
286 colourwait
= (colour
[0],colour
[1],colour
[2],0.4)
287 colourrun
= (colour
[0]*1.5,colour
[1]*1.5,colour
[2]*1.5,0.5)
288 colourdone
= (colour
[0],colour
[1],colour
[2],1.0)
291 sfsub
= (1.0/(len(jobs
)))*w
295 if j
== None: colour
= colourdone
296 else: colour
= colourwait
298 px
= (cur
+ (i
)*sfsub
) * sf
299 px1
= (cur
+ (i
+1.0)*sfsub
) * sf
302 verts
+= [(px
,0), (px
, h
), (px1
, 0.0), (px1
,h
)]
303 colours
+= [colour
,colour
,colour
,colour
]
304 indices
+= [(ci
+0,ci
+2,ci
+3),(ci
+0,ci
+3,ci
+1)]
307 cxr_jobs_inf
+= [((sf
*cur
), sys
['title'])]
310 cxr_jobs_batch
= batch_for_shader(
311 cxr_ui_shader
, 'TRIS',
312 { "aPos": verts
, "aColour": colours
},
316 # view_layer.update() doesnt seem to work,
317 # tag_redraw() seems to have broken
318 # therefore, change a property
320 ob
= bpy
.context
.scene
.objects
[0]
321 ob
.hide_render
= ob
.hide_render
323 # the 'real' way to refresh the scene
324 for area
in bpy
.context
.window
.screen
.areas
:
325 if area
.type == 'view_3d':
329 # ------------------------------------------------------------------------------
332 # dlclose for reloading modules manually
334 libc_dlclose
= cdll
.LoadLibrary(None).dlclose
335 libc_dlclose
.argtypes
= [c_void_p
]
337 # wrapper for ctypes binding
339 def __init__(_
,name
,argtypes
,restype
):
341 _
.argtypes
= argtypes
346 _
.call
= getattr(so
,_
.name
)
347 _
.call
.argtypes
= _
.argtypes
349 if _
.restype
!= None:
350 _
.call
.restype
= _
.restype
353 # ------------------------------------------------------------------------------
357 # Structure definitions
359 class cxr_edge(Structure
):
360 _fields_
= [("i0",c_int32
),
362 ("freestyle",c_int32
),
365 class cxr_static_loop(Structure
):
366 _fields_
= [("index",c_int32
),
367 ("edge_index",c_int32
),
371 class cxr_polygon(Structure
):
372 _fields_
= [("loop_start",c_int32
),
373 ("loop_total",c_int32
),
374 ("normal",c_double
* 3),
375 ("center",c_double
* 3),
376 ("material_id",c_int32
)]
378 class cxr_material(Structure
):
379 _fields_
= [("res",c_int32
* 2),
382 class cxr_static_mesh(Structure
):
383 _fields_
= [("vertices",POINTER(c_double
* 3)),
384 ("edges",POINTER(cxr_edge
)),
385 ("loops",POINTER(cxr_static_loop
)),
386 ("polys",POINTER(cxr_polygon
)),
387 ("materials",POINTER(cxr_material
)),
389 ("poly_count",c_int32
),
390 ("vertex_count",c_int32
),
391 ("edge_count",c_int32
),
392 ("loop_count",c_int32
),
393 ("material_count",c_int32
)]
395 class cxr_tri_mesh(Structure
):
396 _fields_
= [("vertices",POINTER(c_double
*3)),
397 ("normals",POINTER(c_double
*3)),
398 ("uvs",POINTER(c_double
*2)),
399 ("colours",POINTER(c_double
*4)),
400 ("indices",POINTER(c_int32
)),
401 ("indices_count",c_int32
),
402 ("vertex_count",c_int32
)]
404 class cxr_visgroup(Structure
):
405 _fields_
= [("name",c_char_p
)]
407 class cxr_vmf_context(Structure
):
408 _fields_
= [("mapversion",c_int32
),
409 ("skyname",c_char_p
),
410 ("detailvbsp",c_char_p
),
411 ("detailmaterial",c_char_p
),
412 ("visgroups",POINTER(cxr_visgroup
)),
413 ("visgroup_count",c_int32
),
415 ("offset",c_double
*3),
416 ("lightmap_scale",c_int32
),
417 ("visgroupid",c_int32
),
418 ("brush_count",c_int32
),
419 ("entity_count",c_int32
),
420 ("face_count",c_int32
)]
422 # Convert blenders mesh format into CXR's static format (they are very similar)
424 def mesh_cxr_format(obj
):
427 if bpy
.context
.active_object
!= None:
428 orig_state
= obj
.mode
429 if orig_state
!= 'OBJECT':
430 bpy
.ops
.object.mode_set(mode
='OBJECT')
432 dgraph
= bpy
.context
.evaluated_depsgraph_get()
433 data
= obj
.evaluated_get(dgraph
).data
435 _
,mtx_rot
,_
= obj
.matrix_world
.decompose()
437 mesh
= cxr_static_mesh()
439 vertex_data
= ((c_double
*3)*len(data
.vertices
))()
440 for i
, vert
in enumerate(data
.vertices
):
441 v
= obj
.matrix_world
@ vert
.co
442 vertex_data
[i
][0] = c_double(v
[0])
443 vertex_data
[i
][1] = c_double(v
[1])
444 vertex_data
[i
][2] = c_double(v
[2])
446 loop_data
= (cxr_static_loop
*len(data
.loops
))()
447 polygon_data
= (cxr_polygon
*len(data
.polygons
))()
449 for i
, poly
in enumerate(data
.polygons
):
450 loop_start
= poly
.loop_start
451 loop_end
= poly
.loop_start
+ poly
.loop_total
452 for loop_index
in range(loop_start
, loop_end
):
453 loop
= data
.loops
[loop_index
]
454 loop_data
[loop_index
].index
= loop
.vertex_index
455 loop_data
[loop_index
].edge_index
= loop
.edge_index
458 uv
= data
.uv_layers
.active
.data
[loop_index
].uv
459 loop_data
[loop_index
].uv
[0] = c_double(uv
[0])
460 loop_data
[loop_index
].uv
[1] = c_double(uv
[1])
462 loop_data
[loop_index
].uv
[0] = c_double(0.0)
463 loop_data
[loop_index
].uv
[1] = c_double(0.0)
465 if data
.vertex_colors
:
466 alpha
= data
.vertex_colors
.active
.data
[loop_index
].color
[0]
470 loop_data
[loop_index
].alpha
= alpha
472 center
= obj
.matrix_world
@ poly
.center
473 normal
= mtx_rot
@ poly
.normal
475 polygon_data
[i
].loop_start
= poly
.loop_start
476 polygon_data
[i
].loop_total
= poly
.loop_total
477 polygon_data
[i
].normal
[0] = normal
[0]
478 polygon_data
[i
].normal
[1] = normal
[1]
479 polygon_data
[i
].normal
[2] = normal
[2]
480 polygon_data
[i
].center
[0] = center
[0]
481 polygon_data
[i
].center
[1] = center
[1]
482 polygon_data
[i
].center
[2] = center
[2]
483 polygon_data
[i
].material_id
= poly
.material_index
485 edge_data
= (cxr_edge
*len(data
.edges
))()
487 for i
, edge
in enumerate(data
.edges
):
488 edge_data
[i
].i0
= edge
.vertices
[0]
489 edge_data
[i
].i1
= edge
.vertices
[1]
490 edge_data
[i
].freestyle
= edge
.use_freestyle_mark
491 edge_data
[i
].sharp
= edge
.use_edge_sharp
493 material_data
= (cxr_material
*len(obj
.material_slots
))()
495 for i
, ms
in enumerate(obj
.material_slots
):
496 inf
= material_info(ms
.material
)
497 material_data
[i
].res
[0] = inf
['res'][0]
498 material_data
[i
].res
[1] = inf
['res'][1]
499 material_data
[i
].name
= inf
['name'].encode('utf-8')
501 mesh
.edges
= cast(edge_data
, POINTER(cxr_edge
))
502 mesh
.vertices
= cast(vertex_data
, POINTER(c_double
*3))
503 mesh
.loops
= cast(loop_data
,POINTER(cxr_static_loop
))
504 mesh
.polys
= cast(polygon_data
, POINTER(cxr_polygon
))
505 mesh
.materials
= cast(material_data
, POINTER(cxr_material
))
507 mesh
.poly_count
= len(data
.polygons
)
508 mesh
.vertex_count
= len(data
.vertices
)
509 mesh
.edge_count
= len(data
.edges
)
510 mesh
.loop_count
= len(data
.loops
)
511 mesh
.material_count
= len(obj
.material_slots
)
513 if orig_state
!= None:
514 bpy
.ops
.object.mode_set(mode
=orig_state
)
518 # Callback ctypes indirection things.. not really sure.
519 c_libcxr_log_callback
= None
520 c_libcxr_line_callback
= None
523 # -------------------------------------------------------------
524 libcxr_decompose
= extern( "cxr_decompose",
525 [POINTER(cxr_static_mesh
), POINTER(c_int32
)],
528 libcxr_free_world
= extern( "cxr_free_world",
532 libcxr_write_test_data
= extern( "cxr_write_test_data",
533 [POINTER(cxr_static_mesh
)],
536 libcxr_world_preview
= extern( "cxr_world_preview",
538 POINTER(cxr_tri_mesh
)
540 libcxr_free_tri_mesh
= extern( "cxr_free_tri_mesh",
544 libcxr_begin_vmf
= extern( "cxr_begin_vmf",
545 [POINTER(cxr_vmf_context
), c_void_p
],
548 libcxr_vmf_begin_entities
= extern( "cxr_vmf_begin_entities",
549 [POINTER(cxr_vmf_context
), c_void_p
],
552 libcxr_push_world_vmf
= extern("cxr_push_world_vmf",
553 [c_void_p
,POINTER(cxr_vmf_context
),c_void_p
],
556 libcxr_end_vmf
= extern( "cxr_end_vmf",
557 [POINTER(cxr_vmf_context
),c_void_p
],
561 # VDF + with open wrapper
562 libcxr_vdf_open
= extern( "cxr_vdf_open", [c_char_p
], c_void_p
)
563 libcxr_vdf_close
= extern( "cxr_vdf_close", [c_void_p
], None )
564 libcxr_vdf_put
= extern( "cxr_vdf_put", [c_void_p
,c_char_p
], None )
565 libcxr_vdf_node
= extern( "cxr_vdf_node", [c_void_p
,c_char_p
], None )
566 libcxr_vdf_edon
= extern( "cxr_vdf_edon", [c_void_p
], None )
567 libcxr_vdf_kv
= extern( "cxr_vdf_kv", [c_void_p
,c_char_p
,c_char_p
], None )
569 class vdf_structure():
570 def __init__(_
,path
):
573 _
.fp
= libcxr_vdf_open
.call( _
.path
.encode('utf-8') )
575 print( F
"Could not open file {_.path}" )
578 def __exit__(_
,type,value
,traceback
):
580 libcxr_vdf_close
.call(_
.fp
)
582 libcxr_vdf_put
.call(_
.fp
, s
.encode('utf-8') )
584 libcxr_vdf_node
.call(_
.fp
, name
.encode('utf-8') )
586 libcxr_vdf_edon
.call(_
.fp
)
588 libcxr_vdf_kv
.call(_
.fp
, k
.encode('utf-8'), v
.encode('utf-8'))
591 libcxr_lightpatch_bsp
= extern( "cxr_lightpatch_bsp", [c_char_p
], None )
593 # Binary file formats and FS
594 libcxr_fs_set_gameinfo
= extern( "cxr_fs_set_gameinfo", [c_char_p
], c_int32
)
595 libcxr_fs_exit
= extern( "cxr_fs_exit", [], None )
596 libcxr_fs_get
= extern( "cxr_fs_get", [c_char_p
], c_char_p
)
597 libcxr_load_mdl
= extern( "cxr_load_mdl", [c_char_p
], POINTER(cxr_tri_mesh
) )
599 libcxr_funcs
= [ libcxr_decompose
, libcxr_free_world
, libcxr_begin_vmf
, \
600 libcxr_vmf_begin_entities
, libcxr_push_world_vmf
, \
601 libcxr_end_vmf
, libcxr_vdf_open
, libcxr_vdf_close
, \
602 libcxr_vdf_put
, libcxr_vdf_node
, libcxr_vdf_edon
,
603 libcxr_vdf_kv
, libcxr_lightpatch_bsp
, libcxr_write_test_data
,\
604 libcxr_world_preview
, libcxr_free_tri_mesh
, \
605 libcxr_fs_set_gameinfo
, libcxr_fs_exit
, libcxr_fs_get
, \
609 def libcxr_log_callback(logStr
):
610 print( F
"{logStr.decode('utf-8')}",end
='' )
612 cxr_line_positions
= None
613 cxr_line_colours
= None
615 def cxr_reset_lines():
616 global cxr_line_positions
, cxr_line_colours
618 cxr_line_positions
= []
619 cxr_line_colours
= []
621 def cxr_batch_lines():
622 global cxr_line_positions
, cxr_line_colours
, cxr_view_shader
, cxr_view_lines
624 cxr_view_lines
= batch_for_shader(\
625 cxr_view_shader
, 'LINES',\
626 { "pos": cxr_line_positions
, "color": cxr_line_colours
})
628 def libcxr_line_callback( p0
,p1
,colour
):
629 global cxr_line_colours
, cxr_line_positions
631 cxr_line_positions
+= [(p0
[0],p0
[1],p0
[2])]
632 cxr_line_positions
+= [(p1
[0],p1
[1],p1
[2])]
633 cxr_line_colours
+= [(colour
[0],colour
[1],colour
[2],colour
[3])]
634 cxr_line_colours
+= [(colour
[0],colour
[1],colour
[2],colour
[3])]
637 global cxr_jobs_inf
, cxr_jobs_batch
, cxr_error_inf
, cxr_view_mesh
639 cxr_jobs_batch
= None
649 # ------------------------------------------------------------------------------
654 NBVTF_IMAGE_FORMAT_ABGR8888
= 1
655 NBVTF_IMAGE_FORMAT_BGR888
= 3
656 NBVTF_IMAGE_FORMAT_DXT1
= 13
657 NBVTF_IMAGE_FORMAT_DXT5
= 15
658 NBVTF_TEXTUREFLAGS_CLAMPS
= 0x00000004
659 NBVTF_TEXTUREFLAGS_CLAMPT
= 0x00000008
660 NBVTF_TEXTUREFLAGS_NORMAL
= 0x00000080
661 NBVTF_TEXTUREFLAGS_NOMIP
= 0x00000100
662 NBVTF_TEXTUREFLAGS_NOLOD
= 0x00000200
664 libnbvtf_convert
= extern( "nbvtf_convert", \
665 [c_char_p
,c_int32
,c_int32
,c_int32
,c_int32
,c_int32
,c_uint32
,c_char_p
], \
668 libnbvtf_init
= extern( "nbvtf_init", [], None )
669 libnbvtf_funcs
= [ libnbvtf_convert
, libnbvtf_init
]
672 # --------------------------
675 global libcxr
, libnbvtf
, libcxr_funcs
, libnbvtf_funcs
677 # Unload libraries if existing
678 def _reload( lib
, path
):
681 _handle
= lib
._handle
682 for i
in range(10): libc_dlclose( _handle
)
686 libpath
= F
'{os.path.dirname(__file__)}/{path}{CXR_SHARED_EXT}'
687 return cdll
.LoadLibrary( libpath
)
689 libnbvtf
= _reload( libnbvtf
, "libnbvtf" )
690 libcxr
= _reload( libcxr
, "libcxr" )
692 for fd
in libnbvtf_funcs
:
693 fd
.loadfrom( libnbvtf
)
696 for fd
in libcxr_funcs
:
697 fd
.loadfrom( libcxr
)
700 global c_libcxr_log_callback
, c_libcxr_line_callback
702 LOG_FUNCTION_TYPE
= CFUNCTYPE(None,c_char_p
)
703 c_libcxr_log_callback
= LOG_FUNCTION_TYPE(libcxr_log_callback
)
705 LINE_FUNCTION_TYPE
= CFUNCTYPE(None,\
706 POINTER(c_double
), POINTER(c_double
), POINTER(c_double
))
707 c_libcxr_line_callback
= LINE_FUNCTION_TYPE(libcxr_line_callback
)
709 libcxr
.cxr_set_log_function(cast(c_libcxr_log_callback
,c_void_p
))
710 libcxr
.cxr_set_line_function(cast(c_libcxr_line_callback
,c_void_p
))
712 build_time
= c_char_p
.in_dll(libcxr
,'cxr_build_time')
713 print( F
"libcxr build time: {build_time.value}" )
718 # ------------------------------------------------------------------------------
720 # Standard entity functions, think of like base.fgd
722 def cxr_get_origin(context
):
723 return context
['object'].location
* context
['transform']['scale'] + \
724 mathutils
.Vector(context
['transform']['offset'])
726 def cxr_get_angles(context
):
727 obj
= context
['object']
728 euler
= [ a
*57.295779513 for a
in obj
.rotation_euler
]
735 def cxr_baseclass(classes
, other
):
738 base
.update(x
.copy())
741 def ent_soundscape(context
):
742 obj
= context
['object']
743 kvs
= cxr_baseclass([ent_origin
],\
745 "radius": obj
.scale
.x
* bpy
.context
.scene
.cxr_data
.scale_factor
,
746 "soundscape": {"type":"string","default":""}
751 # EEVEE Light component converter -> Source 1
753 def ent_lights(context
):
754 obj
= context
['object']
755 kvs
= cxr_baseclass([ent_origin
],\
757 "_distance": (0.0 if obj
.data
.cxr_data
.realtime
else -1.0),
758 "_lightHDR": '-1 -1 -1 1',
762 light_base
= [(pow(obj
.data
.color
[i
],1.0/2.2)*255.0) for i
in range(3)] +\
763 [obj
.data
.energy
* bpy
.context
.scene
.cxr_data
.light_scale
]
765 if obj
.data
.type == 'SPOT' or obj
.data
.type == 'SUN':
766 # Blenders directional lights are -z forward
767 # Source is +x, however, it seems to use a completely different system.
768 # Since we dont care about roll for spotlights, we just take the
769 # pitch and yaw via trig
771 _
,mtx_rot
,_
= obj
.matrix_world
.decompose()
772 fwd
= mtx_rot
@ mathutils
.Vector((0,0,-1))
773 dir_pitch
= math
.asin(fwd
[2]) * 57.295779513
774 dir_yaw
= math
.atan2(fwd
[1],fwd
[0]) * 57.295779513
776 if obj
.data
.type == 'SPOT':
777 kvs
['_light'] = [ int(x
) for x
in light_base
]
778 kvs
['_cone'] = obj
.data
.spot_size
*(57.295779513/2.0)
779 kvs
['_inner_cone'] = (1.0-obj
.data
.spot_blend
)*kvs
['_cone']
781 kvs
['pitch'] = dir_pitch
782 kvs
['angles'] = [ 0, dir_yaw
, 0 ]
783 kvs
['_quadratic_attn'] = 0.0 # Source spotlights + quadratic falloff look
786 # Blender's default has a much more 'nice'
788 kvs
['_linear_attn'] = 1.0
790 elif obj
.data
.type == 'POINT':
791 kvs
['_light'] = [ int(x
) for x
in light_base
]
792 kvs
['_quadratic_attn'] = 1.0
793 kvs
['_linear_attn'] = 1.0
795 elif obj
.data
.type == 'SUN':
796 light_base
[3] *= 300.0 * 5
797 kvs
['_light'] = [ int(x
) for x
in light_base
]
799 ambient
= bpy
.context
.scene
.world
.color
800 kvs
['_ambient'] = [int(pow(ambient
[i
],1.0/2.2)*255.0) for i
in range(3)] +\
802 kvs
['_ambientHDR'] = [-1,-1,-1,1]
803 kvs
['_AmbientScaleHDR'] = 1
804 kvs
['pitch'] = dir_pitch
805 kvs
['angles'] = [ dir_pitch
, dir_yaw
, 0.0 ]
806 kvs
['SunSpreadAngle'] = 0
810 def ent_prop(context
):
811 if isinstance( context
['object'], bpy
.types
.Collection
):
813 target
= context
['object']
814 pos
= mathutils
.Vector(context
['origin'])
815 pos
+= mathutils
.Vector(context
['transform']['offset'])
817 kvs
['origin'] = [pos
[1],-pos
[0],pos
[2]]
818 kvs
['angles'] = [0,180,0]
819 kvs
['uniformscale'] = 1.0
821 kvs
= cxr_baseclass([ent_origin
],{})
822 target
= context
['object'].instance_collection
824 obj
= context
['object']
825 euler
= [ a
*57.295779513 for a
in obj
.rotation_euler
]
828 angle
[1] = euler
[2] + 180.0 # Dunno...
831 kvs
['angles'] = angle
832 kvs
['uniformscale'] = obj
.scale
[0]
834 if target
.cxr_data
.shadow_caster
:
835 kvs
['enablelightbounce'] = 1
836 kvs
['disableshadows'] = 0
838 kvs
['enablelightbounce'] = 0
839 kvs
['disableshadows'] = 1
841 kvs
['fademindist'] = -1
843 kvs
['model'] = F
"{asset_path('models',target)}.mdl".lower()
844 kvs
['renderamt'] = 255
845 kvs
['rendercolor'] = [255, 255, 255]
851 def ent_sky_camera(context
):
852 settings
= bpy
.context
.scene
.cxr_data
853 scale
= settings
.scale_factor
/ settings
.skybox_scale_factor
856 "origin": [_
for _
in context
['transform']['offset']],
857 "angles": [ 0, 0, 0 ],
858 "fogcolor": [255, 255, 255],
859 "fogcolor2": [255, 255, 255],
864 "HDRColorScale": 1.0,
869 def ent_cubemap(context
):
870 obj
= context
['object']
871 return cxr_baseclass([ent_origin
], {"cubemapsize": obj
.data
.cxr_data
.size
})
873 ent_origin
= { "origin": cxr_get_origin
}
874 ent_angles
= { "angles": cxr_get_angles
}
875 ent_transform
= cxr_baseclass( [ent_origin
], ent_angles
)
877 #include the user config
878 exec(open(F
'{os.path.dirname(__file__)}/config.py').read())
880 # Blender state callbacks
881 # ------------------------------------------------------------------------------
884 def cxr_on_load(dummy
):
885 global cxr_view_lines
, cxr_view_mesh
887 cxr_view_lines
= None
891 def cxr_dgraph_update(scene
,dgraph
):
893 print( F
"Hallo {time.time()}" )
895 # Convexer compilation functions
896 # ------------------------------------------------------------------------------
898 # Asset path management
900 def asset_uid(asset
):
901 if isinstance(asset
,str):
904 # Create a unique ID string
906 v
= asset
.cxr_data
.asset_id
915 dig
.append( int( v
% len(base
) ) )
921 if bpy
.context
.scene
.cxr_data
.include_names
:
922 name
+= asset
.name
.replace('.','_')
926 # -> <project_name>/<asset_name>
927 def asset_name(asset
):
928 return F
"{bpy.context.scene.cxr_data.project_name}/{asset_uid(asset)}"
930 # -> <subdir>/<project_name>/<asset_name>
931 def asset_path(subdir
, asset
):
932 return F
"{subdir}/{asset_name(asset_uid(asset))}"
934 # -> <csgo>/<subdir>/<project_name>/<asset_name>
935 def asset_full_path(sdir
,asset
):
936 return F
"{bpy.context.scene.cxr_data.subdir}/"+\
937 F
"{asset_path(sdir,asset_uid(asset))}"
939 # Decomposes mesh, and sets global error information if failed.
940 # - returns None on fail
941 # - returns world on success
942 def cxr_decompose_globalerr( mesh_src
):
946 world
= libcxr_decompose
.call( mesh_src
, pointer(err
) )
953 ("No Error", "There is no error?"),\
954 ("Bad input", "Non manifold geometry is present in the input mesh"),\
955 ("Bad result","An invalid manifold was generated, try to simplify"),\
956 ("Bad result","Make sure there is a clear starting point"),\
957 ("Bad result","Implicit vertex was invalid, try to simplify"),\
958 ("Bad input","Non coplanar vertices are in the source mesh"),\
959 ("Bad input","Non convex polygon is in the source mesh"),\
960 ("Bad result","Undefined failure"),\
961 ("Invalid Input", "Undefined failure"),\
968 # Entity functions / infos
969 # ------------------------
971 def cxr_collection_purpose(collection
):
972 if collection
.name
.startswith('.'): return None
973 if collection
.hide_render
: return None
974 if collection
.name
.startswith('mdl_'): return 'model'
977 def cxr_object_purpose(obj
):
981 def _search(collection
):
982 nonlocal objpurpose
, group
, obj
984 purpose
= cxr_collection_purpose( collection
)
985 if purpose
== None: return
986 if purpose
== 'model':
987 for o
in collection
.objects
:
989 if o
.type != 'EMPTY':
994 for o
in collection
.objects
:
996 classname
= cxr_classname(o
)
997 if classname
!= None:
998 objpurpose
= 'entity'
1000 objpurpose
= 'brush_entity'
1003 if o
.type == 'MESH':
1004 objpurpose
= 'brush'
1007 for c
in collection
.children
:
1010 if 'main' in bpy
.data
.collections
:
1011 _search( bpy
.data
.collections
['main'] )
1013 if objpurpose
== None and 'skybox' in bpy
.data
.collections
:
1014 _search( bpy
.data
.collections
['skybox'] )
1016 return (group
,objpurpose
)
1018 def cxr_intrinsic_classname(obj
):
1019 if obj
.type == 'LIGHT':
1021 'SPOT': "light_spot",
1023 'SUN': "light_environment" }[ obj
.data
.type ]
1025 elif obj
.type == 'LIGHT_PROBE':
1026 return "env_cubemap"
1027 elif obj
.type == 'EMPTY':
1028 if obj
.is_instancer
:
1029 return "prop_static"
1033 def cxr_custom_class(obj
):
1034 if obj
.type == 'MESH': custom_class
= obj
.cxr_data
.brushclass
1035 else: custom_class
= obj
.cxr_data
.classname
1039 def cxr_classname(obj
):
1040 intr
= cxr_intrinsic_classname(obj
)
1041 if intr
!= None: return intr
1043 custom_class
= cxr_custom_class(obj
)
1044 if custom_class
!= 'NONE':
1050 # intinsic: (k, False, value)
1051 # property: (k, True, value or default)
1055 def cxr_entity_keyvalues(context
):
1056 classname
= context
['classname']
1057 obj
= context
['object']
1058 if classname
not in cxr_entities
: return None
1062 entdef
= cxr_entities
[classname
]
1063 kvs
= entdef
['keyvalues']
1065 if callable(kvs
): kvs
= kvs(context
)
1072 if isinstance(kv
,dict):
1074 value
= obj
[ F
"cxrkv_{k}" ]
1079 if isinstance(value
,mathutils
.Vector
):
1080 value
= [_
for _
in value
]
1082 result
+= [(k
, isprop
, value
)]
1086 # Extract material information from shader graph data
1088 def material_info(mat
):
1090 info
['res'] = (512,512)
1091 info
['name'] = 'tools/toolsnodraw'
1093 if mat
== None or mat
.use_nodes
== False:
1097 if mat
.cxr_data
.shader
== 'Builtin':
1098 info
['name'] = mat
.name
1102 info
['name'] = asset_name(mat
)
1104 # Using the cxr_graph_mapping as a reference, go through the shader
1105 # graph and gather all $props from it.
1107 def _graph_read( node_def
, node
=None, depth
=0 ):
1111 def _variant_apply( val
):
1114 if isinstance( val
, list ):
1115 for shader_variant
in val
:
1116 if shader_variant
[0] == mat
.cxr_data
.shader
:
1117 return shader_variant
[1]
1124 _graph_read
.extracted
= []
1126 for node_idname
in node_def
:
1127 for n
in mat
.node_tree
.nodes
:
1128 if n
.name
== node_idname
:
1129 node_def
= node_def
[node_idname
]
1133 for link
in node_def
:
1134 link_def
= _variant_apply( node_def
[link
] )
1136 if isinstance( link_def
, dict ):
1137 node_link
= node
.inputs
[link
]
1139 if node_link
.is_linked
:
1141 # look for definitions for the connected node type
1142 from_node
= node_link
.links
[0].from_node
1144 node_name
= from_node
.name
.split('.')[0]
1145 if node_name
in link_def
:
1146 from_node_def
= link_def
[ node_name
]
1148 _graph_read( from_node_def
, from_node
, depth
+1 )
1151 # TODO: Make a warning for this?
1154 if "default" in link_def
:
1155 prop
= _variant_apply( link_def
['default'] )
1156 info
[prop
] = node_link
.default_value
1158 prop
= _variant_apply( link_def
)
1159 info
[prop
] = getattr( node
, link
)
1161 _graph_read(cxr_graph_mapping
)
1163 if "$basetexture" in info
:
1164 export_res
= info
['$basetexture'].cxr_data
.export_res
1165 info
['res'] = (export_res
[0], export_res
[1])
1169 def vec3_min( a
, b
):
1170 return mathutils
.Vector((min(a
[0],b
[0]),min(a
[1],b
[1]),min(a
[2],b
[2])))
1171 def vec3_max( a
, b
):
1172 return mathutils
.Vector((max(a
[0],b
[0]),max(a
[1],b
[1]),max(a
[2],b
[2])))
1174 def cxr_collection_center(collection
, transform
):
1176 bounds_min
= mathutils
.Vector((BIG
,BIG
,BIG
))
1177 bounds_max
= mathutils
.Vector((-BIG
,-BIG
,-BIG
))
1179 for obj
in collection
.objects
:
1180 if obj
.type == 'MESH':
1181 corners
= [ mathutils
.Vector(c
) for c
in obj
.bound_box
]
1183 for corner
in [ obj
.matrix_world
@c for c
in corners
]:
1184 bounds_min
= vec3_min( bounds_min
, corner
)
1185 bounds_max
= vec3_max( bounds_max
, corner
)
1187 center
= (bounds_min
+ bounds_max
) / 2.0
1189 origin
= mathutils
.Vector((-center
[1],center
[0],center
[2]))
1190 origin
*= transform
['scale']
1194 # Prepares Scene into dictionary format
1196 def cxr_scene_collect():
1197 context
= bpy
.context
1199 # Make sure all of our asset types have a unique ID
1200 def _uid_prepare(objtype
):
1206 if vs
.asset_id
in used_ids
:
1209 id_max
= max(id_max
,vs
.asset_id
)
1210 used_ids
+=[vs
.asset_id
]
1211 for vs
in to_generate
:
1213 vs
.asset_id
= id_max
1214 _uid_prepare(bpy
.data
.materials
)
1215 _uid_prepare(bpy
.data
.images
)
1216 _uid_prepare(bpy
.data
.collections
)
1219 "entities": [], # Everything with a classname
1220 "geo": [], # All meshes without a classname
1221 "heros": [] # Collections prefixed with mdl_
1224 def _collect(collection
,transform
):
1227 purpose
= cxr_collection_purpose( collection
)
1228 if purpose
== None: return
1229 if purpose
== 'model':
1230 sceneinfo
['entities'] += [{
1231 "object": collection
,
1232 "classname": "prop_static",
1233 "transform": transform
,
1234 "origin": cxr_collection_center( collection
, transform
)
1237 sceneinfo
['heros'] += [{
1238 "collection": collection
,
1239 "transform": transform
,
1240 "origin": cxr_collection_center( collection
, transform
)
1244 for obj
in collection
.objects
:
1245 if obj
.hide_get(): continue
1247 classname
= cxr_classname( obj
)
1249 if classname
!= None:
1250 sceneinfo
['entities'] += [{
1252 "classname": classname
,
1253 "transform": transform
1255 elif obj
.type == 'MESH':
1256 sceneinfo
['geo'] += [{
1258 "transform": transform
1261 for c
in collection
.children
:
1262 _collect( c
, transform
)
1265 "scale": context
.scene
.cxr_data
.scale_factor
,
1270 "scale": context
.scene
.cxr_data
.skybox_scale_factor
,
1271 "offset": (0,0,context
.scene
.cxr_data
.skybox_offset
)
1274 if 'main' in bpy
.data
.collections
:
1275 _collect( bpy
.data
.collections
['main'], transform_main
)
1277 if 'skybox' in bpy
.data
.collections
:
1278 _collect( bpy
.data
.collections
['skybox'], transform_sky
)
1280 sceneinfo
['entities'] += [{
1282 "transform": transform_sky
,
1283 "classname": "sky_camera"
1288 # Write VMF out to file (JOB HANDLER)
1290 def cxr_export_vmf(sceneinfo
, output_vmf
):
1293 with
vdf_structure(output_vmf
) as m
:
1294 print( F
"Write: {output_vmf}" )
1296 vmfinfo
= cxr_vmf_context()
1297 vmfinfo
.mapversion
= 4
1299 #TODO: These need to be in options...
1300 vmfinfo
.skyname
= bpy
.context
.scene
.cxr_data
.skyname
.encode('utf-8')
1301 vmfinfo
.detailvbsp
= b
"detail.vbsp"
1302 vmfinfo
.detailmaterial
= b
"detail/detailsprites"
1303 vmfinfo
.lightmap_scale
= 12
1305 vmfinfo
.brush_count
= 0
1306 vmfinfo
.entity_count
= 0
1307 vmfinfo
.face_count
= 0
1309 visgroups
= (cxr_visgroup
*len(cxr_visgroups
))()
1310 for i
, vg
in enumerate(cxr_visgroups
):
1311 visgroups
[i
].name
= vg
.encode('utf-8')
1312 vmfinfo
.visgroups
= cast(visgroups
, POINTER(cxr_visgroup
))
1313 vmfinfo
.visgroup_count
= len(cxr_visgroups
)
1315 libcxr_begin_vmf
.call( pointer(vmfinfo
), m
.fp
)
1317 def _buildsolid( cmd
):
1320 print( F
"{vmfinfo.brush_count} :: {cmd['object'].name}" )
1322 baked
= mesh_cxr_format( cmd
['object'] )
1323 world
= cxr_decompose_globalerr( baked
)
1328 vmfinfo
.scale
= cmd
['transform']['scale']
1330 offset
= cmd
['transform']['offset']
1331 vmfinfo
.offset
[0] = offset
[0]
1332 vmfinfo
.offset
[1] = offset
[1]
1333 vmfinfo
.offset
[2] = offset
[2]
1335 if cmd
['object'].cxr_data
.lightmap_override
> 0:
1336 vmfinfo
.lightmap_scale
= cmd
['object'].cxr_data
.lightmap_override
1338 vmfinfo
.lightmap_scale
= bpy
.context
.scene
.cxr_data
.lightmap_scale
1340 libcxr_push_world_vmf
.call( world
, pointer(vmfinfo
), m
.fp
)
1341 libcxr_free_world
.call( world
)
1346 for brush
in sceneinfo
['geo']:
1347 vmfinfo
.visgroupid
= int(brush
['object'].cxr_data
.visgroup
)
1348 if not _buildsolid( brush
):
1352 vmfinfo
.visgroupid
= 0
1354 libcxr_vmf_begin_entities
.call(pointer(vmfinfo
), m
.fp
)
1357 for ent
in sceneinfo
['entities']:
1359 ctx
= ent
['transform']
1360 cls
= ent
['classname']
1363 m
.kv( 'classname', cls
)
1365 kvs
= cxr_entity_keyvalues( ent
)
1368 if isinstance(kv
[2], list):
1369 m
.kv( kv
[0], ' '.join([str(_
) for _
in kv
[2]]) )
1370 else: m
.kv( kv
[0], str(kv
[2]) )
1374 elif not isinstance( obj
, bpy
.types
.Collection
):
1375 if obj
.type == 'MESH':
1376 vmfinfo
.visgroupid
= int(obj
.cxr_data
.visgroup
)
1377 if not _buildsolid( ent
):
1384 m
.kv( 'visgroupid', str(obj
.cxr_data
.visgroup
) )
1385 m
.kv( 'visgroupshown', '1' )
1386 m
.kv( 'visgroupautoshown', '1' )
1390 vmfinfo
.visgroupid
= 0
1395 # COmpile image using NBVTF and hash it (JOB HANDLER)
1397 def compile_image(img
):
1401 name
= asset_name(img
)
1402 src_path
= bpy
.path
.abspath(img
.filepath
)
1404 dims
= img
.cxr_data
.export_res
1406 'RGBA': NBVTF_IMAGE_FORMAT_ABGR8888
,
1407 'DXT1': NBVTF_IMAGE_FORMAT_DXT1
,
1408 'DXT5': NBVTF_IMAGE_FORMAT_DXT5
,
1409 'RGB': NBVTF_IMAGE_FORMAT_BGR888
1410 }[ img
.cxr_data
.fmt
]
1412 mipmap
= img
.cxr_data
.mipmap
1413 lod
= img
.cxr_data
.lod
1414 clamp
= img
.cxr_data
.clamp
1415 flags
= img
.cxr_data
.flags
1417 q
=bpy
.context
.scene
.cxr_data
.image_quality
1419 userflag_hash
= F
"{mipmap}.{lod}.{clamp}.{flags}"
1420 file_hash
= F
"{name}.{os.path.getmtime(src_path)}"
1421 comphash
= F
"{file_hash}.{dims[0]}.{dims[1]}.{fmt}.{userflag_hash}.{q}"
1423 if img
.cxr_data
.last_hash
!= comphash
:
1424 print( F
"Texture update: {img.filepath}" )
1426 src
= src_path
.encode('utf-8')
1427 dst
= (asset_full_path('materials',img
)+'.vtf').encode('utf-8')
1431 # texture setting flags
1432 if not lod
: flags_full |
= NBVTF_TEXTUREFLAGS_NOLOD
1434 flags_full |
= NBVTF_TEXTUREFLAGS_CLAMPS
1435 flags_full |
= NBVTF_TEXTUREFLAGS_CLAMPT
1437 if libnbvtf_convert
.call(src
,dims
[0],dims
[1],mipmap
,fmt
,q
,flags_full
,dst
):
1438 img
.cxr_data
.last_hash
= comphash
1443 # Compile a material to VMT format. This is quick to run, doesnt need to be a
1446 def compile_material(mat
):
1447 info
= material_info(mat
)
1448 properties
= mat
.cxr_data
1450 print( F
"Compile {asset_full_path('materials',mat)}.vmt" )
1451 if properties
.shader
== 'Builtin':
1456 # Walk the property tree
1457 def _mlayer( layer
):
1458 nonlocal properties
, props
1461 if isinstance(layer
[decl
],dict): # $property definition
1463 ptype
= pdef
['type']
1469 if 'shaders' in pdef
and properties
.shader
not in pdef
['shaders']:
1472 # Group expansion (does it have subdefinitions?)
1474 if isinstance(pdef
[ch
],dict):
1483 if ptype
== 'intrinsic':
1487 prop
= getattr(properties
,decl
)
1488 default
= pdef
['default']
1490 if not isinstance(prop
,str) and \
1491 not isinstance(prop
,bpy
.types
.Image
) and \
1492 hasattr(prop
,'__getitem__'):
1493 prop
= tuple([p
for p
in prop
])
1497 props
+= [(decl
,pdef
,prop
)]
1502 if expandview
: _mlayer(pdef
)
1504 _mlayer( cxr_shader_params
)
1507 with
vdf_structure( F
"{asset_full_path('materials',mat)}.vmt" ) as vmt
:
1508 vmt
.node( properties
.shader
)
1509 vmt
.put( "// Convexer export\n" )
1518 if 'exponent' in pdef
: return str(pow( v
, pdef
['exponent'] ))
1521 if isinstance(prop
,bpy
.types
.Image
):
1522 vmt
.kv( decl
, asset_name(prop
))
1523 elif isinstance(prop
,bool):
1524 vmt
.kv( decl
, '1' if prop
else '0' )
1525 elif isinstance(prop
,str):
1526 vmt
.kv( decl
, prop
)
1527 elif isinstance(prop
,float) or isinstance(prop
,int):
1528 vmt
.kv( decl
, _numeric(prop
) )
1529 elif isinstance(prop
,tuple):
1530 vmt
.kv( decl
, F
"[{' '.join([_numeric(_) for _ in prop])}]" )
1532 vmt
.put( F
"// (cxr) unkown shader value type'{type(prop)}'" )
1537 def cxr_modelsrc_vphys( mdl
):
1538 for obj
in mdl
.objects
:
1539 if obj
.name
== F
"{mdl.name}_phy":
1543 def cxr_export_modelsrc( mdl
, origin
, asset_dir
, project_name
, transform
):
1544 dgraph
= bpy
.context
.evaluated_depsgraph_get()
1546 # Compute hash value
1547 chash
= asset_uid(mdl
)+str(origin
)+str(transform
)
1549 #for obj in mdl.objects:
1550 # if obj.type != 'MESH':
1553 # ev = obj.evaluated_get(dgraph).data
1554 # srcverts=[(v.co[0],v.co[1],v.co[2]) for v in ev.vertices]
1555 # srcloops=[(l.normal[0],l.normal[1],l.normal[2]) for l in ev.loops]
1557 # chash=hashlib.sha224((str(srcverts)+chash).encode()).hexdigest()
1558 # chash=hashlib.sha224((str(srcloops)+chash).encode()).hexdigest()
1560 # if ev.uv_layers.active != None:
1561 # uv_layer = ev.uv_layers.active.data
1562 # srcuv=[(uv.uv[0],uv.uv[1]) for uv in uv_layer]
1566 # chash=hashlib.sha224((str(srcuv)+chash).encode()).hexdigest()
1567 # srcmats=[ ms.material.name for ms in obj.material_slots ]
1568 # chash=hashlib.sha224((str(srcmats)+chash).encode()).hexdigest()
1569 # transforms=[ obj.location, obj.rotation_euler, obj.scale ]
1570 # srctr=[(v[0],v[1],v[2]) for v in transforms]
1571 # chash=hashlib.sha224((str(srctr)+chash).encode()).hexdigest()
1573 #if chash != mdl.cxr_data.last_hash:
1574 # mdl.cxr_data.last_hash = chash
1575 # print( F"Compile: {mdl.name}" )
1579 bpy
.ops
.object.select_all(action
='DESELECT')
1582 def _get_layer(col
,name
):
1583 for c
in col
.children
:
1586 sub
= _get_layer(c
,name
)
1590 layer
= _get_layer(bpy
.context
.view_layer
.layer_collection
,mdl
.name
)
1592 prev_state
= layer
.hide_viewport
1593 layer
.hide_viewport
=False
1595 # Collect materials to be compiled, and temp rename for export
1599 for obj
in mdl
.objects
:
1600 if obj
.name
== F
"{mdl.name}_phy":
1604 obj
.select_set(state
=True)
1605 for ms
in obj
.material_slots
:
1606 if ms
.material
!= None:
1607 if ms
.material
not in mat_dict
:
1608 mat_dict
[ms
.material
] = ms
.material
.name
1609 ms
.material
.name
= asset_uid(ms
.material
)
1610 ms
.material
.use_nodes
= False
1613 bpy
.ops
.export_scene
.fbx( filepath
=F
'{asset_dir}/{uid}_ref.fbx',\
1614 check_existing
=False,
1616 apply_unit_scale
=False,
1617 bake_space_transform
=False
1620 bpy
.ops
.object.select_all(action
='DESELECT')
1623 vphys
.select_set(state
=True)
1624 bpy
.ops
.export_scene
.fbx( filepath
=F
'{asset_dir}/{uid}_phy.fbx',\
1625 check_existing
=False,
1627 apply_unit_scale
=False,
1628 bake_space_transform
=False
1630 bpy
.ops
.object.select_all(action
='DESELECT')
1632 # Fix material names back to original
1633 for mat
in mat_dict
:
1634 mat
.name
= mat_dict
[mat
]
1635 mat
.use_nodes
= True
1637 layer
.hide_viewport
=prev_state
1640 with
open(F
'{asset_dir}/{uid}.qc','w') as o
:
1641 o
.write(F
'$modelname "{project_name}/{uid}"\n')
1642 #o.write(F'$scale .32\n')
1643 o
.write(F
'$scale {transform["scale"]/100.0}\n')
1644 o
.write(F
'$body _ "{uid}_ref.fbx"\n')
1645 o
.write(F
'$staticprop\n')
1646 o
.write(F
'$origin {origin[0]:.6f} {origin[1]:.6f} {origin[2]:.6f}\n')
1648 if mdl
.cxr_data
.preserve_order
:
1649 o
.write(F
"$preservetriangleorder\n")
1651 if mdl
.cxr_data
.texture_shadows
:
1652 o
.write(F
"$casttextureshadows\n")
1654 o
.write(F
"$surfaceprop {mdl.cxr_data.surfaceprop}\n")
1657 o
.write(F
'$collisionmodel "{uid}_phy.fbx"\n')
1659 o
.write(" $concave\n")
1662 o
.write(F
'$cdmaterials {project_name}\n')
1663 o
.write(F
'$sequence idle {uid}_ref.fbx\n')
1667 # Copy bsp file (and also lightpatch it)
1669 def cxr_patchmap( src
, dst
):
1670 libcxr_lightpatch_bsp
.call( src
.encode('utf-8') )
1671 shutil
.copyfile( src
, dst
)
1674 # Convexer operators
1675 # ------------------------------------------------------------------------------
1677 # Force reload of shared libraries
1679 class CXR_RELOAD(bpy
.types
.Operator
):
1680 bl_idname
="convexer.reload"
1682 def execute(_
,context
):
1686 # Reset all debugging/ui information
1688 class CXR_RESET(bpy
.types
.Operator
):
1689 bl_idname
="convexer.reset"
1690 bl_label
="Reset Convexer"
1691 def execute(_
,context
):
1695 # Used for exporting data to use with ASAN builds
1697 class CXR_DEV_OPERATOR(bpy
.types
.Operator
):
1698 bl_idname
="convexer.dev_test"
1699 bl_label
="Export development data"
1701 def execute(_
,context
):
1702 # Prepare input data
1703 mesh_src
= mesh_cxr_format(context
.active_object
)
1704 libcxr_write_test_data
.call( pointer(mesh_src
) )
1707 class CXR_INIT_FS_OPERATOR(bpy
.types
.Operator
):
1708 bl_idname
="convexer.fs_init"
1709 bl_label
="Initialize filesystem"
1711 def execute(_
,context
):
1712 gameinfo
= F
'{bpy.context.scene.cxr_data.subdir}/gameinfo.txt'
1714 if libcxr_fs_set_gameinfo
.call( gameinfo
.encode('utf-8') ) == 1:
1715 print( "File system ready" )
1717 print( "File system failed to initialize" )
1721 class CXR_LOAD_MODEL_OPERATOR(bpy
.types
.Operator
):
1722 bl_idname
="convexer.model_load"
1723 bl_label
="Load model"
1725 def execute(_
,context
):
1726 global cxr_mdl_mesh
, cxr_mdl_shader
1728 mdlpath
= bpy
.context
.scene
.cxr_data
.dev_mdl
.encode('utf-8')
1729 pmesh
= libcxr_load_mdl
.call( mdlpath
)
1732 print( "Failed to load model" )
1737 #TODO: remove code dupe
1738 vertices
= mesh
.vertices
[:mesh
.vertex_count
]
1739 vertices
= [(_
[0],_
[1],_
[2]) for _
in vertices
]
1741 normals
= mesh
.normals
[:mesh
.vertex_count
]
1742 normals
= [(_
[0],_
[1],_
[2]) for _
in normals
]
1744 indices
= mesh
.indices
[:mesh
.indices_count
]
1745 indices
= [ (indices
[i
*3+0],indices
[i
*3+1],indices
[i
*3+2]) \
1746 for i
in range(int(mesh
.indices_count
/3)) ]
1748 cxr_mdl_mesh
= batch_for_shader(
1749 cxr_mdl_shader
, 'TRIS',
1750 { "aPos": vertices
, "aNormal": normals
},
1754 libcxr_free_tri_mesh
.call( pmesh
)
1759 # UI: Preview how the brushes will looks in 3D view
1761 class CXR_PREVIEW_OPERATOR(bpy
.types
.Operator
):
1762 bl_idname
="convexer.preview"
1763 bl_label
="Preview Brushes"
1767 def execute(_
,context
):
1768 global cxr_view_mesh
1769 global cxr_view_shader
, cxr_view_mesh
, cxr_error_inf
1773 static
= _
.__class
__
1775 mesh_src
= mesh_cxr_format(context
.active_object
)
1776 world
= cxr_decompose_globalerr( mesh_src
)
1781 # Generate preview using cxr
1783 ptrpreview
= libcxr_world_preview
.call( world
)
1784 preview
= ptrpreview
[0]
1786 vertices
= preview
.vertices
[:preview
.vertex_count
]
1787 vertices
= [(_
[0],_
[1],_
[2]) for _
in vertices
]
1789 colours
= preview
.colours
[:preview
.vertex_count
]
1790 colours
= [(_
[0],_
[1],_
[2],_
[3]) for _
in colours
]
1792 indices
= preview
.indices
[:preview
.indices_count
]
1793 indices
= [ (indices
[i
*3+0],indices
[i
*3+1],indices
[i
*3+2]) \
1794 for i
in range(int(preview
.indices_count
/3)) ]
1796 cxr_view_mesh
= batch_for_shader(
1797 cxr_view_shader
, 'TRIS',
1798 { "pos": vertices
, "color": colours
},
1802 libcxr_free_tri_mesh
.call( ptrpreview
)
1803 libcxr_free_world
.call( world
)
1809 # Search for VMF compiler executables in subdirectory
1811 class CXR_DETECT_COMPILERS(bpy
.types
.Operator
):
1812 bl_idname
="convexer.detect_compilers"
1813 bl_label
="Find compilers"
1815 def execute(self
,context
):
1816 scene
= context
.scene
1817 settings
= scene
.cxr_data
1818 subdir
= settings
.subdir
1820 for exename
in ['studiomdl','vbsp','vvis','vrad']:
1821 searchpath
= os
.path
.normpath(F
'{subdir}/../bin/{exename}.exe')
1822 if os
.path
.exists(searchpath
):
1823 settings
[F
'exe_{exename}'] = searchpath
1827 def cxr_compiler_path( compiler
):
1828 settings
= bpy
.context
.scene
.cxr_data
1829 subdir
= settings
.subdir
1830 path
= os
.path
.normpath(F
'{subdir}/../bin/{compiler}.exe')
1832 if os
.path
.exists( path
): return path
1835 # Compatibility layer
1837 def cxr_temp_file( fn
):
1838 if CXR_GNU_LINUX
== 1:
1841 filepath
= bpy
.data
.filepath
1842 directory
= os
.path
.dirname(filepath
)
1843 return F
"{directory}/{fn}"
1845 def cxr_winepath( path
):
1846 if CXR_GNU_LINUX
== 1:
1847 return 'z:'+path
.replace('/','\\')
1851 # Main compile function
1853 class CXR_COMPILER_CHAIN(bpy
.types
.Operator
):
1854 bl_idname
="convexer.chain"
1855 bl_label
="Compile Chain"
1870 def cancel(_
,context
):
1871 #global cxr_jobs_batch
1872 static
= _
.__class
__
1873 wm
= context
.window_manager
1875 if static
.SUBPROC
!= None:
1876 static
.SUBPROC
.terminate()
1877 static
.SUBPROC
= None
1879 if static
.TIMER
!= None:
1880 wm
.event_timer_remove( static
.TIMER
)
1885 #cxr_jobs_batch = None
1889 def modal(_
,context
,ev
):
1890 static
= _
.__class
__
1892 if ev
.type == 'TIMER':
1893 global cxr_jobs_batch
, cxr_error_inf
1895 if static
.WAIT_REDRAW
:
1897 return {'PASS_THROUGH'}
1898 static
.WAIT_REDRAW
= True
1900 if static
.USER_EXIT
:
1901 print( "Chain USER_EXIT" )
1902 return _
.cancel(context
)
1904 if static
.SUBPROC
!= None:
1905 # Deal with async modes
1906 status
= static
.SUBPROC
.poll()
1908 # Cannot redirect STDOUT through here without causing
1909 # undefined behaviour due to the Blender Python specification.
1911 # Have to write it out to a file and read it back in.
1914 with
open(cxr_temp_file("convexer_compile_log.txt"),"r") as log
:
1915 static
.LOG
= log
.readlines()
1917 return {'PASS_THROUGH'}
1919 #for l in static.SUBPROC.stdout:
1920 # print( F'-> {l.decode("utf-8")}',end='' )
1921 static
.SUBPROC
= None
1924 print(F
'Compiler () error: {status}')
1926 jobn
= static
.JOBSYS
['jobs'][static
.JOBID
]
1927 cxr_error_inf
= ( F
"{static.JOBSYS['title']} error {status}", jobn
)
1929 return _
.cancel(context
)
1931 static
.JOBSYS
['jobs'][static
.JOBID
] = None
1932 cxr_jobs_update_graph( static
.JOBINFO
)
1934 return {'PASS_THROUGH'}
1936 # Compile syncronous thing
1937 for sys
in static
.JOBINFO
:
1938 for i
,target
in enumerate(sys
['jobs']):
1941 if callable(sys
['exec']):
1942 print( F
"Run (sync): {static.JOBID} @{time.time()}" )
1944 if not sys
['exec'](*target
):
1945 print( "Job failed" )
1946 return _
.cancel(context
)
1948 sys
['jobs'][i
] = None
1951 # Run external executable (wine)
1952 static
.SUBPROC
= subprocess
.Popen( target
,
1953 stdout
=static
.FILE
,\
1954 stderr
=subprocess
.PIPE
,\
1959 cxr_jobs_update_graph( static
.JOBINFO
)
1961 return {'PASS_THROUGH'}
1964 print( "All jobs completed!" )
1965 #cxr_jobs_batch = None
1967 return _
.cancel(context
)
1969 return {'PASS_THROUGH'}
1971 def invoke(_
,context
,event
):
1972 global cxr_error_inf
1974 static
= _
.__class
__
1975 wm
= context
.window_manager
1977 if static
.TIMER
!= None:
1978 print("Chain exiting...")
1979 static
.USER_EXIT
=True
1980 return {'RUNNING_MODAL'}
1982 print("Launching compiler toolchain")
1985 # Run static compilation units now (collect, vmt..)
1986 filepath
= bpy
.data
.filepath
1987 directory
= os
.path
.dirname(filepath
)
1988 settings
= bpy
.context
.scene
.cxr_data
1990 asset_dir
= F
"{directory}/modelsrc"
1991 material_dir
= F
"{settings.subdir}/materials/{settings.project_name}"
1992 model_dir
= F
"{settings.subdir}/models/{settings.project_name}"
1993 output_vmf
= F
"{directory}/{settings.project_name}.vmf"
1995 bsp_local
= F
"{directory}/{settings.project_name}.bsp"
1996 bsp_remote
= F
"{settings.subdir}/maps/{settings.project_name}.bsp"
1997 bsp_packed
= F
"{settings.subdir}/maps/{settings.project_name}_pack.bsp"
1998 packlist
= F
"{directory}/{settings.project_name}_assets.txt"
2000 os
.makedirs( asset_dir
, exist_ok
=True )
2001 os
.makedirs( material_dir
, exist_ok
=True )
2002 os
.makedirs( model_dir
, exist_ok
=True )
2004 static
.FILE
= open(cxr_temp_file("convexer_compile_log.txt"),"w")
2007 sceneinfo
= cxr_scene_collect()
2013 for brush
in sceneinfo
['geo']:
2014 for ms
in brush
['object'].material_slots
:
2015 a_materials
.add( ms
.material
)
2016 if ms
.material
.cxr_data
.shader
== 'VertexLitGeneric':
2017 errmat
= ms
.material
.name
2018 errnam
= brush
['object'].name
2020 cxr_error_inf
= ( "Shader error", \
2021 F
"Vertex shader ({errmat}) used on model ({errnam})" )
2023 print( F
"Vertex shader {errmat} used on {errnam}")
2025 return {'CANCELLED'}
2029 for ent
in sceneinfo
['entities']:
2030 if ent
['object'] == None: continue
2032 if ent
['classname'] == 'prop_static':
2034 if isinstance(obj
,bpy
.types
.Collection
):
2036 a_models
.add( target
)
2037 model_jobs
+= [(target
, ent
['origin'], asset_dir
, \
2038 settings
.project_name
, ent
['transform'])]
2040 target
= obj
.instance_collection
2041 if target
in a_models
:
2043 a_models
.add( target
)
2045 # TODO: Should take into account collection instancing offset
2046 model_jobs
+= [(target
, [0,0,0], asset_dir
, \
2047 settings
.project_name
, ent
['transform'])]
2049 elif ent
['object'].type == 'MESH':
2050 for ms
in ent
['object'].material_slots
:
2051 a_materials
.add( ms
.material
)
2053 for mdl
in a_models
:
2054 uid
= asset_uid(mdl
)
2055 qc_jobs
+= [F
'{uid}.qc']
2057 for obj
in mdl
.objects
:
2058 for ms
in obj
.material_slots
:
2059 a_materials
.add( ms
.material
)
2060 if ms
.material
.cxr_data
.shader
== 'LightMappedGeneric' or \
2061 ms
.material
.cxr_data
.shader
== 'WorldVertexTransition':
2063 errmat
= ms
.material
.name
2066 cxr_error_inf
= ( "Shader error", \
2067 F
"Lightmapped shader ({errmat}) used on model ({errnam})" )
2069 print( F
"Lightmapped shader {errmat} used on {errnam}")
2071 return {'CANCELLED'}
2074 for mat
in a_materials
:
2075 for pair
in compile_material(mat
):
2080 if isinstance(prop
,bpy
.types
.Image
):
2082 if 'flags' in pdef
: flags
= pdef
['flags']
2083 if prop
not in image_jobs
:
2084 image_jobs
+= [(prop
,)]
2085 prop
.cxr_data
.flags
= flags
2088 with
open( packlist
, "w" ) as fp
:
2090 for mat
in a_materials
:
2091 if mat
.cxr_data
.shader
== 'Builtin': continue
2092 fp
.write(F
"{asset_path('materials',mat)}.vmt\n")
2093 fp
.write(F
"{cxr_winepath(asset_full_path('materials',mat))}.vmt\n")
2095 for img_job
in image_jobs
:
2097 fp
.write(F
"{asset_path('materials',img)}.vtf\n")
2098 fp
.write(F
"{cxr_winepath(asset_full_path('materials',img))}.vtf\n")
2100 for mdl
in a_models
:
2101 local
= asset_path('models',mdl
)
2102 winep
= cxr_winepath(asset_full_path('models',mdl
))
2104 fp
.write(F
"{local}.vvd\n")
2105 fp
.write(F
"{winep}.vvd\n")
2106 fp
.write(F
"{local}.dx90.vtx\n")
2107 fp
.write(F
"{winep}.dx90.vtx\n")
2108 fp
.write(F
"{local}.mdl\n")
2109 fp
.write(F
"{winep}.mdl\n")
2110 fp
.write(F
"{local}.vvd\n")
2111 fp
.write(F
"{winep}.vvd\n")
2113 if cxr_modelsrc_vphys(mdl
):
2114 fp
.write(F
"{local}.phy\n")
2115 fp
.write(F
"{winep}.phy\n")
2121 if settings
.comp_vmf
:
2122 static
.JOBINFO
+= [{
2123 "title": "Convexer",
2125 "colour": (0.863, 0.078, 0.235,1.0),
2126 "exec": cxr_export_vmf
,
2127 "jobs": [(sceneinfo
,output_vmf
)]
2130 if settings
.comp_textures
:
2131 if len(image_jobs
) > 0:
2132 static
.JOBINFO
+= [{
2133 "title": "Textures",
2135 "colour": (1.000, 0.271, 0.000,1.0),
2136 "exec": compile_image
,
2140 game
= cxr_winepath( settings
.subdir
)
2142 '-game', game
, settings
.project_name
2146 if settings
.comp_models
:
2147 if len(model_jobs
) > 0:
2148 static
.JOBINFO
+= [{
2151 "colour": (1.000, 0.647, 0.000,1.0),
2152 "exec": cxr_export_modelsrc
,
2156 if len(qc_jobs
) > 0:
2157 static
.JOBINFO
+= [{
2158 "title": "StudioMDL",
2160 "colour": (1.000, 0.843, 0.000, 1.0),
2161 "exec": "studiomdl",
2162 "jobs": [[settings
[F
'exe_studiomdl']] + [\
2163 '-nop4', '-game', game
, qc
] for qc
in qc_jobs
],
2168 if settings
.comp_compile
:
2169 if not settings
.opt_vbsp
.startswith( 'disable' ):
2170 vbsp_opt
= settings
.opt_vbsp
.split()
2171 static
.JOBINFO
+= [{
2174 "colour": (0.678, 1.000, 0.184,1.0),
2176 "jobs": [[settings
[F
'exe_vbsp']] + vbsp_opt
+ args
],
2180 if not settings
.opt_vvis
.startswith( 'disable' ):
2181 vvis_opt
= settings
.opt_vvis
.split()
2182 static
.JOBINFO
+= [{
2185 "colour": (0.000, 1.000, 0.498,1.0),
2187 "jobs": [[settings
[F
'exe_vvis']] + vvis_opt
+ args
],
2191 if not settings
.opt_vrad
.startswith( 'disable' ):
2192 vrad_opt
= settings
.opt_vrad
.split()
2193 static
.JOBINFO
+= [{
2196 "colour": (0.125, 0.698, 0.667,1.0),
2198 "jobs": [[settings
[F
'exe_vrad']] + vrad_opt
+ args
],
2202 static
.JOBINFO
+= [{
2205 "colour": (0.118, 0.565, 1.000,1.0),
2206 "exec": cxr_patchmap
,
2207 "jobs": [(bsp_local
,bsp_remote
)]
2210 if settings
.comp_pack
:
2211 static
.JOBINFO
+= [{
2214 "colour": (0.541, 0.169, 0.886,1.0),
2216 "jobs": [[cxr_compiler_path("bspzip"), '-addlist', \
2217 cxr_winepath(bsp_remote
),
2218 cxr_winepath(packlist
),
2219 cxr_winepath(bsp_packed
) ]],
2223 if len(static
.JOBINFO
) == 0:
2224 return {'CANCELLED'}
2226 static
.USER_EXIT
=False
2227 static
.TIMER
=wm
.event_timer_add(0.1,window
=context
.window
)
2228 wm
.modal_handler_add(_
)
2230 cxr_jobs_update_graph( static
.JOBINFO
)
2232 return {'RUNNING_MODAL'}
2234 class CXR_RESET_HASHES(bpy
.types
.Operator
):
2235 bl_idname
="convexer.hash_reset"
2236 bl_label
="Reset asset hashes"
2238 def execute(_
,context
):
2239 for c
in bpy
.data
.collections
:
2240 c
.cxr_data
.last_hash
= F
"<RESET>{time.time()}"
2241 c
.cxr_data
.asset_id
=0
2243 for t
in bpy
.data
.images
:
2244 t
.cxr_data
.last_hash
= F
"<RESET>{time.time()}"
2245 t
.cxr_data
.asset_id
=0
2249 class CXR_COMPILE_MATERIAL(bpy
.types
.Operator
):
2250 bl_idname
="convexer.matcomp"
2251 bl_label
="Recompile Material"
2253 def execute(_
,context
):
2254 active_obj
= bpy
.context
.active_object
2255 active_mat
= active_obj
.active_material
2257 #TODO: reduce code dupe (L1663)
2258 for pair
in compile_material(active_mat
):
2263 if isinstance(prop
,bpy
.types
.Image
):
2265 if 'flags' in pdef
: flags
= pdef
['flags']
2266 prop
.cxr_data
.flags
= flags
2268 compile_image( prop
)
2270 settings
= bpy
.context
.scene
.cxr_data
2271 with
open(F
'{settings.subdir}/cfg/convexer_mat_update.cfg','w') as o
:
2272 o
.write(F
'mat_reloadmaterial {asset_name(active_mat)}')
2275 with
open(F
'{settings.subdir}/cfg/convexer.cfg','w') as o
:
2276 o
.write('sv_cheats 1\n')
2277 o
.write('mp_warmup_pausetimer 1\n')
2278 o
.write('bot_kick\n')
2279 o
.write('alias cxr_reload "exec convexer_mat_update"\n')
2284 # ------------------------------------------------------------------------------
2286 # Helper buttons for 3d toolbox view
2288 class CXR_VIEW3D( bpy
.types
.Panel
):
2289 bl_idname
= "VIEW3D_PT_convexer"
2290 bl_label
= "Convexer"
2291 bl_space_type
= 'VIEW_3D'
2292 bl_region_type
= 'UI'
2293 bl_category
= "Convexer"
2295 def draw(_
, context
):
2298 active_object
= context
.object
2299 if active_object
== None: return
2301 purpose
= cxr_object_purpose( active_object
)
2303 if purpose
[0] == None or purpose
[1] == None:
2304 usage_str
= "No purpose"
2306 if purpose
[1] == 'model':
2307 usage_str
= F
'mesh in {asset_name( purpose[0] )}.mdl'
2309 usage_str
= F
'{purpose[1]} in {purpose[0].name}'
2311 layout
.label(text
=F
"Currently editing:")
2313 box
.label(text
=usage_str
)
2315 if purpose
[1] == 'brush' or purpose
[1] == 'brush_entity':
2318 row
.operator("convexer.preview")
2322 row
.operator("convexer.reset")
2324 layout
.prop( bpy
.context
.scene
.cxr_data
, "dev_mdl" )
2325 layout
.operator( "convexer.model_load" )
2327 # Main scene properties interface, where all the settings go
2329 class CXR_INTERFACE(bpy
.types
.Panel
):
2331 bl_idname
="SCENE_PT_convexer"
2332 bl_space_type
='PROPERTIES'
2333 bl_region_type
='WINDOW'
2336 def draw(_
,context
):
2337 if CXR_GNU_LINUX
==1:
2338 _
.layout
.operator("convexer.reload")
2339 _
.layout
.operator("convexer.dev_test")
2340 _
.layout
.operator("convexer.fs_init")
2342 _
.layout
.operator("convexer.hash_reset")
2343 settings
= context
.scene
.cxr_data
2345 _
.layout
.prop(settings
, "scale_factor")
2346 _
.layout
.prop(settings
, "skybox_scale_factor")
2347 _
.layout
.prop(settings
, "skyname" )
2348 _
.layout
.prop(settings
, "lightmap_scale")
2349 _
.layout
.prop(settings
, "light_scale" )
2350 _
.layout
.prop(settings
, "image_quality" )
2352 box
= _
.layout
.box()
2354 box
.prop(settings
, "project_name")
2355 box
.prop(settings
, "subdir")
2357 box
= _
.layout
.box()
2358 box
.operator("convexer.detect_compilers")
2359 box
.prop(settings
, "exe_studiomdl")
2360 box
.prop(settings
, "exe_vbsp")
2361 box
.prop(settings
, "opt_vbsp")
2363 box
.prop(settings
, "exe_vvis")
2364 box
.prop(settings
, "opt_vvis")
2366 box
.prop(settings
, "exe_vrad")
2367 box
.prop(settings
, "opt_vrad")
2371 row
.prop(settings
,"comp_vmf")
2372 row
.prop(settings
,"comp_textures")
2373 row
.prop(settings
,"comp_models")
2374 row
.prop(settings
,"comp_compile")
2375 row
.prop(settings
,"comp_pack")
2377 text
= "Compile" if CXR_COMPILER_CHAIN
.TIMER
== None else "Cancel"
2380 row
.operator("convexer.chain", text
=text
)
2384 row
.operator("convexer.reset")
2385 if CXR_COMPILER_CHAIN
.TIMER
!= None:
2388 class CXR_MATERIAL_PANEL(bpy
.types
.Panel
):
2389 bl_label
="VMT Properties"
2390 bl_idname
="SCENE_PT_convexer_vmt"
2391 bl_space_type
='PROPERTIES'
2392 bl_region_type
='WINDOW'
2393 bl_context
="material"
2395 def draw(_
,context
):
2396 active_object
= bpy
.context
.active_object
2397 if active_object
== None: return
2399 active_material
= active_object
.active_material
2400 if active_material
== None: return
2402 properties
= active_material
.cxr_data
2403 info
= material_info( active_material
)
2405 _
.layout
.label(text
=F
"{info['name']} @{info['res'][0]}x{info['res'][1]}")
2406 row
= _
.layout
.row()
2407 row
.prop( properties
, "shader" )
2408 row
.operator( "convexer.matcomp" )
2410 #for xk in info: _.layout.label(text=F"{xk}:={info[xk]}")
2412 def _mtex( name
, img
, uiParent
):
2415 box
= uiParent
.box()
2416 box
.label( text
=F
'{name} "{img.filepath}"' )
2418 if ((x
& (x
- 1)) == 0):
2421 closest_diff
= 10000000
2423 dist
= abs((1 << i
)-x
)
2424 if dist
< closest_diff
:
2429 return 1 << (closest
+1)
2431 return 1 << (closest
-1)
2436 row
.prop( img
.cxr_data
, "export_res" )
2437 row
.prop( img
.cxr_data
, "fmt" )
2440 row
.prop( img
.cxr_data
, "mipmap" )
2441 row
.prop( img
.cxr_data
, "lod" )
2442 row
.prop( img
.cxr_data
, "clamp" )
2444 img
.cxr_data
.export_res
[0] = _p2( img
.cxr_data
.export_res
[0] )
2445 img
.cxr_data
.export_res
[1] = _p2( img
.cxr_data
.export_res
[1] )
2447 def _mview( layer
, uiParent
):
2451 if isinstance(layer
[decl
],dict): # $property definition
2453 ptype
= pdef
['type']
2459 if ('shaders' in pdef
) and \
2460 (properties
.shader
not in pdef
['shaders']):
2463 if ptype
== 'intrinsic':
2464 if decl
not in info
:
2469 if isinstance(pdef
[ch
],dict):
2470 if ptype
== 'ui' or ptype
== 'intrinsic':
2472 elif getattr(properties
,decl
) == pdef
['default']:
2475 thisnode
= uiParent
.box()
2479 thisnode
.label( text
=decl
)
2480 elif ptype
== 'intrinsic':
2481 if isinstance(info
[decl
], bpy
.types
.Image
):
2482 _mtex( decl
, info
[decl
], thisnode
)
2484 # hidden intrinsic value.
2485 # Means its a float array or something not an image
2486 thisnode
.label(text
=F
"-- hidden intrinsic '{decl}' --")
2488 thisnode
.prop(properties
,decl
)
2489 if expandview
: _mview(pdef
,thisnode
)
2491 _mview( cxr_shader_params
, _
.layout
)
2493 def cxr_entity_changeclass(_
,context
):
2494 active_object
= context
.active_object
2496 # Create ID properties
2498 classname
= cxr_custom_class(active_object
)
2500 if classname
in cxr_entities
:
2501 entdef
= cxr_entities
[classname
]
2503 kvs
= entdef
['keyvalues']
2504 if callable(kvs
): kvs
= kvs( {'object': active_object
} )
2510 if callable(kv
) or not isinstance(kv
,dict): continue
2512 if key
not in active_object
:
2513 active_object
[key
] = kv
['default']
2514 id_prop
= active_object
.id_properties_ui(key
)
2515 id_prop
.update(default
=kv
['default'])
2517 class CXR_ENTITY_PANEL(bpy
.types
.Panel
):
2518 bl_label
="Entity Config"
2519 bl_idname
="SCENE_PT_convexer_entity"
2520 bl_space_type
='PROPERTIES'
2521 bl_region_type
='WINDOW'
2524 def draw(_
,context
):
2525 active_object
= bpy
.context
.active_object
2527 if active_object
== None: return
2530 "scale": bpy
.context
.scene
.cxr_data
.scale_factor
,
2534 ecn
= cxr_intrinsic_classname( active_object
)
2535 classname
= cxr_custom_class( active_object
)
2538 if active_object
.type == 'MESH':
2539 _
.layout
.prop( active_object
.cxr_data
, 'brushclass' )
2540 else: _
.layout
.prop( active_object
.cxr_data
, 'classname' )
2542 _
.layout
.prop( active_object
.cxr_data
, 'visgroup' )
2543 _
.layout
.prop( active_object
.cxr_data
, 'lightmap_override' )
2545 if classname
== 'NONE':
2548 _
.layout
.label(text
=F
"<implementation defined ({ecn})>")
2549 _
.layout
.enabled
=False
2552 kvs
= cxr_entity_keyvalues( {
2553 "object": active_object
,
2554 "transform": default_context
,
2555 "classname": classname
2561 _
.layout
.prop( active_object
, F
'["cxrkv_{kv[0]}"]', text
=kv
[0])
2563 row
= _
.layout
.row()
2565 row
.label( text
=F
'{kv[0]}: {repr(kv[2])}' )
2567 _
.layout
.label( text
=F
"ERROR: NO CLASS DEFINITION" )
2569 class CXR_LIGHT_PANEL(bpy
.types
.Panel
):
2570 bl_label
= "Source Settings"
2571 bl_idname
= "LIGHT_PT_cxr"
2572 bl_space_type
= 'PROPERTIES'
2573 bl_region_type
= 'WINDOW'
2576 def draw(self
, context
):
2577 layout
= self
.layout
2578 scene
= context
.scene
2580 active_object
= bpy
.context
.active_object
2581 if active_object
== None: return
2583 if active_object
.type == 'LIGHT' or \
2584 active_object
.type == 'LIGHT_PROBE':
2586 properties
= active_object
.data
.cxr_data
2588 if active_object
.type == 'LIGHT':
2589 layout
.prop( properties
, "realtime" )
2590 elif active_object
.type == 'LIGHT_PROBE':
2591 layout
.prop( properties
, "size" )
2593 class CXR_COLLECTION_PANEL(bpy
.types
.Panel
):
2594 bl_label
= "Source Settings"
2595 bl_idname
= "COL_PT_cxr"
2596 bl_space_type
= 'PROPERTIES'
2597 bl_region_type
= 'WINDOW'
2598 bl_context
= "collection"
2600 def draw(self
, context
):
2601 layout
= self
.layout
2602 scene
= context
.scene
2604 active_collection
= bpy
.context
.collection
2606 if active_collection
!= None:
2607 layout
.prop( active_collection
.cxr_data
, "shadow_caster" )
2608 layout
.prop( active_collection
.cxr_data
, "texture_shadows" )
2609 layout
.prop( active_collection
.cxr_data
, "preserve_order" )
2610 layout
.prop( active_collection
.cxr_data
, "surfaceprop" )
2611 layout
.prop( active_collection
.cxr_data
, "visgroup" )
2614 # ------------------------------------------------------------------------------
2616 class CXR_IMAGE_SETTINGS(bpy
.types
.PropertyGroup
):
2617 export_res
: bpy
.props
.IntVectorProperty(
2619 description
="Texture Export Resolution",
2625 fmt
: bpy
.props
.EnumProperty(
2628 ('DXT1', "DXT1", "BC1 compressed", '', 0),
2629 ('DXT5', "DXT5", "BC3 compressed", '', 1),
2630 ('RGB', "RGB", "Uncompressed", '', 2),
2631 ('RGBA', "RGBA", "Uncompressed (with alpha)", '', 3)
2633 description
="Image format",
2636 last_hash
: bpy
.props
.StringProperty( name
="" )
2637 asset_id
: bpy
.props
.IntProperty(name
="intl_assetid",default
=0)
2639 mipmap
: bpy
.props
.BoolProperty(name
="MIP",default
=True)
2640 lod
: bpy
.props
.BoolProperty(name
="LOD",default
=True)
2641 clamp
: bpy
.props
.BoolProperty(name
="CLAMP",default
=False)
2642 flags
: bpy
.props
.IntProperty(name
="flags",default
=0)
2644 class CXR_LIGHT_SETTINGS(bpy
.types
.PropertyGroup
):
2645 realtime
: bpy
.props
.BoolProperty(name
="Realtime Light", default
=True)
2647 class CXR_CUBEMAP_SETTINGS(bpy
.types
.PropertyGroup
):
2648 size
: bpy
.props
.EnumProperty(
2651 ('1',"1x1",'','',0),
2652 ('2',"2x2",'','',1),
2653 ('3',"4x4",'','',2),
2654 ('4',"8x8",'','',3),
2655 ('5',"16x16",'','',4),
2656 ('6',"32x32",'','',5),
2657 ('7',"64x64",'','',6),
2658 ('8',"128x128",'','',7),
2659 ('9',"256x256",'','',8)
2661 description
="Texture resolution",
2664 class CXR_ENTITY_SETTINGS(bpy
.types
.PropertyGroup
):
2665 entity
: bpy
.props
.BoolProperty(name
="")
2667 enum_pointents
= [('NONE',"None","")]
2668 enum_brushents
= [('NONE',"None","")]
2670 for classname
in cxr_entities
:
2671 entdef
= cxr_entities
[classname
]
2672 if 'allow' in entdef
:
2673 itm
= [(classname
, classname
, "")]
2674 if 'EMPTY' in entdef
['allow']: enum_pointents
+= itm
2675 else: enum_brushents
+= itm
2677 classname
: bpy
.props
.EnumProperty(items
=enum_pointents
, name
="Class", \
2678 update
=cxr_entity_changeclass
, default
='NONE' )
2680 brushclass
: bpy
.props
.EnumProperty(items
=enum_brushents
, name
="Class", \
2681 update
=cxr_entity_changeclass
, default
='NONE' )
2683 enum_classes
= [('0',"None","")]
2684 for i
, vg
in enumerate(cxr_visgroups
):
2685 enum_classes
+= [(str(i
+1),vg
,"")]
2686 visgroup
: bpy
.props
.EnumProperty(name
="visgroup",items
=enum_classes
,default
=0)
2687 lightmap_override
: bpy
.props
.IntProperty(name
="Lightmap Override",default
=0)
2689 class CXR_MODEL_SETTINGS(bpy
.types
.PropertyGroup
):
2690 last_hash
: bpy
.props
.StringProperty( name
="" )
2691 asset_id
: bpy
.props
.IntProperty(name
="vmf_settings",default
=0)
2692 shadow_caster
: bpy
.props
.BoolProperty( name
="Shadow caster", default
=True )
2693 texture_shadows
: bpy
.props
.BoolProperty( name
="Texture Shadows", default
=False )
2694 preserve_order
: bpy
.props
.BoolProperty( name
="Preserve Order", default
=False )
2695 surfaceprop
: bpy
.props
.StringProperty( name
="Suface prop",default
="default" )
2697 enum_classes
= [('0',"None","")]
2698 for i
, vg
in enumerate(cxr_visgroups
):
2699 enum_classes
+= [(str(i
+1),vg
,"")]
2700 visgroup
: bpy
.props
.EnumProperty(name
="visgroup",items
=enum_classes
,default
=0)
2702 class CXR_SCENE_SETTINGS(bpy
.types
.PropertyGroup
):
2703 project_name
: bpy
.props
.StringProperty( name
="Project Name" )
2704 subdir
: bpy
.props
.StringProperty( name
="../csgo/ folder" )
2706 exe_studiomdl
: bpy
.props
.StringProperty( name
="studiomdl" )
2707 exe_vbsp
: bpy
.props
.StringProperty( name
="vbsp" )
2708 opt_vbsp
: bpy
.props
.StringProperty( name
="args" )
2709 exe_vvis
: bpy
.props
.StringProperty( name
="vvis" )
2710 opt_vvis
: bpy
.props
.StringProperty( name
="args" )
2711 exe_vrad
: bpy
.props
.StringProperty( name
="vrad" )
2712 opt_vrad
: bpy
.props
.StringProperty( name
="args", \
2713 default
="-reflectivityScale 0.35 -aoscale 1.4 -final -textureshadows -hdr -StaticPropLighting -StaticPropPolys" )
2715 scale_factor
: bpy
.props
.FloatProperty( name
="VMF Scale factor", \
2716 default
=32.0,min=1.0)
2717 skybox_scale_factor
: bpy
.props
.FloatProperty( name
="Sky Scale factor", \
2718 default
=1.0,min=0.01)
2719 skyname
: bpy
.props
.StringProperty(name
="Skyname",default
="sky_csgo_night02b")
2720 skybox_offset
: bpy
.props
.FloatProperty(name
="Sky offset",default
=-4096.0)
2721 light_scale
: bpy
.props
.FloatProperty(name
="Light Scale",default
=1.0/5.0)
2722 include_names
: bpy
.props
.BoolProperty(name
="Append original file names",\
2724 lightmap_scale
: bpy
.props
.IntProperty(name
="Global Lightmap Scale",\
2726 image_quality
: bpy
.props
.IntProperty(name
="Texture Quality (0-18)",\
2727 default
=8, min=0, max=18 )
2729 comp_vmf
: bpy
.props
.BoolProperty(name
="VMF",default
=True)
2730 comp_models
: bpy
.props
.BoolProperty(name
="Models",default
=True)
2731 comp_textures
: bpy
.props
.BoolProperty(name
="Textures",default
=True)
2732 comp_compile
: bpy
.props
.BoolProperty(name
="Compile",default
=True)
2733 comp_pack
: bpy
.props
.BoolProperty(name
="Pack",default
=False)
2735 dev_mdl
: bpy
.props
.StringProperty(name
="Model",default
="")
2737 classes
= [ CXR_RELOAD
, CXR_DEV_OPERATOR
, CXR_INTERFACE
, \
2738 CXR_MATERIAL_PANEL
, CXR_IMAGE_SETTINGS
,\
2739 CXR_MODEL_SETTINGS
, CXR_ENTITY_SETTINGS
, CXR_CUBEMAP_SETTINGS
,\
2740 CXR_LIGHT_SETTINGS
, CXR_SCENE_SETTINGS
, CXR_DETECT_COMPILERS
,\
2741 CXR_ENTITY_PANEL
, CXR_LIGHT_PANEL
, CXR_PREVIEW_OPERATOR
,\
2742 CXR_VIEW3D
, CXR_COMPILER_CHAIN
, CXR_RESET_HASHES
,\
2743 CXR_COMPILE_MATERIAL
, CXR_COLLECTION_PANEL
, CXR_RESET
, \
2744 CXR_INIT_FS_OPERATOR
, CXR_LOAD_MODEL_OPERATOR
]
2746 vmt_param_dynamic_class
= None
2749 global cxr_view_draw_handler
, vmt_param_dynamic_class
, cxr_ui_handler
2752 bpy
.utils
.register_class(c
)
2754 # Build dynamic VMT properties class defined by cxr_shader_params
2755 annotations_dict
= {}
2757 def _dvmt_propogate(layer
):
2758 nonlocal annotations_dict
2761 if isinstance(layer
[decl
],dict): # $property definition
2765 if pdef
['type'] == 'bool':
2766 prop
= bpy
.props
.BoolProperty(\
2767 name
= pdef
['name'],\
2768 default
= pdef
['default'])
2770 elif pdef
['type'] == 'float':
2771 prop
= bpy
.props
.FloatProperty(\
2772 name
= pdef
['name'],\
2773 default
= pdef
['default'])
2775 elif pdef
['type'] == 'vector':
2776 if 'subtype' in pdef
:
2777 prop
= bpy
.props
.FloatVectorProperty(\
2778 name
= pdef
['name'],\
2779 subtype
= pdef
['subtype'],\
2780 default
= pdef
['default'],\
2781 size
= len(pdef
['default']))
2783 prop
= bpy
.props
.FloatVectorProperty(\
2784 name
= pdef
['name'],\
2785 default
= pdef
['default'],\
2786 size
= len(pdef
['default']))
2788 elif pdef
['type'] == 'string':
2789 prop
= bpy
.props
.StringProperty(\
2790 name
= pdef
['name'],\
2791 default
= pdef
['default'])
2793 elif pdef
['type'] == 'enum':
2794 prop
= bpy
.props
.EnumProperty(\
2795 name
= pdef
['name'],\
2796 items
= pdef
['items'],\
2797 default
= pdef
['default'])
2800 annotations_dict
[decl
] = prop
2802 # Recurse into sub-definitions
2803 _dvmt_propogate(pdef
)
2805 annotations_dict
["shader"] = bpy
.props
.EnumProperty(\
2808 cxr_shaders
[_
]["name"],\
2809 '') for _
in cxr_shaders
],\
2810 default
= next(iter(cxr_shaders
)))
2812 annotations_dict
["asset_id"] = bpy
.props
.IntProperty(name
="intl_assetid",\
2815 _dvmt_propogate( cxr_shader_params
)
2816 vmt_param_dynamic_class
= type(
2818 (bpy
.types
.PropertyGroup
,),{
2819 "__annotations__": annotations_dict
2823 bpy
.utils
.register_class( vmt_param_dynamic_class
)
2826 bpy
.types
.Material
.cxr_data
= \
2827 bpy
.props
.PointerProperty(type=vmt_param_dynamic_class
)
2828 bpy
.types
.Image
.cxr_data
= \
2829 bpy
.props
.PointerProperty(type=CXR_IMAGE_SETTINGS
)
2830 bpy
.types
.Object
.cxr_data
= \
2831 bpy
.props
.PointerProperty(type=CXR_ENTITY_SETTINGS
)
2832 bpy
.types
.Collection
.cxr_data
= \
2833 bpy
.props
.PointerProperty(type=CXR_MODEL_SETTINGS
)
2834 bpy
.types
.Light
.cxr_data
= \
2835 bpy
.props
.PointerProperty(type=CXR_LIGHT_SETTINGS
)
2836 bpy
.types
.LightProbe
.cxr_data
= \
2837 bpy
.props
.PointerProperty(type=CXR_CUBEMAP_SETTINGS
)
2838 bpy
.types
.Scene
.cxr_data
= \
2839 bpy
.props
.PointerProperty(type=CXR_SCENE_SETTINGS
)
2841 # CXR Scene settings
2844 cxr_view_draw_handler
= bpy
.types
.SpaceView3D
.draw_handler_add(\
2845 cxr_draw
,(),'WINDOW','POST_VIEW')
2847 cxr_ui_handler
= bpy
.types
.SpaceView3D
.draw_handler_add(\
2848 cxr_ui
,(None,None),'WINDOW','POST_PIXEL')
2850 bpy
.app
.handlers
.load_post
.append(cxr_on_load
)
2851 bpy
.app
.handlers
.depsgraph_update_post
.append(cxr_dgraph_update
)
2854 global cxr_view_draw_handler
, vmt_param_dynamic_class
, cxr_ui_handler
2856 bpy
.utils
.unregister_class( vmt_param_dynamic_class
)
2858 bpy
.utils
.unregister_class(c
)
2860 bpy
.app
.handlers
.depsgraph_update_post
.remove(cxr_dgraph_update
)
2861 bpy
.app
.handlers
.load_post
.remove(cxr_on_load
)
2863 bpy
.types
.SpaceView3D
.draw_handler_remove(cxr_view_draw_handler
,'WINDOW')
2864 bpy
.types
.SpaceView3D
.draw_handler_remove(cxr_ui_handler
,'WINDOW')