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
56 cxr_view_shader
= gpu
.shader
.from_builtin('3D_SMOOTH_COLOR')
57 cxr_ui_shader
= gpu
.types
.GPUShader("""
58 uniform mat4 ModelViewProjectionMatrix;
68 gl_Position = ModelViewProjectionMatrix * vec4(aPos.x*scale,aPos.y, 0.0, 1.0);
83 def cxr_ui(_
,context
):
84 global cxr_jobs_batch
, cxr_ui_shader
, cxr_jobs_inf
, cxr_error_inf
86 w
= gpu
.state
.viewport_get()[2]
88 cxr_ui_shader
.uniform_float( "scale", w
)
90 if cxr_error_inf
!= None:
93 if isinstance(cxr_error_inf
[1],list):
94 err_begin
+= 20*(len(cxr_error_inf
[1])-1)
96 blf
.position(0,2,err_begin
,0)
98 blf
.color(0, 1.0,0.2,0.2,0.9)
99 blf
.draw(0,cxr_error_inf
[0])
102 blf
.color(0, 1.0,1.0,1.0,1.0)
104 if isinstance(cxr_error_inf
[1],list):
105 for i
,inf
in enumerate(cxr_error_inf
[1]):
106 blf
.position(0,2,err_begin
-30-i
*20,0)
109 blf
.position(0,2,err_begin
-30,0)
110 blf
.draw(0,cxr_error_inf
[1])
112 elif cxr_jobs_batch
!= None:
113 gpu
.state
.blend_set('ALPHA')
114 cxr_jobs_batch
.draw(cxr_ui_shader
)
116 blf
.position(0,2,50,0)
118 blf
.color(0,1.0,1.0,1.0,1.0)
119 blf
.draw(0,"Compiling")
121 for ji
in cxr_jobs_inf
:
122 blf
.position(0,ji
[0]*w
,35,0)
128 for ln
in reversed(CXR_COMPILER_CHAIN
.LOG
[-25:]):
129 blf
.position(0,2,py
,0)
133 # Something is off with TIMER,
134 # this forces the viewport to redraw before we can continue with our
137 CXR_COMPILER_CHAIN
.WAIT_REDRAW
= False
140 global cxr_view_shader
, cxr_view_mesh
, cxr_view_lines
142 cxr_view_shader
.bind()
144 gpu
.state
.depth_mask_set(False)
145 gpu
.state
.line_width_set(1.5)
146 gpu
.state
.face_culling_set('BACK')
147 gpu
.state
.depth_test_set('NONE')
148 gpu
.state
.blend_set('ALPHA')
150 if cxr_view_lines
!= None:
151 cxr_view_lines
.draw( cxr_view_shader
)
153 gpu
.state
.depth_test_set('LESS_EQUAL')
154 gpu
.state
.blend_set('ADDITIVE')
155 if cxr_view_mesh
!= None:
156 cxr_view_mesh
.draw( cxr_view_shader
)
158 def cxr_jobs_update_graph(jobs
):
159 global cxr_jobs_batch
, cxr_ui_shader
, cxr_jobs_inf
169 total_width
+= sys
['w']
178 colour
= sys
['colour']
179 colourwait
= (colour
[0],colour
[1],colour
[2],0.4)
180 colourrun
= (colour
[0]*1.5,colour
[1]*1.5,colour
[2]*1.5,0.5)
181 colourdone
= (colour
[0],colour
[1],colour
[2],1.0)
184 sfsub
= (1.0/(len(jobs
)))*w
188 if j
== None: colour
= colourdone
189 else: colour
= colourwait
191 px
= (cur
+ (i
)*sfsub
) * sf
192 px1
= (cur
+ (i
+1.0)*sfsub
) * sf
195 verts
+= [(px
,0), (px
, h
), (px1
, 0.0), (px1
,h
)]
196 colours
+= [colour
,colour
,colour
,colour
]
197 indices
+= [(ci
+0,ci
+2,ci
+3),(ci
+0,ci
+3,ci
+1)]
200 cxr_jobs_inf
+= [((sf
*cur
), sys
['title'])]
203 cxr_jobs_batch
= batch_for_shader(
204 cxr_ui_shader
, 'TRIS',
205 { "aPos": verts
, "aColour": colours
},
209 # view_layer.update() doesnt seem to work,
210 # tag_redraw() seems to have broken
211 # therefore, change a property
213 ob
= bpy
.context
.scene
.objects
[0]
214 ob
.hide_render
= ob
.hide_render
216 # the 'real' way to refresh the scene
217 for area
in bpy
.context
.window
.screen
.areas
:
218 if area
.type == 'view_3d':
222 # ------------------------------------------------------------------------------
225 # dlclose for reloading modules manually
227 libc_dlclose
= cdll
.LoadLibrary(None).dlclose
228 libc_dlclose
.argtypes
= [c_void_p
]
230 # wrapper for ctypes binding
232 def __init__(_
,name
,argtypes
,restype
):
234 _
.argtypes
= argtypes
239 _
.call
= getattr(so
,_
.name
)
240 _
.call
.argtypes
= _
.argtypes
242 if _
.restype
!= None:
243 _
.call
.restype
= _
.restype
246 # ------------------------------------------------------------------------------
250 # Structure definitions
252 class cxr_edge(Structure
):
253 _fields_
= [("i0",c_int32
),
255 ("freestyle",c_int32
),
258 class cxr_static_loop(Structure
):
259 _fields_
= [("index",c_int32
),
260 ("edge_index",c_int32
),
263 class cxr_polygon(Structure
):
264 _fields_
= [("loop_start",c_int32
),
265 ("loop_total",c_int32
),
266 ("normal",c_double
* 3),
267 ("center",c_double
* 3),
268 ("material_id",c_int32
)]
270 class cxr_material(Structure
):
271 _fields_
= [("res",c_int32
* 2),
274 class cxr_static_mesh(Structure
):
275 _fields_
= [("vertices",POINTER(c_double
* 3)),
276 ("edges",POINTER(cxr_edge
)),
277 ("loops",POINTER(cxr_static_loop
)),
278 ("polys",POINTER(cxr_polygon
)),
279 ("materials",POINTER(cxr_material
)),
281 ("poly_count",c_int32
),
282 ("vertex_count",c_int32
),
283 ("edge_count",c_int32
),
284 ("loop_count",c_int32
),
285 ("material_count",c_int32
)]
287 class cxr_tri_mesh(Structure
):
288 _fields_
= [("vertices",POINTER(c_double
*3)),
289 ("colours",POINTER(c_double
*4)),
290 ("indices",POINTER(c_int32
)),
291 ("indices_count",c_int32
),
292 ("vertex_count",c_int32
)]
294 class cxr_visgroup(Structure
):
295 _fields_
= [("name",c_char_p
)]
297 class cxr_vmf_context(Structure
):
298 _fields_
= [("mapversion",c_int32
),
299 ("skyname",c_char_p
),
300 ("detailvbsp",c_char_p
),
301 ("detailmaterial",c_char_p
),
302 ("visgroups",POINTER(cxr_visgroup
)),
303 ("visgroup_count",c_int32
),
305 ("offset",c_double
*3),
306 ("lightmap_scale",c_int32
),
307 ("visgroupid",c_int32
),
308 ("brush_count",c_int32
),
309 ("entity_count",c_int32
),
310 ("face_count",c_int32
)]
312 # Convert blenders mesh format into CXR's static format (they are very similar)
314 def mesh_cxr_format(obj
):
317 if bpy
.context
.active_object
!= None:
318 orig_state
= obj
.mode
319 if orig_state
!= 'OBJECT':
320 bpy
.ops
.object.mode_set(mode
='OBJECT')
322 dgraph
= bpy
.context
.evaluated_depsgraph_get()
323 data
= obj
.evaluated_get(dgraph
).data
325 _
,mtx_rot
,_
= obj
.matrix_world
.decompose()
327 mesh
= cxr_static_mesh()
329 vertex_data
= ((c_double
*3)*len(data
.vertices
))()
330 for i
, vert
in enumerate(data
.vertices
):
331 v
= obj
.matrix_world
@ vert
.co
332 vertex_data
[i
][0] = c_double(v
[0])
333 vertex_data
[i
][1] = c_double(v
[1])
334 vertex_data
[i
][2] = c_double(v
[2])
336 loop_data
= (cxr_static_loop
*len(data
.loops
))()
337 polygon_data
= (cxr_polygon
*len(data
.polygons
))()
339 for i
, poly
in enumerate(data
.polygons
):
340 loop_start
= poly
.loop_start
341 loop_end
= poly
.loop_start
+ poly
.loop_total
342 for loop_index
in range(loop_start
, loop_end
):
343 loop
= data
.loops
[loop_index
]
344 loop_data
[loop_index
].index
= loop
.vertex_index
345 loop_data
[loop_index
].edge_index
= loop
.edge_index
348 uv
= data
.uv_layers
.active
.data
[loop_index
].uv
349 loop_data
[loop_index
].uv
[0] = c_double(uv
[0])
350 loop_data
[loop_index
].uv
[1] = c_double(uv
[1])
352 loop_data
[loop_index
].uv
[0] = c_double(0.0)
353 loop_data
[loop_index
].uv
[1] = c_double(0.0)
354 center
= obj
.matrix_world
@ poly
.center
355 normal
= mtx_rot
@ poly
.normal
357 polygon_data
[i
].loop_start
= poly
.loop_start
358 polygon_data
[i
].loop_total
= poly
.loop_total
359 polygon_data
[i
].normal
[0] = normal
[0]
360 polygon_data
[i
].normal
[1] = normal
[1]
361 polygon_data
[i
].normal
[2] = normal
[2]
362 polygon_data
[i
].center
[0] = center
[0]
363 polygon_data
[i
].center
[1] = center
[1]
364 polygon_data
[i
].center
[2] = center
[2]
365 polygon_data
[i
].material_id
= poly
.material_index
367 edge_data
= (cxr_edge
*len(data
.edges
))()
369 for i
, edge
in enumerate(data
.edges
):
370 edge_data
[i
].i0
= edge
.vertices
[0]
371 edge_data
[i
].i1
= edge
.vertices
[1]
372 edge_data
[i
].freestyle
= edge
.use_freestyle_mark
373 edge_data
[i
].sharp
= edge
.use_edge_sharp
375 material_data
= (cxr_material
*len(obj
.material_slots
))()
377 for i
, ms
in enumerate(obj
.material_slots
):
378 inf
= material_info(ms
.material
)
379 material_data
[i
].res
[0] = inf
['res'][0]
380 material_data
[i
].res
[1] = inf
['res'][1]
381 material_data
[i
].name
= inf
['name'].encode('utf-8')
383 mesh
.edges
= cast(edge_data
, POINTER(cxr_edge
))
384 mesh
.vertices
= cast(vertex_data
, POINTER(c_double
*3))
385 mesh
.loops
= cast(loop_data
,POINTER(cxr_static_loop
))
386 mesh
.polys
= cast(polygon_data
, POINTER(cxr_polygon
))
387 mesh
.materials
= cast(material_data
, POINTER(cxr_material
))
389 mesh
.poly_count
= len(data
.polygons
)
390 mesh
.vertex_count
= len(data
.vertices
)
391 mesh
.edge_count
= len(data
.edges
)
392 mesh
.loop_count
= len(data
.loops
)
393 mesh
.material_count
= len(obj
.material_slots
)
395 if orig_state
!= None:
396 bpy
.ops
.object.mode_set(mode
=orig_state
)
400 # Callback ctypes indirection things.. not really sure.
401 c_libcxr_log_callback
= None
402 c_libcxr_line_callback
= None
405 # -------------------------------------------------------------
406 libcxr_decompose
= extern( "cxr_decompose",
407 [POINTER(cxr_static_mesh
), POINTER(c_int32
)],
410 libcxr_free_world
= extern( "cxr_free_world",
414 libcxr_write_test_data
= extern( "cxr_write_test_data",
415 [POINTER(cxr_static_mesh
)],
418 libcxr_world_preview
= extern( "cxr_world_preview",
420 POINTER(cxr_tri_mesh
)
422 libcxr_free_tri_mesh
= extern( "cxr_free_tri_mesh",
426 libcxr_begin_vmf
= extern( "cxr_begin_vmf",
427 [POINTER(cxr_vmf_context
), c_void_p
],
430 libcxr_vmf_begin_entities
= extern( "cxr_vmf_begin_entities",
431 [POINTER(cxr_vmf_context
), c_void_p
],
434 libcxr_push_world_vmf
= extern("cxr_push_world_vmf",
435 [c_void_p
,POINTER(cxr_vmf_context
),c_void_p
],
438 libcxr_end_vmf
= extern( "cxr_end_vmf",
439 [POINTER(cxr_vmf_context
),c_void_p
],
443 # VDF + with open wrapper
444 libcxr_vdf_open
= extern( "cxr_vdf_open", [c_char_p
], c_void_p
)
445 libcxr_vdf_close
= extern( "cxr_vdf_close", [c_void_p
], None )
446 libcxr_vdf_put
= extern( "cxr_vdf_put", [c_void_p
,c_char_p
], None )
447 libcxr_vdf_node
= extern( "cxr_vdf_node", [c_void_p
,c_char_p
], None )
448 libcxr_vdf_edon
= extern( "cxr_vdf_edon", [c_void_p
], None )
449 libcxr_vdf_kv
= extern( "cxr_vdf_kv", [c_void_p
,c_char_p
,c_char_p
], None )
451 class vdf_structure():
452 def __init__(_
,path
):
455 _
.fp
= libcxr_vdf_open
.call( _
.path
.encode('utf-8') )
457 print( F
"Could not open file {_.path}" )
460 def __exit__(_
,type,value
,traceback
):
462 libcxr_vdf_close
.call(_
.fp
)
464 libcxr_vdf_put
.call(_
.fp
, s
.encode('utf-8') )
466 libcxr_vdf_node
.call(_
.fp
, name
.encode('utf-8') )
468 libcxr_vdf_edon
.call(_
.fp
)
470 libcxr_vdf_kv
.call(_
.fp
, k
.encode('utf-8'), v
.encode('utf-8'))
473 libcxr_lightpatch_bsp
= extern( "cxr_lightpatch_bsp", [c_char_p
], None )
475 libcxr_funcs
= [ libcxr_decompose
, libcxr_free_world
, libcxr_begin_vmf
, \
476 libcxr_vmf_begin_entities
, libcxr_push_world_vmf
, \
477 libcxr_end_vmf
, libcxr_vdf_open
, libcxr_vdf_close
, \
478 libcxr_vdf_put
, libcxr_vdf_node
, libcxr_vdf_edon
,
479 libcxr_vdf_kv
, libcxr_lightpatch_bsp
, libcxr_write_test_data
,\
480 libcxr_world_preview
, libcxr_free_tri_mesh
]
484 def libcxr_log_callback(logStr
):
485 print( F
"{logStr.decode('utf-8')}",end
='' )
487 cxr_line_positions
= None
488 cxr_line_colours
= None
490 def cxr_reset_lines():
491 global cxr_line_positions
, cxr_line_colours
493 cxr_line_positions
= []
494 cxr_line_colours
= []
496 def cxr_batch_lines():
497 global cxr_line_positions
, cxr_line_colours
, cxr_view_shader
, cxr_view_lines
499 cxr_view_lines
= batch_for_shader(\
500 cxr_view_shader
, 'LINES',\
501 { "pos": cxr_line_positions
, "color": cxr_line_colours
})
503 def libcxr_line_callback( p0
,p1
,colour
):
504 global cxr_line_colours
, cxr_line_positions
506 cxr_line_positions
+= [(p0
[0],p0
[1],p0
[2])]
507 cxr_line_positions
+= [(p1
[0],p1
[1],p1
[2])]
508 cxr_line_colours
+= [(colour
[0],colour
[1],colour
[2],colour
[3])]
509 cxr_line_colours
+= [(colour
[0],colour
[1],colour
[2],colour
[3])]
512 global cxr_jobs_inf
, cxr_jobs_batch
, cxr_error_inf
, cxr_view_mesh
514 cxr_jobs_batch
= None
524 # ------------------------------------------------------------------------------
529 NBVTF_IMAGE_FORMAT_ABGR8888
= 1
530 NBVTF_IMAGE_FORMAT_BGR888
= 3
531 NBVTF_IMAGE_FORMAT_DXT1
= 13
532 NBVTF_IMAGE_FORMAT_DXT5
= 15
533 NBVTF_TEXTUREFLAGS_CLAMPS
= 0x00000004
534 NBVTF_TEXTUREFLAGS_CLAMPT
= 0x00000008
535 NBVTF_TEXTUREFLAGS_NORMAL
= 0x00000080
536 NBVTF_TEXTUREFLAGS_NOMIP
= 0x00000100
537 NBVTF_TEXTUREFLAGS_NOLOD
= 0x00000200
539 libnbvtf_convert
= extern( "nbvtf_convert", \
540 [c_char_p
,c_int32
,c_int32
,c_int32
,c_int32
,c_int32
,c_uint32
,c_char_p
], \
543 libnbvtf_init
= extern( "nbvtf_init", [], None )
544 libnbvtf_funcs
= [ libnbvtf_convert
, libnbvtf_init
]
547 # --------------------------
550 global libcxr
, libnbvtf
, libcxr_funcs
, libnbvtf_funcs
552 # Unload libraries if existing
553 def _reload( lib
, path
):
556 _handle
= lib
._handle
557 for i
in range(10): libc_dlclose( _handle
)
561 libpath
= F
'{os.path.dirname(__file__)}/{path}{CXR_SHARED_EXT}'
562 return cdll
.LoadLibrary( libpath
)
564 libnbvtf
= _reload( libnbvtf
, "libnbvtf" )
565 libcxr
= _reload( libcxr
, "libcxr" )
567 for fd
in libnbvtf_funcs
:
568 fd
.loadfrom( libnbvtf
)
571 for fd
in libcxr_funcs
:
572 fd
.loadfrom( libcxr
)
575 global c_libcxr_log_callback
, c_libcxr_line_callback
577 LOG_FUNCTION_TYPE
= CFUNCTYPE(None,c_char_p
)
578 c_libcxr_log_callback
= LOG_FUNCTION_TYPE(libcxr_log_callback
)
580 LINE_FUNCTION_TYPE
= CFUNCTYPE(None,\
581 POINTER(c_double
), POINTER(c_double
), POINTER(c_double
))
582 c_libcxr_line_callback
= LINE_FUNCTION_TYPE(libcxr_line_callback
)
584 libcxr
.cxr_set_log_function(cast(c_libcxr_log_callback
,c_void_p
))
585 libcxr
.cxr_set_line_function(cast(c_libcxr_line_callback
,c_void_p
))
587 build_time
= c_char_p
.in_dll(libcxr
,'cxr_build_time')
588 print( F
"libcxr build time: {build_time.value}" )
593 # ------------------------------------------------------------------------------
595 # Standard entity functions, think of like base.fgd
597 def cxr_get_origin(context
):
598 return context
['object'].location
* context
['transform']['scale'] + \
599 mathutils
.Vector(context
['transform']['offset'])
601 def cxr_get_angles(context
):
602 obj
= context
['object']
603 euler
= [ a
*57.295779513 for a
in obj
.rotation_euler
]
610 def cxr_baseclass(classes
, other
):
613 base
.update(x
.copy())
616 def ent_soundscape(context
):
617 obj
= context
['object']
618 kvs
= cxr_baseclass([ent_origin
],\
620 "radius": obj
.scale
.x
* bpy
.context
.scene
.cxr_data
.scale_factor
,
621 "soundscape": {"type":"string","default":""}
626 # EEVEE Light component converter -> Source 1
628 def ent_lights(context
):
629 obj
= context
['object']
630 kvs
= cxr_baseclass([ent_origin
],\
632 "_distance": (0.0 if obj
.data
.cxr_data
.realtime
else -1.0),
633 "_lightHDR": '-1 -1 -1 1',
637 light_base
= [(pow(obj
.data
.color
[i
],1.0/2.2)*255.0) for i
in range(3)] +\
638 [obj
.data
.energy
* bpy
.context
.scene
.cxr_data
.light_scale
]
640 if obj
.data
.type == 'SPOT' or obj
.data
.type == 'SUN':
641 # Blenders directional lights are -z forward
642 # Source is +x, however, it seems to use a completely different system.
643 # Since we dont care about roll for spotlights, we just take the
644 # pitch and yaw via trig
646 _
,mtx_rot
,_
= obj
.matrix_world
.decompose()
647 fwd
= mtx_rot
@ mathutils
.Vector((0,0,-1))
648 dir_pitch
= math
.asin(fwd
[2]) * 57.295779513
649 dir_yaw
= math
.atan2(fwd
[1],fwd
[0]) * 57.295779513
651 if obj
.data
.type == 'SPOT':
652 kvs
['_light'] = [ int(x
) for x
in light_base
]
653 kvs
['_cone'] = obj
.data
.spot_size
*(57.295779513/2.0)
654 kvs
['_inner_cone'] = (1.0-obj
.data
.spot_blend
)*kvs
['_cone']
656 kvs
['pitch'] = dir_pitch
657 kvs
['angles'] = [ 0, dir_yaw
, 0 ]
658 kvs
['_quadratic_attn'] = 0.0 # Source spotlights + quadratic falloff look
661 # Blender's default has a much more 'nice'
663 kvs
['_linear_attn'] = 1.0
665 elif obj
.data
.type == 'POINT':
666 kvs
['_light'] = [ int(x
) for x
in light_base
]
667 kvs
['_quadratic_attn'] = 1.0
668 kvs
['_linear_attn'] = 1.0
670 elif obj
.data
.type == 'SUN':
671 light_base
[3] *= 300.0 * 5
672 kvs
['_light'] = [ int(x
) for x
in light_base
]
674 ambient
= bpy
.context
.scene
.world
.color
675 kvs
['_ambient'] = [int(pow(ambient
[i
],1.0/2.2)*255.0) for i
in range(3)] +\
677 kvs
['_ambientHDR'] = [-1,-1,-1,1]
678 kvs
['_AmbientScaleHDR'] = 1
679 kvs
['pitch'] = dir_pitch
680 kvs
['angles'] = [ dir_pitch
, dir_yaw
, 0.0 ]
681 kvs
['SunSpreadAngle'] = 0
685 def ent_prop(context
):
686 if isinstance( context
['object'], bpy
.types
.Collection
):
688 target
= context
['object']
689 pos
= mathutils
.Vector(context
['origin'])
690 pos
+= mathutils
.Vector(context
['transform']['offset'])
692 kvs
['origin'] = [pos
[1],-pos
[0],pos
[2]]
693 kvs
['angles'] = [0,180,0]
694 kvs
['uniformscale'] = 1.0
696 kvs
= cxr_baseclass([ent_origin
],{})
697 target
= context
['object'].instance_collection
699 obj
= context
['object']
700 euler
= [ a
*57.295779513 for a
in obj
.rotation_euler
]
703 angle
[1] = euler
[2] + 180.0 # Dunno...
706 kvs
['angles'] = angle
707 kvs
['uniformscale'] = obj
.scale
[0]
709 if target
.cxr_data
.shadow_caster
:
710 kvs
['enablelightbounce'] = 1
711 kvs
['disableshadows'] = 0
713 kvs
['enablelightbounce'] = 0
714 kvs
['disableshadows'] = 1
716 kvs
['fademindist'] = -1
718 kvs
['model'] = F
"{asset_path('models',target)}.mdl".lower()
719 kvs
['renderamt'] = 255
720 kvs
['rendercolor'] = [255, 255, 255]
726 def ent_sky_camera(context
):
727 settings
= bpy
.context
.scene
.cxr_data
728 scale
= settings
.scale_factor
/ settings
.skybox_scale_factor
731 "origin": [_
for _
in context
['transform']['offset']],
732 "angles": [ 0, 0, 0 ],
733 "fogcolor": [255, 255, 255],
734 "fogcolor2": [255, 255, 255],
739 "HDRColorScale": 1.0,
744 def ent_cubemap(context
):
745 obj
= context
['object']
746 return cxr_baseclass([ent_origin
], {"cubemapsize": obj
.data
.cxr_data
.size
})
748 ent_origin
= { "origin": cxr_get_origin
}
749 ent_angles
= { "angles": cxr_get_angles
}
750 ent_transform
= cxr_baseclass( [ent_origin
], ent_angles
)
752 #include the user config
753 exec(open(F
'{os.path.dirname(__file__)}/config.py').read())
755 # Blender state callbacks
756 # ------------------------------------------------------------------------------
759 def cxr_on_load(dummy
):
760 global cxr_view_lines
, cxr_view_mesh
762 cxr_view_lines
= None
766 def cxr_dgraph_update(scene
,dgraph
):
768 print( F
"Hallo {time.time()}" )
770 # Convexer compilation functions
771 # ------------------------------------------------------------------------------
773 # Asset path management
775 def asset_uid(asset
):
776 if isinstance(asset
,str):
779 # Create a unique ID string
781 v
= asset
.cxr_data
.asset_id
790 dig
.append( int( v
% len(base
) ) )
796 if bpy
.context
.scene
.cxr_data
.include_names
:
797 name
+= asset
.name
.replace('.','_')
801 # -> <project_name>/<asset_name>
802 def asset_name(asset
):
803 return F
"{bpy.context.scene.cxr_data.project_name}/{asset_uid(asset)}"
805 # -> <subdir>/<project_name>/<asset_name>
806 def asset_path(subdir
, asset
):
807 return F
"{subdir}/{asset_name(asset_uid(asset))}"
809 # -> <csgo>/<subdir>/<project_name>/<asset_name>
810 def asset_full_path(sdir
,asset
):
811 return F
"{bpy.context.scene.cxr_data.subdir}/"+\
812 F
"{asset_path(sdir,asset_uid(asset))}"
814 # Decomposes mesh, and sets global error information if failed.
815 # - returns None on fail
816 # - returns world on success
817 def cxr_decompose_globalerr( mesh_src
):
821 world
= libcxr_decompose
.call( mesh_src
, pointer(err
) )
828 ("No Error", "There is no error?"),\
829 ("Bad input", "Non manifold geometry is present in the input mesh"),\
830 ("Bad result","An invalid manifold was generated, try to simplify"),\
831 ("Bad result","Make sure there is a clear starting point"),\
832 ("Bad result","Implicit vertex was invalid, try to simplify"),\
833 ("Bad input","Non coplanar vertices are in the source mesh"),\
834 ("Bad input","Non convex polygon is in the source mesh"),\
835 ("Bad result","Undefined failure"),\
836 ("Invalid Input", "Undefined failure"),\
843 # Entity functions / infos
844 # ------------------------
846 def cxr_collection_purpose(collection
):
847 if collection
.name
.startswith('.'): return None
848 if collection
.hide_render
: return None
849 if collection
.name
.startswith('mdl_'): return 'model'
852 def cxr_object_purpose(obj
):
856 def _search(collection
):
857 nonlocal objpurpose
, group
, obj
859 purpose
= cxr_collection_purpose( collection
)
860 if purpose
== None: return
861 if purpose
== 'model':
862 for o
in collection
.objects
:
864 if o
.type != 'EMPTY':
869 for o
in collection
.objects
:
871 classname
= cxr_classname(o
)
872 if classname
!= None:
873 objpurpose
= 'entity'
875 objpurpose
= 'brush_entity'
882 for c
in collection
.children
:
885 if 'main' in bpy
.data
.collections
:
886 _search( bpy
.data
.collections
['main'] )
888 if objpurpose
== None and 'skybox' in bpy
.data
.collections
:
889 _search( bpy
.data
.collections
['skybox'] )
891 return (group
,objpurpose
)
893 def cxr_intrinsic_classname(obj
):
894 if obj
.type == 'LIGHT':
896 'SPOT': "light_spot",
898 'SUN': "light_environment" }[ obj
.data
.type ]
900 elif obj
.type == 'LIGHT_PROBE':
902 elif obj
.type == 'EMPTY':
908 def cxr_custom_class(obj
):
909 if obj
.type == 'MESH': custom_class
= obj
.cxr_data
.brushclass
910 else: custom_class
= obj
.cxr_data
.classname
914 def cxr_classname(obj
):
915 intr
= cxr_intrinsic_classname(obj
)
916 if intr
!= None: return intr
918 custom_class
= cxr_custom_class(obj
)
919 if custom_class
!= 'NONE':
925 # intinsic: (k, False, value)
926 # property: (k, True, value or default)
930 def cxr_entity_keyvalues(context
):
931 classname
= context
['classname']
932 obj
= context
['object']
933 if classname
not in cxr_entities
: return None
937 entdef
= cxr_entities
[classname
]
938 kvs
= entdef
['keyvalues']
940 if callable(kvs
): kvs
= kvs(context
)
947 if isinstance(kv
,dict):
949 value
= obj
[ F
"cxrkv_{k}" ]
954 if isinstance(value
,mathutils
.Vector
):
955 value
= [_
for _
in value
]
957 result
+= [(k
, isprop
, value
)]
961 # Extract material information from shader graph data
963 def material_info(mat
):
965 info
['res'] = (512,512)
966 info
['name'] = 'tools/toolsnodraw'
968 if mat
== None or mat
.use_nodes
== False:
972 if mat
.cxr_data
.shader
== 'Builtin':
973 info
['name'] = mat
.name
977 info
['name'] = asset_name(mat
)
979 # Using the cxr_graph_mapping as a reference, go through the shader
980 # graph and gather all $props from it.
982 def _graph_read( node_def
, node
=None, depth
=0 ):
986 def _variant_apply( val
):
989 if isinstance( val
, str ):
992 for shader_variant
in val
:
993 if shader_variant
[0] == mat
.cxr_data
.shader
:
994 return shader_variant
[1]
998 _graph_read
.extracted
= []
1000 for node_idname
in node_def
:
1001 for n
in mat
.node_tree
.nodes
:
1002 if n
.bl_idname
== node_idname
:
1003 node_def
= node_def
[node_idname
]
1007 for link
in node_def
:
1008 if isinstance( node_def
[link
], dict ):
1009 inputt
= node
.inputs
[link
]
1010 inputt_def
= node_def
[link
]
1012 if inputt
.is_linked
:
1014 # look for definitions for the connected node type
1015 con
= inputt
.links
[0].from_node
1017 for node_idname
in inputt_def
:
1018 if con
.bl_idname
== node_idname
:
1019 con_def
= inputt_def
[ node_idname
]
1020 _graph_read( con_def
, con
, depth
+1 )
1022 # No definition found! :(
1023 # TODO: Make a warning for this?
1026 if "default" in inputt_def
:
1027 prop
= _variant_apply( inputt_def
['default'] )
1028 info
[prop
] = inputt
.default_value
1030 prop
= _variant_apply( node_def
[link
] )
1031 info
[prop
] = getattr(node
,link
)
1033 _graph_read(cxr_graph_mapping
)
1035 if "$basetexture" in info
:
1036 export_res
= info
['$basetexture'].cxr_data
.export_res
1037 info
['res'] = (export_res
[0], export_res
[1])
1041 def vec3_min( a
, b
):
1042 return mathutils
.Vector((min(a
[0],b
[0]),min(a
[1],b
[1]),min(a
[2],b
[2])))
1043 def vec3_max( a
, b
):
1044 return mathutils
.Vector((max(a
[0],b
[0]),max(a
[1],b
[1]),max(a
[2],b
[2])))
1046 def cxr_collection_center(collection
, transform
):
1048 bounds_min
= mathutils
.Vector((BIG
,BIG
,BIG
))
1049 bounds_max
= mathutils
.Vector((-BIG
,-BIG
,-BIG
))
1051 for obj
in collection
.objects
:
1052 if obj
.type == 'MESH':
1053 corners
= [ mathutils
.Vector(c
) for c
in obj
.bound_box
]
1055 for corner
in [ obj
.matrix_world
@c for c
in corners
]:
1056 bounds_min
= vec3_min( bounds_min
, corner
)
1057 bounds_max
= vec3_max( bounds_max
, corner
)
1059 center
= (bounds_min
+ bounds_max
) / 2.0
1061 origin
= mathutils
.Vector((-center
[1],center
[0],center
[2]))
1062 origin
*= transform
['scale']
1066 # Prepares Scene into dictionary format
1068 def cxr_scene_collect():
1069 context
= bpy
.context
1071 # Make sure all of our asset types have a unique ID
1072 def _uid_prepare(objtype
):
1078 if vs
.asset_id
in used_ids
:
1081 id_max
= max(id_max
,vs
.asset_id
)
1082 used_ids
+=[vs
.asset_id
]
1083 for vs
in to_generate
:
1085 vs
.asset_id
= id_max
1086 _uid_prepare(bpy
.data
.materials
)
1087 _uid_prepare(bpy
.data
.images
)
1088 _uid_prepare(bpy
.data
.collections
)
1091 "entities": [], # Everything with a classname
1092 "geo": [], # All meshes without a classname
1093 "heros": [] # Collections prefixed with mdl_
1096 def _collect(collection
,transform
):
1099 purpose
= cxr_collection_purpose( collection
)
1100 if purpose
== None: return
1101 if purpose
== 'model':
1102 sceneinfo
['entities'] += [{
1103 "object": collection
,
1104 "classname": "prop_static",
1105 "transform": transform
,
1106 "origin": cxr_collection_center( collection
, transform
)
1109 sceneinfo
['heros'] += [{
1110 "collection": collection
,
1111 "transform": transform
,
1112 "origin": cxr_collection_center( collection
, transform
)
1116 for obj
in collection
.objects
:
1117 if obj
.hide_get(): continue
1119 classname
= cxr_classname( obj
)
1121 if classname
!= None:
1122 sceneinfo
['entities'] += [{
1124 "classname": classname
,
1125 "transform": transform
1127 elif obj
.type == 'MESH':
1128 sceneinfo
['geo'] += [{
1130 "transform": transform
1133 for c
in collection
.children
:
1134 _collect( c
, transform
)
1137 "scale": context
.scene
.cxr_data
.scale_factor
,
1142 "scale": context
.scene
.cxr_data
.skybox_scale_factor
,
1143 "offset": (0,0,context
.scene
.cxr_data
.skybox_offset
)
1146 if 'main' in bpy
.data
.collections
:
1147 _collect( bpy
.data
.collections
['main'], transform_main
)
1149 if 'skybox' in bpy
.data
.collections
:
1150 _collect( bpy
.data
.collections
['skybox'], transform_sky
)
1152 sceneinfo
['entities'] += [{
1154 "transform": transform_sky
,
1155 "classname": "sky_camera"
1160 # Write VMF out to file (JOB HANDLER)
1162 def cxr_export_vmf(sceneinfo
, output_vmf
):
1165 with
vdf_structure(output_vmf
) as m
:
1166 print( F
"Write: {output_vmf}" )
1168 vmfinfo
= cxr_vmf_context()
1169 vmfinfo
.mapversion
= 4
1171 #TODO: These need to be in options...
1172 vmfinfo
.skyname
= bpy
.context
.scene
.cxr_data
.skyname
.encode('utf-8')
1173 vmfinfo
.detailvbsp
= b
"detail.vbsp"
1174 vmfinfo
.detailmaterial
= b
"detail/detailsprites"
1175 vmfinfo
.lightmap_scale
= 12
1177 vmfinfo
.brush_count
= 0
1178 vmfinfo
.entity_count
= 0
1179 vmfinfo
.face_count
= 0
1181 visgroups
= (cxr_visgroup
*len(cxr_visgroups
))()
1182 for i
, vg
in enumerate(cxr_visgroups
):
1183 visgroups
[i
].name
= vg
.encode('utf-8')
1184 vmfinfo
.visgroups
= cast(visgroups
, POINTER(cxr_visgroup
))
1185 vmfinfo
.visgroup_count
= len(cxr_visgroups
)
1187 libcxr_begin_vmf
.call( pointer(vmfinfo
), m
.fp
)
1189 def _buildsolid( cmd
):
1192 print( F
"{vmfinfo.brush_count} :: {cmd['object'].name}" )
1194 baked
= mesh_cxr_format( cmd
['object'] )
1195 world
= cxr_decompose_globalerr( baked
)
1200 vmfinfo
.scale
= cmd
['transform']['scale']
1202 offset
= cmd
['transform']['offset']
1203 vmfinfo
.offset
[0] = offset
[0]
1204 vmfinfo
.offset
[1] = offset
[1]
1205 vmfinfo
.offset
[2] = offset
[2]
1207 if cmd
['object'].cxr_data
.lightmap_override
> 0:
1208 vmfinfo
.lightmap_scale
= cmd
['object'].cxr_data
.lightmap_override
1210 vmfinfo
.lightmap_scale
= bpy
.context
.scene
.cxr_data
.lightmap_scale
1212 libcxr_push_world_vmf
.call( world
, pointer(vmfinfo
), m
.fp
)
1213 libcxr_free_world
.call( world
)
1218 for brush
in sceneinfo
['geo']:
1219 vmfinfo
.visgroupid
= int(brush
['object'].cxr_data
.visgroup
)
1220 if not _buildsolid( brush
):
1224 vmfinfo
.visgroupid
= 0
1226 libcxr_vmf_begin_entities
.call(pointer(vmfinfo
), m
.fp
)
1229 for ent
in sceneinfo
['entities']:
1231 ctx
= ent
['transform']
1232 cls
= ent
['classname']
1235 m
.kv( 'classname', cls
)
1237 kvs
= cxr_entity_keyvalues( ent
)
1240 if isinstance(kv
[2], list):
1241 m
.kv( kv
[0], ' '.join([str(_
) for _
in kv
[2]]) )
1242 else: m
.kv( kv
[0], str(kv
[2]) )
1246 elif not isinstance( obj
, bpy
.types
.Collection
):
1247 if obj
.type == 'MESH':
1248 vmfinfo
.visgroupid
= int(obj
.cxr_data
.visgroup
)
1249 if not _buildsolid( ent
):
1256 m
.kv( 'visgroupid', str(obj
.cxr_data
.visgroup
) )
1257 m
.kv( 'visgroupshown', '1' )
1258 m
.kv( 'visgroupautoshown', '1' )
1262 vmfinfo
.visgroupid
= 0
1267 # COmpile image using NBVTF and hash it (JOB HANDLER)
1269 def compile_image(img
):
1273 name
= asset_name(img
)
1274 src_path
= bpy
.path
.abspath(img
.filepath
)
1276 dims
= img
.cxr_data
.export_res
1278 'RGBA': NBVTF_IMAGE_FORMAT_ABGR8888
,
1279 'DXT1': NBVTF_IMAGE_FORMAT_DXT1
,
1280 'DXT5': NBVTF_IMAGE_FORMAT_DXT5
,
1281 'RGB': NBVTF_IMAGE_FORMAT_BGR888
1282 }[ img
.cxr_data
.fmt
]
1284 mipmap
= img
.cxr_data
.mipmap
1285 lod
= img
.cxr_data
.lod
1286 clamp
= img
.cxr_data
.clamp
1287 flags
= img
.cxr_data
.flags
1289 q
=bpy
.context
.scene
.cxr_data
.image_quality
1291 userflag_hash
= F
"{mipmap}.{lod}.{clamp}.{flags}"
1292 file_hash
= F
"{name}.{os.path.getmtime(src_path)}"
1293 comphash
= F
"{file_hash}.{dims[0]}.{dims[1]}.{fmt}.{userflag_hash}.{q}"
1295 if img
.cxr_data
.last_hash
!= comphash
:
1296 print( F
"Texture update: {img.filepath}" )
1298 src
= src_path
.encode('utf-8')
1299 dst
= (asset_full_path('materials',img
)+'.vtf').encode('utf-8')
1303 # texture setting flags
1304 if not lod
: flags_full |
= NBVTF_TEXTUREFLAGS_NOLOD
1306 flags_full |
= NBVTF_TEXTUREFLAGS_CLAMPS
1307 flags_full |
= NBVTF_TEXTUREFLAGS_CLAMPT
1309 if libnbvtf_convert
.call(src
,dims
[0],dims
[1],mipmap
,fmt
,q
,flags_full
,dst
):
1310 img
.cxr_data
.last_hash
= comphash
1315 # Compile a material to VMT format. This is quick to run, doesnt need to be a
1318 def compile_material(mat
):
1319 info
= material_info(mat
)
1320 properties
= mat
.cxr_data
1322 print( F
"Compile {asset_full_path('materials',mat)}.vmt" )
1323 if properties
.shader
== 'Builtin':
1328 # Walk the property tree
1329 def _mlayer( layer
):
1330 nonlocal properties
, props
1333 if isinstance(layer
[decl
],dict): # $property definition
1335 ptype
= pdef
['type']
1341 if 'shaders' in pdef
and properties
.shader
not in pdef
['shaders']:
1344 # Group expansion (does it have subdefinitions?)
1346 if isinstance(pdef
[ch
],dict):
1355 if ptype
== 'intrinsic':
1359 prop
= getattr(properties
,decl
)
1360 default
= pdef
['default']
1362 if not isinstance(prop
,str) and \
1363 not isinstance(prop
,bpy
.types
.Image
) and \
1364 hasattr(prop
,'__getitem__'):
1365 prop
= tuple([p
for p
in prop
])
1369 props
+= [(decl
,pdef
,prop
)]
1374 if expandview
: _mlayer(pdef
)
1376 _mlayer( cxr_shader_params
)
1379 with
vdf_structure( F
"{asset_full_path('materials',mat)}.vmt" ) as vmt
:
1380 vmt
.node( properties
.shader
)
1381 vmt
.put( "// Convexer export\n" )
1390 if 'exponent' in pdef
: return str(pow( v
, pdef
['exponent'] ))
1393 if isinstance(prop
,bpy
.types
.Image
):
1394 vmt
.kv( decl
, asset_name(prop
))
1395 elif isinstance(prop
,bool):
1396 vmt
.kv( decl
, '1' if prop
else '0' )
1397 elif isinstance(prop
,str):
1398 vmt
.kv( decl
, prop
)
1399 elif isinstance(prop
,float) or isinstance(prop
,int):
1400 vmt
.kv( decl
, _numeric(prop
) )
1401 elif isinstance(prop
,tuple):
1402 vmt
.kv( decl
, F
"[{' '.join([_numeric(_) for _ in prop])}]" )
1404 vmt
.put( F
"// (cxr) unkown shader value type'{type(prop)}'" )
1409 def cxr_modelsrc_vphys( mdl
):
1410 for obj
in mdl
.objects
:
1411 if obj
.name
== F
"{mdl.name}_phy":
1415 def cxr_export_modelsrc( mdl
, origin
, asset_dir
, project_name
, transform
):
1416 dgraph
= bpy
.context
.evaluated_depsgraph_get()
1418 # Compute hash value
1419 chash
= asset_uid(mdl
)+str(origin
)+str(transform
)
1421 #for obj in mdl.objects:
1422 # if obj.type != 'MESH':
1425 # ev = obj.evaluated_get(dgraph).data
1426 # srcverts=[(v.co[0],v.co[1],v.co[2]) for v in ev.vertices]
1427 # srcloops=[(l.normal[0],l.normal[1],l.normal[2]) for l in ev.loops]
1429 # chash=hashlib.sha224((str(srcverts)+chash).encode()).hexdigest()
1430 # chash=hashlib.sha224((str(srcloops)+chash).encode()).hexdigest()
1432 # if ev.uv_layers.active != None:
1433 # uv_layer = ev.uv_layers.active.data
1434 # srcuv=[(uv.uv[0],uv.uv[1]) for uv in uv_layer]
1438 # chash=hashlib.sha224((str(srcuv)+chash).encode()).hexdigest()
1439 # srcmats=[ ms.material.name for ms in obj.material_slots ]
1440 # chash=hashlib.sha224((str(srcmats)+chash).encode()).hexdigest()
1441 # transforms=[ obj.location, obj.rotation_euler, obj.scale ]
1442 # srctr=[(v[0],v[1],v[2]) for v in transforms]
1443 # chash=hashlib.sha224((str(srctr)+chash).encode()).hexdigest()
1445 #if chash != mdl.cxr_data.last_hash:
1446 # mdl.cxr_data.last_hash = chash
1447 # print( F"Compile: {mdl.name}" )
1451 bpy
.ops
.object.select_all(action
='DESELECT')
1454 def _get_layer(col
,name
):
1455 for c
in col
.children
:
1458 sub
= _get_layer(c
,name
)
1462 layer
= _get_layer(bpy
.context
.view_layer
.layer_collection
,mdl
.name
)
1464 prev_state
= layer
.hide_viewport
1465 layer
.hide_viewport
=False
1467 # Collect materials to be compiled, and temp rename for export
1471 for obj
in mdl
.objects
:
1472 if obj
.name
== F
"{mdl.name}_phy":
1476 obj
.select_set(state
=True)
1477 for ms
in obj
.material_slots
:
1478 if ms
.material
!= None:
1479 if ms
.material
not in mat_dict
:
1480 mat_dict
[ms
.material
] = ms
.material
.name
1481 ms
.material
.name
= asset_uid(ms
.material
)
1482 ms
.material
.use_nodes
= False
1485 bpy
.ops
.export_scene
.fbx( filepath
=F
'{asset_dir}/{uid}_ref.fbx',\
1486 check_existing
=False,
1488 apply_unit_scale
=False,
1489 bake_space_transform
=False
1492 bpy
.ops
.object.select_all(action
='DESELECT')
1495 vphys
.select_set(state
=True)
1496 bpy
.ops
.export_scene
.fbx( filepath
=F
'{asset_dir}/{uid}_phy.fbx',\
1497 check_existing
=False,
1499 apply_unit_scale
=False,
1500 bake_space_transform
=False
1502 bpy
.ops
.object.select_all(action
='DESELECT')
1504 # Fix material names back to original
1505 for mat
in mat_dict
:
1506 mat
.name
= mat_dict
[mat
]
1507 mat
.use_nodes
= True
1509 layer
.hide_viewport
=prev_state
1512 with
open(F
'{asset_dir}/{uid}.qc','w') as o
:
1513 o
.write(F
'$modelname "{project_name}/{uid}"\n')
1514 #o.write(F'$scale .32\n')
1515 o
.write(F
'$scale {transform["scale"]/100.0}\n')
1516 o
.write(F
'$body _ "{uid}_ref.fbx"\n')
1517 o
.write(F
'$staticprop\n')
1518 o
.write(F
'$origin {origin[0]:.6f} {origin[1]:.6f} {origin[2]:.6f}\n')
1520 if mdl
.cxr_data
.preserve_order
:
1521 o
.write(F
"$preservetriangleorder\n")
1523 if mdl
.cxr_data
.texture_shadows
:
1524 o
.write(F
"$casttextureshadows\n")
1526 o
.write(F
"$surfaceprop {mdl.cxr_data.surfaceprop}\n")
1529 o
.write(F
'$collisionmodel "{uid}_phy.fbx"\n')
1531 o
.write(" $concave\n")
1534 o
.write(F
'$cdmaterials {project_name}\n')
1535 o
.write(F
'$sequence idle {uid}_ref.fbx\n')
1539 # Copy bsp file (and also lightpatch it)
1541 def cxr_patchmap( src
, dst
):
1542 libcxr_lightpatch_bsp
.call( src
.encode('utf-8') )
1543 shutil
.copyfile( src
, dst
)
1546 # Convexer operators
1547 # ------------------------------------------------------------------------------
1549 # Force reload of shared libraries
1551 class CXR_RELOAD(bpy
.types
.Operator
):
1552 bl_idname
="convexer.reload"
1554 def execute(_
,context
):
1558 # Reset all debugging/ui information
1560 class CXR_RESET(bpy
.types
.Operator
):
1561 bl_idname
="convexer.reset"
1562 bl_label
="Reset Convexer"
1563 def execute(_
,context
):
1567 # Used for exporting data to use with ASAN builds
1569 class CXR_DEV_OPERATOR(bpy
.types
.Operator
):
1570 bl_idname
="convexer.dev_test"
1571 bl_label
="Export development data"
1573 def execute(_
,context
):
1574 # Prepare input data
1575 mesh_src
= mesh_cxr_format(context
.active_object
)
1576 libcxr_write_test_data
.call( pointer(mesh_src
) )
1579 # UI: Preview how the brushes will looks in 3D view
1581 class CXR_PREVIEW_OPERATOR(bpy
.types
.Operator
):
1582 bl_idname
="convexer.preview"
1583 bl_label
="Preview Brushes"
1587 def execute(_
,context
):
1588 global cxr_view_mesh
1589 global cxr_view_shader
, cxr_view_mesh
, cxr_error_inf
1593 static
= _
.__class
__
1595 mesh_src
= mesh_cxr_format(context
.active_object
)
1596 world
= cxr_decompose_globalerr( mesh_src
)
1601 # Generate preview using cxr
1603 ptrpreview
= libcxr_world_preview
.call( world
)
1604 preview
= ptrpreview
[0]
1606 vertices
= preview
.vertices
[:preview
.vertex_count
]
1607 vertices
= [(_
[0],_
[1],_
[2]) for _
in vertices
]
1609 colours
= preview
.colours
[:preview
.vertex_count
]
1610 colours
= [(_
[0],_
[1],_
[2],_
[3]) for _
in colours
]
1612 indices
= preview
.indices
[:preview
.indices_count
]
1613 indices
= [ (indices
[i
*3+0],indices
[i
*3+1],indices
[i
*3+2]) \
1614 for i
in range(int(preview
.indices_count
/3)) ]
1616 cxr_view_mesh
= batch_for_shader(
1617 cxr_view_shader
, 'TRIS',
1618 { "pos": vertices
, "color": colours
},
1622 libcxr_free_tri_mesh
.call( ptrpreview
)
1623 libcxr_free_world
.call( world
)
1629 # Search for VMF compiler executables in subdirectory
1631 class CXR_DETECT_COMPILERS(bpy
.types
.Operator
):
1632 bl_idname
="convexer.detect_compilers"
1633 bl_label
="Find compilers"
1635 def execute(self
,context
):
1636 scene
= context
.scene
1637 settings
= scene
.cxr_data
1638 subdir
= settings
.subdir
1640 for exename
in ['studiomdl','vbsp','vvis','vrad']:
1641 searchpath
= os
.path
.normpath(F
'{subdir}/../bin/{exename}.exe')
1642 if os
.path
.exists(searchpath
):
1643 settings
[F
'exe_{exename}'] = searchpath
1647 def cxr_compiler_path( compiler
):
1648 settings
= bpy
.context
.scene
.cxr_data
1649 subdir
= settings
.subdir
1650 path
= os
.path
.normpath(F
'{subdir}/../bin/{compiler}.exe')
1652 if os
.path
.exists( path
): return path
1655 # Compatibility layer
1657 def cxr_temp_file( fn
):
1658 if CXR_GNU_LINUX
== 1:
1661 filepath
= bpy
.data
.filepath
1662 directory
= os
.path
.dirname(filepath
)
1663 return F
"{directory}/{fn}"
1665 def cxr_winepath( path
):
1666 if CXR_GNU_LINUX
== 1:
1667 return 'z:'+path
.replace('/','\\')
1671 # Main compile function
1673 class CXR_COMPILER_CHAIN(bpy
.types
.Operator
):
1674 bl_idname
="convexer.chain"
1675 bl_label
="Compile Chain"
1690 def cancel(_
,context
):
1691 #global cxr_jobs_batch
1692 static
= _
.__class
__
1693 wm
= context
.window_manager
1695 if static
.SUBPROC
!= None:
1696 static
.SUBPROC
.terminate()
1697 static
.SUBPROC
= None
1699 if static
.TIMER
!= None:
1700 wm
.event_timer_remove( static
.TIMER
)
1705 #cxr_jobs_batch = None
1709 def modal(_
,context
,ev
):
1710 static
= _
.__class
__
1712 if ev
.type == 'TIMER':
1713 global cxr_jobs_batch
, cxr_error_inf
1715 if static
.WAIT_REDRAW
:
1717 return {'PASS_THROUGH'}
1718 static
.WAIT_REDRAW
= True
1720 if static
.USER_EXIT
:
1721 print( "Chain USER_EXIT" )
1722 return _
.cancel(context
)
1724 if static
.SUBPROC
!= None:
1725 # Deal with async modes
1726 status
= static
.SUBPROC
.poll()
1728 # Cannot redirect STDOUT through here without causing
1729 # undefined behaviour due to the Blender Python specification.
1731 # Have to write it out to a file and read it back in.
1734 with
open(cxr_temp_file("convexer_compile_log.txt"),"r") as log
:
1735 static
.LOG
= log
.readlines()
1737 return {'PASS_THROUGH'}
1739 #for l in static.SUBPROC.stdout:
1740 # print( F'-> {l.decode("utf-8")}',end='' )
1741 static
.SUBPROC
= None
1744 print(F
'Compiler () error: {status}')
1746 jobn
= static
.JOBSYS
['jobs'][static
.JOBID
]
1747 cxr_error_inf
= ( F
"{static.JOBSYS['title']} error {status}", jobn
)
1749 return _
.cancel(context
)
1751 static
.JOBSYS
['jobs'][static
.JOBID
] = None
1752 cxr_jobs_update_graph( static
.JOBINFO
)
1754 return {'PASS_THROUGH'}
1756 # Compile syncronous thing
1757 for sys
in static
.JOBINFO
:
1758 for i
,target
in enumerate(sys
['jobs']):
1761 if callable(sys
['exec']):
1762 print( F
"Run (sync): {static.JOBID} @{time.time()}" )
1764 if not sys
['exec'](*target
):
1765 print( "Job failed" )
1766 return _
.cancel(context
)
1768 sys
['jobs'][i
] = None
1771 # Run external executable (wine)
1772 static
.SUBPROC
= subprocess
.Popen( target
,
1773 stdout
=static
.FILE
,\
1774 stderr
=subprocess
.PIPE
,\
1779 cxr_jobs_update_graph( static
.JOBINFO
)
1781 return {'PASS_THROUGH'}
1784 print( "All jobs completed!" )
1785 #cxr_jobs_batch = None
1787 return _
.cancel(context
)
1789 return {'PASS_THROUGH'}
1791 def invoke(_
,context
,event
):
1792 global cxr_error_inf
1794 static
= _
.__class
__
1795 wm
= context
.window_manager
1797 if static
.TIMER
!= None:
1798 print("Chain exiting...")
1799 static
.USER_EXIT
=True
1800 return {'RUNNING_MODAL'}
1802 print("Launching compiler toolchain")
1805 # Run static compilation units now (collect, vmt..)
1806 filepath
= bpy
.data
.filepath
1807 directory
= os
.path
.dirname(filepath
)
1808 settings
= bpy
.context
.scene
.cxr_data
1810 asset_dir
= F
"{directory}/modelsrc"
1811 material_dir
= F
"{settings.subdir}/materials/{settings.project_name}"
1812 model_dir
= F
"{settings.subdir}/models/{settings.project_name}"
1813 output_vmf
= F
"{directory}/{settings.project_name}.vmf"
1815 bsp_local
= F
"{directory}/{settings.project_name}.bsp"
1816 bsp_remote
= F
"{settings.subdir}/maps/{settings.project_name}.bsp"
1817 bsp_packed
= F
"{settings.subdir}/maps/{settings.project_name}_pack.bsp"
1818 packlist
= F
"{directory}/{settings.project_name}_assets.txt"
1820 os
.makedirs( asset_dir
, exist_ok
=True )
1821 os
.makedirs( material_dir
, exist_ok
=True )
1822 os
.makedirs( model_dir
, exist_ok
=True )
1824 static
.FILE
= open(cxr_temp_file("convexer_compile_log.txt"),"w")
1827 sceneinfo
= cxr_scene_collect()
1833 for brush
in sceneinfo
['geo']:
1834 for ms
in brush
['object'].material_slots
:
1835 a_materials
.add( ms
.material
)
1836 if ms
.material
.cxr_data
.shader
== 'VertexLitGeneric':
1837 errmat
= ms
.material
.name
1838 errnam
= brush
['object'].name
1840 cxr_error_inf
= ( "Shader error", \
1841 F
"Vertex shader ({errmat}) used on model ({errnam})" )
1843 print( F
"Vertex shader {errmat} used on {errnam}")
1845 return {'CANCELLED'}
1849 for ent
in sceneinfo
['entities']:
1850 if ent
['object'] == None: continue
1852 if ent
['classname'] == 'prop_static':
1854 if isinstance(obj
,bpy
.types
.Collection
):
1856 a_models
.add( target
)
1857 model_jobs
+= [(target
, ent
['origin'], asset_dir
, \
1858 settings
.project_name
, ent
['transform'])]
1860 target
= obj
.instance_collection
1861 if target
in a_models
:
1863 a_models
.add( target
)
1865 # TODO: Should take into account collection instancing offset
1866 model_jobs
+= [(target
, [0,0,0], asset_dir
, \
1867 settings
.project_name
, ent
['transform'])]
1869 elif ent
['object'].type == 'MESH':
1870 for ms
in ent
['object'].material_slots
:
1871 a_materials
.add( ms
.material
)
1873 for mdl
in a_models
:
1874 uid
= asset_uid(mdl
)
1875 qc_jobs
+= [F
'{uid}.qc']
1877 for obj
in mdl
.objects
:
1878 for ms
in obj
.material_slots
:
1879 a_materials
.add( ms
.material
)
1880 if ms
.material
.cxr_data
.shader
== 'LightMappedGeneric' or \
1881 ms
.material
.cxr_data
.shader
== 'WorldVertexTransition':
1883 errmat
= ms
.material
.name
1886 cxr_error_inf
= ( "Shader error", \
1887 F
"Lightmapped shader ({errmat}) used on model ({errnam})" )
1889 print( F
"Lightmapped shader {errmat} used on {errnam}")
1891 return {'CANCELLED'}
1894 for mat
in a_materials
:
1895 for pair
in compile_material(mat
):
1900 if isinstance(prop
,bpy
.types
.Image
):
1902 if 'flags' in pdef
: flags
= pdef
['flags']
1903 if prop
not in image_jobs
:
1904 image_jobs
+= [(prop
,)]
1905 prop
.cxr_data
.flags
= flags
1908 with
open( packlist
, "w" ) as fp
:
1910 for mat
in a_materials
:
1911 if mat
.cxr_data
.shader
== 'Builtin': continue
1912 fp
.write(F
"{asset_path('materials',mat)}.vmt\n")
1913 fp
.write(F
"{cxr_winepath(asset_full_path('materials',mat))}.vmt\n")
1915 for img_job
in image_jobs
:
1917 fp
.write(F
"{asset_path('materials',img)}.vtf\n")
1918 fp
.write(F
"{cxr_winepath(asset_full_path('materials',img))}.vtf\n")
1920 for mdl
in a_models
:
1921 local
= asset_path('models',mdl
)
1922 winep
= cxr_winepath(asset_full_path('models',mdl
))
1924 fp
.write(F
"{local}.vvd\n")
1925 fp
.write(F
"{winep}.vvd\n")
1926 fp
.write(F
"{local}.dx90.vtx\n")
1927 fp
.write(F
"{winep}.dx90.vtx\n")
1928 fp
.write(F
"{local}.mdl\n")
1929 fp
.write(F
"{winep}.mdl\n")
1930 fp
.write(F
"{local}.vvd\n")
1931 fp
.write(F
"{winep}.vvd\n")
1933 if cxr_modelsrc_vphys(mdl
):
1934 fp
.write(F
"{local}.phy\n")
1935 fp
.write(F
"{winep}.phy\n")
1941 if settings
.comp_vmf
:
1942 static
.JOBINFO
+= [{
1943 "title": "Convexer",
1945 "colour": (0.863, 0.078, 0.235,1.0),
1946 "exec": cxr_export_vmf
,
1947 "jobs": [(sceneinfo
,output_vmf
)]
1950 if settings
.comp_textures
:
1951 if len(image_jobs
) > 0:
1952 static
.JOBINFO
+= [{
1953 "title": "Textures",
1955 "colour": (1.000, 0.271, 0.000,1.0),
1956 "exec": compile_image
,
1960 game
= cxr_winepath( settings
.subdir
)
1962 '-game', game
, settings
.project_name
1966 if settings
.comp_models
:
1967 if len(model_jobs
) > 0:
1968 static
.JOBINFO
+= [{
1971 "colour": (1.000, 0.647, 0.000,1.0),
1972 "exec": cxr_export_modelsrc
,
1976 if len(qc_jobs
) > 0:
1977 static
.JOBINFO
+= [{
1978 "title": "StudioMDL",
1980 "colour": (1.000, 0.843, 0.000, 1.0),
1981 "exec": "studiomdl",
1982 "jobs": [[settings
[F
'exe_studiomdl']] + [\
1983 '-nop4', '-game', game
, qc
] for qc
in qc_jobs
],
1988 if settings
.comp_compile
:
1989 if not settings
.opt_vbsp
.startswith( 'disable' ):
1990 vbsp_opt
= settings
.opt_vbsp
.split()
1991 static
.JOBINFO
+= [{
1994 "colour": (0.678, 1.000, 0.184,1.0),
1996 "jobs": [[settings
[F
'exe_vbsp']] + vbsp_opt
+ args
],
2000 if not settings
.opt_vvis
.startswith( 'disable' ):
2001 vvis_opt
= settings
.opt_vvis
.split()
2002 static
.JOBINFO
+= [{
2005 "colour": (0.000, 1.000, 0.498,1.0),
2007 "jobs": [[settings
[F
'exe_vvis']] + vvis_opt
+ args
],
2011 if not settings
.opt_vrad
.startswith( 'disable' ):
2012 vrad_opt
= settings
.opt_vrad
.split()
2013 static
.JOBINFO
+= [{
2016 "colour": (0.125, 0.698, 0.667,1.0),
2018 "jobs": [[settings
[F
'exe_vrad']] + vrad_opt
+ args
],
2022 static
.JOBINFO
+= [{
2025 "colour": (0.118, 0.565, 1.000,1.0),
2026 "exec": cxr_patchmap
,
2027 "jobs": [(bsp_local
,bsp_remote
)]
2030 if settings
.comp_pack
:
2031 static
.JOBINFO
+= [{
2034 "colour": (0.541, 0.169, 0.886,1.0),
2036 "jobs": [[cxr_compiler_path("bspzip"), '-addlist', \
2037 cxr_winepath(bsp_remote
),
2038 cxr_winepath(packlist
),
2039 cxr_winepath(bsp_packed
) ]],
2043 if len(static
.JOBINFO
) == 0:
2044 return {'CANCELLED'}
2046 static
.USER_EXIT
=False
2047 static
.TIMER
=wm
.event_timer_add(0.1,window
=context
.window
)
2048 wm
.modal_handler_add(_
)
2050 cxr_jobs_update_graph( static
.JOBINFO
)
2052 return {'RUNNING_MODAL'}
2054 class CXR_RESET_HASHES(bpy
.types
.Operator
):
2055 bl_idname
="convexer.hash_reset"
2056 bl_label
="Reset asset hashes"
2058 def execute(_
,context
):
2059 for c
in bpy
.data
.collections
:
2060 c
.cxr_data
.last_hash
= F
"<RESET>{time.time()}"
2061 c
.cxr_data
.asset_id
=0
2063 for t
in bpy
.data
.images
:
2064 t
.cxr_data
.last_hash
= F
"<RESET>{time.time()}"
2065 t
.cxr_data
.asset_id
=0
2069 class CXR_COMPILE_MATERIAL(bpy
.types
.Operator
):
2070 bl_idname
="convexer.matcomp"
2071 bl_label
="Recompile Material"
2073 def execute(_
,context
):
2074 active_obj
= bpy
.context
.active_object
2075 active_mat
= active_obj
.active_material
2077 #TODO: reduce code dupe (L1663)
2078 for pair
in compile_material(active_mat
):
2083 if isinstance(prop
,bpy
.types
.Image
):
2085 if 'flags' in pdef
: flags
= pdef
['flags']
2086 prop
.cxr_data
.flags
= flags
2088 compile_image( prop
)
2090 settings
= bpy
.context
.scene
.cxr_data
2091 with
open(F
'{settings.subdir}/cfg/convexer_mat_update.cfg','w') as o
:
2092 o
.write(F
'mat_reloadmaterial {asset_name(active_mat)}')
2095 with
open(F
'{settings.subdir}/cfg/convexer.cfg','w') as o
:
2096 o
.write('sv_cheats 1\n')
2097 o
.write('mp_warmup_pausetimer 1\n')
2098 o
.write('bot_kick\n')
2099 o
.write('alias cxr_reload "exec convexer_mat_update"\n')
2104 # ------------------------------------------------------------------------------
2106 # Helper buttons for 3d toolbox view
2108 class CXR_VIEW3D( bpy
.types
.Panel
):
2109 bl_idname
= "VIEW3D_PT_convexer"
2110 bl_label
= "Convexer"
2111 bl_space_type
= 'VIEW_3D'
2112 bl_region_type
= 'UI'
2113 bl_category
= "Convexer"
2115 def draw(_
, context
):
2118 active_object
= context
.object
2119 if active_object
== None: return
2121 purpose
= cxr_object_purpose( active_object
)
2123 if purpose
[0] == None or purpose
[1] == None:
2124 usage_str
= "No purpose"
2126 if purpose
[1] == 'model':
2127 usage_str
= F
'mesh in {asset_name( purpose[0] )}.mdl'
2129 usage_str
= F
'{purpose[1]} in {purpose[0].name}'
2131 layout
.label(text
=F
"Currently editing:")
2133 box
.label(text
=usage_str
)
2135 if purpose
[1] == 'brush' or purpose
[1] == 'brush_entity':
2138 row
.operator("convexer.preview")
2142 row
.operator("convexer.reset")
2144 # Main scene properties interface, where all the settings go
2146 class CXR_INTERFACE(bpy
.types
.Panel
):
2148 bl_idname
="SCENE_PT_convexer"
2149 bl_space_type
='PROPERTIES'
2150 bl_region_type
='WINDOW'
2153 def draw(_
,context
):
2154 if CXR_GNU_LINUX
==1:
2155 _
.layout
.operator("convexer.reload")
2156 _
.layout
.operator("convexer.dev_test")
2158 _
.layout
.operator("convexer.hash_reset")
2159 settings
= context
.scene
.cxr_data
2161 _
.layout
.prop(settings
, "scale_factor")
2162 _
.layout
.prop(settings
, "skybox_scale_factor")
2163 _
.layout
.prop(settings
, "skyname" )
2164 _
.layout
.prop(settings
, "lightmap_scale")
2165 _
.layout
.prop(settings
, "light_scale" )
2166 _
.layout
.prop(settings
, "image_quality" )
2168 box
= _
.layout
.box()
2170 box
.prop(settings
, "project_name")
2171 box
.prop(settings
, "subdir")
2173 box
= _
.layout
.box()
2174 box
.operator("convexer.detect_compilers")
2175 box
.prop(settings
, "exe_studiomdl")
2176 box
.prop(settings
, "exe_vbsp")
2177 box
.prop(settings
, "opt_vbsp")
2179 box
.prop(settings
, "exe_vvis")
2180 box
.prop(settings
, "opt_vvis")
2182 box
.prop(settings
, "exe_vrad")
2183 box
.prop(settings
, "opt_vrad")
2187 row
.prop(settings
,"comp_vmf")
2188 row
.prop(settings
,"comp_textures")
2189 row
.prop(settings
,"comp_models")
2190 row
.prop(settings
,"comp_compile")
2191 row
.prop(settings
,"comp_pack")
2193 text
= "Compile" if CXR_COMPILER_CHAIN
.TIMER
== None else "Cancel"
2196 row
.operator("convexer.chain", text
=text
)
2200 row
.operator("convexer.reset")
2201 if CXR_COMPILER_CHAIN
.TIMER
!= None:
2204 class CXR_MATERIAL_PANEL(bpy
.types
.Panel
):
2205 bl_label
="VMT Properties"
2206 bl_idname
="SCENE_PT_convexer_vmt"
2207 bl_space_type
='PROPERTIES'
2208 bl_region_type
='WINDOW'
2209 bl_context
="material"
2211 def draw(_
,context
):
2212 active_object
= bpy
.context
.active_object
2213 if active_object
== None: return
2215 active_material
= active_object
.active_material
2216 if active_material
== None: return
2218 properties
= active_material
.cxr_data
2219 info
= material_info( active_material
)
2221 _
.layout
.label(text
=F
"{info['name']} @{info['res'][0]}x{info['res'][1]}")
2222 row
= _
.layout
.row()
2223 row
.prop( properties
, "shader" )
2224 row
.operator( "convexer.matcomp" )
2226 #for xk in info: _.layout.label(text=F"{xk}:={info[xk]}")
2228 def _mtex( name
, img
, uiParent
):
2231 box
= uiParent
.box()
2232 box
.label( text
=F
'{name} "{img.filepath}"' )
2234 if ((x
& (x
- 1)) == 0):
2237 closest_diff
= 10000000
2239 dist
= abs((1 << i
)-x
)
2240 if dist
< closest_diff
:
2245 return 1 << (closest
+1)
2247 return 1 << (closest
-1)
2252 row
.prop( img
.cxr_data
, "export_res" )
2253 row
.prop( img
.cxr_data
, "fmt" )
2256 row
.prop( img
.cxr_data
, "mipmap" )
2257 row
.prop( img
.cxr_data
, "lod" )
2258 row
.prop( img
.cxr_data
, "clamp" )
2260 img
.cxr_data
.export_res
[0] = _p2( img
.cxr_data
.export_res
[0] )
2261 img
.cxr_data
.export_res
[1] = _p2( img
.cxr_data
.export_res
[1] )
2263 def _mview( layer
, uiParent
):
2267 if isinstance(layer
[decl
],dict): # $property definition
2269 ptype
= pdef
['type']
2275 if ('shaders' in pdef
) and \
2276 (properties
.shader
not in pdef
['shaders']):
2279 if ptype
== 'intrinsic':
2280 if decl
not in info
:
2285 if isinstance(pdef
[ch
],dict):
2286 if ptype
== 'ui' or ptype
== 'intrinsic':
2288 elif getattr(properties
,decl
) == pdef
['default']:
2291 thisnode
= uiParent
.box()
2295 thisnode
.label( text
=decl
)
2296 elif ptype
== 'intrinsic':
2297 if isinstance(info
[decl
], bpy
.types
.Image
):
2298 _mtex( decl
, info
[decl
], thisnode
)
2300 # hidden intrinsic value.
2301 # Means its a float array or something not an image
2302 thisnode
.label(text
=F
"-- hidden intrinsic '{decl}' --")
2304 thisnode
.prop(properties
,decl
)
2305 if expandview
: _mview(pdef
,thisnode
)
2307 _mview( cxr_shader_params
, _
.layout
)
2309 def cxr_entity_changeclass(_
,context
):
2310 active_object
= context
.active_object
2312 # Create ID properties
2314 classname
= cxr_custom_class(active_object
)
2316 if classname
in cxr_entities
:
2317 entdef
= cxr_entities
[classname
]
2319 kvs
= entdef
['keyvalues']
2320 if callable(kvs
): kvs
= kvs( {'object': active_object
} )
2326 if callable(kv
) or not isinstance(kv
,dict): continue
2328 if key
not in active_object
:
2329 active_object
[key
] = kv
['default']
2330 id_prop
= active_object
.id_properties_ui(key
)
2331 id_prop
.update(default
=kv
['default'])
2333 class CXR_ENTITY_PANEL(bpy
.types
.Panel
):
2334 bl_label
="Entity Config"
2335 bl_idname
="SCENE_PT_convexer_entity"
2336 bl_space_type
='PROPERTIES'
2337 bl_region_type
='WINDOW'
2340 def draw(_
,context
):
2341 active_object
= bpy
.context
.active_object
2343 if active_object
== None: return
2346 "scale": bpy
.context
.scene
.cxr_data
.scale_factor
,
2350 ecn
= cxr_intrinsic_classname( active_object
)
2351 classname
= cxr_custom_class( active_object
)
2354 if active_object
.type == 'MESH':
2355 _
.layout
.prop( active_object
.cxr_data
, 'brushclass' )
2356 else: _
.layout
.prop( active_object
.cxr_data
, 'classname' )
2358 _
.layout
.prop( active_object
.cxr_data
, 'visgroup' )
2359 _
.layout
.prop( active_object
.cxr_data
, 'lightmap_override' )
2361 if classname
== 'NONE':
2364 _
.layout
.label(text
=F
"<implementation defined ({ecn})>")
2365 _
.layout
.enabled
=False
2368 kvs
= cxr_entity_keyvalues( {
2369 "object": active_object
,
2370 "transform": default_context
,
2371 "classname": classname
2377 _
.layout
.prop( active_object
, F
'["cxrkv_{kv[0]}"]', text
=kv
[0])
2379 row
= _
.layout
.row()
2381 row
.label( text
=F
'{kv[0]}: {repr(kv[2])}' )
2383 _
.layout
.label( text
=F
"ERROR: NO CLASS DEFINITION" )
2385 class CXR_LIGHT_PANEL(bpy
.types
.Panel
):
2386 bl_label
= "Source Settings"
2387 bl_idname
= "LIGHT_PT_cxr"
2388 bl_space_type
= 'PROPERTIES'
2389 bl_region_type
= 'WINDOW'
2392 def draw(self
, context
):
2393 layout
= self
.layout
2394 scene
= context
.scene
2396 active_object
= bpy
.context
.active_object
2397 if active_object
== None: return
2399 if active_object
.type == 'LIGHT' or \
2400 active_object
.type == 'LIGHT_PROBE':
2402 properties
= active_object
.data
.cxr_data
2404 if active_object
.type == 'LIGHT':
2405 layout
.prop( properties
, "realtime" )
2406 elif active_object
.type == 'LIGHT_PROBE':
2407 layout
.prop( properties
, "size" )
2409 class CXR_COLLECTION_PANEL(bpy
.types
.Panel
):
2410 bl_label
= "Source Settings"
2411 bl_idname
= "COL_PT_cxr"
2412 bl_space_type
= 'PROPERTIES'
2413 bl_region_type
= 'WINDOW'
2414 bl_context
= "collection"
2416 def draw(self
, context
):
2417 layout
= self
.layout
2418 scene
= context
.scene
2420 active_collection
= bpy
.context
.collection
2422 if active_collection
!= None:
2423 layout
.prop( active_collection
.cxr_data
, "shadow_caster" )
2424 layout
.prop( active_collection
.cxr_data
, "texture_shadows" )
2425 layout
.prop( active_collection
.cxr_data
, "preserve_order" )
2426 layout
.prop( active_collection
.cxr_data
, "surfaceprop" )
2427 layout
.prop( active_collection
.cxr_data
, "visgroup" )
2430 # ------------------------------------------------------------------------------
2432 class CXR_IMAGE_SETTINGS(bpy
.types
.PropertyGroup
):
2433 export_res
: bpy
.props
.IntVectorProperty(
2435 description
="Texture Export Resolution",
2441 fmt
: bpy
.props
.EnumProperty(
2444 ('DXT1', "DXT1", "BC1 compressed", '', 0),
2445 ('DXT5', "DXT5", "BC3 compressed", '', 1),
2446 ('RGB', "RGB", "Uncompressed", '', 2),
2447 ('RGBA', "RGBA", "Uncompressed (with alpha)", '', 3)
2449 description
="Image format",
2452 last_hash
: bpy
.props
.StringProperty( name
="" )
2453 asset_id
: bpy
.props
.IntProperty(name
="intl_assetid",default
=0)
2455 mipmap
: bpy
.props
.BoolProperty(name
="MIP",default
=True)
2456 lod
: bpy
.props
.BoolProperty(name
="LOD",default
=True)
2457 clamp
: bpy
.props
.BoolProperty(name
="CLAMP",default
=False)
2458 flags
: bpy
.props
.IntProperty(name
="flags",default
=0)
2460 class CXR_LIGHT_SETTINGS(bpy
.types
.PropertyGroup
):
2461 realtime
: bpy
.props
.BoolProperty(name
="Realtime Light", default
=True)
2463 class CXR_CUBEMAP_SETTINGS(bpy
.types
.PropertyGroup
):
2464 size
: bpy
.props
.EnumProperty(
2467 ('1',"1x1",'','',0),
2468 ('2',"2x2",'','',1),
2469 ('3',"4x4",'','',2),
2470 ('4',"8x8",'','',3),
2471 ('5',"16x16",'','',4),
2472 ('6',"32x32",'','',5),
2473 ('7',"64x64",'','',6),
2474 ('8',"128x128",'','',7),
2475 ('9',"256x256",'','',8)
2477 description
="Texture resolution",
2480 class CXR_ENTITY_SETTINGS(bpy
.types
.PropertyGroup
):
2481 entity
: bpy
.props
.BoolProperty(name
="")
2483 enum_pointents
= [('NONE',"None","")]
2484 enum_brushents
= [('NONE',"None","")]
2486 for classname
in cxr_entities
:
2487 entdef
= cxr_entities
[classname
]
2488 if 'allow' in entdef
:
2489 itm
= [(classname
, classname
, "")]
2490 if 'EMPTY' in entdef
['allow']: enum_pointents
+= itm
2491 else: enum_brushents
+= itm
2493 classname
: bpy
.props
.EnumProperty(items
=enum_pointents
, name
="Class", \
2494 update
=cxr_entity_changeclass
, default
='NONE' )
2496 brushclass
: bpy
.props
.EnumProperty(items
=enum_brushents
, name
="Class", \
2497 update
=cxr_entity_changeclass
, default
='NONE' )
2499 enum_classes
= [('0',"None","")]
2500 for i
, vg
in enumerate(cxr_visgroups
):
2501 enum_classes
+= [(str(i
+1),vg
,"")]
2502 visgroup
: bpy
.props
.EnumProperty(name
="visgroup",items
=enum_classes
,default
=0)
2503 lightmap_override
: bpy
.props
.IntProperty(name
="Lightmap Override",default
=0)
2505 class CXR_MODEL_SETTINGS(bpy
.types
.PropertyGroup
):
2506 last_hash
: bpy
.props
.StringProperty( name
="" )
2507 asset_id
: bpy
.props
.IntProperty(name
="vmf_settings",default
=0)
2508 shadow_caster
: bpy
.props
.BoolProperty( name
="Shadow caster", default
=True )
2509 texture_shadows
: bpy
.props
.BoolProperty( name
="Texture Shadows", default
=False )
2510 preserve_order
: bpy
.props
.BoolProperty( name
="Preserve Order", default
=False )
2511 surfaceprop
: bpy
.props
.StringProperty( name
="Suface prop",default
="default" )
2513 enum_classes
= [('0',"None","")]
2514 for i
, vg
in enumerate(cxr_visgroups
):
2515 enum_classes
+= [(str(i
+1),vg
,"")]
2516 visgroup
: bpy
.props
.EnumProperty(name
="visgroup",items
=enum_classes
,default
=0)
2518 class CXR_SCENE_SETTINGS(bpy
.types
.PropertyGroup
):
2519 project_name
: bpy
.props
.StringProperty( name
="Project Name" )
2520 subdir
: bpy
.props
.StringProperty( name
="../csgo/ folder" )
2522 exe_studiomdl
: bpy
.props
.StringProperty( name
="studiomdl" )
2523 exe_vbsp
: bpy
.props
.StringProperty( name
="vbsp" )
2524 opt_vbsp
: bpy
.props
.StringProperty( name
="args" )
2525 exe_vvis
: bpy
.props
.StringProperty( name
="vvis" )
2526 opt_vvis
: bpy
.props
.StringProperty( name
="args" )
2527 exe_vrad
: bpy
.props
.StringProperty( name
="vrad" )
2528 opt_vrad
: bpy
.props
.StringProperty( name
="args", \
2529 default
="-reflectivityScale 0.35 -aoscale 1.4 -final -textureshadows -hdr -StaticPropLighting -StaticPropPolys" )
2531 scale_factor
: bpy
.props
.FloatProperty( name
="VMF Scale factor", \
2532 default
=32.0,min=1.0)
2533 skybox_scale_factor
: bpy
.props
.FloatProperty( name
="Sky Scale factor", \
2534 default
=1.0,min=0.01)
2535 skyname
: bpy
.props
.StringProperty(name
="Skyname",default
="sky_csgo_night02b")
2536 skybox_offset
: bpy
.props
.FloatProperty(name
="Sky offset",default
=-4096.0)
2537 light_scale
: bpy
.props
.FloatProperty(name
="Light Scale",default
=1.0/5.0)
2538 include_names
: bpy
.props
.BoolProperty(name
="Append original file names",\
2540 lightmap_scale
: bpy
.props
.IntProperty(name
="Global Lightmap Scale",\
2542 image_quality
: bpy
.props
.IntProperty(name
="Texture Quality (0-18)",\
2543 default
=8, min=0, max=18 )
2545 comp_vmf
: bpy
.props
.BoolProperty(name
="VMF",default
=True)
2546 comp_models
: bpy
.props
.BoolProperty(name
="Models",default
=True)
2547 comp_textures
: bpy
.props
.BoolProperty(name
="Textures",default
=True)
2548 comp_compile
: bpy
.props
.BoolProperty(name
="Compile",default
=True)
2549 comp_pack
: bpy
.props
.BoolProperty(name
="Pack",default
=False)
2551 classes
= [ CXR_RELOAD
, CXR_DEV_OPERATOR
, CXR_INTERFACE
, \
2552 CXR_MATERIAL_PANEL
, CXR_IMAGE_SETTINGS
,\
2553 CXR_MODEL_SETTINGS
, CXR_ENTITY_SETTINGS
, CXR_CUBEMAP_SETTINGS
,\
2554 CXR_LIGHT_SETTINGS
, CXR_SCENE_SETTINGS
, CXR_DETECT_COMPILERS
,\
2555 CXR_ENTITY_PANEL
, CXR_LIGHT_PANEL
, CXR_PREVIEW_OPERATOR
,\
2556 CXR_VIEW3D
, CXR_COMPILER_CHAIN
, CXR_RESET_HASHES
,\
2557 CXR_COMPILE_MATERIAL
, CXR_COLLECTION_PANEL
, CXR_RESET
]
2559 vmt_param_dynamic_class
= None
2562 global cxr_view_draw_handler
, vmt_param_dynamic_class
, cxr_ui_handler
2565 bpy
.utils
.register_class(c
)
2567 # Build dynamic VMT properties class defined by cxr_shader_params
2568 annotations_dict
= {}
2570 def _dvmt_propogate(layer
):
2571 nonlocal annotations_dict
2574 if isinstance(layer
[decl
],dict): # $property definition
2578 if pdef
['type'] == 'bool':
2579 prop
= bpy
.props
.BoolProperty(\
2580 name
= pdef
['name'],\
2581 default
= pdef
['default'])
2583 elif pdef
['type'] == 'float':
2584 prop
= bpy
.props
.FloatProperty(\
2585 name
= pdef
['name'],\
2586 default
= pdef
['default'])
2588 elif pdef
['type'] == 'vector':
2589 if 'subtype' in pdef
:
2590 prop
= bpy
.props
.FloatVectorProperty(\
2591 name
= pdef
['name'],\
2592 subtype
= pdef
['subtype'],\
2593 default
= pdef
['default'],\
2594 size
= len(pdef
['default']))
2596 prop
= bpy
.props
.FloatVectorProperty(\
2597 name
= pdef
['name'],\
2598 default
= pdef
['default'],\
2599 size
= len(pdef
['default']))
2601 elif pdef
['type'] == 'string':
2602 prop
= bpy
.props
.StringProperty(\
2603 name
= pdef
['name'],\
2604 default
= pdef
['default'])
2606 elif pdef
['type'] == 'enum':
2607 prop
= bpy
.props
.EnumProperty(\
2608 name
= pdef
['name'],\
2609 items
= pdef
['items'],\
2610 default
= pdef
['default'])
2613 annotations_dict
[decl
] = prop
2615 # Recurse into sub-definitions
2616 _dvmt_propogate(pdef
)
2618 annotations_dict
["shader"] = bpy
.props
.EnumProperty(\
2621 cxr_shaders
[_
]["name"],\
2622 '') for _
in cxr_shaders
],\
2623 default
= next(iter(cxr_shaders
)))
2625 annotations_dict
["asset_id"] = bpy
.props
.IntProperty(name
="intl_assetid",\
2628 _dvmt_propogate( cxr_shader_params
)
2629 vmt_param_dynamic_class
= type(
2631 (bpy
.types
.PropertyGroup
,),{
2632 "__annotations__": annotations_dict
2636 bpy
.utils
.register_class( vmt_param_dynamic_class
)
2639 bpy
.types
.Material
.cxr_data
= \
2640 bpy
.props
.PointerProperty(type=vmt_param_dynamic_class
)
2641 bpy
.types
.Image
.cxr_data
= \
2642 bpy
.props
.PointerProperty(type=CXR_IMAGE_SETTINGS
)
2643 bpy
.types
.Object
.cxr_data
= \
2644 bpy
.props
.PointerProperty(type=CXR_ENTITY_SETTINGS
)
2645 bpy
.types
.Collection
.cxr_data
= \
2646 bpy
.props
.PointerProperty(type=CXR_MODEL_SETTINGS
)
2647 bpy
.types
.Light
.cxr_data
= \
2648 bpy
.props
.PointerProperty(type=CXR_LIGHT_SETTINGS
)
2649 bpy
.types
.LightProbe
.cxr_data
= \
2650 bpy
.props
.PointerProperty(type=CXR_CUBEMAP_SETTINGS
)
2651 bpy
.types
.Scene
.cxr_data
= \
2652 bpy
.props
.PointerProperty(type=CXR_SCENE_SETTINGS
)
2654 # CXR Scene settings
2657 cxr_view_draw_handler
= bpy
.types
.SpaceView3D
.draw_handler_add(\
2658 cxr_draw
,(),'WINDOW','POST_VIEW')
2660 cxr_ui_handler
= bpy
.types
.SpaceView3D
.draw_handler_add(\
2661 cxr_ui
,(None,None),'WINDOW','POST_PIXEL')
2663 bpy
.app
.handlers
.load_post
.append(cxr_on_load
)
2664 bpy
.app
.handlers
.depsgraph_update_post
.append(cxr_dgraph_update
)
2667 global cxr_view_draw_handler
, vmt_param_dynamic_class
, cxr_ui_handler
2669 bpy
.utils
.unregister_class( vmt_param_dynamic_class
)
2671 bpy
.utils
.unregister_class(c
)
2673 bpy
.app
.handlers
.depsgraph_update_post
.remove(cxr_dgraph_update
)
2674 bpy
.app
.handlers
.load_post
.remove(cxr_on_load
)
2676 bpy
.types
.SpaceView3D
.draw_handler_remove(cxr_view_draw_handler
,'WINDOW')
2677 bpy
.types
.SpaceView3D
.draw_handler_remove(cxr_ui_handler
,'WINDOW')