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
),
264 class cxr_polygon(Structure
):
265 _fields_
= [("loop_start",c_int32
),
266 ("loop_total",c_int32
),
267 ("normal",c_double
* 3),
268 ("center",c_double
* 3),
269 ("material_id",c_int32
)]
271 class cxr_material(Structure
):
272 _fields_
= [("res",c_int32
* 2),
275 class cxr_static_mesh(Structure
):
276 _fields_
= [("vertices",POINTER(c_double
* 3)),
277 ("edges",POINTER(cxr_edge
)),
278 ("loops",POINTER(cxr_static_loop
)),
279 ("polys",POINTER(cxr_polygon
)),
280 ("materials",POINTER(cxr_material
)),
282 ("poly_count",c_int32
),
283 ("vertex_count",c_int32
),
284 ("edge_count",c_int32
),
285 ("loop_count",c_int32
),
286 ("material_count",c_int32
)]
288 class cxr_tri_mesh(Structure
):
289 _fields_
= [("vertices",POINTER(c_double
*3)),
290 ("colours",POINTER(c_double
*4)),
291 ("indices",POINTER(c_int32
)),
292 ("indices_count",c_int32
),
293 ("vertex_count",c_int32
)]
295 class cxr_visgroup(Structure
):
296 _fields_
= [("name",c_char_p
)]
298 class cxr_vmf_context(Structure
):
299 _fields_
= [("mapversion",c_int32
),
300 ("skyname",c_char_p
),
301 ("detailvbsp",c_char_p
),
302 ("detailmaterial",c_char_p
),
303 ("visgroups",POINTER(cxr_visgroup
)),
304 ("visgroup_count",c_int32
),
306 ("offset",c_double
*3),
307 ("lightmap_scale",c_int32
),
308 ("visgroupid",c_int32
),
309 ("brush_count",c_int32
),
310 ("entity_count",c_int32
),
311 ("face_count",c_int32
)]
313 # Convert blenders mesh format into CXR's static format (they are very similar)
315 def mesh_cxr_format(obj
):
318 if bpy
.context
.active_object
!= None:
319 orig_state
= obj
.mode
320 if orig_state
!= 'OBJECT':
321 bpy
.ops
.object.mode_set(mode
='OBJECT')
323 dgraph
= bpy
.context
.evaluated_depsgraph_get()
324 data
= obj
.evaluated_get(dgraph
).data
326 _
,mtx_rot
,_
= obj
.matrix_world
.decompose()
328 mesh
= cxr_static_mesh()
330 vertex_data
= ((c_double
*3)*len(data
.vertices
))()
331 for i
, vert
in enumerate(data
.vertices
):
332 v
= obj
.matrix_world
@ vert
.co
333 vertex_data
[i
][0] = c_double(v
[0])
334 vertex_data
[i
][1] = c_double(v
[1])
335 vertex_data
[i
][2] = c_double(v
[2])
337 loop_data
= (cxr_static_loop
*len(data
.loops
))()
338 polygon_data
= (cxr_polygon
*len(data
.polygons
))()
340 for i
, poly
in enumerate(data
.polygons
):
341 loop_start
= poly
.loop_start
342 loop_end
= poly
.loop_start
+ poly
.loop_total
343 for loop_index
in range(loop_start
, loop_end
):
344 loop
= data
.loops
[loop_index
]
345 loop_data
[loop_index
].index
= loop
.vertex_index
346 loop_data
[loop_index
].edge_index
= loop
.edge_index
349 uv
= data
.uv_layers
.active
.data
[loop_index
].uv
350 loop_data
[loop_index
].uv
[0] = c_double(uv
[0])
351 loop_data
[loop_index
].uv
[1] = c_double(uv
[1])
353 loop_data
[loop_index
].uv
[0] = c_double(0.0)
354 loop_data
[loop_index
].uv
[1] = c_double(0.0)
356 if data
.vertex_colors
:
357 alpha
= data
.vertex_colors
.active
.data
[loop_index
].color
[0]
361 loop_data
[loop_index
].alpha
= alpha
363 center
= obj
.matrix_world
@ poly
.center
364 normal
= mtx_rot
@ poly
.normal
366 polygon_data
[i
].loop_start
= poly
.loop_start
367 polygon_data
[i
].loop_total
= poly
.loop_total
368 polygon_data
[i
].normal
[0] = normal
[0]
369 polygon_data
[i
].normal
[1] = normal
[1]
370 polygon_data
[i
].normal
[2] = normal
[2]
371 polygon_data
[i
].center
[0] = center
[0]
372 polygon_data
[i
].center
[1] = center
[1]
373 polygon_data
[i
].center
[2] = center
[2]
374 polygon_data
[i
].material_id
= poly
.material_index
376 edge_data
= (cxr_edge
*len(data
.edges
))()
378 for i
, edge
in enumerate(data
.edges
):
379 edge_data
[i
].i0
= edge
.vertices
[0]
380 edge_data
[i
].i1
= edge
.vertices
[1]
381 edge_data
[i
].freestyle
= edge
.use_freestyle_mark
382 edge_data
[i
].sharp
= edge
.use_edge_sharp
384 material_data
= (cxr_material
*len(obj
.material_slots
))()
386 for i
, ms
in enumerate(obj
.material_slots
):
387 inf
= material_info(ms
.material
)
388 material_data
[i
].res
[0] = inf
['res'][0]
389 material_data
[i
].res
[1] = inf
['res'][1]
390 material_data
[i
].name
= inf
['name'].encode('utf-8')
392 mesh
.edges
= cast(edge_data
, POINTER(cxr_edge
))
393 mesh
.vertices
= cast(vertex_data
, POINTER(c_double
*3))
394 mesh
.loops
= cast(loop_data
,POINTER(cxr_static_loop
))
395 mesh
.polys
= cast(polygon_data
, POINTER(cxr_polygon
))
396 mesh
.materials
= cast(material_data
, POINTER(cxr_material
))
398 mesh
.poly_count
= len(data
.polygons
)
399 mesh
.vertex_count
= len(data
.vertices
)
400 mesh
.edge_count
= len(data
.edges
)
401 mesh
.loop_count
= len(data
.loops
)
402 mesh
.material_count
= len(obj
.material_slots
)
404 if orig_state
!= None:
405 bpy
.ops
.object.mode_set(mode
=orig_state
)
409 # Callback ctypes indirection things.. not really sure.
410 c_libcxr_log_callback
= None
411 c_libcxr_line_callback
= None
414 # -------------------------------------------------------------
415 libcxr_decompose
= extern( "cxr_decompose",
416 [POINTER(cxr_static_mesh
), POINTER(c_int32
)],
419 libcxr_free_world
= extern( "cxr_free_world",
423 libcxr_write_test_data
= extern( "cxr_write_test_data",
424 [POINTER(cxr_static_mesh
)],
427 libcxr_world_preview
= extern( "cxr_world_preview",
429 POINTER(cxr_tri_mesh
)
431 libcxr_free_tri_mesh
= extern( "cxr_free_tri_mesh",
435 libcxr_begin_vmf
= extern( "cxr_begin_vmf",
436 [POINTER(cxr_vmf_context
), c_void_p
],
439 libcxr_vmf_begin_entities
= extern( "cxr_vmf_begin_entities",
440 [POINTER(cxr_vmf_context
), c_void_p
],
443 libcxr_push_world_vmf
= extern("cxr_push_world_vmf",
444 [c_void_p
,POINTER(cxr_vmf_context
),c_void_p
],
447 libcxr_end_vmf
= extern( "cxr_end_vmf",
448 [POINTER(cxr_vmf_context
),c_void_p
],
452 # VDF + with open wrapper
453 libcxr_vdf_open
= extern( "cxr_vdf_open", [c_char_p
], c_void_p
)
454 libcxr_vdf_close
= extern( "cxr_vdf_close", [c_void_p
], None )
455 libcxr_vdf_put
= extern( "cxr_vdf_put", [c_void_p
,c_char_p
], None )
456 libcxr_vdf_node
= extern( "cxr_vdf_node", [c_void_p
,c_char_p
], None )
457 libcxr_vdf_edon
= extern( "cxr_vdf_edon", [c_void_p
], None )
458 libcxr_vdf_kv
= extern( "cxr_vdf_kv", [c_void_p
,c_char_p
,c_char_p
], None )
460 class vdf_structure():
461 def __init__(_
,path
):
464 _
.fp
= libcxr_vdf_open
.call( _
.path
.encode('utf-8') )
466 print( F
"Could not open file {_.path}" )
469 def __exit__(_
,type,value
,traceback
):
471 libcxr_vdf_close
.call(_
.fp
)
473 libcxr_vdf_put
.call(_
.fp
, s
.encode('utf-8') )
475 libcxr_vdf_node
.call(_
.fp
, name
.encode('utf-8') )
477 libcxr_vdf_edon
.call(_
.fp
)
479 libcxr_vdf_kv
.call(_
.fp
, k
.encode('utf-8'), v
.encode('utf-8'))
482 libcxr_lightpatch_bsp
= extern( "cxr_lightpatch_bsp", [c_char_p
], None )
484 libcxr_funcs
= [ libcxr_decompose
, libcxr_free_world
, libcxr_begin_vmf
, \
485 libcxr_vmf_begin_entities
, libcxr_push_world_vmf
, \
486 libcxr_end_vmf
, libcxr_vdf_open
, libcxr_vdf_close
, \
487 libcxr_vdf_put
, libcxr_vdf_node
, libcxr_vdf_edon
,
488 libcxr_vdf_kv
, libcxr_lightpatch_bsp
, libcxr_write_test_data
,\
489 libcxr_world_preview
, libcxr_free_tri_mesh
]
493 def libcxr_log_callback(logStr
):
494 print( F
"{logStr.decode('utf-8')}",end
='' )
496 cxr_line_positions
= None
497 cxr_line_colours
= None
499 def cxr_reset_lines():
500 global cxr_line_positions
, cxr_line_colours
502 cxr_line_positions
= []
503 cxr_line_colours
= []
505 def cxr_batch_lines():
506 global cxr_line_positions
, cxr_line_colours
, cxr_view_shader
, cxr_view_lines
508 cxr_view_lines
= batch_for_shader(\
509 cxr_view_shader
, 'LINES',\
510 { "pos": cxr_line_positions
, "color": cxr_line_colours
})
512 def libcxr_line_callback( p0
,p1
,colour
):
513 global cxr_line_colours
, cxr_line_positions
515 cxr_line_positions
+= [(p0
[0],p0
[1],p0
[2])]
516 cxr_line_positions
+= [(p1
[0],p1
[1],p1
[2])]
517 cxr_line_colours
+= [(colour
[0],colour
[1],colour
[2],colour
[3])]
518 cxr_line_colours
+= [(colour
[0],colour
[1],colour
[2],colour
[3])]
521 global cxr_jobs_inf
, cxr_jobs_batch
, cxr_error_inf
, cxr_view_mesh
523 cxr_jobs_batch
= None
533 # ------------------------------------------------------------------------------
538 NBVTF_IMAGE_FORMAT_ABGR8888
= 1
539 NBVTF_IMAGE_FORMAT_BGR888
= 3
540 NBVTF_IMAGE_FORMAT_DXT1
= 13
541 NBVTF_IMAGE_FORMAT_DXT5
= 15
542 NBVTF_TEXTUREFLAGS_CLAMPS
= 0x00000004
543 NBVTF_TEXTUREFLAGS_CLAMPT
= 0x00000008
544 NBVTF_TEXTUREFLAGS_NORMAL
= 0x00000080
545 NBVTF_TEXTUREFLAGS_NOMIP
= 0x00000100
546 NBVTF_TEXTUREFLAGS_NOLOD
= 0x00000200
548 libnbvtf_convert
= extern( "nbvtf_convert", \
549 [c_char_p
,c_int32
,c_int32
,c_int32
,c_int32
,c_int32
,c_uint32
,c_char_p
], \
552 libnbvtf_init
= extern( "nbvtf_init", [], None )
553 libnbvtf_funcs
= [ libnbvtf_convert
, libnbvtf_init
]
556 # --------------------------
559 global libcxr
, libnbvtf
, libcxr_funcs
, libnbvtf_funcs
561 # Unload libraries if existing
562 def _reload( lib
, path
):
565 _handle
= lib
._handle
566 for i
in range(10): libc_dlclose( _handle
)
570 libpath
= F
'{os.path.dirname(__file__)}/{path}{CXR_SHARED_EXT}'
571 return cdll
.LoadLibrary( libpath
)
573 libnbvtf
= _reload( libnbvtf
, "libnbvtf" )
574 libcxr
= _reload( libcxr
, "libcxr" )
576 for fd
in libnbvtf_funcs
:
577 fd
.loadfrom( libnbvtf
)
580 for fd
in libcxr_funcs
:
581 fd
.loadfrom( libcxr
)
584 global c_libcxr_log_callback
, c_libcxr_line_callback
586 LOG_FUNCTION_TYPE
= CFUNCTYPE(None,c_char_p
)
587 c_libcxr_log_callback
= LOG_FUNCTION_TYPE(libcxr_log_callback
)
589 LINE_FUNCTION_TYPE
= CFUNCTYPE(None,\
590 POINTER(c_double
), POINTER(c_double
), POINTER(c_double
))
591 c_libcxr_line_callback
= LINE_FUNCTION_TYPE(libcxr_line_callback
)
593 libcxr
.cxr_set_log_function(cast(c_libcxr_log_callback
,c_void_p
))
594 libcxr
.cxr_set_line_function(cast(c_libcxr_line_callback
,c_void_p
))
596 build_time
= c_char_p
.in_dll(libcxr
,'cxr_build_time')
597 print( F
"libcxr build time: {build_time.value}" )
602 # ------------------------------------------------------------------------------
604 # Standard entity functions, think of like base.fgd
606 def cxr_get_origin(context
):
607 return context
['object'].location
* context
['transform']['scale'] + \
608 mathutils
.Vector(context
['transform']['offset'])
610 def cxr_get_angles(context
):
611 obj
= context
['object']
612 euler
= [ a
*57.295779513 for a
in obj
.rotation_euler
]
619 def cxr_baseclass(classes
, other
):
622 base
.update(x
.copy())
625 def ent_soundscape(context
):
626 obj
= context
['object']
627 kvs
= cxr_baseclass([ent_origin
],\
629 "radius": obj
.scale
.x
* bpy
.context
.scene
.cxr_data
.scale_factor
,
630 "soundscape": {"type":"string","default":""}
635 # EEVEE Light component converter -> Source 1
637 def ent_lights(context
):
638 obj
= context
['object']
639 kvs
= cxr_baseclass([ent_origin
],\
641 "_distance": (0.0 if obj
.data
.cxr_data
.realtime
else -1.0),
642 "_lightHDR": '-1 -1 -1 1',
646 light_base
= [(pow(obj
.data
.color
[i
],1.0/2.2)*255.0) for i
in range(3)] +\
647 [obj
.data
.energy
* bpy
.context
.scene
.cxr_data
.light_scale
]
649 if obj
.data
.type == 'SPOT' or obj
.data
.type == 'SUN':
650 # Blenders directional lights are -z forward
651 # Source is +x, however, it seems to use a completely different system.
652 # Since we dont care about roll for spotlights, we just take the
653 # pitch and yaw via trig
655 _
,mtx_rot
,_
= obj
.matrix_world
.decompose()
656 fwd
= mtx_rot
@ mathutils
.Vector((0,0,-1))
657 dir_pitch
= math
.asin(fwd
[2]) * 57.295779513
658 dir_yaw
= math
.atan2(fwd
[1],fwd
[0]) * 57.295779513
660 if obj
.data
.type == 'SPOT':
661 kvs
['_light'] = [ int(x
) for x
in light_base
]
662 kvs
['_cone'] = obj
.data
.spot_size
*(57.295779513/2.0)
663 kvs
['_inner_cone'] = (1.0-obj
.data
.spot_blend
)*kvs
['_cone']
665 kvs
['pitch'] = dir_pitch
666 kvs
['angles'] = [ 0, dir_yaw
, 0 ]
667 kvs
['_quadratic_attn'] = 0.0 # Source spotlights + quadratic falloff look
670 # Blender's default has a much more 'nice'
672 kvs
['_linear_attn'] = 1.0
674 elif obj
.data
.type == 'POINT':
675 kvs
['_light'] = [ int(x
) for x
in light_base
]
676 kvs
['_quadratic_attn'] = 1.0
677 kvs
['_linear_attn'] = 1.0
679 elif obj
.data
.type == 'SUN':
680 light_base
[3] *= 300.0 * 5
681 kvs
['_light'] = [ int(x
) for x
in light_base
]
683 ambient
= bpy
.context
.scene
.world
.color
684 kvs
['_ambient'] = [int(pow(ambient
[i
],1.0/2.2)*255.0) for i
in range(3)] +\
686 kvs
['_ambientHDR'] = [-1,-1,-1,1]
687 kvs
['_AmbientScaleHDR'] = 1
688 kvs
['pitch'] = dir_pitch
689 kvs
['angles'] = [ dir_pitch
, dir_yaw
, 0.0 ]
690 kvs
['SunSpreadAngle'] = 0
694 def ent_prop(context
):
695 if isinstance( context
['object'], bpy
.types
.Collection
):
697 target
= context
['object']
698 pos
= mathutils
.Vector(context
['origin'])
699 pos
+= mathutils
.Vector(context
['transform']['offset'])
701 kvs
['origin'] = [pos
[1],-pos
[0],pos
[2]]
702 kvs
['angles'] = [0,180,0]
703 kvs
['uniformscale'] = 1.0
705 kvs
= cxr_baseclass([ent_origin
],{})
706 target
= context
['object'].instance_collection
708 obj
= context
['object']
709 euler
= [ a
*57.295779513 for a
in obj
.rotation_euler
]
712 angle
[1] = euler
[2] + 180.0 # Dunno...
715 kvs
['angles'] = angle
716 kvs
['uniformscale'] = obj
.scale
[0]
718 if target
.cxr_data
.shadow_caster
:
719 kvs
['enablelightbounce'] = 1
720 kvs
['disableshadows'] = 0
722 kvs
['enablelightbounce'] = 0
723 kvs
['disableshadows'] = 1
725 kvs
['fademindist'] = -1
727 kvs
['model'] = F
"{asset_path('models',target)}.mdl".lower()
728 kvs
['renderamt'] = 255
729 kvs
['rendercolor'] = [255, 255, 255]
735 def ent_sky_camera(context
):
736 settings
= bpy
.context
.scene
.cxr_data
737 scale
= settings
.scale_factor
/ settings
.skybox_scale_factor
740 "origin": [_
for _
in context
['transform']['offset']],
741 "angles": [ 0, 0, 0 ],
742 "fogcolor": [255, 255, 255],
743 "fogcolor2": [255, 255, 255],
748 "HDRColorScale": 1.0,
753 def ent_cubemap(context
):
754 obj
= context
['object']
755 return cxr_baseclass([ent_origin
], {"cubemapsize": obj
.data
.cxr_data
.size
})
757 ent_origin
= { "origin": cxr_get_origin
}
758 ent_angles
= { "angles": cxr_get_angles
}
759 ent_transform
= cxr_baseclass( [ent_origin
], ent_angles
)
761 #include the user config
762 exec(open(F
'{os.path.dirname(__file__)}/config.py').read())
764 # Blender state callbacks
765 # ------------------------------------------------------------------------------
768 def cxr_on_load(dummy
):
769 global cxr_view_lines
, cxr_view_mesh
771 cxr_view_lines
= None
775 def cxr_dgraph_update(scene
,dgraph
):
777 print( F
"Hallo {time.time()}" )
779 # Convexer compilation functions
780 # ------------------------------------------------------------------------------
782 # Asset path management
784 def asset_uid(asset
):
785 if isinstance(asset
,str):
788 # Create a unique ID string
790 v
= asset
.cxr_data
.asset_id
799 dig
.append( int( v
% len(base
) ) )
805 if bpy
.context
.scene
.cxr_data
.include_names
:
806 name
+= asset
.name
.replace('.','_')
810 # -> <project_name>/<asset_name>
811 def asset_name(asset
):
812 return F
"{bpy.context.scene.cxr_data.project_name}/{asset_uid(asset)}"
814 # -> <subdir>/<project_name>/<asset_name>
815 def asset_path(subdir
, asset
):
816 return F
"{subdir}/{asset_name(asset_uid(asset))}"
818 # -> <csgo>/<subdir>/<project_name>/<asset_name>
819 def asset_full_path(sdir
,asset
):
820 return F
"{bpy.context.scene.cxr_data.subdir}/"+\
821 F
"{asset_path(sdir,asset_uid(asset))}"
823 # Decomposes mesh, and sets global error information if failed.
824 # - returns None on fail
825 # - returns world on success
826 def cxr_decompose_globalerr( mesh_src
):
830 world
= libcxr_decompose
.call( mesh_src
, pointer(err
) )
837 ("No Error", "There is no error?"),\
838 ("Bad input", "Non manifold geometry is present in the input mesh"),\
839 ("Bad result","An invalid manifold was generated, try to simplify"),\
840 ("Bad result","Make sure there is a clear starting point"),\
841 ("Bad result","Implicit vertex was invalid, try to simplify"),\
842 ("Bad input","Non coplanar vertices are in the source mesh"),\
843 ("Bad input","Non convex polygon is in the source mesh"),\
844 ("Bad result","Undefined failure"),\
845 ("Invalid Input", "Undefined failure"),\
852 # Entity functions / infos
853 # ------------------------
855 def cxr_collection_purpose(collection
):
856 if collection
.name
.startswith('.'): return None
857 if collection
.hide_render
: return None
858 if collection
.name
.startswith('mdl_'): return 'model'
861 def cxr_object_purpose(obj
):
865 def _search(collection
):
866 nonlocal objpurpose
, group
, obj
868 purpose
= cxr_collection_purpose( collection
)
869 if purpose
== None: return
870 if purpose
== 'model':
871 for o
in collection
.objects
:
873 if o
.type != 'EMPTY':
878 for o
in collection
.objects
:
880 classname
= cxr_classname(o
)
881 if classname
!= None:
882 objpurpose
= 'entity'
884 objpurpose
= 'brush_entity'
891 for c
in collection
.children
:
894 if 'main' in bpy
.data
.collections
:
895 _search( bpy
.data
.collections
['main'] )
897 if objpurpose
== None and 'skybox' in bpy
.data
.collections
:
898 _search( bpy
.data
.collections
['skybox'] )
900 return (group
,objpurpose
)
902 def cxr_intrinsic_classname(obj
):
903 if obj
.type == 'LIGHT':
905 'SPOT': "light_spot",
907 'SUN': "light_environment" }[ obj
.data
.type ]
909 elif obj
.type == 'LIGHT_PROBE':
911 elif obj
.type == 'EMPTY':
917 def cxr_custom_class(obj
):
918 if obj
.type == 'MESH': custom_class
= obj
.cxr_data
.brushclass
919 else: custom_class
= obj
.cxr_data
.classname
923 def cxr_classname(obj
):
924 intr
= cxr_intrinsic_classname(obj
)
925 if intr
!= None: return intr
927 custom_class
= cxr_custom_class(obj
)
928 if custom_class
!= 'NONE':
934 # intinsic: (k, False, value)
935 # property: (k, True, value or default)
939 def cxr_entity_keyvalues(context
):
940 classname
= context
['classname']
941 obj
= context
['object']
942 if classname
not in cxr_entities
: return None
946 entdef
= cxr_entities
[classname
]
947 kvs
= entdef
['keyvalues']
949 if callable(kvs
): kvs
= kvs(context
)
956 if isinstance(kv
,dict):
958 value
= obj
[ F
"cxrkv_{k}" ]
963 if isinstance(value
,mathutils
.Vector
):
964 value
= [_
for _
in value
]
966 result
+= [(k
, isprop
, value
)]
970 # Extract material information from shader graph data
972 def material_info(mat
):
974 info
['res'] = (512,512)
975 info
['name'] = 'tools/toolsnodraw'
977 if mat
== None or mat
.use_nodes
== False:
981 if mat
.cxr_data
.shader
== 'Builtin':
982 info
['name'] = mat
.name
986 info
['name'] = asset_name(mat
)
988 # Using the cxr_graph_mapping as a reference, go through the shader
989 # graph and gather all $props from it.
991 def _graph_read( node_def
, node
=None, depth
=0 ):
995 def _variant_apply( val
):
998 if isinstance( val
, list ):
999 for shader_variant
in val
:
1000 if shader_variant
[0] == mat
.cxr_data
.shader
:
1001 return shader_variant
[1]
1008 _graph_read
.extracted
= []
1010 for node_idname
in node_def
:
1011 for n
in mat
.node_tree
.nodes
:
1012 if n
.name
== node_idname
:
1013 node_def
= node_def
[node_idname
]
1017 for link
in node_def
:
1018 link_def
= _variant_apply( node_def
[link
] )
1020 if isinstance( link_def
, dict ):
1021 node_link
= node
.inputs
[link
]
1023 if node_link
.is_linked
:
1025 # look for definitions for the connected node type
1026 from_node
= node_link
.links
[0].from_node
1028 node_name
= from_node
.name
.split('.')[0]
1029 if node_name
in link_def
:
1030 from_node_def
= link_def
[ node_name
]
1032 _graph_read( from_node_def
, from_node
, depth
+1 )
1035 # TODO: Make a warning for this?
1038 if "default" in link_def
:
1039 prop
= _variant_apply( link_def
['default'] )
1040 info
[prop
] = node_link
.default_value
1042 prop
= _variant_apply( link_def
)
1043 info
[prop
] = getattr( node
, link
)
1045 _graph_read(cxr_graph_mapping
)
1047 if "$basetexture" in info
:
1048 export_res
= info
['$basetexture'].cxr_data
.export_res
1049 info
['res'] = (export_res
[0], export_res
[1])
1053 def vec3_min( a
, b
):
1054 return mathutils
.Vector((min(a
[0],b
[0]),min(a
[1],b
[1]),min(a
[2],b
[2])))
1055 def vec3_max( a
, b
):
1056 return mathutils
.Vector((max(a
[0],b
[0]),max(a
[1],b
[1]),max(a
[2],b
[2])))
1058 def cxr_collection_center(collection
, transform
):
1060 bounds_min
= mathutils
.Vector((BIG
,BIG
,BIG
))
1061 bounds_max
= mathutils
.Vector((-BIG
,-BIG
,-BIG
))
1063 for obj
in collection
.objects
:
1064 if obj
.type == 'MESH':
1065 corners
= [ mathutils
.Vector(c
) for c
in obj
.bound_box
]
1067 for corner
in [ obj
.matrix_world
@c for c
in corners
]:
1068 bounds_min
= vec3_min( bounds_min
, corner
)
1069 bounds_max
= vec3_max( bounds_max
, corner
)
1071 center
= (bounds_min
+ bounds_max
) / 2.0
1073 origin
= mathutils
.Vector((-center
[1],center
[0],center
[2]))
1074 origin
*= transform
['scale']
1078 # Prepares Scene into dictionary format
1080 def cxr_scene_collect():
1081 context
= bpy
.context
1083 # Make sure all of our asset types have a unique ID
1084 def _uid_prepare(objtype
):
1090 if vs
.asset_id
in used_ids
:
1093 id_max
= max(id_max
,vs
.asset_id
)
1094 used_ids
+=[vs
.asset_id
]
1095 for vs
in to_generate
:
1097 vs
.asset_id
= id_max
1098 _uid_prepare(bpy
.data
.materials
)
1099 _uid_prepare(bpy
.data
.images
)
1100 _uid_prepare(bpy
.data
.collections
)
1103 "entities": [], # Everything with a classname
1104 "geo": [], # All meshes without a classname
1105 "heros": [] # Collections prefixed with mdl_
1108 def _collect(collection
,transform
):
1111 purpose
= cxr_collection_purpose( collection
)
1112 if purpose
== None: return
1113 if purpose
== 'model':
1114 sceneinfo
['entities'] += [{
1115 "object": collection
,
1116 "classname": "prop_static",
1117 "transform": transform
,
1118 "origin": cxr_collection_center( collection
, transform
)
1121 sceneinfo
['heros'] += [{
1122 "collection": collection
,
1123 "transform": transform
,
1124 "origin": cxr_collection_center( collection
, transform
)
1128 for obj
in collection
.objects
:
1129 if obj
.hide_get(): continue
1131 classname
= cxr_classname( obj
)
1133 if classname
!= None:
1134 sceneinfo
['entities'] += [{
1136 "classname": classname
,
1137 "transform": transform
1139 elif obj
.type == 'MESH':
1140 sceneinfo
['geo'] += [{
1142 "transform": transform
1145 for c
in collection
.children
:
1146 _collect( c
, transform
)
1149 "scale": context
.scene
.cxr_data
.scale_factor
,
1154 "scale": context
.scene
.cxr_data
.skybox_scale_factor
,
1155 "offset": (0,0,context
.scene
.cxr_data
.skybox_offset
)
1158 if 'main' in bpy
.data
.collections
:
1159 _collect( bpy
.data
.collections
['main'], transform_main
)
1161 if 'skybox' in bpy
.data
.collections
:
1162 _collect( bpy
.data
.collections
['skybox'], transform_sky
)
1164 sceneinfo
['entities'] += [{
1166 "transform": transform_sky
,
1167 "classname": "sky_camera"
1172 # Write VMF out to file (JOB HANDLER)
1174 def cxr_export_vmf(sceneinfo
, output_vmf
):
1177 with
vdf_structure(output_vmf
) as m
:
1178 print( F
"Write: {output_vmf}" )
1180 vmfinfo
= cxr_vmf_context()
1181 vmfinfo
.mapversion
= 4
1183 #TODO: These need to be in options...
1184 vmfinfo
.skyname
= bpy
.context
.scene
.cxr_data
.skyname
.encode('utf-8')
1185 vmfinfo
.detailvbsp
= b
"detail.vbsp"
1186 vmfinfo
.detailmaterial
= b
"detail/detailsprites"
1187 vmfinfo
.lightmap_scale
= 12
1189 vmfinfo
.brush_count
= 0
1190 vmfinfo
.entity_count
= 0
1191 vmfinfo
.face_count
= 0
1193 visgroups
= (cxr_visgroup
*len(cxr_visgroups
))()
1194 for i
, vg
in enumerate(cxr_visgroups
):
1195 visgroups
[i
].name
= vg
.encode('utf-8')
1196 vmfinfo
.visgroups
= cast(visgroups
, POINTER(cxr_visgroup
))
1197 vmfinfo
.visgroup_count
= len(cxr_visgroups
)
1199 libcxr_begin_vmf
.call( pointer(vmfinfo
), m
.fp
)
1201 def _buildsolid( cmd
):
1204 print( F
"{vmfinfo.brush_count} :: {cmd['object'].name}" )
1206 baked
= mesh_cxr_format( cmd
['object'] )
1207 world
= cxr_decompose_globalerr( baked
)
1212 vmfinfo
.scale
= cmd
['transform']['scale']
1214 offset
= cmd
['transform']['offset']
1215 vmfinfo
.offset
[0] = offset
[0]
1216 vmfinfo
.offset
[1] = offset
[1]
1217 vmfinfo
.offset
[2] = offset
[2]
1219 if cmd
['object'].cxr_data
.lightmap_override
> 0:
1220 vmfinfo
.lightmap_scale
= cmd
['object'].cxr_data
.lightmap_override
1222 vmfinfo
.lightmap_scale
= bpy
.context
.scene
.cxr_data
.lightmap_scale
1224 libcxr_push_world_vmf
.call( world
, pointer(vmfinfo
), m
.fp
)
1225 libcxr_free_world
.call( world
)
1230 for brush
in sceneinfo
['geo']:
1231 vmfinfo
.visgroupid
= int(brush
['object'].cxr_data
.visgroup
)
1232 if not _buildsolid( brush
):
1236 vmfinfo
.visgroupid
= 0
1238 libcxr_vmf_begin_entities
.call(pointer(vmfinfo
), m
.fp
)
1241 for ent
in sceneinfo
['entities']:
1243 ctx
= ent
['transform']
1244 cls
= ent
['classname']
1247 m
.kv( 'classname', cls
)
1249 kvs
= cxr_entity_keyvalues( ent
)
1252 if isinstance(kv
[2], list):
1253 m
.kv( kv
[0], ' '.join([str(_
) for _
in kv
[2]]) )
1254 else: m
.kv( kv
[0], str(kv
[2]) )
1258 elif not isinstance( obj
, bpy
.types
.Collection
):
1259 if obj
.type == 'MESH':
1260 vmfinfo
.visgroupid
= int(obj
.cxr_data
.visgroup
)
1261 if not _buildsolid( ent
):
1268 m
.kv( 'visgroupid', str(obj
.cxr_data
.visgroup
) )
1269 m
.kv( 'visgroupshown', '1' )
1270 m
.kv( 'visgroupautoshown', '1' )
1274 vmfinfo
.visgroupid
= 0
1279 # COmpile image using NBVTF and hash it (JOB HANDLER)
1281 def compile_image(img
):
1285 name
= asset_name(img
)
1286 src_path
= bpy
.path
.abspath(img
.filepath
)
1288 dims
= img
.cxr_data
.export_res
1290 'RGBA': NBVTF_IMAGE_FORMAT_ABGR8888
,
1291 'DXT1': NBVTF_IMAGE_FORMAT_DXT1
,
1292 'DXT5': NBVTF_IMAGE_FORMAT_DXT5
,
1293 'RGB': NBVTF_IMAGE_FORMAT_BGR888
1294 }[ img
.cxr_data
.fmt
]
1296 mipmap
= img
.cxr_data
.mipmap
1297 lod
= img
.cxr_data
.lod
1298 clamp
= img
.cxr_data
.clamp
1299 flags
= img
.cxr_data
.flags
1301 q
=bpy
.context
.scene
.cxr_data
.image_quality
1303 userflag_hash
= F
"{mipmap}.{lod}.{clamp}.{flags}"
1304 file_hash
= F
"{name}.{os.path.getmtime(src_path)}"
1305 comphash
= F
"{file_hash}.{dims[0]}.{dims[1]}.{fmt}.{userflag_hash}.{q}"
1307 if img
.cxr_data
.last_hash
!= comphash
:
1308 print( F
"Texture update: {img.filepath}" )
1310 src
= src_path
.encode('utf-8')
1311 dst
= (asset_full_path('materials',img
)+'.vtf').encode('utf-8')
1315 # texture setting flags
1316 if not lod
: flags_full |
= NBVTF_TEXTUREFLAGS_NOLOD
1318 flags_full |
= NBVTF_TEXTUREFLAGS_CLAMPS
1319 flags_full |
= NBVTF_TEXTUREFLAGS_CLAMPT
1321 if libnbvtf_convert
.call(src
,dims
[0],dims
[1],mipmap
,fmt
,q
,flags_full
,dst
):
1322 img
.cxr_data
.last_hash
= comphash
1327 # Compile a material to VMT format. This is quick to run, doesnt need to be a
1330 def compile_material(mat
):
1331 info
= material_info(mat
)
1332 properties
= mat
.cxr_data
1334 print( F
"Compile {asset_full_path('materials',mat)}.vmt" )
1335 if properties
.shader
== 'Builtin':
1340 # Walk the property tree
1341 def _mlayer( layer
):
1342 nonlocal properties
, props
1345 if isinstance(layer
[decl
],dict): # $property definition
1347 ptype
= pdef
['type']
1353 if 'shaders' in pdef
and properties
.shader
not in pdef
['shaders']:
1356 # Group expansion (does it have subdefinitions?)
1358 if isinstance(pdef
[ch
],dict):
1367 if ptype
== 'intrinsic':
1371 prop
= getattr(properties
,decl
)
1372 default
= pdef
['default']
1374 if not isinstance(prop
,str) and \
1375 not isinstance(prop
,bpy
.types
.Image
) and \
1376 hasattr(prop
,'__getitem__'):
1377 prop
= tuple([p
for p
in prop
])
1381 props
+= [(decl
,pdef
,prop
)]
1386 if expandview
: _mlayer(pdef
)
1388 _mlayer( cxr_shader_params
)
1391 with
vdf_structure( F
"{asset_full_path('materials',mat)}.vmt" ) as vmt
:
1392 vmt
.node( properties
.shader
)
1393 vmt
.put( "// Convexer export\n" )
1402 if 'exponent' in pdef
: return str(pow( v
, pdef
['exponent'] ))
1405 if isinstance(prop
,bpy
.types
.Image
):
1406 vmt
.kv( decl
, asset_name(prop
))
1407 elif isinstance(prop
,bool):
1408 vmt
.kv( decl
, '1' if prop
else '0' )
1409 elif isinstance(prop
,str):
1410 vmt
.kv( decl
, prop
)
1411 elif isinstance(prop
,float) or isinstance(prop
,int):
1412 vmt
.kv( decl
, _numeric(prop
) )
1413 elif isinstance(prop
,tuple):
1414 vmt
.kv( decl
, F
"[{' '.join([_numeric(_) for _ in prop])}]" )
1416 vmt
.put( F
"// (cxr) unkown shader value type'{type(prop)}'" )
1421 def cxr_modelsrc_vphys( mdl
):
1422 for obj
in mdl
.objects
:
1423 if obj
.name
== F
"{mdl.name}_phy":
1427 def cxr_export_modelsrc( mdl
, origin
, asset_dir
, project_name
, transform
):
1428 dgraph
= bpy
.context
.evaluated_depsgraph_get()
1430 # Compute hash value
1431 chash
= asset_uid(mdl
)+str(origin
)+str(transform
)
1433 #for obj in mdl.objects:
1434 # if obj.type != 'MESH':
1437 # ev = obj.evaluated_get(dgraph).data
1438 # srcverts=[(v.co[0],v.co[1],v.co[2]) for v in ev.vertices]
1439 # srcloops=[(l.normal[0],l.normal[1],l.normal[2]) for l in ev.loops]
1441 # chash=hashlib.sha224((str(srcverts)+chash).encode()).hexdigest()
1442 # chash=hashlib.sha224((str(srcloops)+chash).encode()).hexdigest()
1444 # if ev.uv_layers.active != None:
1445 # uv_layer = ev.uv_layers.active.data
1446 # srcuv=[(uv.uv[0],uv.uv[1]) for uv in uv_layer]
1450 # chash=hashlib.sha224((str(srcuv)+chash).encode()).hexdigest()
1451 # srcmats=[ ms.material.name for ms in obj.material_slots ]
1452 # chash=hashlib.sha224((str(srcmats)+chash).encode()).hexdigest()
1453 # transforms=[ obj.location, obj.rotation_euler, obj.scale ]
1454 # srctr=[(v[0],v[1],v[2]) for v in transforms]
1455 # chash=hashlib.sha224((str(srctr)+chash).encode()).hexdigest()
1457 #if chash != mdl.cxr_data.last_hash:
1458 # mdl.cxr_data.last_hash = chash
1459 # print( F"Compile: {mdl.name}" )
1463 bpy
.ops
.object.select_all(action
='DESELECT')
1466 def _get_layer(col
,name
):
1467 for c
in col
.children
:
1470 sub
= _get_layer(c
,name
)
1474 layer
= _get_layer(bpy
.context
.view_layer
.layer_collection
,mdl
.name
)
1476 prev_state
= layer
.hide_viewport
1477 layer
.hide_viewport
=False
1479 # Collect materials to be compiled, and temp rename for export
1483 for obj
in mdl
.objects
:
1484 if obj
.name
== F
"{mdl.name}_phy":
1488 obj
.select_set(state
=True)
1489 for ms
in obj
.material_slots
:
1490 if ms
.material
!= None:
1491 if ms
.material
not in mat_dict
:
1492 mat_dict
[ms
.material
] = ms
.material
.name
1493 ms
.material
.name
= asset_uid(ms
.material
)
1494 ms
.material
.use_nodes
= False
1497 bpy
.ops
.export_scene
.fbx( filepath
=F
'{asset_dir}/{uid}_ref.fbx',\
1498 check_existing
=False,
1500 apply_unit_scale
=False,
1501 bake_space_transform
=False
1504 bpy
.ops
.object.select_all(action
='DESELECT')
1507 vphys
.select_set(state
=True)
1508 bpy
.ops
.export_scene
.fbx( filepath
=F
'{asset_dir}/{uid}_phy.fbx',\
1509 check_existing
=False,
1511 apply_unit_scale
=False,
1512 bake_space_transform
=False
1514 bpy
.ops
.object.select_all(action
='DESELECT')
1516 # Fix material names back to original
1517 for mat
in mat_dict
:
1518 mat
.name
= mat_dict
[mat
]
1519 mat
.use_nodes
= True
1521 layer
.hide_viewport
=prev_state
1524 with
open(F
'{asset_dir}/{uid}.qc','w') as o
:
1525 o
.write(F
'$modelname "{project_name}/{uid}"\n')
1526 #o.write(F'$scale .32\n')
1527 o
.write(F
'$scale {transform["scale"]/100.0}\n')
1528 o
.write(F
'$body _ "{uid}_ref.fbx"\n')
1529 o
.write(F
'$staticprop\n')
1530 o
.write(F
'$origin {origin[0]:.6f} {origin[1]:.6f} {origin[2]:.6f}\n')
1532 if mdl
.cxr_data
.preserve_order
:
1533 o
.write(F
"$preservetriangleorder\n")
1535 if mdl
.cxr_data
.texture_shadows
:
1536 o
.write(F
"$casttextureshadows\n")
1538 o
.write(F
"$surfaceprop {mdl.cxr_data.surfaceprop}\n")
1541 o
.write(F
'$collisionmodel "{uid}_phy.fbx"\n')
1543 o
.write(" $concave\n")
1546 o
.write(F
'$cdmaterials {project_name}\n')
1547 o
.write(F
'$sequence idle {uid}_ref.fbx\n')
1551 # Copy bsp file (and also lightpatch it)
1553 def cxr_patchmap( src
, dst
):
1554 libcxr_lightpatch_bsp
.call( src
.encode('utf-8') )
1555 shutil
.copyfile( src
, dst
)
1558 # Convexer operators
1559 # ------------------------------------------------------------------------------
1561 # Force reload of shared libraries
1563 class CXR_RELOAD(bpy
.types
.Operator
):
1564 bl_idname
="convexer.reload"
1566 def execute(_
,context
):
1570 # Reset all debugging/ui information
1572 class CXR_RESET(bpy
.types
.Operator
):
1573 bl_idname
="convexer.reset"
1574 bl_label
="Reset Convexer"
1575 def execute(_
,context
):
1579 # Used for exporting data to use with ASAN builds
1581 class CXR_DEV_OPERATOR(bpy
.types
.Operator
):
1582 bl_idname
="convexer.dev_test"
1583 bl_label
="Export development data"
1585 def execute(_
,context
):
1586 # Prepare input data
1587 mesh_src
= mesh_cxr_format(context
.active_object
)
1588 libcxr_write_test_data
.call( pointer(mesh_src
) )
1591 # UI: Preview how the brushes will looks in 3D view
1593 class CXR_PREVIEW_OPERATOR(bpy
.types
.Operator
):
1594 bl_idname
="convexer.preview"
1595 bl_label
="Preview Brushes"
1599 def execute(_
,context
):
1600 global cxr_view_mesh
1601 global cxr_view_shader
, cxr_view_mesh
, cxr_error_inf
1605 static
= _
.__class
__
1607 mesh_src
= mesh_cxr_format(context
.active_object
)
1608 world
= cxr_decompose_globalerr( mesh_src
)
1613 # Generate preview using cxr
1615 ptrpreview
= libcxr_world_preview
.call( world
)
1616 preview
= ptrpreview
[0]
1618 vertices
= preview
.vertices
[:preview
.vertex_count
]
1619 vertices
= [(_
[0],_
[1],_
[2]) for _
in vertices
]
1621 colours
= preview
.colours
[:preview
.vertex_count
]
1622 colours
= [(_
[0],_
[1],_
[2],_
[3]) for _
in colours
]
1624 indices
= preview
.indices
[:preview
.indices_count
]
1625 indices
= [ (indices
[i
*3+0],indices
[i
*3+1],indices
[i
*3+2]) \
1626 for i
in range(int(preview
.indices_count
/3)) ]
1628 cxr_view_mesh
= batch_for_shader(
1629 cxr_view_shader
, 'TRIS',
1630 { "pos": vertices
, "color": colours
},
1634 libcxr_free_tri_mesh
.call( ptrpreview
)
1635 libcxr_free_world
.call( world
)
1641 # Search for VMF compiler executables in subdirectory
1643 class CXR_DETECT_COMPILERS(bpy
.types
.Operator
):
1644 bl_idname
="convexer.detect_compilers"
1645 bl_label
="Find compilers"
1647 def execute(self
,context
):
1648 scene
= context
.scene
1649 settings
= scene
.cxr_data
1650 subdir
= settings
.subdir
1652 for exename
in ['studiomdl','vbsp','vvis','vrad']:
1653 searchpath
= os
.path
.normpath(F
'{subdir}/../bin/{exename}.exe')
1654 if os
.path
.exists(searchpath
):
1655 settings
[F
'exe_{exename}'] = searchpath
1659 def cxr_compiler_path( compiler
):
1660 settings
= bpy
.context
.scene
.cxr_data
1661 subdir
= settings
.subdir
1662 path
= os
.path
.normpath(F
'{subdir}/../bin/{compiler}.exe')
1664 if os
.path
.exists( path
): return path
1667 # Compatibility layer
1669 def cxr_temp_file( fn
):
1670 if CXR_GNU_LINUX
== 1:
1673 filepath
= bpy
.data
.filepath
1674 directory
= os
.path
.dirname(filepath
)
1675 return F
"{directory}/{fn}"
1677 def cxr_winepath( path
):
1678 if CXR_GNU_LINUX
== 1:
1679 return 'z:'+path
.replace('/','\\')
1683 # Main compile function
1685 class CXR_COMPILER_CHAIN(bpy
.types
.Operator
):
1686 bl_idname
="convexer.chain"
1687 bl_label
="Compile Chain"
1702 def cancel(_
,context
):
1703 #global cxr_jobs_batch
1704 static
= _
.__class
__
1705 wm
= context
.window_manager
1707 if static
.SUBPROC
!= None:
1708 static
.SUBPROC
.terminate()
1709 static
.SUBPROC
= None
1711 if static
.TIMER
!= None:
1712 wm
.event_timer_remove( static
.TIMER
)
1717 #cxr_jobs_batch = None
1721 def modal(_
,context
,ev
):
1722 static
= _
.__class
__
1724 if ev
.type == 'TIMER':
1725 global cxr_jobs_batch
, cxr_error_inf
1727 if static
.WAIT_REDRAW
:
1729 return {'PASS_THROUGH'}
1730 static
.WAIT_REDRAW
= True
1732 if static
.USER_EXIT
:
1733 print( "Chain USER_EXIT" )
1734 return _
.cancel(context
)
1736 if static
.SUBPROC
!= None:
1737 # Deal with async modes
1738 status
= static
.SUBPROC
.poll()
1740 # Cannot redirect STDOUT through here without causing
1741 # undefined behaviour due to the Blender Python specification.
1743 # Have to write it out to a file and read it back in.
1746 with
open(cxr_temp_file("convexer_compile_log.txt"),"r") as log
:
1747 static
.LOG
= log
.readlines()
1749 return {'PASS_THROUGH'}
1751 #for l in static.SUBPROC.stdout:
1752 # print( F'-> {l.decode("utf-8")}',end='' )
1753 static
.SUBPROC
= None
1756 print(F
'Compiler () error: {status}')
1758 jobn
= static
.JOBSYS
['jobs'][static
.JOBID
]
1759 cxr_error_inf
= ( F
"{static.JOBSYS['title']} error {status}", jobn
)
1761 return _
.cancel(context
)
1763 static
.JOBSYS
['jobs'][static
.JOBID
] = None
1764 cxr_jobs_update_graph( static
.JOBINFO
)
1766 return {'PASS_THROUGH'}
1768 # Compile syncronous thing
1769 for sys
in static
.JOBINFO
:
1770 for i
,target
in enumerate(sys
['jobs']):
1773 if callable(sys
['exec']):
1774 print( F
"Run (sync): {static.JOBID} @{time.time()}" )
1776 if not sys
['exec'](*target
):
1777 print( "Job failed" )
1778 return _
.cancel(context
)
1780 sys
['jobs'][i
] = None
1783 # Run external executable (wine)
1784 static
.SUBPROC
= subprocess
.Popen( target
,
1785 stdout
=static
.FILE
,\
1786 stderr
=subprocess
.PIPE
,\
1791 cxr_jobs_update_graph( static
.JOBINFO
)
1793 return {'PASS_THROUGH'}
1796 print( "All jobs completed!" )
1797 #cxr_jobs_batch = None
1799 return _
.cancel(context
)
1801 return {'PASS_THROUGH'}
1803 def invoke(_
,context
,event
):
1804 global cxr_error_inf
1806 static
= _
.__class
__
1807 wm
= context
.window_manager
1809 if static
.TIMER
!= None:
1810 print("Chain exiting...")
1811 static
.USER_EXIT
=True
1812 return {'RUNNING_MODAL'}
1814 print("Launching compiler toolchain")
1817 # Run static compilation units now (collect, vmt..)
1818 filepath
= bpy
.data
.filepath
1819 directory
= os
.path
.dirname(filepath
)
1820 settings
= bpy
.context
.scene
.cxr_data
1822 asset_dir
= F
"{directory}/modelsrc"
1823 material_dir
= F
"{settings.subdir}/materials/{settings.project_name}"
1824 model_dir
= F
"{settings.subdir}/models/{settings.project_name}"
1825 output_vmf
= F
"{directory}/{settings.project_name}.vmf"
1827 bsp_local
= F
"{directory}/{settings.project_name}.bsp"
1828 bsp_remote
= F
"{settings.subdir}/maps/{settings.project_name}.bsp"
1829 bsp_packed
= F
"{settings.subdir}/maps/{settings.project_name}_pack.bsp"
1830 packlist
= F
"{directory}/{settings.project_name}_assets.txt"
1832 os
.makedirs( asset_dir
, exist_ok
=True )
1833 os
.makedirs( material_dir
, exist_ok
=True )
1834 os
.makedirs( model_dir
, exist_ok
=True )
1836 static
.FILE
= open(cxr_temp_file("convexer_compile_log.txt"),"w")
1839 sceneinfo
= cxr_scene_collect()
1845 for brush
in sceneinfo
['geo']:
1846 for ms
in brush
['object'].material_slots
:
1847 a_materials
.add( ms
.material
)
1848 if ms
.material
.cxr_data
.shader
== 'VertexLitGeneric':
1849 errmat
= ms
.material
.name
1850 errnam
= brush
['object'].name
1852 cxr_error_inf
= ( "Shader error", \
1853 F
"Vertex shader ({errmat}) used on model ({errnam})" )
1855 print( F
"Vertex shader {errmat} used on {errnam}")
1857 return {'CANCELLED'}
1861 for ent
in sceneinfo
['entities']:
1862 if ent
['object'] == None: continue
1864 if ent
['classname'] == 'prop_static':
1866 if isinstance(obj
,bpy
.types
.Collection
):
1868 a_models
.add( target
)
1869 model_jobs
+= [(target
, ent
['origin'], asset_dir
, \
1870 settings
.project_name
, ent
['transform'])]
1872 target
= obj
.instance_collection
1873 if target
in a_models
:
1875 a_models
.add( target
)
1877 # TODO: Should take into account collection instancing offset
1878 model_jobs
+= [(target
, [0,0,0], asset_dir
, \
1879 settings
.project_name
, ent
['transform'])]
1881 elif ent
['object'].type == 'MESH':
1882 for ms
in ent
['object'].material_slots
:
1883 a_materials
.add( ms
.material
)
1885 for mdl
in a_models
:
1886 uid
= asset_uid(mdl
)
1887 qc_jobs
+= [F
'{uid}.qc']
1889 for obj
in mdl
.objects
:
1890 for ms
in obj
.material_slots
:
1891 a_materials
.add( ms
.material
)
1892 if ms
.material
.cxr_data
.shader
== 'LightMappedGeneric' or \
1893 ms
.material
.cxr_data
.shader
== 'WorldVertexTransition':
1895 errmat
= ms
.material
.name
1898 cxr_error_inf
= ( "Shader error", \
1899 F
"Lightmapped shader ({errmat}) used on model ({errnam})" )
1901 print( F
"Lightmapped shader {errmat} used on {errnam}")
1903 return {'CANCELLED'}
1906 for mat
in a_materials
:
1907 for pair
in compile_material(mat
):
1912 if isinstance(prop
,bpy
.types
.Image
):
1914 if 'flags' in pdef
: flags
= pdef
['flags']
1915 if prop
not in image_jobs
:
1916 image_jobs
+= [(prop
,)]
1917 prop
.cxr_data
.flags
= flags
1920 with
open( packlist
, "w" ) as fp
:
1922 for mat
in a_materials
:
1923 if mat
.cxr_data
.shader
== 'Builtin': continue
1924 fp
.write(F
"{asset_path('materials',mat)}.vmt\n")
1925 fp
.write(F
"{cxr_winepath(asset_full_path('materials',mat))}.vmt\n")
1927 for img_job
in image_jobs
:
1929 fp
.write(F
"{asset_path('materials',img)}.vtf\n")
1930 fp
.write(F
"{cxr_winepath(asset_full_path('materials',img))}.vtf\n")
1932 for mdl
in a_models
:
1933 local
= asset_path('models',mdl
)
1934 winep
= cxr_winepath(asset_full_path('models',mdl
))
1936 fp
.write(F
"{local}.vvd\n")
1937 fp
.write(F
"{winep}.vvd\n")
1938 fp
.write(F
"{local}.dx90.vtx\n")
1939 fp
.write(F
"{winep}.dx90.vtx\n")
1940 fp
.write(F
"{local}.mdl\n")
1941 fp
.write(F
"{winep}.mdl\n")
1942 fp
.write(F
"{local}.vvd\n")
1943 fp
.write(F
"{winep}.vvd\n")
1945 if cxr_modelsrc_vphys(mdl
):
1946 fp
.write(F
"{local}.phy\n")
1947 fp
.write(F
"{winep}.phy\n")
1953 if settings
.comp_vmf
:
1954 static
.JOBINFO
+= [{
1955 "title": "Convexer",
1957 "colour": (0.863, 0.078, 0.235,1.0),
1958 "exec": cxr_export_vmf
,
1959 "jobs": [(sceneinfo
,output_vmf
)]
1962 if settings
.comp_textures
:
1963 if len(image_jobs
) > 0:
1964 static
.JOBINFO
+= [{
1965 "title": "Textures",
1967 "colour": (1.000, 0.271, 0.000,1.0),
1968 "exec": compile_image
,
1972 game
= cxr_winepath( settings
.subdir
)
1974 '-game', game
, settings
.project_name
1978 if settings
.comp_models
:
1979 if len(model_jobs
) > 0:
1980 static
.JOBINFO
+= [{
1983 "colour": (1.000, 0.647, 0.000,1.0),
1984 "exec": cxr_export_modelsrc
,
1988 if len(qc_jobs
) > 0:
1989 static
.JOBINFO
+= [{
1990 "title": "StudioMDL",
1992 "colour": (1.000, 0.843, 0.000, 1.0),
1993 "exec": "studiomdl",
1994 "jobs": [[settings
[F
'exe_studiomdl']] + [\
1995 '-nop4', '-game', game
, qc
] for qc
in qc_jobs
],
2000 if settings
.comp_compile
:
2001 if not settings
.opt_vbsp
.startswith( 'disable' ):
2002 vbsp_opt
= settings
.opt_vbsp
.split()
2003 static
.JOBINFO
+= [{
2006 "colour": (0.678, 1.000, 0.184,1.0),
2008 "jobs": [[settings
[F
'exe_vbsp']] + vbsp_opt
+ args
],
2012 if not settings
.opt_vvis
.startswith( 'disable' ):
2013 vvis_opt
= settings
.opt_vvis
.split()
2014 static
.JOBINFO
+= [{
2017 "colour": (0.000, 1.000, 0.498,1.0),
2019 "jobs": [[settings
[F
'exe_vvis']] + vvis_opt
+ args
],
2023 if not settings
.opt_vrad
.startswith( 'disable' ):
2024 vrad_opt
= settings
.opt_vrad
.split()
2025 static
.JOBINFO
+= [{
2028 "colour": (0.125, 0.698, 0.667,1.0),
2030 "jobs": [[settings
[F
'exe_vrad']] + vrad_opt
+ args
],
2034 static
.JOBINFO
+= [{
2037 "colour": (0.118, 0.565, 1.000,1.0),
2038 "exec": cxr_patchmap
,
2039 "jobs": [(bsp_local
,bsp_remote
)]
2042 if settings
.comp_pack
:
2043 static
.JOBINFO
+= [{
2046 "colour": (0.541, 0.169, 0.886,1.0),
2048 "jobs": [[cxr_compiler_path("bspzip"), '-addlist', \
2049 cxr_winepath(bsp_remote
),
2050 cxr_winepath(packlist
),
2051 cxr_winepath(bsp_packed
) ]],
2055 if len(static
.JOBINFO
) == 0:
2056 return {'CANCELLED'}
2058 static
.USER_EXIT
=False
2059 static
.TIMER
=wm
.event_timer_add(0.1,window
=context
.window
)
2060 wm
.modal_handler_add(_
)
2062 cxr_jobs_update_graph( static
.JOBINFO
)
2064 return {'RUNNING_MODAL'}
2066 class CXR_RESET_HASHES(bpy
.types
.Operator
):
2067 bl_idname
="convexer.hash_reset"
2068 bl_label
="Reset asset hashes"
2070 def execute(_
,context
):
2071 for c
in bpy
.data
.collections
:
2072 c
.cxr_data
.last_hash
= F
"<RESET>{time.time()}"
2073 c
.cxr_data
.asset_id
=0
2075 for t
in bpy
.data
.images
:
2076 t
.cxr_data
.last_hash
= F
"<RESET>{time.time()}"
2077 t
.cxr_data
.asset_id
=0
2081 class CXR_COMPILE_MATERIAL(bpy
.types
.Operator
):
2082 bl_idname
="convexer.matcomp"
2083 bl_label
="Recompile Material"
2085 def execute(_
,context
):
2086 active_obj
= bpy
.context
.active_object
2087 active_mat
= active_obj
.active_material
2089 #TODO: reduce code dupe (L1663)
2090 for pair
in compile_material(active_mat
):
2095 if isinstance(prop
,bpy
.types
.Image
):
2097 if 'flags' in pdef
: flags
= pdef
['flags']
2098 prop
.cxr_data
.flags
= flags
2100 compile_image( prop
)
2102 settings
= bpy
.context
.scene
.cxr_data
2103 with
open(F
'{settings.subdir}/cfg/convexer_mat_update.cfg','w') as o
:
2104 o
.write(F
'mat_reloadmaterial {asset_name(active_mat)}')
2107 with
open(F
'{settings.subdir}/cfg/convexer.cfg','w') as o
:
2108 o
.write('sv_cheats 1\n')
2109 o
.write('mp_warmup_pausetimer 1\n')
2110 o
.write('bot_kick\n')
2111 o
.write('alias cxr_reload "exec convexer_mat_update"\n')
2116 # ------------------------------------------------------------------------------
2118 # Helper buttons for 3d toolbox view
2120 class CXR_VIEW3D( bpy
.types
.Panel
):
2121 bl_idname
= "VIEW3D_PT_convexer"
2122 bl_label
= "Convexer"
2123 bl_space_type
= 'VIEW_3D'
2124 bl_region_type
= 'UI'
2125 bl_category
= "Convexer"
2127 def draw(_
, context
):
2130 active_object
= context
.object
2131 if active_object
== None: return
2133 purpose
= cxr_object_purpose( active_object
)
2135 if purpose
[0] == None or purpose
[1] == None:
2136 usage_str
= "No purpose"
2138 if purpose
[1] == 'model':
2139 usage_str
= F
'mesh in {asset_name( purpose[0] )}.mdl'
2141 usage_str
= F
'{purpose[1]} in {purpose[0].name}'
2143 layout
.label(text
=F
"Currently editing:")
2145 box
.label(text
=usage_str
)
2147 if purpose
[1] == 'brush' or purpose
[1] == 'brush_entity':
2150 row
.operator("convexer.preview")
2154 row
.operator("convexer.reset")
2156 # Main scene properties interface, where all the settings go
2158 class CXR_INTERFACE(bpy
.types
.Panel
):
2160 bl_idname
="SCENE_PT_convexer"
2161 bl_space_type
='PROPERTIES'
2162 bl_region_type
='WINDOW'
2165 def draw(_
,context
):
2166 if CXR_GNU_LINUX
==1:
2167 _
.layout
.operator("convexer.reload")
2168 _
.layout
.operator("convexer.dev_test")
2170 _
.layout
.operator("convexer.hash_reset")
2171 settings
= context
.scene
.cxr_data
2173 _
.layout
.prop(settings
, "scale_factor")
2174 _
.layout
.prop(settings
, "skybox_scale_factor")
2175 _
.layout
.prop(settings
, "skyname" )
2176 _
.layout
.prop(settings
, "lightmap_scale")
2177 _
.layout
.prop(settings
, "light_scale" )
2178 _
.layout
.prop(settings
, "image_quality" )
2180 box
= _
.layout
.box()
2182 box
.prop(settings
, "project_name")
2183 box
.prop(settings
, "subdir")
2185 box
= _
.layout
.box()
2186 box
.operator("convexer.detect_compilers")
2187 box
.prop(settings
, "exe_studiomdl")
2188 box
.prop(settings
, "exe_vbsp")
2189 box
.prop(settings
, "opt_vbsp")
2191 box
.prop(settings
, "exe_vvis")
2192 box
.prop(settings
, "opt_vvis")
2194 box
.prop(settings
, "exe_vrad")
2195 box
.prop(settings
, "opt_vrad")
2199 row
.prop(settings
,"comp_vmf")
2200 row
.prop(settings
,"comp_textures")
2201 row
.prop(settings
,"comp_models")
2202 row
.prop(settings
,"comp_compile")
2203 row
.prop(settings
,"comp_pack")
2205 text
= "Compile" if CXR_COMPILER_CHAIN
.TIMER
== None else "Cancel"
2208 row
.operator("convexer.chain", text
=text
)
2212 row
.operator("convexer.reset")
2213 if CXR_COMPILER_CHAIN
.TIMER
!= None:
2216 class CXR_MATERIAL_PANEL(bpy
.types
.Panel
):
2217 bl_label
="VMT Properties"
2218 bl_idname
="SCENE_PT_convexer_vmt"
2219 bl_space_type
='PROPERTIES'
2220 bl_region_type
='WINDOW'
2221 bl_context
="material"
2223 def draw(_
,context
):
2224 active_object
= bpy
.context
.active_object
2225 if active_object
== None: return
2227 active_material
= active_object
.active_material
2228 if active_material
== None: return
2230 properties
= active_material
.cxr_data
2231 info
= material_info( active_material
)
2233 _
.layout
.label(text
=F
"{info['name']} @{info['res'][0]}x{info['res'][1]}")
2234 row
= _
.layout
.row()
2235 row
.prop( properties
, "shader" )
2236 row
.operator( "convexer.matcomp" )
2238 #for xk in info: _.layout.label(text=F"{xk}:={info[xk]}")
2240 def _mtex( name
, img
, uiParent
):
2243 box
= uiParent
.box()
2244 box
.label( text
=F
'{name} "{img.filepath}"' )
2246 if ((x
& (x
- 1)) == 0):
2249 closest_diff
= 10000000
2251 dist
= abs((1 << i
)-x
)
2252 if dist
< closest_diff
:
2257 return 1 << (closest
+1)
2259 return 1 << (closest
-1)
2264 row
.prop( img
.cxr_data
, "export_res" )
2265 row
.prop( img
.cxr_data
, "fmt" )
2268 row
.prop( img
.cxr_data
, "mipmap" )
2269 row
.prop( img
.cxr_data
, "lod" )
2270 row
.prop( img
.cxr_data
, "clamp" )
2272 img
.cxr_data
.export_res
[0] = _p2( img
.cxr_data
.export_res
[0] )
2273 img
.cxr_data
.export_res
[1] = _p2( img
.cxr_data
.export_res
[1] )
2275 def _mview( layer
, uiParent
):
2279 if isinstance(layer
[decl
],dict): # $property definition
2281 ptype
= pdef
['type']
2287 if ('shaders' in pdef
) and \
2288 (properties
.shader
not in pdef
['shaders']):
2291 if ptype
== 'intrinsic':
2292 if decl
not in info
:
2297 if isinstance(pdef
[ch
],dict):
2298 if ptype
== 'ui' or ptype
== 'intrinsic':
2300 elif getattr(properties
,decl
) == pdef
['default']:
2303 thisnode
= uiParent
.box()
2307 thisnode
.label( text
=decl
)
2308 elif ptype
== 'intrinsic':
2309 if isinstance(info
[decl
], bpy
.types
.Image
):
2310 _mtex( decl
, info
[decl
], thisnode
)
2312 # hidden intrinsic value.
2313 # Means its a float array or something not an image
2314 thisnode
.label(text
=F
"-- hidden intrinsic '{decl}' --")
2316 thisnode
.prop(properties
,decl
)
2317 if expandview
: _mview(pdef
,thisnode
)
2319 _mview( cxr_shader_params
, _
.layout
)
2321 def cxr_entity_changeclass(_
,context
):
2322 active_object
= context
.active_object
2324 # Create ID properties
2326 classname
= cxr_custom_class(active_object
)
2328 if classname
in cxr_entities
:
2329 entdef
= cxr_entities
[classname
]
2331 kvs
= entdef
['keyvalues']
2332 if callable(kvs
): kvs
= kvs( {'object': active_object
} )
2338 if callable(kv
) or not isinstance(kv
,dict): continue
2340 if key
not in active_object
:
2341 active_object
[key
] = kv
['default']
2342 id_prop
= active_object
.id_properties_ui(key
)
2343 id_prop
.update(default
=kv
['default'])
2345 class CXR_ENTITY_PANEL(bpy
.types
.Panel
):
2346 bl_label
="Entity Config"
2347 bl_idname
="SCENE_PT_convexer_entity"
2348 bl_space_type
='PROPERTIES'
2349 bl_region_type
='WINDOW'
2352 def draw(_
,context
):
2353 active_object
= bpy
.context
.active_object
2355 if active_object
== None: return
2358 "scale": bpy
.context
.scene
.cxr_data
.scale_factor
,
2362 ecn
= cxr_intrinsic_classname( active_object
)
2363 classname
= cxr_custom_class( active_object
)
2366 if active_object
.type == 'MESH':
2367 _
.layout
.prop( active_object
.cxr_data
, 'brushclass' )
2368 else: _
.layout
.prop( active_object
.cxr_data
, 'classname' )
2370 _
.layout
.prop( active_object
.cxr_data
, 'visgroup' )
2371 _
.layout
.prop( active_object
.cxr_data
, 'lightmap_override' )
2373 if classname
== 'NONE':
2376 _
.layout
.label(text
=F
"<implementation defined ({ecn})>")
2377 _
.layout
.enabled
=False
2380 kvs
= cxr_entity_keyvalues( {
2381 "object": active_object
,
2382 "transform": default_context
,
2383 "classname": classname
2389 _
.layout
.prop( active_object
, F
'["cxrkv_{kv[0]}"]', text
=kv
[0])
2391 row
= _
.layout
.row()
2393 row
.label( text
=F
'{kv[0]}: {repr(kv[2])}' )
2395 _
.layout
.label( text
=F
"ERROR: NO CLASS DEFINITION" )
2397 class CXR_LIGHT_PANEL(bpy
.types
.Panel
):
2398 bl_label
= "Source Settings"
2399 bl_idname
= "LIGHT_PT_cxr"
2400 bl_space_type
= 'PROPERTIES'
2401 bl_region_type
= 'WINDOW'
2404 def draw(self
, context
):
2405 layout
= self
.layout
2406 scene
= context
.scene
2408 active_object
= bpy
.context
.active_object
2409 if active_object
== None: return
2411 if active_object
.type == 'LIGHT' or \
2412 active_object
.type == 'LIGHT_PROBE':
2414 properties
= active_object
.data
.cxr_data
2416 if active_object
.type == 'LIGHT':
2417 layout
.prop( properties
, "realtime" )
2418 elif active_object
.type == 'LIGHT_PROBE':
2419 layout
.prop( properties
, "size" )
2421 class CXR_COLLECTION_PANEL(bpy
.types
.Panel
):
2422 bl_label
= "Source Settings"
2423 bl_idname
= "COL_PT_cxr"
2424 bl_space_type
= 'PROPERTIES'
2425 bl_region_type
= 'WINDOW'
2426 bl_context
= "collection"
2428 def draw(self
, context
):
2429 layout
= self
.layout
2430 scene
= context
.scene
2432 active_collection
= bpy
.context
.collection
2434 if active_collection
!= None:
2435 layout
.prop( active_collection
.cxr_data
, "shadow_caster" )
2436 layout
.prop( active_collection
.cxr_data
, "texture_shadows" )
2437 layout
.prop( active_collection
.cxr_data
, "preserve_order" )
2438 layout
.prop( active_collection
.cxr_data
, "surfaceprop" )
2439 layout
.prop( active_collection
.cxr_data
, "visgroup" )
2442 # ------------------------------------------------------------------------------
2444 class CXR_IMAGE_SETTINGS(bpy
.types
.PropertyGroup
):
2445 export_res
: bpy
.props
.IntVectorProperty(
2447 description
="Texture Export Resolution",
2453 fmt
: bpy
.props
.EnumProperty(
2456 ('DXT1', "DXT1", "BC1 compressed", '', 0),
2457 ('DXT5', "DXT5", "BC3 compressed", '', 1),
2458 ('RGB', "RGB", "Uncompressed", '', 2),
2459 ('RGBA', "RGBA", "Uncompressed (with alpha)", '', 3)
2461 description
="Image format",
2464 last_hash
: bpy
.props
.StringProperty( name
="" )
2465 asset_id
: bpy
.props
.IntProperty(name
="intl_assetid",default
=0)
2467 mipmap
: bpy
.props
.BoolProperty(name
="MIP",default
=True)
2468 lod
: bpy
.props
.BoolProperty(name
="LOD",default
=True)
2469 clamp
: bpy
.props
.BoolProperty(name
="CLAMP",default
=False)
2470 flags
: bpy
.props
.IntProperty(name
="flags",default
=0)
2472 class CXR_LIGHT_SETTINGS(bpy
.types
.PropertyGroup
):
2473 realtime
: bpy
.props
.BoolProperty(name
="Realtime Light", default
=True)
2475 class CXR_CUBEMAP_SETTINGS(bpy
.types
.PropertyGroup
):
2476 size
: bpy
.props
.EnumProperty(
2479 ('1',"1x1",'','',0),
2480 ('2',"2x2",'','',1),
2481 ('3',"4x4",'','',2),
2482 ('4',"8x8",'','',3),
2483 ('5',"16x16",'','',4),
2484 ('6',"32x32",'','',5),
2485 ('7',"64x64",'','',6),
2486 ('8',"128x128",'','',7),
2487 ('9',"256x256",'','',8)
2489 description
="Texture resolution",
2492 class CXR_ENTITY_SETTINGS(bpy
.types
.PropertyGroup
):
2493 entity
: bpy
.props
.BoolProperty(name
="")
2495 enum_pointents
= [('NONE',"None","")]
2496 enum_brushents
= [('NONE',"None","")]
2498 for classname
in cxr_entities
:
2499 entdef
= cxr_entities
[classname
]
2500 if 'allow' in entdef
:
2501 itm
= [(classname
, classname
, "")]
2502 if 'EMPTY' in entdef
['allow']: enum_pointents
+= itm
2503 else: enum_brushents
+= itm
2505 classname
: bpy
.props
.EnumProperty(items
=enum_pointents
, name
="Class", \
2506 update
=cxr_entity_changeclass
, default
='NONE' )
2508 brushclass
: bpy
.props
.EnumProperty(items
=enum_brushents
, name
="Class", \
2509 update
=cxr_entity_changeclass
, default
='NONE' )
2511 enum_classes
= [('0',"None","")]
2512 for i
, vg
in enumerate(cxr_visgroups
):
2513 enum_classes
+= [(str(i
+1),vg
,"")]
2514 visgroup
: bpy
.props
.EnumProperty(name
="visgroup",items
=enum_classes
,default
=0)
2515 lightmap_override
: bpy
.props
.IntProperty(name
="Lightmap Override",default
=0)
2517 class CXR_MODEL_SETTINGS(bpy
.types
.PropertyGroup
):
2518 last_hash
: bpy
.props
.StringProperty( name
="" )
2519 asset_id
: bpy
.props
.IntProperty(name
="vmf_settings",default
=0)
2520 shadow_caster
: bpy
.props
.BoolProperty( name
="Shadow caster", default
=True )
2521 texture_shadows
: bpy
.props
.BoolProperty( name
="Texture Shadows", default
=False )
2522 preserve_order
: bpy
.props
.BoolProperty( name
="Preserve Order", default
=False )
2523 surfaceprop
: bpy
.props
.StringProperty( name
="Suface prop",default
="default" )
2525 enum_classes
= [('0',"None","")]
2526 for i
, vg
in enumerate(cxr_visgroups
):
2527 enum_classes
+= [(str(i
+1),vg
,"")]
2528 visgroup
: bpy
.props
.EnumProperty(name
="visgroup",items
=enum_classes
,default
=0)
2530 class CXR_SCENE_SETTINGS(bpy
.types
.PropertyGroup
):
2531 project_name
: bpy
.props
.StringProperty( name
="Project Name" )
2532 subdir
: bpy
.props
.StringProperty( name
="../csgo/ folder" )
2534 exe_studiomdl
: bpy
.props
.StringProperty( name
="studiomdl" )
2535 exe_vbsp
: bpy
.props
.StringProperty( name
="vbsp" )
2536 opt_vbsp
: bpy
.props
.StringProperty( name
="args" )
2537 exe_vvis
: bpy
.props
.StringProperty( name
="vvis" )
2538 opt_vvis
: bpy
.props
.StringProperty( name
="args" )
2539 exe_vrad
: bpy
.props
.StringProperty( name
="vrad" )
2540 opt_vrad
: bpy
.props
.StringProperty( name
="args", \
2541 default
="-reflectivityScale 0.35 -aoscale 1.4 -final -textureshadows -hdr -StaticPropLighting -StaticPropPolys" )
2543 scale_factor
: bpy
.props
.FloatProperty( name
="VMF Scale factor", \
2544 default
=32.0,min=1.0)
2545 skybox_scale_factor
: bpy
.props
.FloatProperty( name
="Sky Scale factor", \
2546 default
=1.0,min=0.01)
2547 skyname
: bpy
.props
.StringProperty(name
="Skyname",default
="sky_csgo_night02b")
2548 skybox_offset
: bpy
.props
.FloatProperty(name
="Sky offset",default
=-4096.0)
2549 light_scale
: bpy
.props
.FloatProperty(name
="Light Scale",default
=1.0/5.0)
2550 include_names
: bpy
.props
.BoolProperty(name
="Append original file names",\
2552 lightmap_scale
: bpy
.props
.IntProperty(name
="Global Lightmap Scale",\
2554 image_quality
: bpy
.props
.IntProperty(name
="Texture Quality (0-18)",\
2555 default
=8, min=0, max=18 )
2557 comp_vmf
: bpy
.props
.BoolProperty(name
="VMF",default
=True)
2558 comp_models
: bpy
.props
.BoolProperty(name
="Models",default
=True)
2559 comp_textures
: bpy
.props
.BoolProperty(name
="Textures",default
=True)
2560 comp_compile
: bpy
.props
.BoolProperty(name
="Compile",default
=True)
2561 comp_pack
: bpy
.props
.BoolProperty(name
="Pack",default
=False)
2563 classes
= [ CXR_RELOAD
, CXR_DEV_OPERATOR
, CXR_INTERFACE
, \
2564 CXR_MATERIAL_PANEL
, CXR_IMAGE_SETTINGS
,\
2565 CXR_MODEL_SETTINGS
, CXR_ENTITY_SETTINGS
, CXR_CUBEMAP_SETTINGS
,\
2566 CXR_LIGHT_SETTINGS
, CXR_SCENE_SETTINGS
, CXR_DETECT_COMPILERS
,\
2567 CXR_ENTITY_PANEL
, CXR_LIGHT_PANEL
, CXR_PREVIEW_OPERATOR
,\
2568 CXR_VIEW3D
, CXR_COMPILER_CHAIN
, CXR_RESET_HASHES
,\
2569 CXR_COMPILE_MATERIAL
, CXR_COLLECTION_PANEL
, CXR_RESET
]
2571 vmt_param_dynamic_class
= None
2574 global cxr_view_draw_handler
, vmt_param_dynamic_class
, cxr_ui_handler
2577 bpy
.utils
.register_class(c
)
2579 # Build dynamic VMT properties class defined by cxr_shader_params
2580 annotations_dict
= {}
2582 def _dvmt_propogate(layer
):
2583 nonlocal annotations_dict
2586 if isinstance(layer
[decl
],dict): # $property definition
2590 if pdef
['type'] == 'bool':
2591 prop
= bpy
.props
.BoolProperty(\
2592 name
= pdef
['name'],\
2593 default
= pdef
['default'])
2595 elif pdef
['type'] == 'float':
2596 prop
= bpy
.props
.FloatProperty(\
2597 name
= pdef
['name'],\
2598 default
= pdef
['default'])
2600 elif pdef
['type'] == 'vector':
2601 if 'subtype' in pdef
:
2602 prop
= bpy
.props
.FloatVectorProperty(\
2603 name
= pdef
['name'],\
2604 subtype
= pdef
['subtype'],\
2605 default
= pdef
['default'],\
2606 size
= len(pdef
['default']))
2608 prop
= bpy
.props
.FloatVectorProperty(\
2609 name
= pdef
['name'],\
2610 default
= pdef
['default'],\
2611 size
= len(pdef
['default']))
2613 elif pdef
['type'] == 'string':
2614 prop
= bpy
.props
.StringProperty(\
2615 name
= pdef
['name'],\
2616 default
= pdef
['default'])
2618 elif pdef
['type'] == 'enum':
2619 prop
= bpy
.props
.EnumProperty(\
2620 name
= pdef
['name'],\
2621 items
= pdef
['items'],\
2622 default
= pdef
['default'])
2625 annotations_dict
[decl
] = prop
2627 # Recurse into sub-definitions
2628 _dvmt_propogate(pdef
)
2630 annotations_dict
["shader"] = bpy
.props
.EnumProperty(\
2633 cxr_shaders
[_
]["name"],\
2634 '') for _
in cxr_shaders
],\
2635 default
= next(iter(cxr_shaders
)))
2637 annotations_dict
["asset_id"] = bpy
.props
.IntProperty(name
="intl_assetid",\
2640 _dvmt_propogate( cxr_shader_params
)
2641 vmt_param_dynamic_class
= type(
2643 (bpy
.types
.PropertyGroup
,),{
2644 "__annotations__": annotations_dict
2648 bpy
.utils
.register_class( vmt_param_dynamic_class
)
2651 bpy
.types
.Material
.cxr_data
= \
2652 bpy
.props
.PointerProperty(type=vmt_param_dynamic_class
)
2653 bpy
.types
.Image
.cxr_data
= \
2654 bpy
.props
.PointerProperty(type=CXR_IMAGE_SETTINGS
)
2655 bpy
.types
.Object
.cxr_data
= \
2656 bpy
.props
.PointerProperty(type=CXR_ENTITY_SETTINGS
)
2657 bpy
.types
.Collection
.cxr_data
= \
2658 bpy
.props
.PointerProperty(type=CXR_MODEL_SETTINGS
)
2659 bpy
.types
.Light
.cxr_data
= \
2660 bpy
.props
.PointerProperty(type=CXR_LIGHT_SETTINGS
)
2661 bpy
.types
.LightProbe
.cxr_data
= \
2662 bpy
.props
.PointerProperty(type=CXR_CUBEMAP_SETTINGS
)
2663 bpy
.types
.Scene
.cxr_data
= \
2664 bpy
.props
.PointerProperty(type=CXR_SCENE_SETTINGS
)
2666 # CXR Scene settings
2669 cxr_view_draw_handler
= bpy
.types
.SpaceView3D
.draw_handler_add(\
2670 cxr_draw
,(),'WINDOW','POST_VIEW')
2672 cxr_ui_handler
= bpy
.types
.SpaceView3D
.draw_handler_add(\
2673 cxr_ui
,(None,None),'WINDOW','POST_PIXEL')
2675 bpy
.app
.handlers
.load_post
.append(cxr_on_load
)
2676 bpy
.app
.handlers
.depsgraph_update_post
.append(cxr_dgraph_update
)
2679 global cxr_view_draw_handler
, vmt_param_dynamic_class
, cxr_ui_handler
2681 bpy
.utils
.unregister_class( vmt_param_dynamic_class
)
2683 bpy
.utils
.unregister_class(c
)
2685 bpy
.app
.handlers
.depsgraph_update_post
.remove(cxr_dgraph_update
)
2686 bpy
.app
.handlers
.load_post
.remove(cxr_on_load
)
2688 bpy
.types
.SpaceView3D
.draw_handler_remove(cxr_view_draw_handler
,'WINDOW')
2689 bpy
.types
.SpaceView3D
.draw_handler_remove(cxr_ui_handler
,'WINDOW')