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
, list ):
990 for shader_variant
in val
:
991 if shader_variant
[0] == mat
.cxr_data
.shader
:
992 return shader_variant
[1]
999 _graph_read
.extracted
= []
1001 for node_idname
in node_def
:
1002 for n
in mat
.node_tree
.nodes
:
1003 if n
.name
== node_idname
:
1004 node_def
= node_def
[node_idname
]
1008 for link
in node_def
:
1009 link_def
= _variant_apply( node_def
[link
] )
1011 if isinstance( link_def
, dict ):
1012 node_link
= node
.inputs
[link
]
1014 if node_link
.is_linked
:
1016 # look for definitions for the connected node type
1017 from_node
= node_link
.links
[0].from_node
1019 node_name
= from_node
.name
.split('.')[0]
1020 if node_name
in link_def
:
1021 from_node_def
= link_def
[ node_name
]
1023 _graph_read( from_node_def
, from_node
, depth
+1 )
1026 # TODO: Make a warning for this?
1029 if "default" in link_def
:
1030 prop
= _variant_apply( link_def
['default'] )
1031 info
[prop
] = node_link
.default_value
1033 prop
= _variant_apply( link_def
)
1034 info
[prop
] = getattr( node
, link
)
1036 _graph_read(cxr_graph_mapping
)
1038 if "$basetexture" in info
:
1039 export_res
= info
['$basetexture'].cxr_data
.export_res
1040 info
['res'] = (export_res
[0], export_res
[1])
1044 def vec3_min( a
, b
):
1045 return mathutils
.Vector((min(a
[0],b
[0]),min(a
[1],b
[1]),min(a
[2],b
[2])))
1046 def vec3_max( a
, b
):
1047 return mathutils
.Vector((max(a
[0],b
[0]),max(a
[1],b
[1]),max(a
[2],b
[2])))
1049 def cxr_collection_center(collection
, transform
):
1051 bounds_min
= mathutils
.Vector((BIG
,BIG
,BIG
))
1052 bounds_max
= mathutils
.Vector((-BIG
,-BIG
,-BIG
))
1054 for obj
in collection
.objects
:
1055 if obj
.type == 'MESH':
1056 corners
= [ mathutils
.Vector(c
) for c
in obj
.bound_box
]
1058 for corner
in [ obj
.matrix_world
@c for c
in corners
]:
1059 bounds_min
= vec3_min( bounds_min
, corner
)
1060 bounds_max
= vec3_max( bounds_max
, corner
)
1062 center
= (bounds_min
+ bounds_max
) / 2.0
1064 origin
= mathutils
.Vector((-center
[1],center
[0],center
[2]))
1065 origin
*= transform
['scale']
1069 # Prepares Scene into dictionary format
1071 def cxr_scene_collect():
1072 context
= bpy
.context
1074 # Make sure all of our asset types have a unique ID
1075 def _uid_prepare(objtype
):
1081 if vs
.asset_id
in used_ids
:
1084 id_max
= max(id_max
,vs
.asset_id
)
1085 used_ids
+=[vs
.asset_id
]
1086 for vs
in to_generate
:
1088 vs
.asset_id
= id_max
1089 _uid_prepare(bpy
.data
.materials
)
1090 _uid_prepare(bpy
.data
.images
)
1091 _uid_prepare(bpy
.data
.collections
)
1094 "entities": [], # Everything with a classname
1095 "geo": [], # All meshes without a classname
1096 "heros": [] # Collections prefixed with mdl_
1099 def _collect(collection
,transform
):
1102 purpose
= cxr_collection_purpose( collection
)
1103 if purpose
== None: return
1104 if purpose
== 'model':
1105 sceneinfo
['entities'] += [{
1106 "object": collection
,
1107 "classname": "prop_static",
1108 "transform": transform
,
1109 "origin": cxr_collection_center( collection
, transform
)
1112 sceneinfo
['heros'] += [{
1113 "collection": collection
,
1114 "transform": transform
,
1115 "origin": cxr_collection_center( collection
, transform
)
1119 for obj
in collection
.objects
:
1120 if obj
.hide_get(): continue
1122 classname
= cxr_classname( obj
)
1124 if classname
!= None:
1125 sceneinfo
['entities'] += [{
1127 "classname": classname
,
1128 "transform": transform
1130 elif obj
.type == 'MESH':
1131 sceneinfo
['geo'] += [{
1133 "transform": transform
1136 for c
in collection
.children
:
1137 _collect( c
, transform
)
1140 "scale": context
.scene
.cxr_data
.scale_factor
,
1145 "scale": context
.scene
.cxr_data
.skybox_scale_factor
,
1146 "offset": (0,0,context
.scene
.cxr_data
.skybox_offset
)
1149 if 'main' in bpy
.data
.collections
:
1150 _collect( bpy
.data
.collections
['main'], transform_main
)
1152 if 'skybox' in bpy
.data
.collections
:
1153 _collect( bpy
.data
.collections
['skybox'], transform_sky
)
1155 sceneinfo
['entities'] += [{
1157 "transform": transform_sky
,
1158 "classname": "sky_camera"
1163 # Write VMF out to file (JOB HANDLER)
1165 def cxr_export_vmf(sceneinfo
, output_vmf
):
1168 with
vdf_structure(output_vmf
) as m
:
1169 print( F
"Write: {output_vmf}" )
1171 vmfinfo
= cxr_vmf_context()
1172 vmfinfo
.mapversion
= 4
1174 #TODO: These need to be in options...
1175 vmfinfo
.skyname
= bpy
.context
.scene
.cxr_data
.skyname
.encode('utf-8')
1176 vmfinfo
.detailvbsp
= b
"detail.vbsp"
1177 vmfinfo
.detailmaterial
= b
"detail/detailsprites"
1178 vmfinfo
.lightmap_scale
= 12
1180 vmfinfo
.brush_count
= 0
1181 vmfinfo
.entity_count
= 0
1182 vmfinfo
.face_count
= 0
1184 visgroups
= (cxr_visgroup
*len(cxr_visgroups
))()
1185 for i
, vg
in enumerate(cxr_visgroups
):
1186 visgroups
[i
].name
= vg
.encode('utf-8')
1187 vmfinfo
.visgroups
= cast(visgroups
, POINTER(cxr_visgroup
))
1188 vmfinfo
.visgroup_count
= len(cxr_visgroups
)
1190 libcxr_begin_vmf
.call( pointer(vmfinfo
), m
.fp
)
1192 def _buildsolid( cmd
):
1195 print( F
"{vmfinfo.brush_count} :: {cmd['object'].name}" )
1197 baked
= mesh_cxr_format( cmd
['object'] )
1198 world
= cxr_decompose_globalerr( baked
)
1203 vmfinfo
.scale
= cmd
['transform']['scale']
1205 offset
= cmd
['transform']['offset']
1206 vmfinfo
.offset
[0] = offset
[0]
1207 vmfinfo
.offset
[1] = offset
[1]
1208 vmfinfo
.offset
[2] = offset
[2]
1210 if cmd
['object'].cxr_data
.lightmap_override
> 0:
1211 vmfinfo
.lightmap_scale
= cmd
['object'].cxr_data
.lightmap_override
1213 vmfinfo
.lightmap_scale
= bpy
.context
.scene
.cxr_data
.lightmap_scale
1215 libcxr_push_world_vmf
.call( world
, pointer(vmfinfo
), m
.fp
)
1216 libcxr_free_world
.call( world
)
1221 for brush
in sceneinfo
['geo']:
1222 vmfinfo
.visgroupid
= int(brush
['object'].cxr_data
.visgroup
)
1223 if not _buildsolid( brush
):
1227 vmfinfo
.visgroupid
= 0
1229 libcxr_vmf_begin_entities
.call(pointer(vmfinfo
), m
.fp
)
1232 for ent
in sceneinfo
['entities']:
1234 ctx
= ent
['transform']
1235 cls
= ent
['classname']
1238 m
.kv( 'classname', cls
)
1240 kvs
= cxr_entity_keyvalues( ent
)
1243 if isinstance(kv
[2], list):
1244 m
.kv( kv
[0], ' '.join([str(_
) for _
in kv
[2]]) )
1245 else: m
.kv( kv
[0], str(kv
[2]) )
1249 elif not isinstance( obj
, bpy
.types
.Collection
):
1250 if obj
.type == 'MESH':
1251 vmfinfo
.visgroupid
= int(obj
.cxr_data
.visgroup
)
1252 if not _buildsolid( ent
):
1259 m
.kv( 'visgroupid', str(obj
.cxr_data
.visgroup
) )
1260 m
.kv( 'visgroupshown', '1' )
1261 m
.kv( 'visgroupautoshown', '1' )
1265 vmfinfo
.visgroupid
= 0
1270 # COmpile image using NBVTF and hash it (JOB HANDLER)
1272 def compile_image(img
):
1276 name
= asset_name(img
)
1277 src_path
= bpy
.path
.abspath(img
.filepath
)
1279 dims
= img
.cxr_data
.export_res
1281 'RGBA': NBVTF_IMAGE_FORMAT_ABGR8888
,
1282 'DXT1': NBVTF_IMAGE_FORMAT_DXT1
,
1283 'DXT5': NBVTF_IMAGE_FORMAT_DXT5
,
1284 'RGB': NBVTF_IMAGE_FORMAT_BGR888
1285 }[ img
.cxr_data
.fmt
]
1287 mipmap
= img
.cxr_data
.mipmap
1288 lod
= img
.cxr_data
.lod
1289 clamp
= img
.cxr_data
.clamp
1290 flags
= img
.cxr_data
.flags
1292 q
=bpy
.context
.scene
.cxr_data
.image_quality
1294 userflag_hash
= F
"{mipmap}.{lod}.{clamp}.{flags}"
1295 file_hash
= F
"{name}.{os.path.getmtime(src_path)}"
1296 comphash
= F
"{file_hash}.{dims[0]}.{dims[1]}.{fmt}.{userflag_hash}.{q}"
1298 if img
.cxr_data
.last_hash
!= comphash
:
1299 print( F
"Texture update: {img.filepath}" )
1301 src
= src_path
.encode('utf-8')
1302 dst
= (asset_full_path('materials',img
)+'.vtf').encode('utf-8')
1306 # texture setting flags
1307 if not lod
: flags_full |
= NBVTF_TEXTUREFLAGS_NOLOD
1309 flags_full |
= NBVTF_TEXTUREFLAGS_CLAMPS
1310 flags_full |
= NBVTF_TEXTUREFLAGS_CLAMPT
1312 if libnbvtf_convert
.call(src
,dims
[0],dims
[1],mipmap
,fmt
,q
,flags_full
,dst
):
1313 img
.cxr_data
.last_hash
= comphash
1318 # Compile a material to VMT format. This is quick to run, doesnt need to be a
1321 def compile_material(mat
):
1322 info
= material_info(mat
)
1323 properties
= mat
.cxr_data
1325 print( F
"Compile {asset_full_path('materials',mat)}.vmt" )
1326 if properties
.shader
== 'Builtin':
1331 # Walk the property tree
1332 def _mlayer( layer
):
1333 nonlocal properties
, props
1336 if isinstance(layer
[decl
],dict): # $property definition
1338 ptype
= pdef
['type']
1344 if 'shaders' in pdef
and properties
.shader
not in pdef
['shaders']:
1347 # Group expansion (does it have subdefinitions?)
1349 if isinstance(pdef
[ch
],dict):
1358 if ptype
== 'intrinsic':
1362 prop
= getattr(properties
,decl
)
1363 default
= pdef
['default']
1365 if not isinstance(prop
,str) and \
1366 not isinstance(prop
,bpy
.types
.Image
) and \
1367 hasattr(prop
,'__getitem__'):
1368 prop
= tuple([p
for p
in prop
])
1372 props
+= [(decl
,pdef
,prop
)]
1377 if expandview
: _mlayer(pdef
)
1379 _mlayer( cxr_shader_params
)
1382 with
vdf_structure( F
"{asset_full_path('materials',mat)}.vmt" ) as vmt
:
1383 vmt
.node( properties
.shader
)
1384 vmt
.put( "// Convexer export\n" )
1393 if 'exponent' in pdef
: return str(pow( v
, pdef
['exponent'] ))
1396 if isinstance(prop
,bpy
.types
.Image
):
1397 vmt
.kv( decl
, asset_name(prop
))
1398 elif isinstance(prop
,bool):
1399 vmt
.kv( decl
, '1' if prop
else '0' )
1400 elif isinstance(prop
,str):
1401 vmt
.kv( decl
, prop
)
1402 elif isinstance(prop
,float) or isinstance(prop
,int):
1403 vmt
.kv( decl
, _numeric(prop
) )
1404 elif isinstance(prop
,tuple):
1405 vmt
.kv( decl
, F
"[{' '.join([_numeric(_) for _ in prop])}]" )
1407 vmt
.put( F
"// (cxr) unkown shader value type'{type(prop)}'" )
1412 def cxr_modelsrc_vphys( mdl
):
1413 for obj
in mdl
.objects
:
1414 if obj
.name
== F
"{mdl.name}_phy":
1418 def cxr_export_modelsrc( mdl
, origin
, asset_dir
, project_name
, transform
):
1419 dgraph
= bpy
.context
.evaluated_depsgraph_get()
1421 # Compute hash value
1422 chash
= asset_uid(mdl
)+str(origin
)+str(transform
)
1424 #for obj in mdl.objects:
1425 # if obj.type != 'MESH':
1428 # ev = obj.evaluated_get(dgraph).data
1429 # srcverts=[(v.co[0],v.co[1],v.co[2]) for v in ev.vertices]
1430 # srcloops=[(l.normal[0],l.normal[1],l.normal[2]) for l in ev.loops]
1432 # chash=hashlib.sha224((str(srcverts)+chash).encode()).hexdigest()
1433 # chash=hashlib.sha224((str(srcloops)+chash).encode()).hexdigest()
1435 # if ev.uv_layers.active != None:
1436 # uv_layer = ev.uv_layers.active.data
1437 # srcuv=[(uv.uv[0],uv.uv[1]) for uv in uv_layer]
1441 # chash=hashlib.sha224((str(srcuv)+chash).encode()).hexdigest()
1442 # srcmats=[ ms.material.name for ms in obj.material_slots ]
1443 # chash=hashlib.sha224((str(srcmats)+chash).encode()).hexdigest()
1444 # transforms=[ obj.location, obj.rotation_euler, obj.scale ]
1445 # srctr=[(v[0],v[1],v[2]) for v in transforms]
1446 # chash=hashlib.sha224((str(srctr)+chash).encode()).hexdigest()
1448 #if chash != mdl.cxr_data.last_hash:
1449 # mdl.cxr_data.last_hash = chash
1450 # print( F"Compile: {mdl.name}" )
1454 bpy
.ops
.object.select_all(action
='DESELECT')
1457 def _get_layer(col
,name
):
1458 for c
in col
.children
:
1461 sub
= _get_layer(c
,name
)
1465 layer
= _get_layer(bpy
.context
.view_layer
.layer_collection
,mdl
.name
)
1467 prev_state
= layer
.hide_viewport
1468 layer
.hide_viewport
=False
1470 # Collect materials to be compiled, and temp rename for export
1474 for obj
in mdl
.objects
:
1475 if obj
.name
== F
"{mdl.name}_phy":
1479 obj
.select_set(state
=True)
1480 for ms
in obj
.material_slots
:
1481 if ms
.material
!= None:
1482 if ms
.material
not in mat_dict
:
1483 mat_dict
[ms
.material
] = ms
.material
.name
1484 ms
.material
.name
= asset_uid(ms
.material
)
1485 ms
.material
.use_nodes
= False
1488 bpy
.ops
.export_scene
.fbx( filepath
=F
'{asset_dir}/{uid}_ref.fbx',\
1489 check_existing
=False,
1491 apply_unit_scale
=False,
1492 bake_space_transform
=False
1495 bpy
.ops
.object.select_all(action
='DESELECT')
1498 vphys
.select_set(state
=True)
1499 bpy
.ops
.export_scene
.fbx( filepath
=F
'{asset_dir}/{uid}_phy.fbx',\
1500 check_existing
=False,
1502 apply_unit_scale
=False,
1503 bake_space_transform
=False
1505 bpy
.ops
.object.select_all(action
='DESELECT')
1507 # Fix material names back to original
1508 for mat
in mat_dict
:
1509 mat
.name
= mat_dict
[mat
]
1510 mat
.use_nodes
= True
1512 layer
.hide_viewport
=prev_state
1515 with
open(F
'{asset_dir}/{uid}.qc','w') as o
:
1516 o
.write(F
'$modelname "{project_name}/{uid}"\n')
1517 #o.write(F'$scale .32\n')
1518 o
.write(F
'$scale {transform["scale"]/100.0}\n')
1519 o
.write(F
'$body _ "{uid}_ref.fbx"\n')
1520 o
.write(F
'$staticprop\n')
1521 o
.write(F
'$origin {origin[0]:.6f} {origin[1]:.6f} {origin[2]:.6f}\n')
1523 if mdl
.cxr_data
.preserve_order
:
1524 o
.write(F
"$preservetriangleorder\n")
1526 if mdl
.cxr_data
.texture_shadows
:
1527 o
.write(F
"$casttextureshadows\n")
1529 o
.write(F
"$surfaceprop {mdl.cxr_data.surfaceprop}\n")
1532 o
.write(F
'$collisionmodel "{uid}_phy.fbx"\n')
1534 o
.write(" $concave\n")
1537 o
.write(F
'$cdmaterials {project_name}\n')
1538 o
.write(F
'$sequence idle {uid}_ref.fbx\n')
1542 # Copy bsp file (and also lightpatch it)
1544 def cxr_patchmap( src
, dst
):
1545 libcxr_lightpatch_bsp
.call( src
.encode('utf-8') )
1546 shutil
.copyfile( src
, dst
)
1549 # Convexer operators
1550 # ------------------------------------------------------------------------------
1552 # Force reload of shared libraries
1554 class CXR_RELOAD(bpy
.types
.Operator
):
1555 bl_idname
="convexer.reload"
1557 def execute(_
,context
):
1561 # Reset all debugging/ui information
1563 class CXR_RESET(bpy
.types
.Operator
):
1564 bl_idname
="convexer.reset"
1565 bl_label
="Reset Convexer"
1566 def execute(_
,context
):
1570 # Used for exporting data to use with ASAN builds
1572 class CXR_DEV_OPERATOR(bpy
.types
.Operator
):
1573 bl_idname
="convexer.dev_test"
1574 bl_label
="Export development data"
1576 def execute(_
,context
):
1577 # Prepare input data
1578 mesh_src
= mesh_cxr_format(context
.active_object
)
1579 libcxr_write_test_data
.call( pointer(mesh_src
) )
1582 # UI: Preview how the brushes will looks in 3D view
1584 class CXR_PREVIEW_OPERATOR(bpy
.types
.Operator
):
1585 bl_idname
="convexer.preview"
1586 bl_label
="Preview Brushes"
1590 def execute(_
,context
):
1591 global cxr_view_mesh
1592 global cxr_view_shader
, cxr_view_mesh
, cxr_error_inf
1596 static
= _
.__class
__
1598 mesh_src
= mesh_cxr_format(context
.active_object
)
1599 world
= cxr_decompose_globalerr( mesh_src
)
1604 # Generate preview using cxr
1606 ptrpreview
= libcxr_world_preview
.call( world
)
1607 preview
= ptrpreview
[0]
1609 vertices
= preview
.vertices
[:preview
.vertex_count
]
1610 vertices
= [(_
[0],_
[1],_
[2]) for _
in vertices
]
1612 colours
= preview
.colours
[:preview
.vertex_count
]
1613 colours
= [(_
[0],_
[1],_
[2],_
[3]) for _
in colours
]
1615 indices
= preview
.indices
[:preview
.indices_count
]
1616 indices
= [ (indices
[i
*3+0],indices
[i
*3+1],indices
[i
*3+2]) \
1617 for i
in range(int(preview
.indices_count
/3)) ]
1619 cxr_view_mesh
= batch_for_shader(
1620 cxr_view_shader
, 'TRIS',
1621 { "pos": vertices
, "color": colours
},
1625 libcxr_free_tri_mesh
.call( ptrpreview
)
1626 libcxr_free_world
.call( world
)
1632 # Search for VMF compiler executables in subdirectory
1634 class CXR_DETECT_COMPILERS(bpy
.types
.Operator
):
1635 bl_idname
="convexer.detect_compilers"
1636 bl_label
="Find compilers"
1638 def execute(self
,context
):
1639 scene
= context
.scene
1640 settings
= scene
.cxr_data
1641 subdir
= settings
.subdir
1643 for exename
in ['studiomdl','vbsp','vvis','vrad']:
1644 searchpath
= os
.path
.normpath(F
'{subdir}/../bin/{exename}.exe')
1645 if os
.path
.exists(searchpath
):
1646 settings
[F
'exe_{exename}'] = searchpath
1650 def cxr_compiler_path( compiler
):
1651 settings
= bpy
.context
.scene
.cxr_data
1652 subdir
= settings
.subdir
1653 path
= os
.path
.normpath(F
'{subdir}/../bin/{compiler}.exe')
1655 if os
.path
.exists( path
): return path
1658 # Compatibility layer
1660 def cxr_temp_file( fn
):
1661 if CXR_GNU_LINUX
== 1:
1664 filepath
= bpy
.data
.filepath
1665 directory
= os
.path
.dirname(filepath
)
1666 return F
"{directory}/{fn}"
1668 def cxr_winepath( path
):
1669 if CXR_GNU_LINUX
== 1:
1670 return 'z:'+path
.replace('/','\\')
1674 # Main compile function
1676 class CXR_COMPILER_CHAIN(bpy
.types
.Operator
):
1677 bl_idname
="convexer.chain"
1678 bl_label
="Compile Chain"
1693 def cancel(_
,context
):
1694 #global cxr_jobs_batch
1695 static
= _
.__class
__
1696 wm
= context
.window_manager
1698 if static
.SUBPROC
!= None:
1699 static
.SUBPROC
.terminate()
1700 static
.SUBPROC
= None
1702 if static
.TIMER
!= None:
1703 wm
.event_timer_remove( static
.TIMER
)
1708 #cxr_jobs_batch = None
1712 def modal(_
,context
,ev
):
1713 static
= _
.__class
__
1715 if ev
.type == 'TIMER':
1716 global cxr_jobs_batch
, cxr_error_inf
1718 if static
.WAIT_REDRAW
:
1720 return {'PASS_THROUGH'}
1721 static
.WAIT_REDRAW
= True
1723 if static
.USER_EXIT
:
1724 print( "Chain USER_EXIT" )
1725 return _
.cancel(context
)
1727 if static
.SUBPROC
!= None:
1728 # Deal with async modes
1729 status
= static
.SUBPROC
.poll()
1731 # Cannot redirect STDOUT through here without causing
1732 # undefined behaviour due to the Blender Python specification.
1734 # Have to write it out to a file and read it back in.
1737 with
open(cxr_temp_file("convexer_compile_log.txt"),"r") as log
:
1738 static
.LOG
= log
.readlines()
1740 return {'PASS_THROUGH'}
1742 #for l in static.SUBPROC.stdout:
1743 # print( F'-> {l.decode("utf-8")}',end='' )
1744 static
.SUBPROC
= None
1747 print(F
'Compiler () error: {status}')
1749 jobn
= static
.JOBSYS
['jobs'][static
.JOBID
]
1750 cxr_error_inf
= ( F
"{static.JOBSYS['title']} error {status}", jobn
)
1752 return _
.cancel(context
)
1754 static
.JOBSYS
['jobs'][static
.JOBID
] = None
1755 cxr_jobs_update_graph( static
.JOBINFO
)
1757 return {'PASS_THROUGH'}
1759 # Compile syncronous thing
1760 for sys
in static
.JOBINFO
:
1761 for i
,target
in enumerate(sys
['jobs']):
1764 if callable(sys
['exec']):
1765 print( F
"Run (sync): {static.JOBID} @{time.time()}" )
1767 if not sys
['exec'](*target
):
1768 print( "Job failed" )
1769 return _
.cancel(context
)
1771 sys
['jobs'][i
] = None
1774 # Run external executable (wine)
1775 static
.SUBPROC
= subprocess
.Popen( target
,
1776 stdout
=static
.FILE
,\
1777 stderr
=subprocess
.PIPE
,\
1782 cxr_jobs_update_graph( static
.JOBINFO
)
1784 return {'PASS_THROUGH'}
1787 print( "All jobs completed!" )
1788 #cxr_jobs_batch = None
1790 return _
.cancel(context
)
1792 return {'PASS_THROUGH'}
1794 def invoke(_
,context
,event
):
1795 global cxr_error_inf
1797 static
= _
.__class
__
1798 wm
= context
.window_manager
1800 if static
.TIMER
!= None:
1801 print("Chain exiting...")
1802 static
.USER_EXIT
=True
1803 return {'RUNNING_MODAL'}
1805 print("Launching compiler toolchain")
1808 # Run static compilation units now (collect, vmt..)
1809 filepath
= bpy
.data
.filepath
1810 directory
= os
.path
.dirname(filepath
)
1811 settings
= bpy
.context
.scene
.cxr_data
1813 asset_dir
= F
"{directory}/modelsrc"
1814 material_dir
= F
"{settings.subdir}/materials/{settings.project_name}"
1815 model_dir
= F
"{settings.subdir}/models/{settings.project_name}"
1816 output_vmf
= F
"{directory}/{settings.project_name}.vmf"
1818 bsp_local
= F
"{directory}/{settings.project_name}.bsp"
1819 bsp_remote
= F
"{settings.subdir}/maps/{settings.project_name}.bsp"
1820 bsp_packed
= F
"{settings.subdir}/maps/{settings.project_name}_pack.bsp"
1821 packlist
= F
"{directory}/{settings.project_name}_assets.txt"
1823 os
.makedirs( asset_dir
, exist_ok
=True )
1824 os
.makedirs( material_dir
, exist_ok
=True )
1825 os
.makedirs( model_dir
, exist_ok
=True )
1827 static
.FILE
= open(cxr_temp_file("convexer_compile_log.txt"),"w")
1830 sceneinfo
= cxr_scene_collect()
1836 for brush
in sceneinfo
['geo']:
1837 for ms
in brush
['object'].material_slots
:
1838 a_materials
.add( ms
.material
)
1839 if ms
.material
.cxr_data
.shader
== 'VertexLitGeneric':
1840 errmat
= ms
.material
.name
1841 errnam
= brush
['object'].name
1843 cxr_error_inf
= ( "Shader error", \
1844 F
"Vertex shader ({errmat}) used on model ({errnam})" )
1846 print( F
"Vertex shader {errmat} used on {errnam}")
1848 return {'CANCELLED'}
1852 for ent
in sceneinfo
['entities']:
1853 if ent
['object'] == None: continue
1855 if ent
['classname'] == 'prop_static':
1857 if isinstance(obj
,bpy
.types
.Collection
):
1859 a_models
.add( target
)
1860 model_jobs
+= [(target
, ent
['origin'], asset_dir
, \
1861 settings
.project_name
, ent
['transform'])]
1863 target
= obj
.instance_collection
1864 if target
in a_models
:
1866 a_models
.add( target
)
1868 # TODO: Should take into account collection instancing offset
1869 model_jobs
+= [(target
, [0,0,0], asset_dir
, \
1870 settings
.project_name
, ent
['transform'])]
1872 elif ent
['object'].type == 'MESH':
1873 for ms
in ent
['object'].material_slots
:
1874 a_materials
.add( ms
.material
)
1876 for mdl
in a_models
:
1877 uid
= asset_uid(mdl
)
1878 qc_jobs
+= [F
'{uid}.qc']
1880 for obj
in mdl
.objects
:
1881 for ms
in obj
.material_slots
:
1882 a_materials
.add( ms
.material
)
1883 if ms
.material
.cxr_data
.shader
== 'LightMappedGeneric' or \
1884 ms
.material
.cxr_data
.shader
== 'WorldVertexTransition':
1886 errmat
= ms
.material
.name
1889 cxr_error_inf
= ( "Shader error", \
1890 F
"Lightmapped shader ({errmat}) used on model ({errnam})" )
1892 print( F
"Lightmapped shader {errmat} used on {errnam}")
1894 return {'CANCELLED'}
1897 for mat
in a_materials
:
1898 for pair
in compile_material(mat
):
1903 if isinstance(prop
,bpy
.types
.Image
):
1905 if 'flags' in pdef
: flags
= pdef
['flags']
1906 if prop
not in image_jobs
:
1907 image_jobs
+= [(prop
,)]
1908 prop
.cxr_data
.flags
= flags
1911 with
open( packlist
, "w" ) as fp
:
1913 for mat
in a_materials
:
1914 if mat
.cxr_data
.shader
== 'Builtin': continue
1915 fp
.write(F
"{asset_path('materials',mat)}.vmt\n")
1916 fp
.write(F
"{cxr_winepath(asset_full_path('materials',mat))}.vmt\n")
1918 for img_job
in image_jobs
:
1920 fp
.write(F
"{asset_path('materials',img)}.vtf\n")
1921 fp
.write(F
"{cxr_winepath(asset_full_path('materials',img))}.vtf\n")
1923 for mdl
in a_models
:
1924 local
= asset_path('models',mdl
)
1925 winep
= cxr_winepath(asset_full_path('models',mdl
))
1927 fp
.write(F
"{local}.vvd\n")
1928 fp
.write(F
"{winep}.vvd\n")
1929 fp
.write(F
"{local}.dx90.vtx\n")
1930 fp
.write(F
"{winep}.dx90.vtx\n")
1931 fp
.write(F
"{local}.mdl\n")
1932 fp
.write(F
"{winep}.mdl\n")
1933 fp
.write(F
"{local}.vvd\n")
1934 fp
.write(F
"{winep}.vvd\n")
1936 if cxr_modelsrc_vphys(mdl
):
1937 fp
.write(F
"{local}.phy\n")
1938 fp
.write(F
"{winep}.phy\n")
1944 if settings
.comp_vmf
:
1945 static
.JOBINFO
+= [{
1946 "title": "Convexer",
1948 "colour": (0.863, 0.078, 0.235,1.0),
1949 "exec": cxr_export_vmf
,
1950 "jobs": [(sceneinfo
,output_vmf
)]
1953 if settings
.comp_textures
:
1954 if len(image_jobs
) > 0:
1955 static
.JOBINFO
+= [{
1956 "title": "Textures",
1958 "colour": (1.000, 0.271, 0.000,1.0),
1959 "exec": compile_image
,
1963 game
= cxr_winepath( settings
.subdir
)
1965 '-game', game
, settings
.project_name
1969 if settings
.comp_models
:
1970 if len(model_jobs
) > 0:
1971 static
.JOBINFO
+= [{
1974 "colour": (1.000, 0.647, 0.000,1.0),
1975 "exec": cxr_export_modelsrc
,
1979 if len(qc_jobs
) > 0:
1980 static
.JOBINFO
+= [{
1981 "title": "StudioMDL",
1983 "colour": (1.000, 0.843, 0.000, 1.0),
1984 "exec": "studiomdl",
1985 "jobs": [[settings
[F
'exe_studiomdl']] + [\
1986 '-nop4', '-game', game
, qc
] for qc
in qc_jobs
],
1991 if settings
.comp_compile
:
1992 if not settings
.opt_vbsp
.startswith( 'disable' ):
1993 vbsp_opt
= settings
.opt_vbsp
.split()
1994 static
.JOBINFO
+= [{
1997 "colour": (0.678, 1.000, 0.184,1.0),
1999 "jobs": [[settings
[F
'exe_vbsp']] + vbsp_opt
+ args
],
2003 if not settings
.opt_vvis
.startswith( 'disable' ):
2004 vvis_opt
= settings
.opt_vvis
.split()
2005 static
.JOBINFO
+= [{
2008 "colour": (0.000, 1.000, 0.498,1.0),
2010 "jobs": [[settings
[F
'exe_vvis']] + vvis_opt
+ args
],
2014 if not settings
.opt_vrad
.startswith( 'disable' ):
2015 vrad_opt
= settings
.opt_vrad
.split()
2016 static
.JOBINFO
+= [{
2019 "colour": (0.125, 0.698, 0.667,1.0),
2021 "jobs": [[settings
[F
'exe_vrad']] + vrad_opt
+ args
],
2025 static
.JOBINFO
+= [{
2028 "colour": (0.118, 0.565, 1.000,1.0),
2029 "exec": cxr_patchmap
,
2030 "jobs": [(bsp_local
,bsp_remote
)]
2033 if settings
.comp_pack
:
2034 static
.JOBINFO
+= [{
2037 "colour": (0.541, 0.169, 0.886,1.0),
2039 "jobs": [[cxr_compiler_path("bspzip"), '-addlist', \
2040 cxr_winepath(bsp_remote
),
2041 cxr_winepath(packlist
),
2042 cxr_winepath(bsp_packed
) ]],
2046 if len(static
.JOBINFO
) == 0:
2047 return {'CANCELLED'}
2049 static
.USER_EXIT
=False
2050 static
.TIMER
=wm
.event_timer_add(0.1,window
=context
.window
)
2051 wm
.modal_handler_add(_
)
2053 cxr_jobs_update_graph( static
.JOBINFO
)
2055 return {'RUNNING_MODAL'}
2057 class CXR_RESET_HASHES(bpy
.types
.Operator
):
2058 bl_idname
="convexer.hash_reset"
2059 bl_label
="Reset asset hashes"
2061 def execute(_
,context
):
2062 for c
in bpy
.data
.collections
:
2063 c
.cxr_data
.last_hash
= F
"<RESET>{time.time()}"
2064 c
.cxr_data
.asset_id
=0
2066 for t
in bpy
.data
.images
:
2067 t
.cxr_data
.last_hash
= F
"<RESET>{time.time()}"
2068 t
.cxr_data
.asset_id
=0
2072 class CXR_COMPILE_MATERIAL(bpy
.types
.Operator
):
2073 bl_idname
="convexer.matcomp"
2074 bl_label
="Recompile Material"
2076 def execute(_
,context
):
2077 active_obj
= bpy
.context
.active_object
2078 active_mat
= active_obj
.active_material
2080 #TODO: reduce code dupe (L1663)
2081 for pair
in compile_material(active_mat
):
2086 if isinstance(prop
,bpy
.types
.Image
):
2088 if 'flags' in pdef
: flags
= pdef
['flags']
2089 prop
.cxr_data
.flags
= flags
2091 compile_image( prop
)
2093 settings
= bpy
.context
.scene
.cxr_data
2094 with
open(F
'{settings.subdir}/cfg/convexer_mat_update.cfg','w') as o
:
2095 o
.write(F
'mat_reloadmaterial {asset_name(active_mat)}')
2098 with
open(F
'{settings.subdir}/cfg/convexer.cfg','w') as o
:
2099 o
.write('sv_cheats 1\n')
2100 o
.write('mp_warmup_pausetimer 1\n')
2101 o
.write('bot_kick\n')
2102 o
.write('alias cxr_reload "exec convexer_mat_update"\n')
2107 # ------------------------------------------------------------------------------
2109 # Helper buttons for 3d toolbox view
2111 class CXR_VIEW3D( bpy
.types
.Panel
):
2112 bl_idname
= "VIEW3D_PT_convexer"
2113 bl_label
= "Convexer"
2114 bl_space_type
= 'VIEW_3D'
2115 bl_region_type
= 'UI'
2116 bl_category
= "Convexer"
2118 def draw(_
, context
):
2121 active_object
= context
.object
2122 if active_object
== None: return
2124 purpose
= cxr_object_purpose( active_object
)
2126 if purpose
[0] == None or purpose
[1] == None:
2127 usage_str
= "No purpose"
2129 if purpose
[1] == 'model':
2130 usage_str
= F
'mesh in {asset_name( purpose[0] )}.mdl'
2132 usage_str
= F
'{purpose[1]} in {purpose[0].name}'
2134 layout
.label(text
=F
"Currently editing:")
2136 box
.label(text
=usage_str
)
2138 if purpose
[1] == 'brush' or purpose
[1] == 'brush_entity':
2141 row
.operator("convexer.preview")
2145 row
.operator("convexer.reset")
2147 # Main scene properties interface, where all the settings go
2149 class CXR_INTERFACE(bpy
.types
.Panel
):
2151 bl_idname
="SCENE_PT_convexer"
2152 bl_space_type
='PROPERTIES'
2153 bl_region_type
='WINDOW'
2156 def draw(_
,context
):
2157 if CXR_GNU_LINUX
==1:
2158 _
.layout
.operator("convexer.reload")
2159 _
.layout
.operator("convexer.dev_test")
2161 _
.layout
.operator("convexer.hash_reset")
2162 settings
= context
.scene
.cxr_data
2164 _
.layout
.prop(settings
, "scale_factor")
2165 _
.layout
.prop(settings
, "skybox_scale_factor")
2166 _
.layout
.prop(settings
, "skyname" )
2167 _
.layout
.prop(settings
, "lightmap_scale")
2168 _
.layout
.prop(settings
, "light_scale" )
2169 _
.layout
.prop(settings
, "image_quality" )
2171 box
= _
.layout
.box()
2173 box
.prop(settings
, "project_name")
2174 box
.prop(settings
, "subdir")
2176 box
= _
.layout
.box()
2177 box
.operator("convexer.detect_compilers")
2178 box
.prop(settings
, "exe_studiomdl")
2179 box
.prop(settings
, "exe_vbsp")
2180 box
.prop(settings
, "opt_vbsp")
2182 box
.prop(settings
, "exe_vvis")
2183 box
.prop(settings
, "opt_vvis")
2185 box
.prop(settings
, "exe_vrad")
2186 box
.prop(settings
, "opt_vrad")
2190 row
.prop(settings
,"comp_vmf")
2191 row
.prop(settings
,"comp_textures")
2192 row
.prop(settings
,"comp_models")
2193 row
.prop(settings
,"comp_compile")
2194 row
.prop(settings
,"comp_pack")
2196 text
= "Compile" if CXR_COMPILER_CHAIN
.TIMER
== None else "Cancel"
2199 row
.operator("convexer.chain", text
=text
)
2203 row
.operator("convexer.reset")
2204 if CXR_COMPILER_CHAIN
.TIMER
!= None:
2207 class CXR_MATERIAL_PANEL(bpy
.types
.Panel
):
2208 bl_label
="VMT Properties"
2209 bl_idname
="SCENE_PT_convexer_vmt"
2210 bl_space_type
='PROPERTIES'
2211 bl_region_type
='WINDOW'
2212 bl_context
="material"
2214 def draw(_
,context
):
2215 active_object
= bpy
.context
.active_object
2216 if active_object
== None: return
2218 active_material
= active_object
.active_material
2219 if active_material
== None: return
2221 properties
= active_material
.cxr_data
2222 info
= material_info( active_material
)
2224 _
.layout
.label(text
=F
"{info['name']} @{info['res'][0]}x{info['res'][1]}")
2225 row
= _
.layout
.row()
2226 row
.prop( properties
, "shader" )
2227 row
.operator( "convexer.matcomp" )
2229 #for xk in info: _.layout.label(text=F"{xk}:={info[xk]}")
2231 def _mtex( name
, img
, uiParent
):
2234 box
= uiParent
.box()
2235 box
.label( text
=F
'{name} "{img.filepath}"' )
2237 if ((x
& (x
- 1)) == 0):
2240 closest_diff
= 10000000
2242 dist
= abs((1 << i
)-x
)
2243 if dist
< closest_diff
:
2248 return 1 << (closest
+1)
2250 return 1 << (closest
-1)
2255 row
.prop( img
.cxr_data
, "export_res" )
2256 row
.prop( img
.cxr_data
, "fmt" )
2259 row
.prop( img
.cxr_data
, "mipmap" )
2260 row
.prop( img
.cxr_data
, "lod" )
2261 row
.prop( img
.cxr_data
, "clamp" )
2263 img
.cxr_data
.export_res
[0] = _p2( img
.cxr_data
.export_res
[0] )
2264 img
.cxr_data
.export_res
[1] = _p2( img
.cxr_data
.export_res
[1] )
2266 def _mview( layer
, uiParent
):
2270 if isinstance(layer
[decl
],dict): # $property definition
2272 ptype
= pdef
['type']
2278 if ('shaders' in pdef
) and \
2279 (properties
.shader
not in pdef
['shaders']):
2282 if ptype
== 'intrinsic':
2283 if decl
not in info
:
2288 if isinstance(pdef
[ch
],dict):
2289 if ptype
== 'ui' or ptype
== 'intrinsic':
2291 elif getattr(properties
,decl
) == pdef
['default']:
2294 thisnode
= uiParent
.box()
2298 thisnode
.label( text
=decl
)
2299 elif ptype
== 'intrinsic':
2300 if isinstance(info
[decl
], bpy
.types
.Image
):
2301 _mtex( decl
, info
[decl
], thisnode
)
2303 # hidden intrinsic value.
2304 # Means its a float array or something not an image
2305 thisnode
.label(text
=F
"-- hidden intrinsic '{decl}' --")
2307 thisnode
.prop(properties
,decl
)
2308 if expandview
: _mview(pdef
,thisnode
)
2310 _mview( cxr_shader_params
, _
.layout
)
2312 def cxr_entity_changeclass(_
,context
):
2313 active_object
= context
.active_object
2315 # Create ID properties
2317 classname
= cxr_custom_class(active_object
)
2319 if classname
in cxr_entities
:
2320 entdef
= cxr_entities
[classname
]
2322 kvs
= entdef
['keyvalues']
2323 if callable(kvs
): kvs
= kvs( {'object': active_object
} )
2329 if callable(kv
) or not isinstance(kv
,dict): continue
2331 if key
not in active_object
:
2332 active_object
[key
] = kv
['default']
2333 id_prop
= active_object
.id_properties_ui(key
)
2334 id_prop
.update(default
=kv
['default'])
2336 class CXR_ENTITY_PANEL(bpy
.types
.Panel
):
2337 bl_label
="Entity Config"
2338 bl_idname
="SCENE_PT_convexer_entity"
2339 bl_space_type
='PROPERTIES'
2340 bl_region_type
='WINDOW'
2343 def draw(_
,context
):
2344 active_object
= bpy
.context
.active_object
2346 if active_object
== None: return
2349 "scale": bpy
.context
.scene
.cxr_data
.scale_factor
,
2353 ecn
= cxr_intrinsic_classname( active_object
)
2354 classname
= cxr_custom_class( active_object
)
2357 if active_object
.type == 'MESH':
2358 _
.layout
.prop( active_object
.cxr_data
, 'brushclass' )
2359 else: _
.layout
.prop( active_object
.cxr_data
, 'classname' )
2361 _
.layout
.prop( active_object
.cxr_data
, 'visgroup' )
2362 _
.layout
.prop( active_object
.cxr_data
, 'lightmap_override' )
2364 if classname
== 'NONE':
2367 _
.layout
.label(text
=F
"<implementation defined ({ecn})>")
2368 _
.layout
.enabled
=False
2371 kvs
= cxr_entity_keyvalues( {
2372 "object": active_object
,
2373 "transform": default_context
,
2374 "classname": classname
2380 _
.layout
.prop( active_object
, F
'["cxrkv_{kv[0]}"]', text
=kv
[0])
2382 row
= _
.layout
.row()
2384 row
.label( text
=F
'{kv[0]}: {repr(kv[2])}' )
2386 _
.layout
.label( text
=F
"ERROR: NO CLASS DEFINITION" )
2388 class CXR_LIGHT_PANEL(bpy
.types
.Panel
):
2389 bl_label
= "Source Settings"
2390 bl_idname
= "LIGHT_PT_cxr"
2391 bl_space_type
= 'PROPERTIES'
2392 bl_region_type
= 'WINDOW'
2395 def draw(self
, context
):
2396 layout
= self
.layout
2397 scene
= context
.scene
2399 active_object
= bpy
.context
.active_object
2400 if active_object
== None: return
2402 if active_object
.type == 'LIGHT' or \
2403 active_object
.type == 'LIGHT_PROBE':
2405 properties
= active_object
.data
.cxr_data
2407 if active_object
.type == 'LIGHT':
2408 layout
.prop( properties
, "realtime" )
2409 elif active_object
.type == 'LIGHT_PROBE':
2410 layout
.prop( properties
, "size" )
2412 class CXR_COLLECTION_PANEL(bpy
.types
.Panel
):
2413 bl_label
= "Source Settings"
2414 bl_idname
= "COL_PT_cxr"
2415 bl_space_type
= 'PROPERTIES'
2416 bl_region_type
= 'WINDOW'
2417 bl_context
= "collection"
2419 def draw(self
, context
):
2420 layout
= self
.layout
2421 scene
= context
.scene
2423 active_collection
= bpy
.context
.collection
2425 if active_collection
!= None:
2426 layout
.prop( active_collection
.cxr_data
, "shadow_caster" )
2427 layout
.prop( active_collection
.cxr_data
, "texture_shadows" )
2428 layout
.prop( active_collection
.cxr_data
, "preserve_order" )
2429 layout
.prop( active_collection
.cxr_data
, "surfaceprop" )
2430 layout
.prop( active_collection
.cxr_data
, "visgroup" )
2433 # ------------------------------------------------------------------------------
2435 class CXR_IMAGE_SETTINGS(bpy
.types
.PropertyGroup
):
2436 export_res
: bpy
.props
.IntVectorProperty(
2438 description
="Texture Export Resolution",
2444 fmt
: bpy
.props
.EnumProperty(
2447 ('DXT1', "DXT1", "BC1 compressed", '', 0),
2448 ('DXT5', "DXT5", "BC3 compressed", '', 1),
2449 ('RGB', "RGB", "Uncompressed", '', 2),
2450 ('RGBA', "RGBA", "Uncompressed (with alpha)", '', 3)
2452 description
="Image format",
2455 last_hash
: bpy
.props
.StringProperty( name
="" )
2456 asset_id
: bpy
.props
.IntProperty(name
="intl_assetid",default
=0)
2458 mipmap
: bpy
.props
.BoolProperty(name
="MIP",default
=True)
2459 lod
: bpy
.props
.BoolProperty(name
="LOD",default
=True)
2460 clamp
: bpy
.props
.BoolProperty(name
="CLAMP",default
=False)
2461 flags
: bpy
.props
.IntProperty(name
="flags",default
=0)
2463 class CXR_LIGHT_SETTINGS(bpy
.types
.PropertyGroup
):
2464 realtime
: bpy
.props
.BoolProperty(name
="Realtime Light", default
=True)
2466 class CXR_CUBEMAP_SETTINGS(bpy
.types
.PropertyGroup
):
2467 size
: bpy
.props
.EnumProperty(
2470 ('1',"1x1",'','',0),
2471 ('2',"2x2",'','',1),
2472 ('3',"4x4",'','',2),
2473 ('4',"8x8",'','',3),
2474 ('5',"16x16",'','',4),
2475 ('6',"32x32",'','',5),
2476 ('7',"64x64",'','',6),
2477 ('8',"128x128",'','',7),
2478 ('9',"256x256",'','',8)
2480 description
="Texture resolution",
2483 class CXR_ENTITY_SETTINGS(bpy
.types
.PropertyGroup
):
2484 entity
: bpy
.props
.BoolProperty(name
="")
2486 enum_pointents
= [('NONE',"None","")]
2487 enum_brushents
= [('NONE',"None","")]
2489 for classname
in cxr_entities
:
2490 entdef
= cxr_entities
[classname
]
2491 if 'allow' in entdef
:
2492 itm
= [(classname
, classname
, "")]
2493 if 'EMPTY' in entdef
['allow']: enum_pointents
+= itm
2494 else: enum_brushents
+= itm
2496 classname
: bpy
.props
.EnumProperty(items
=enum_pointents
, name
="Class", \
2497 update
=cxr_entity_changeclass
, default
='NONE' )
2499 brushclass
: bpy
.props
.EnumProperty(items
=enum_brushents
, name
="Class", \
2500 update
=cxr_entity_changeclass
, default
='NONE' )
2502 enum_classes
= [('0',"None","")]
2503 for i
, vg
in enumerate(cxr_visgroups
):
2504 enum_classes
+= [(str(i
+1),vg
,"")]
2505 visgroup
: bpy
.props
.EnumProperty(name
="visgroup",items
=enum_classes
,default
=0)
2506 lightmap_override
: bpy
.props
.IntProperty(name
="Lightmap Override",default
=0)
2508 class CXR_MODEL_SETTINGS(bpy
.types
.PropertyGroup
):
2509 last_hash
: bpy
.props
.StringProperty( name
="" )
2510 asset_id
: bpy
.props
.IntProperty(name
="vmf_settings",default
=0)
2511 shadow_caster
: bpy
.props
.BoolProperty( name
="Shadow caster", default
=True )
2512 texture_shadows
: bpy
.props
.BoolProperty( name
="Texture Shadows", default
=False )
2513 preserve_order
: bpy
.props
.BoolProperty( name
="Preserve Order", default
=False )
2514 surfaceprop
: bpy
.props
.StringProperty( name
="Suface prop",default
="default" )
2516 enum_classes
= [('0',"None","")]
2517 for i
, vg
in enumerate(cxr_visgroups
):
2518 enum_classes
+= [(str(i
+1),vg
,"")]
2519 visgroup
: bpy
.props
.EnumProperty(name
="visgroup",items
=enum_classes
,default
=0)
2521 class CXR_SCENE_SETTINGS(bpy
.types
.PropertyGroup
):
2522 project_name
: bpy
.props
.StringProperty( name
="Project Name" )
2523 subdir
: bpy
.props
.StringProperty( name
="../csgo/ folder" )
2525 exe_studiomdl
: bpy
.props
.StringProperty( name
="studiomdl" )
2526 exe_vbsp
: bpy
.props
.StringProperty( name
="vbsp" )
2527 opt_vbsp
: bpy
.props
.StringProperty( name
="args" )
2528 exe_vvis
: bpy
.props
.StringProperty( name
="vvis" )
2529 opt_vvis
: bpy
.props
.StringProperty( name
="args" )
2530 exe_vrad
: bpy
.props
.StringProperty( name
="vrad" )
2531 opt_vrad
: bpy
.props
.StringProperty( name
="args", \
2532 default
="-reflectivityScale 0.35 -aoscale 1.4 -final -textureshadows -hdr -StaticPropLighting -StaticPropPolys" )
2534 scale_factor
: bpy
.props
.FloatProperty( name
="VMF Scale factor", \
2535 default
=32.0,min=1.0)
2536 skybox_scale_factor
: bpy
.props
.FloatProperty( name
="Sky Scale factor", \
2537 default
=1.0,min=0.01)
2538 skyname
: bpy
.props
.StringProperty(name
="Skyname",default
="sky_csgo_night02b")
2539 skybox_offset
: bpy
.props
.FloatProperty(name
="Sky offset",default
=-4096.0)
2540 light_scale
: bpy
.props
.FloatProperty(name
="Light Scale",default
=1.0/5.0)
2541 include_names
: bpy
.props
.BoolProperty(name
="Append original file names",\
2543 lightmap_scale
: bpy
.props
.IntProperty(name
="Global Lightmap Scale",\
2545 image_quality
: bpy
.props
.IntProperty(name
="Texture Quality (0-18)",\
2546 default
=8, min=0, max=18 )
2548 comp_vmf
: bpy
.props
.BoolProperty(name
="VMF",default
=True)
2549 comp_models
: bpy
.props
.BoolProperty(name
="Models",default
=True)
2550 comp_textures
: bpy
.props
.BoolProperty(name
="Textures",default
=True)
2551 comp_compile
: bpy
.props
.BoolProperty(name
="Compile",default
=True)
2552 comp_pack
: bpy
.props
.BoolProperty(name
="Pack",default
=False)
2554 classes
= [ CXR_RELOAD
, CXR_DEV_OPERATOR
, CXR_INTERFACE
, \
2555 CXR_MATERIAL_PANEL
, CXR_IMAGE_SETTINGS
,\
2556 CXR_MODEL_SETTINGS
, CXR_ENTITY_SETTINGS
, CXR_CUBEMAP_SETTINGS
,\
2557 CXR_LIGHT_SETTINGS
, CXR_SCENE_SETTINGS
, CXR_DETECT_COMPILERS
,\
2558 CXR_ENTITY_PANEL
, CXR_LIGHT_PANEL
, CXR_PREVIEW_OPERATOR
,\
2559 CXR_VIEW3D
, CXR_COMPILER_CHAIN
, CXR_RESET_HASHES
,\
2560 CXR_COMPILE_MATERIAL
, CXR_COLLECTION_PANEL
, CXR_RESET
]
2562 vmt_param_dynamic_class
= None
2565 global cxr_view_draw_handler
, vmt_param_dynamic_class
, cxr_ui_handler
2568 bpy
.utils
.register_class(c
)
2570 # Build dynamic VMT properties class defined by cxr_shader_params
2571 annotations_dict
= {}
2573 def _dvmt_propogate(layer
):
2574 nonlocal annotations_dict
2577 if isinstance(layer
[decl
],dict): # $property definition
2581 if pdef
['type'] == 'bool':
2582 prop
= bpy
.props
.BoolProperty(\
2583 name
= pdef
['name'],\
2584 default
= pdef
['default'])
2586 elif pdef
['type'] == 'float':
2587 prop
= bpy
.props
.FloatProperty(\
2588 name
= pdef
['name'],\
2589 default
= pdef
['default'])
2591 elif pdef
['type'] == 'vector':
2592 if 'subtype' in pdef
:
2593 prop
= bpy
.props
.FloatVectorProperty(\
2594 name
= pdef
['name'],\
2595 subtype
= pdef
['subtype'],\
2596 default
= pdef
['default'],\
2597 size
= len(pdef
['default']))
2599 prop
= bpy
.props
.FloatVectorProperty(\
2600 name
= pdef
['name'],\
2601 default
= pdef
['default'],\
2602 size
= len(pdef
['default']))
2604 elif pdef
['type'] == 'string':
2605 prop
= bpy
.props
.StringProperty(\
2606 name
= pdef
['name'],\
2607 default
= pdef
['default'])
2609 elif pdef
['type'] == 'enum':
2610 prop
= bpy
.props
.EnumProperty(\
2611 name
= pdef
['name'],\
2612 items
= pdef
['items'],\
2613 default
= pdef
['default'])
2616 annotations_dict
[decl
] = prop
2618 # Recurse into sub-definitions
2619 _dvmt_propogate(pdef
)
2621 annotations_dict
["shader"] = bpy
.props
.EnumProperty(\
2624 cxr_shaders
[_
]["name"],\
2625 '') for _
in cxr_shaders
],\
2626 default
= next(iter(cxr_shaders
)))
2628 annotations_dict
["asset_id"] = bpy
.props
.IntProperty(name
="intl_assetid",\
2631 _dvmt_propogate( cxr_shader_params
)
2632 vmt_param_dynamic_class
= type(
2634 (bpy
.types
.PropertyGroup
,),{
2635 "__annotations__": annotations_dict
2639 bpy
.utils
.register_class( vmt_param_dynamic_class
)
2642 bpy
.types
.Material
.cxr_data
= \
2643 bpy
.props
.PointerProperty(type=vmt_param_dynamic_class
)
2644 bpy
.types
.Image
.cxr_data
= \
2645 bpy
.props
.PointerProperty(type=CXR_IMAGE_SETTINGS
)
2646 bpy
.types
.Object
.cxr_data
= \
2647 bpy
.props
.PointerProperty(type=CXR_ENTITY_SETTINGS
)
2648 bpy
.types
.Collection
.cxr_data
= \
2649 bpy
.props
.PointerProperty(type=CXR_MODEL_SETTINGS
)
2650 bpy
.types
.Light
.cxr_data
= \
2651 bpy
.props
.PointerProperty(type=CXR_LIGHT_SETTINGS
)
2652 bpy
.types
.LightProbe
.cxr_data
= \
2653 bpy
.props
.PointerProperty(type=CXR_CUBEMAP_SETTINGS
)
2654 bpy
.types
.Scene
.cxr_data
= \
2655 bpy
.props
.PointerProperty(type=CXR_SCENE_SETTINGS
)
2657 # CXR Scene settings
2660 cxr_view_draw_handler
= bpy
.types
.SpaceView3D
.draw_handler_add(\
2661 cxr_draw
,(),'WINDOW','POST_VIEW')
2663 cxr_ui_handler
= bpy
.types
.SpaceView3D
.draw_handler_add(\
2664 cxr_ui
,(None,None),'WINDOW','POST_PIXEL')
2666 bpy
.app
.handlers
.load_post
.append(cxr_on_load
)
2667 bpy
.app
.handlers
.depsgraph_update_post
.append(cxr_dgraph_update
)
2670 global cxr_view_draw_handler
, vmt_param_dynamic_class
, cxr_ui_handler
2672 bpy
.utils
.unregister_class( vmt_param_dynamic_class
)
2674 bpy
.utils
.unregister_class(c
)
2676 bpy
.app
.handlers
.depsgraph_update_post
.remove(cxr_dgraph_update
)
2677 bpy
.app
.handlers
.load_post
.remove(cxr_on_load
)
2679 bpy
.types
.SpaceView3D
.draw_handler_remove(cxr_view_draw_handler
,'WINDOW')
2680 bpy
.types
.SpaceView3D
.draw_handler_remove(cxr_ui_handler
,'WINDOW')