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}.txt"
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 static
= _
.__class
__
1793 wm
= context
.window_manager
1795 if static
.TIMER
!= None:
1796 print("Chain exiting...")
1797 static
.USER_EXIT
=True
1798 return {'RUNNING_MODAL'}
1800 print("Launching compiler toolchain")
1803 # Run static compilation units now (collect, vmt..)
1804 filepath
= bpy
.data
.filepath
1805 directory
= os
.path
.dirname(filepath
)
1806 settings
= bpy
.context
.scene
.cxr_data
1808 asset_dir
= F
"{directory}/modelsrc"
1809 material_dir
= F
"{settings.subdir}/materials/{settings.project_name}"
1810 model_dir
= F
"{settings.subdir}/models/{settings.project_name}"
1811 output_vmf
= F
"{directory}/{settings.project_name}.vmf"
1813 bsp_local
= F
"{directory}/{settings.project_name}.bsp"
1814 bsp_remote
= F
"{settings.subdir}/maps/{settings.project_name}.bsp"
1815 bsp_packed
= F
"{settings.subdir}/maps/{settings.project_name}_pack.bsp"
1816 packlist
= F
"{directory}/{settings.project_name}_assets.txt"
1818 os
.makedirs( asset_dir
, exist_ok
=True )
1819 os
.makedirs( material_dir
, exist_ok
=True )
1820 os
.makedirs( model_dir
, exist_ok
=True )
1822 static
.FILE
= open(cxr_temp_file("convexer_compile_log.txt"),"w")
1825 sceneinfo
= cxr_scene_collect()
1831 for brush
in sceneinfo
['geo']:
1832 for ms
in brush
['object'].material_slots
:
1833 a_materials
.add( ms
.material
)
1834 if ms
.material
.cxr_data
.shader
== 'VertexLitGeneric':
1835 errmat
= ms
.material
.name
1836 errnam
= brush
['object'].name
1837 print( F
"Vertex shader {errmat} used on {errnam}")
1838 return {'CANCELLED'}
1842 for ent
in sceneinfo
['entities']:
1843 if ent
['object'] == None: continue
1845 if ent
['classname'] == 'prop_static':
1847 if isinstance(obj
,bpy
.types
.Collection
):
1849 a_models
.add( target
)
1850 model_jobs
+= [(target
, ent
['origin'], asset_dir
, \
1851 settings
.project_name
, ent
['transform'])]
1853 target
= obj
.instance_collection
1854 if target
in a_models
:
1856 a_models
.add( target
)
1858 # TODO: Should take into account collection instancing offset
1859 model_jobs
+= [(target
, [0,0,0], asset_dir
, \
1860 settings
.project_name
, ent
['transform'])]
1862 elif ent
['object'].type == 'MESH':
1863 for ms
in ent
['object'].material_slots
:
1864 a_materials
.add( ms
.material
)
1866 for mdl
in a_models
:
1867 uid
= asset_uid(mdl
)
1868 qc_jobs
+= [F
'{uid}.qc']
1870 for obj
in mdl
.objects
:
1871 for ms
in obj
.material_slots
:
1872 a_materials
.add( ms
.material
)
1873 if ms
.material
.cxr_data
.shader
== 'LightMappedGeneric' or \
1874 ms
.material
.cxr_data
.shader
== 'WorldVertexTransition':
1876 errmat
= ms
.material
.name
1878 print( F
"Lightmapped shader {errmat} used on {errnam}")
1879 return {'CANCELLED'}
1882 for mat
in a_materials
:
1883 for pair
in compile_material(mat
):
1888 if isinstance(prop
,bpy
.types
.Image
):
1890 if 'flags' in pdef
: flags
= pdef
['flags']
1891 if prop
not in image_jobs
:
1892 image_jobs
+= [(prop
,)]
1893 prop
.cxr_data
.flags
= flags
1896 with
open( packlist
, "w" ) as fp
:
1898 for mat
in a_materials
:
1899 if mat
.cxr_data
.shader
== 'Builtin': continue
1900 fp
.write(F
"{asset_path('materials',mat)}.vmt\n")
1901 fp
.write(F
"{cxr_winepath(asset_full_path('materials',mat))}.vmt\n")
1903 for img_job
in image_jobs
:
1905 fp
.write(F
"{asset_path('materials',img)}.vtf\n")
1906 fp
.write(F
"{cxr_winepath(asset_full_path('materials',img))}.vtf\n")
1908 for mdl
in a_models
:
1909 local
= asset_path('models',mdl
)
1910 winep
= cxr_winepath(asset_full_path('models',mdl
))
1912 fp
.write(F
"{local}.vvd\n")
1913 fp
.write(F
"{winep}.vvd\n")
1914 fp
.write(F
"{local}.dx90.vtx\n")
1915 fp
.write(F
"{winep}.dx90.vtx\n")
1916 fp
.write(F
"{local}.mdl\n")
1917 fp
.write(F
"{winep}.mdl\n")
1918 fp
.write(F
"{local}.vvd\n")
1919 fp
.write(F
"{winep}.vvd\n")
1921 if cxr_modelsrc_vphys(mdl
):
1922 fp
.write(F
"{local}.phy\n")
1923 fp
.write(F
"{winep}.phy\n")
1929 if settings
.comp_vmf
:
1930 static
.JOBINFO
+= [{
1931 "title": "Convexer",
1933 "colour": (0.863, 0.078, 0.235,1.0),
1934 "exec": cxr_export_vmf
,
1935 "jobs": [(sceneinfo
,output_vmf
)]
1938 if settings
.comp_textures
:
1939 if len(image_jobs
) > 0:
1940 static
.JOBINFO
+= [{
1941 "title": "Textures",
1943 "colour": (1.000, 0.271, 0.000,1.0),
1944 "exec": compile_image
,
1948 game
= cxr_winepath( settings
.subdir
)
1950 '-game', game
, settings
.project_name
1954 if settings
.comp_models
:
1955 if len(model_jobs
) > 0:
1956 static
.JOBINFO
+= [{
1959 "colour": (1.000, 0.647, 0.000,1.0),
1960 "exec": cxr_export_modelsrc
,
1964 if len(qc_jobs
) > 0:
1965 static
.JOBINFO
+= [{
1966 "title": "StudioMDL",
1968 "colour": (1.000, 0.843, 0.000, 1.0),
1969 "exec": "studiomdl",
1970 "jobs": [[settings
[F
'exe_studiomdl']] + [\
1971 '-nop4', '-game', game
, qc
] for qc
in qc_jobs
],
1976 if settings
.comp_compile
:
1977 if not settings
.opt_vbsp
.startswith( 'disable' ):
1978 vbsp_opt
= settings
.opt_vbsp
.split()
1979 static
.JOBINFO
+= [{
1982 "colour": (0.678, 1.000, 0.184,1.0),
1984 "jobs": [[settings
[F
'exe_vbsp']] + vbsp_opt
+ args
],
1988 if not settings
.opt_vvis
.startswith( 'disable' ):
1989 vvis_opt
= settings
.opt_vvis
.split()
1990 static
.JOBINFO
+= [{
1993 "colour": (0.000, 1.000, 0.498,1.0),
1995 "jobs": [[settings
[F
'exe_vvis']] + vvis_opt
+ args
],
1999 if not settings
.opt_vrad
.startswith( 'disable' ):
2000 vrad_opt
= settings
.opt_vrad
.split()
2001 static
.JOBINFO
+= [{
2004 "colour": (0.125, 0.698, 0.667,1.0),
2006 "jobs": [[settings
[F
'exe_vrad']] + vrad_opt
+ args
],
2010 static
.JOBINFO
+= [{
2013 "colour": (0.118, 0.565, 1.000,1.0),
2014 "exec": cxr_patchmap
,
2015 "jobs": [(bsp_local
,bsp_remote
)]
2018 if settings
.comp_pack
:
2019 static
.JOBINFO
+= [{
2022 "colour": (0.541, 0.169, 0.886,1.0),
2024 "jobs": [[cxr_compiler_path("bspzip"), '-addlist', \
2025 cxr_winepath(bsp_remote
),
2026 cxr_winepath(packlist
),
2027 cxr_winepath(bsp_packed
) ]],
2031 if len(static
.JOBINFO
) == 0:
2032 return {'CANCELLED'}
2034 static
.USER_EXIT
=False
2035 static
.TIMER
=wm
.event_timer_add(0.1,window
=context
.window
)
2036 wm
.modal_handler_add(_
)
2038 cxr_jobs_update_graph( static
.JOBINFO
)
2040 return {'RUNNING_MODAL'}
2042 class CXR_RESET_HASHES(bpy
.types
.Operator
):
2043 bl_idname
="convexer.hash_reset"
2044 bl_label
="Reset asset hashes"
2046 def execute(_
,context
):
2047 for c
in bpy
.data
.collections
:
2048 c
.cxr_data
.last_hash
= F
"<RESET>{time.time()}"
2049 c
.cxr_data
.asset_id
=0
2051 for t
in bpy
.data
.images
:
2052 t
.cxr_data
.last_hash
= F
"<RESET>{time.time()}"
2053 t
.cxr_data
.asset_id
=0
2057 class CXR_COMPILE_MATERIAL(bpy
.types
.Operator
):
2058 bl_idname
="convexer.matcomp"
2059 bl_label
="Recompile Material"
2061 def execute(_
,context
):
2062 active_obj
= bpy
.context
.active_object
2063 active_mat
= active_obj
.active_material
2065 #TODO: reduce code dupe (L1663)
2066 for pair
in compile_material(active_mat
):
2071 if isinstance(prop
,bpy
.types
.Image
):
2073 if 'flags' in pdef
: flags
= pdef
['flags']
2074 prop
.cxr_data
.flags
= flags
2076 compile_image( prop
)
2078 settings
= bpy
.context
.scene
.cxr_data
2079 with
open(F
'{settings.subdir}/cfg/convexer_mat_update.cfg','w') as o
:
2080 o
.write(F
'mat_reloadmaterial {asset_name(active_mat)}')
2083 with
open(F
'{settings.subdir}/cfg/convexer.cfg','w') as o
:
2084 o
.write('sv_cheats 1\n')
2085 o
.write('mp_warmup_pausetimer 1\n')
2086 o
.write('bot_kick\n')
2087 o
.write('alias cxr_reload "exec convexer_mat_update"\n')
2092 # ------------------------------------------------------------------------------
2094 # Helper buttons for 3d toolbox view
2096 class CXR_VIEW3D( bpy
.types
.Panel
):
2097 bl_idname
= "VIEW3D_PT_convexer"
2098 bl_label
= "Convexer"
2099 bl_space_type
= 'VIEW_3D'
2100 bl_region_type
= 'UI'
2101 bl_category
= "Convexer"
2103 def draw(_
, context
):
2106 active_object
= context
.object
2107 if active_object
== None: return
2109 purpose
= cxr_object_purpose( active_object
)
2111 if purpose
[0] == None or purpose
[1] == None:
2112 usage_str
= "No purpose"
2114 if purpose
[1] == 'model':
2115 usage_str
= F
'mesh in {asset_name( purpose[0] )}.mdl'
2117 usage_str
= F
'{purpose[1]} in {purpose[0].name}'
2119 layout
.label(text
=F
"Currently editing:")
2121 box
.label(text
=usage_str
)
2123 if purpose
[1] == 'brush' or purpose
[1] == 'brush_entity':
2126 row
.operator("convexer.preview")
2130 row
.operator("convexer.reset")
2132 # Main scene properties interface, where all the settings go
2134 class CXR_INTERFACE(bpy
.types
.Panel
):
2136 bl_idname
="SCENE_PT_convexer"
2137 bl_space_type
='PROPERTIES'
2138 bl_region_type
='WINDOW'
2141 def draw(_
,context
):
2142 if CXR_GNU_LINUX
==1:
2143 _
.layout
.operator("convexer.reload")
2144 _
.layout
.operator("convexer.dev_test")
2146 _
.layout
.operator("convexer.hash_reset")
2147 settings
= context
.scene
.cxr_data
2149 _
.layout
.prop(settings
, "scale_factor")
2150 _
.layout
.prop(settings
, "skybox_scale_factor")
2151 _
.layout
.prop(settings
, "skyname" )
2152 _
.layout
.prop(settings
, "lightmap_scale")
2153 _
.layout
.prop(settings
, "light_scale" )
2154 _
.layout
.prop(settings
, "image_quality" )
2156 box
= _
.layout
.box()
2158 box
.prop(settings
, "project_name")
2159 box
.prop(settings
, "subdir")
2161 box
= _
.layout
.box()
2162 box
.operator("convexer.detect_compilers")
2163 box
.prop(settings
, "exe_studiomdl")
2164 box
.prop(settings
, "exe_vbsp")
2165 box
.prop(settings
, "opt_vbsp")
2167 box
.prop(settings
, "exe_vvis")
2168 box
.prop(settings
, "opt_vvis")
2170 box
.prop(settings
, "exe_vrad")
2171 box
.prop(settings
, "opt_vrad")
2175 row
.prop(settings
,"comp_vmf")
2176 row
.prop(settings
,"comp_textures")
2177 row
.prop(settings
,"comp_models")
2178 row
.prop(settings
,"comp_compile")
2179 row
.prop(settings
,"comp_pack")
2181 text
= "Compile" if CXR_COMPILER_CHAIN
.TIMER
== None else "Cancel"
2184 row
.operator("convexer.chain", text
=text
)
2188 row
.operator("convexer.reset")
2189 if CXR_COMPILER_CHAIN
.TIMER
!= None:
2192 class CXR_MATERIAL_PANEL(bpy
.types
.Panel
):
2193 bl_label
="VMT Properties"
2194 bl_idname
="SCENE_PT_convexer_vmt"
2195 bl_space_type
='PROPERTIES'
2196 bl_region_type
='WINDOW'
2197 bl_context
="material"
2199 def draw(_
,context
):
2200 active_object
= bpy
.context
.active_object
2201 if active_object
== None: return
2203 active_material
= active_object
.active_material
2204 if active_material
== None: return
2206 properties
= active_material
.cxr_data
2207 info
= material_info( active_material
)
2209 _
.layout
.label(text
=F
"{info['name']} @{info['res'][0]}x{info['res'][1]}")
2210 row
= _
.layout
.row()
2211 row
.prop( properties
, "shader" )
2212 row
.operator( "convexer.matcomp" )
2214 #for xk in info: _.layout.label(text=F"{xk}:={info[xk]}")
2216 def _mtex( name
, img
, uiParent
):
2219 box
= uiParent
.box()
2220 box
.label( text
=F
'{name} "{img.filepath}"' )
2222 if ((x
& (x
- 1)) == 0):
2225 closest_diff
= 10000000
2227 dist
= abs((1 << i
)-x
)
2228 if dist
< closest_diff
:
2233 return 1 << (closest
+1)
2235 return 1 << (closest
-1)
2240 row
.prop( img
.cxr_data
, "export_res" )
2241 row
.prop( img
.cxr_data
, "fmt" )
2244 row
.prop( img
.cxr_data
, "mipmap" )
2245 row
.prop( img
.cxr_data
, "lod" )
2246 row
.prop( img
.cxr_data
, "clamp" )
2248 img
.cxr_data
.export_res
[0] = _p2( img
.cxr_data
.export_res
[0] )
2249 img
.cxr_data
.export_res
[1] = _p2( img
.cxr_data
.export_res
[1] )
2251 def _mview( layer
, uiParent
):
2255 if isinstance(layer
[decl
],dict): # $property definition
2257 ptype
= pdef
['type']
2263 if ('shaders' in pdef
) and \
2264 (properties
.shader
not in pdef
['shaders']):
2267 if ptype
== 'intrinsic':
2268 if decl
not in info
:
2273 if isinstance(pdef
[ch
],dict):
2274 if ptype
== 'ui' or ptype
== 'intrinsic':
2276 elif getattr(properties
,decl
) == pdef
['default']:
2279 thisnode
= uiParent
.box()
2283 thisnode
.label( text
=decl
)
2284 elif ptype
== 'intrinsic':
2285 if isinstance(info
[decl
], bpy
.types
.Image
):
2286 _mtex( decl
, info
[decl
], thisnode
)
2288 # hidden intrinsic value.
2289 # Means its a float array or something not an image
2290 thisnode
.label(text
=F
"-- hidden intrinsic '{decl}' --")
2292 thisnode
.prop(properties
,decl
)
2293 if expandview
: _mview(pdef
,thisnode
)
2295 _mview( cxr_shader_params
, _
.layout
)
2297 def cxr_entity_changeclass(_
,context
):
2298 active_object
= context
.active_object
2300 # Create ID properties
2302 classname
= cxr_custom_class(active_object
)
2304 if classname
in cxr_entities
:
2305 entdef
= cxr_entities
[classname
]
2307 kvs
= entdef
['keyvalues']
2308 if callable(kvs
): kvs
= kvs( {'object': active_object
} )
2314 if callable(kv
) or not isinstance(kv
,dict): continue
2316 if key
not in active_object
:
2317 active_object
[key
] = kv
['default']
2318 id_prop
= active_object
.id_properties_ui(key
)
2319 id_prop
.update(default
=kv
['default'])
2321 class CXR_ENTITY_PANEL(bpy
.types
.Panel
):
2322 bl_label
="Entity Config"
2323 bl_idname
="SCENE_PT_convexer_entity"
2324 bl_space_type
='PROPERTIES'
2325 bl_region_type
='WINDOW'
2328 def draw(_
,context
):
2329 active_object
= bpy
.context
.active_object
2331 if active_object
== None: return
2334 "scale": bpy
.context
.scene
.cxr_data
.scale_factor
,
2338 ecn
= cxr_intrinsic_classname( active_object
)
2339 classname
= cxr_custom_class( active_object
)
2342 if active_object
.type == 'MESH':
2343 _
.layout
.prop( active_object
.cxr_data
, 'brushclass' )
2344 else: _
.layout
.prop( active_object
.cxr_data
, 'classname' )
2346 _
.layout
.prop( active_object
.cxr_data
, 'visgroup' )
2347 _
.layout
.prop( active_object
.cxr_data
, 'lightmap_override' )
2349 if classname
== 'NONE':
2352 _
.layout
.label(text
=F
"<implementation defined ({ecn})>")
2353 _
.layout
.enabled
=False
2356 kvs
= cxr_entity_keyvalues( {
2357 "object": active_object
,
2358 "transform": default_context
,
2359 "classname": classname
2365 _
.layout
.prop( active_object
, F
'["cxrkv_{kv[0]}"]', text
=kv
[0])
2367 row
= _
.layout
.row()
2369 row
.label( text
=F
'{kv[0]}: {repr(kv[2])}' )
2371 _
.layout
.label( text
=F
"ERROR: NO CLASS DEFINITION" )
2373 class CXR_LIGHT_PANEL(bpy
.types
.Panel
):
2374 bl_label
= "Source Settings"
2375 bl_idname
= "LIGHT_PT_cxr"
2376 bl_space_type
= 'PROPERTIES'
2377 bl_region_type
= 'WINDOW'
2380 def draw(self
, context
):
2381 layout
= self
.layout
2382 scene
= context
.scene
2384 active_object
= bpy
.context
.active_object
2385 if active_object
== None: return
2387 if active_object
.type == 'LIGHT' or \
2388 active_object
.type == 'LIGHT_PROBE':
2390 properties
= active_object
.data
.cxr_data
2392 if active_object
.type == 'LIGHT':
2393 layout
.prop( properties
, "realtime" )
2394 elif active_object
.type == 'LIGHT_PROBE':
2395 layout
.prop( properties
, "size" )
2397 class CXR_COLLECTION_PANEL(bpy
.types
.Panel
):
2398 bl_label
= "Source Settings"
2399 bl_idname
= "COL_PT_cxr"
2400 bl_space_type
= 'PROPERTIES'
2401 bl_region_type
= 'WINDOW'
2402 bl_context
= "collection"
2404 def draw(self
, context
):
2405 layout
= self
.layout
2406 scene
= context
.scene
2408 active_collection
= bpy
.context
.collection
2410 if active_collection
!= None:
2411 layout
.prop( active_collection
.cxr_data
, "shadow_caster" )
2412 layout
.prop( active_collection
.cxr_data
, "texture_shadows" )
2413 layout
.prop( active_collection
.cxr_data
, "preserve_order" )
2414 layout
.prop( active_collection
.cxr_data
, "surfaceprop" )
2415 layout
.prop( active_collection
.cxr_data
, "visgroup" )
2418 # ------------------------------------------------------------------------------
2420 class CXR_IMAGE_SETTINGS(bpy
.types
.PropertyGroup
):
2421 export_res
: bpy
.props
.IntVectorProperty(
2423 description
="Texture Export Resolution",
2429 fmt
: bpy
.props
.EnumProperty(
2432 ('DXT1', "DXT1", "BC1 compressed", '', 0),
2433 ('DXT5', "DXT5", "BC3 compressed", '', 1),
2434 ('RGB', "RGB", "Uncompressed", '', 2),
2435 ('RGBA', "RGBA", "Uncompressed (with alpha)", '', 3)
2437 description
="Image format",
2440 last_hash
: bpy
.props
.StringProperty( name
="" )
2441 asset_id
: bpy
.props
.IntProperty(name
="intl_assetid",default
=0)
2443 mipmap
: bpy
.props
.BoolProperty(name
="MIP",default
=True)
2444 lod
: bpy
.props
.BoolProperty(name
="LOD",default
=True)
2445 clamp
: bpy
.props
.BoolProperty(name
="CLAMP",default
=False)
2446 flags
: bpy
.props
.IntProperty(name
="flags",default
=0)
2448 class CXR_LIGHT_SETTINGS(bpy
.types
.PropertyGroup
):
2449 realtime
: bpy
.props
.BoolProperty(name
="Realtime Light", default
=True)
2451 class CXR_CUBEMAP_SETTINGS(bpy
.types
.PropertyGroup
):
2452 size
: bpy
.props
.EnumProperty(
2455 ('1',"1x1",'','',0),
2456 ('2',"2x2",'','',1),
2457 ('3',"4x4",'','',2),
2458 ('4',"8x8",'','',3),
2459 ('5',"16x16",'','',4),
2460 ('6',"32x32",'','',5),
2461 ('7',"64x64",'','',6),
2462 ('8',"128x128",'','',7),
2463 ('9',"256x256",'','',8)
2465 description
="Texture resolution",
2468 class CXR_ENTITY_SETTINGS(bpy
.types
.PropertyGroup
):
2469 entity
: bpy
.props
.BoolProperty(name
="")
2471 enum_pointents
= [('NONE',"None","")]
2472 enum_brushents
= [('NONE',"None","")]
2474 for classname
in cxr_entities
:
2475 entdef
= cxr_entities
[classname
]
2476 if 'allow' in entdef
:
2477 itm
= [(classname
, classname
, "")]
2478 if 'EMPTY' in entdef
['allow']: enum_pointents
+= itm
2479 else: enum_brushents
+= itm
2481 classname
: bpy
.props
.EnumProperty(items
=enum_pointents
, name
="Class", \
2482 update
=cxr_entity_changeclass
, default
='NONE' )
2484 brushclass
: bpy
.props
.EnumProperty(items
=enum_brushents
, name
="Class", \
2485 update
=cxr_entity_changeclass
, default
='NONE' )
2487 enum_classes
= [('0',"None","")]
2488 for i
, vg
in enumerate(cxr_visgroups
):
2489 enum_classes
+= [(str(i
+1),vg
,"")]
2490 visgroup
: bpy
.props
.EnumProperty(name
="visgroup",items
=enum_classes
,default
=0)
2491 lightmap_override
: bpy
.props
.IntProperty(name
="Lightmap Override",default
=0)
2493 class CXR_MODEL_SETTINGS(bpy
.types
.PropertyGroup
):
2494 last_hash
: bpy
.props
.StringProperty( name
="" )
2495 asset_id
: bpy
.props
.IntProperty(name
="vmf_settings",default
=0)
2496 shadow_caster
: bpy
.props
.BoolProperty( name
="Shadow caster", default
=True )
2497 texture_shadows
: bpy
.props
.BoolProperty( name
="Texture Shadows", default
=False )
2498 preserve_order
: bpy
.props
.BoolProperty( name
="Preserve Order", default
=False )
2499 surfaceprop
: bpy
.props
.StringProperty( name
="Suface prop",default
="default" )
2501 enum_classes
= [('0',"None","")]
2502 for i
, vg
in enumerate(cxr_visgroups
):
2503 enum_classes
+= [(str(i
+1),vg
,"")]
2504 visgroup
: bpy
.props
.EnumProperty(name
="visgroup",items
=enum_classes
,default
=0)
2506 class CXR_SCENE_SETTINGS(bpy
.types
.PropertyGroup
):
2507 project_name
: bpy
.props
.StringProperty( name
="Project Name" )
2508 subdir
: bpy
.props
.StringProperty( name
="../csgo/ folder" )
2510 exe_studiomdl
: bpy
.props
.StringProperty( name
="studiomdl" )
2511 exe_vbsp
: bpy
.props
.StringProperty( name
="vbsp" )
2512 opt_vbsp
: bpy
.props
.StringProperty( name
="args" )
2513 exe_vvis
: bpy
.props
.StringProperty( name
="vvis" )
2514 opt_vvis
: bpy
.props
.StringProperty( name
="args" )
2515 exe_vrad
: bpy
.props
.StringProperty( name
="vrad" )
2516 opt_vrad
: bpy
.props
.StringProperty( name
="args", \
2517 default
="-reflectivityScale 0.35 -aoscale 1.4 -final -textureshadows -hdr -StaticPropLighting -StaticPropPolys" )
2519 scale_factor
: bpy
.props
.FloatProperty( name
="VMF Scale factor", \
2520 default
=32.0,min=1.0)
2521 skybox_scale_factor
: bpy
.props
.FloatProperty( name
="Sky Scale factor", \
2522 default
=1.0,min=0.01)
2523 skyname
: bpy
.props
.StringProperty(name
="Skyname",default
="sky_csgo_night02b")
2524 skybox_offset
: bpy
.props
.FloatProperty(name
="Sky offset",default
=-4096.0)
2525 light_scale
: bpy
.props
.FloatProperty(name
="Light Scale",default
=1.0/5.0)
2526 include_names
: bpy
.props
.BoolProperty(name
="Append original file names",\
2528 lightmap_scale
: bpy
.props
.IntProperty(name
="Global Lightmap Scale",\
2530 image_quality
: bpy
.props
.IntProperty(name
="Texture Quality (0-18)",\
2531 default
=8, min=0, max=18 )
2533 comp_vmf
: bpy
.props
.BoolProperty(name
="VMF",default
=True)
2534 comp_models
: bpy
.props
.BoolProperty(name
="Models",default
=True)
2535 comp_textures
: bpy
.props
.BoolProperty(name
="Textures",default
=True)
2536 comp_compile
: bpy
.props
.BoolProperty(name
="Compile",default
=True)
2537 comp_pack
: bpy
.props
.BoolProperty(name
="Pack",default
=False)
2539 classes
= [ CXR_RELOAD
, CXR_DEV_OPERATOR
, CXR_INTERFACE
, \
2540 CXR_MATERIAL_PANEL
, CXR_IMAGE_SETTINGS
,\
2541 CXR_MODEL_SETTINGS
, CXR_ENTITY_SETTINGS
, CXR_CUBEMAP_SETTINGS
,\
2542 CXR_LIGHT_SETTINGS
, CXR_SCENE_SETTINGS
, CXR_DETECT_COMPILERS
,\
2543 CXR_ENTITY_PANEL
, CXR_LIGHT_PANEL
, CXR_PREVIEW_OPERATOR
,\
2544 CXR_VIEW3D
, CXR_COMPILER_CHAIN
, CXR_RESET_HASHES
,\
2545 CXR_COMPILE_MATERIAL
, CXR_COLLECTION_PANEL
, CXR_RESET
]
2547 vmt_param_dynamic_class
= None
2550 global cxr_view_draw_handler
, vmt_param_dynamic_class
, cxr_ui_handler
2553 bpy
.utils
.register_class(c
)
2555 # Build dynamic VMT properties class defined by cxr_shader_params
2556 annotations_dict
= {}
2558 def _dvmt_propogate(layer
):
2559 nonlocal annotations_dict
2562 if isinstance(layer
[decl
],dict): # $property definition
2566 if pdef
['type'] == 'bool':
2567 prop
= bpy
.props
.BoolProperty(\
2568 name
= pdef
['name'],\
2569 default
= pdef
['default'])
2571 elif pdef
['type'] == 'float':
2572 prop
= bpy
.props
.FloatProperty(\
2573 name
= pdef
['name'],\
2574 default
= pdef
['default'])
2576 elif pdef
['type'] == 'vector':
2577 if 'subtype' in pdef
:
2578 prop
= bpy
.props
.FloatVectorProperty(\
2579 name
= pdef
['name'],\
2580 subtype
= pdef
['subtype'],\
2581 default
= pdef
['default'],\
2582 size
= len(pdef
['default']))
2584 prop
= bpy
.props
.FloatVectorProperty(\
2585 name
= pdef
['name'],\
2586 default
= pdef
['default'],\
2587 size
= len(pdef
['default']))
2589 elif pdef
['type'] == 'string':
2590 prop
= bpy
.props
.StringProperty(\
2591 name
= pdef
['name'],\
2592 default
= pdef
['default'])
2594 elif pdef
['type'] == 'enum':
2595 prop
= bpy
.props
.EnumProperty(\
2596 name
= pdef
['name'],\
2597 items
= pdef
['items'],\
2598 default
= pdef
['default'])
2601 annotations_dict
[decl
] = prop
2603 # Recurse into sub-definitions
2604 _dvmt_propogate(pdef
)
2606 annotations_dict
["shader"] = bpy
.props
.EnumProperty(\
2609 cxr_shaders
[_
]["name"],\
2610 '') for _
in cxr_shaders
],\
2611 default
= next(iter(cxr_shaders
)))
2613 annotations_dict
["asset_id"] = bpy
.props
.IntProperty(name
="intl_assetid",\
2616 _dvmt_propogate( cxr_shader_params
)
2617 vmt_param_dynamic_class
= type(
2619 (bpy
.types
.PropertyGroup
,),{
2620 "__annotations__": annotations_dict
2624 bpy
.utils
.register_class( vmt_param_dynamic_class
)
2627 bpy
.types
.Material
.cxr_data
= \
2628 bpy
.props
.PointerProperty(type=vmt_param_dynamic_class
)
2629 bpy
.types
.Image
.cxr_data
= \
2630 bpy
.props
.PointerProperty(type=CXR_IMAGE_SETTINGS
)
2631 bpy
.types
.Object
.cxr_data
= \
2632 bpy
.props
.PointerProperty(type=CXR_ENTITY_SETTINGS
)
2633 bpy
.types
.Collection
.cxr_data
= \
2634 bpy
.props
.PointerProperty(type=CXR_MODEL_SETTINGS
)
2635 bpy
.types
.Light
.cxr_data
= \
2636 bpy
.props
.PointerProperty(type=CXR_LIGHT_SETTINGS
)
2637 bpy
.types
.LightProbe
.cxr_data
= \
2638 bpy
.props
.PointerProperty(type=CXR_CUBEMAP_SETTINGS
)
2639 bpy
.types
.Scene
.cxr_data
= \
2640 bpy
.props
.PointerProperty(type=CXR_SCENE_SETTINGS
)
2642 # CXR Scene settings
2645 cxr_view_draw_handler
= bpy
.types
.SpaceView3D
.draw_handler_add(\
2646 cxr_draw
,(),'WINDOW','POST_VIEW')
2648 cxr_ui_handler
= bpy
.types
.SpaceView3D
.draw_handler_add(\
2649 cxr_ui
,(None,None),'WINDOW','POST_PIXEL')
2651 bpy
.app
.handlers
.load_post
.append(cxr_on_load
)
2652 bpy
.app
.handlers
.depsgraph_update_post
.append(cxr_dgraph_update
)
2655 global cxr_view_draw_handler
, vmt_param_dynamic_class
, cxr_ui_handler
2657 bpy
.utils
.unregister_class( vmt_param_dynamic_class
)
2659 bpy
.utils
.unregister_class(c
)
2661 bpy
.app
.handlers
.depsgraph_update_post
.remove(cxr_dgraph_update
)
2662 bpy
.app
.handlers
.load_post
.remove(cxr_on_load
)
2664 bpy
.types
.SpaceView3D
.draw_handler_remove(cxr_view_draw_handler
,'WINDOW')
2665 bpy
.types
.SpaceView3D
.draw_handler_remove(cxr_ui_handler
,'WINDOW')