1 # Copyright (C) 2022 Harry Godden (hgn)
5 "author": "Harry Godden (hgn)",
12 "category":"Import/Export",
15 print( "Convexer reload" )
17 #from mathutils import *
18 import bpy
, gpu
, math
, os
, time
, mathutils
, blf
, subprocess
, shutil
, hashlib
20 from gpu_extras
.batch
import batch_for_shader
21 from bpy
.app
.handlers
import persistent
23 # GPU and viewport drawing
24 # ------------------------------------------------------------------------------
27 cxr_view_draw_handler
= None
28 cxr_ui_draw_handler
= None
37 cxr_view_shader
= gpu
.shader
.from_builtin('3D_SMOOTH_COLOR')
38 cxr_ui_shader
= gpu
.types
.GPUShader("""
39 uniform mat4 ModelViewProjectionMatrix;
49 gl_Position = ModelViewProjectionMatrix * vec4(aPos.x*scale,aPos.y, 0.0, 1.0);
64 def cxr_ui(_
,context
):
65 global cxr_jobs_batch
, cxr_ui_shader
, cxr_jobs_inf
67 w
= gpu
.state
.viewport_get()[2]
69 cxr_ui_shader
.uniform_float( "scale", w
)
71 if cxr_jobs_batch
!= None:
72 gpu
.state
.blend_set('ALPHA')
73 cxr_jobs_batch
.draw(cxr_ui_shader
)
75 blf
.position(0,2,50,0)
77 blf
.color(0,1.0,1.0,1.0,1.0)
78 blf
.draw(0,"Compiling")
80 for ji
in cxr_jobs_inf
:
81 blf
.position(0,ji
[0]*w
,35,0)
87 for ln
in reversed(CXR_COMPILER_CHAIN
.LOG
[-25:]):
88 blf
.position(0,2,py
,0)
92 #if CXR_PREVIEW_OPERATOR.LASTERR != None:
93 # blf.position(0,2,80,0)
95 # blf.color(0,1.0,0.2,0.2,0.9)
96 # blf.draw(0,"Invalid geometry")
98 # Something is off with TIMER,
99 # this forces the viewport to redraw before we can continue with our
102 CXR_COMPILER_CHAIN
.WAIT_REDRAW
= False
105 global cxr_view_shader
, cxr_view_mesh
, cxr_view_lines
107 cxr_view_shader
.bind()
109 gpu
.state
.depth_mask_set(False)
110 gpu
.state
.line_width_set(1.5)
111 gpu
.state
.face_culling_set('BACK')
112 gpu
.state
.depth_test_set('NONE')
113 gpu
.state
.blend_set('ALPHA')
115 if cxr_view_lines
!= None:
116 cxr_view_lines
.draw( cxr_view_shader
)
118 gpu
.state
.depth_test_set('LESS_EQUAL')
119 gpu
.state
.blend_set('ADDITIVE')
120 if cxr_view_mesh
!= None:
121 cxr_view_mesh
.draw( cxr_view_shader
)
123 def cxr_jobs_update_graph(jobs
):
124 global cxr_jobs_batch
, cxr_ui_shader
, cxr_jobs_inf
134 total_width
+= sys
['w']
143 colour
= sys
['colour']
144 colourwait
= (colour
[0],colour
[1],colour
[2],0.4)
145 colourrun
= (colour
[0]*1.5,colour
[1]*1.5,colour
[2]*1.5,0.5)
146 colourdone
= (colour
[0],colour
[1],colour
[2],1.0)
149 sfsub
= (1.0/(len(jobs
)))*w
153 if j
== None: colour
= colourdone
154 else: colour
= colourwait
156 px
= (cur
+ (i
)*sfsub
) * sf
157 px1
= (cur
+ (i
+1.0)*sfsub
) * sf
160 verts
+= [(px
,0), (px
, h
), (px1
, 0.0), (px1
,h
)]
161 colours
+= [colour
,colour
,colour
,colour
]
162 indices
+= [(ci
+0,ci
+2,ci
+3),(ci
+0,ci
+3,ci
+1)]
165 cxr_jobs_inf
+= [((sf
*cur
), sys
['title'])]
168 cxr_jobs_batch
= batch_for_shader(
169 cxr_ui_shader
, 'TRIS',
170 { "aPos": verts
, "aColour": colours
},
174 # view_layer.update() doesnt seem to work,
175 # tag_redraw() seems to have broken
176 # therefore, change a property
178 ob
= bpy
.context
.scene
.objects
[0]
179 ob
.hide_render
= ob
.hide_render
181 # the 'real' way to refresh the scene
182 for area
in bpy
.context
.window
.screen
.areas
:
183 if area
.type == 'view_3d':
187 # ------------------------------------------------------------------------------
189 # dlclose for reloading modules manually
191 libc_dlclose
= cdll
.LoadLibrary(None).dlclose
192 libc_dlclose
.argtypes
= [c_void_p
]
194 # wrapper for ctypes binding
196 def __init__(_
,name
,argtypes
,restype
):
198 _
.argtypes
= argtypes
203 _
.call
= getattr(so
,_
.name
)
204 _
.call
.argtypes
= _
.argtypes
206 if _
.restype
!= None:
207 _
.call
.restype
= _
.restype
210 # ------------------------------------------------------------------------------
214 # Structure definitions
216 class cxr_edge(Structure
):
217 _fields_
= [("i0",c_int32
),
219 ("freestyle",c_int32
),
222 class cxr_static_loop(Structure
):
223 _fields_
= [("index",c_int32
),
224 ("edge_index",c_int32
),
227 class cxr_polygon(Structure
):
228 _fields_
= [("loop_start",c_int32
),
229 ("loop_total",c_int32
),
230 ("normal",c_double
* 3),
231 ("center",c_double
* 3),
232 ("material_id",c_int32
)]
234 class cxr_material(Structure
):
235 _fields_
= [("res",c_int32
* 2),
238 class cxr_static_mesh(Structure
):
239 _fields_
= [("vertices",POINTER(c_double
* 3)),
240 ("edges",POINTER(cxr_edge
)),
241 ("loops",POINTER(cxr_static_loop
)),
242 ("polys",POINTER(cxr_polygon
)),
243 ("materials",POINTER(cxr_material
)),
245 ("poly_count",c_int32
),
246 ("vertex_count",c_int32
),
247 ("edge_count",c_int32
),
248 ("loop_count",c_int32
),
249 ("material_count",c_int32
)]
251 class cxr_tri_mesh(Structure
):
252 _fields_
= [("vertices",POINTER(c_double
*3)),
253 ("colours",POINTER(c_double
*4)),
254 ("indices",POINTER(c_int32
)),
255 ("indices_count",c_int32
),
256 ("vertex_count",c_int32
)]
258 class cxr_visgroup(Structure
):
259 _fields_
= [("name",c_char_p
)]
261 class cxr_vmf_context(Structure
):
262 _fields_
= [("mapversion",c_int32
),
263 ("skyname",c_char_p
),
264 ("detailvbsp",c_char_p
),
265 ("detailmaterial",c_char_p
),
266 ("visgroups",POINTER(cxr_visgroup
)),
267 ("visgroup_count",c_int32
),
269 ("offset",c_double
*3),
270 ("lightmap_scale",c_int32
),
271 ("visgroupid",c_int32
),
272 ("brush_count",c_int32
),
273 ("entity_count",c_int32
),
274 ("face_count",c_int32
)]
276 # Convert blenders mesh format into CXR's static format (they are very similar)
278 def mesh_cxr_format(obj
):
281 if bpy
.context
.active_object
!= None:
282 orig_state
= obj
.mode
283 if orig_state
!= 'OBJECT':
284 bpy
.ops
.object.mode_set(mode
='OBJECT')
286 dgraph
= bpy
.context
.evaluated_depsgraph_get()
287 data
= obj
.evaluated_get(dgraph
).data
289 _
,mtx_rot
,_
= obj
.matrix_world
.decompose()
291 mesh
= cxr_static_mesh()
293 vertex_data
= ((c_double
*3)*len(data
.vertices
))()
294 for i
, vert
in enumerate(data
.vertices
):
295 v
= obj
.matrix_world
@ vert
.co
296 vertex_data
[i
][0] = c_double(v
[0])
297 vertex_data
[i
][1] = c_double(v
[1])
298 vertex_data
[i
][2] = c_double(v
[2])
300 loop_data
= (cxr_static_loop
*len(data
.loops
))()
301 polygon_data
= (cxr_polygon
*len(data
.polygons
))()
303 for i
, poly
in enumerate(data
.polygons
):
304 loop_start
= poly
.loop_start
305 loop_end
= poly
.loop_start
+ poly
.loop_total
306 for loop_index
in range(loop_start
, loop_end
):
307 loop
= data
.loops
[loop_index
]
308 loop_data
[loop_index
].index
= loop
.vertex_index
309 loop_data
[loop_index
].edge_index
= loop
.edge_index
312 uv
= data
.uv_layers
.active
.data
[loop_index
].uv
313 loop_data
[loop_index
].uv
[0] = c_double(uv
[0])
314 loop_data
[loop_index
].uv
[1] = c_double(uv
[1])
316 loop_data
[loop_index
].uv
[0] = c_double(0.0)
317 loop_data
[loop_index
].uv
[1] = c_double(0.0)
318 center
= obj
.matrix_world
@ poly
.center
319 normal
= mtx_rot
@ poly
.normal
321 polygon_data
[i
].loop_start
= poly
.loop_start
322 polygon_data
[i
].loop_total
= poly
.loop_total
323 polygon_data
[i
].normal
[0] = normal
[0]
324 polygon_data
[i
].normal
[1] = normal
[1]
325 polygon_data
[i
].normal
[2] = normal
[2]
326 polygon_data
[i
].center
[0] = center
[0]
327 polygon_data
[i
].center
[1] = center
[1]
328 polygon_data
[i
].center
[2] = center
[2]
329 polygon_data
[i
].material_id
= poly
.material_index
331 edge_data
= (cxr_edge
*len(data
.edges
))()
333 for i
, edge
in enumerate(data
.edges
):
334 edge_data
[i
].i0
= edge
.vertices
[0]
335 edge_data
[i
].i1
= edge
.vertices
[1]
336 edge_data
[i
].freestyle
= edge
.use_freestyle_mark
337 edge_data
[i
].sharp
= edge
.use_edge_sharp
339 material_data
= (cxr_material
*len(obj
.material_slots
))()
341 for i
, ms
in enumerate(obj
.material_slots
):
342 inf
= material_info(ms
.material
)
343 material_data
[i
].res
[0] = inf
['res'][0]
344 material_data
[i
].res
[1] = inf
['res'][1]
345 material_data
[i
].name
= inf
['name'].encode('utf-8')
347 mesh
.edges
= cast(edge_data
, POINTER(cxr_edge
))
348 mesh
.vertices
= cast(vertex_data
, POINTER(c_double
*3))
349 mesh
.loops
= cast(loop_data
,POINTER(cxr_static_loop
))
350 mesh
.polys
= cast(polygon_data
, POINTER(cxr_polygon
))
351 mesh
.materials
= cast(material_data
, POINTER(cxr_material
))
353 mesh
.poly_count
= len(data
.polygons
)
354 mesh
.vertex_count
= len(data
.vertices
)
355 mesh
.edge_count
= len(data
.edges
)
356 mesh
.loop_count
= len(data
.loops
)
357 mesh
.material_count
= len(obj
.material_slots
)
359 if orig_state
!= None:
360 bpy
.ops
.object.mode_set(mode
=orig_state
)
364 # Callback ctypes indirection things.. not really sure.
365 c_libcxr_log_callback
= None
366 c_libcxr_line_callback
= None
369 # -------------------------------------------------------------
370 libcxr_decompose
= extern( "cxr_decompose",
371 [POINTER(cxr_static_mesh
), POINTER(c_int32
)],
374 libcxr_free_world
= extern( "cxr_free_world",
378 libcxr_write_test_data
= extern( "cxr_write_test_data",
379 [POINTER(cxr_static_mesh
)],
382 libcxr_world_preview
= extern( "cxr_world_preview",
384 POINTER(cxr_tri_mesh
)
386 libcxr_free_tri_mesh
= extern( "cxr_free_tri_mesh",
390 libcxr_begin_vmf
= extern( "cxr_begin_vmf",
391 [POINTER(cxr_vmf_context
), c_void_p
],
394 libcxr_vmf_begin_entities
= extern( "cxr_vmf_begin_entities",
395 [POINTER(cxr_vmf_context
), c_void_p
],
398 libcxr_push_world_vmf
= extern("cxr_push_world_vmf",
399 [c_void_p
,POINTER(cxr_vmf_context
),c_void_p
],
402 libcxr_end_vmf
= extern( "cxr_end_vmf",
403 [POINTER(cxr_vmf_context
),c_void_p
],
407 # VDF + with open wrapper
408 libcxr_vdf_open
= extern( "cxr_vdf_open", [c_char_p
], c_void_p
)
409 libcxr_vdf_close
= extern( "cxr_vdf_close", [c_void_p
], None )
410 libcxr_vdf_put
= extern( "cxr_vdf_put", [c_void_p
,c_char_p
], None )
411 libcxr_vdf_node
= extern( "cxr_vdf_node", [c_void_p
,c_char_p
], None )
412 libcxr_vdf_edon
= extern( "cxr_vdf_edon", [c_void_p
], None )
413 libcxr_vdf_kv
= extern( "cxr_vdf_kv", [c_void_p
,c_char_p
,c_char_p
], None )
415 class vdf_structure():
416 def __init__(_
,path
):
419 _
.fp
= libcxr_vdf_open
.call( _
.path
.encode('utf-8') )
421 print( F
"Could not open file {_.path}" )
424 def __exit__(_
,type,value
,traceback
):
426 libcxr_vdf_close
.call(_
.fp
)
428 libcxr_vdf_put
.call(_
.fp
, s
.encode('utf-8') )
430 libcxr_vdf_node
.call(_
.fp
, name
.encode('utf-8') )
432 libcxr_vdf_edon
.call(_
.fp
)
434 libcxr_vdf_kv
.call(_
.fp
, k
.encode('utf-8'), v
.encode('utf-8'))
437 libcxr_lightpatch_bsp
= extern( "cxr_lightpatch_bsp", [c_char_p
], None )
439 libcxr_funcs
= [ libcxr_decompose
, libcxr_free_world
, libcxr_begin_vmf
, \
440 libcxr_vmf_begin_entities
, libcxr_push_world_vmf
, \
441 libcxr_end_vmf
, libcxr_vdf_open
, libcxr_vdf_close
, \
442 libcxr_vdf_put
, libcxr_vdf_node
, libcxr_vdf_edon
,
443 libcxr_vdf_kv
, libcxr_lightpatch_bsp
, libcxr_write_test_data
,\
444 libcxr_world_preview
, libcxr_free_tri_mesh
]
448 def libcxr_log_callback(logStr
):
449 print( F
"{logStr.decode('utf-8')}",end
='' )
451 cxr_line_positions
= None
452 cxr_line_colours
= None
454 def cxr_reset_lines():
455 global cxr_line_positions
, cxr_line_colours
457 cxr_line_positions
= []
458 cxr_line_colours
= []
460 def cxr_batch_lines():
461 global cxr_line_positions
, cxr_line_colours
, cxr_view_shader
, cxr_view_lines
463 cxr_view_lines
= batch_for_shader(\
464 cxr_view_shader
, 'LINES',\
465 { "pos": cxr_line_positions
, "color": cxr_line_colours
})
467 def libcxr_line_callback( p0
,p1
,colour
):
468 global cxr_line_colours
, cxr_line_positions
470 cxr_line_positions
+= [(p0
[0],p0
[1],p0
[2])]
471 cxr_line_positions
+= [(p1
[0],p1
[1],p1
[2])]
472 cxr_line_colours
+= [(colour
[0],colour
[1],colour
[2],colour
[3])]
473 cxr_line_colours
+= [(colour
[0],colour
[1],colour
[2],colour
[3])]
476 # ------------------------------------------------------------------------------
481 NBVTF_IMAGE_FORMAT_ABGR8888
= 1
482 NBVTF_IMAGE_FORMAT_BGR888
= 3
483 NBVTF_IMAGE_FORMAT_DXT1
= 13
484 NBVTF_IMAGE_FORMAT_DXT5
= 15
485 NBVTF_TEXTUREFLAGS_CLAMPS
= 0x00000004
486 NBVTF_TEXTUREFLAGS_CLAMPT
= 0x00000008
487 NBVTF_TEXTUREFLAGS_NORMAL
= 0x00000080
488 NBVTF_TEXTUREFLAGS_NOMIP
= 0x00000100
489 NBVTF_TEXTUREFLAGS_NOLOD
= 0x00000200
491 libnbvtf_convert
= extern( "nbvtf_convert", \
492 [c_char_p
,c_int32
,c_int32
,c_int32
,c_int32
,c_int32
,c_uint32
,c_char_p
], \
495 libnbvtf_init
= extern( "nbvtf_init", [], None )
496 libnbvtf_funcs
= [ libnbvtf_convert
, libnbvtf_init
]
499 # --------------------------
502 global libcxr
, libnbvtf
, libcxr_funcs
, libnbvtf_funcs
504 # Unload libraries if existing
505 def _reload( lib
, path
):
507 _handle
= lib
._handle
508 for i
in range(10): libc_dlclose( _handle
)
511 return cdll
.LoadLibrary( F
'{os.path.dirname(__file__)}/{path}.so' )
513 libnbvtf
= _reload( libnbvtf
, "libnbvtf" )
514 libcxr
= _reload( libcxr
, "libcxr" )
516 for fd
in libnbvtf_funcs
:
517 fd
.loadfrom( libnbvtf
)
520 for fd
in libcxr_funcs
:
521 fd
.loadfrom( libcxr
)
524 global c_libcxr_log_callback
, c_libcxr_line_callback
526 LOG_FUNCTION_TYPE
= CFUNCTYPE(None,c_char_p
)
527 c_libcxr_log_callback
= LOG_FUNCTION_TYPE(libcxr_log_callback
)
529 LINE_FUNCTION_TYPE
= CFUNCTYPE(None,\
530 POINTER(c_double
), POINTER(c_double
), POINTER(c_double
))
531 c_libcxr_line_callback
= LINE_FUNCTION_TYPE(libcxr_line_callback
)
533 libcxr
.cxr_set_log_function(cast(c_libcxr_log_callback
,c_void_p
))
534 libcxr
.cxr_set_line_function(cast(c_libcxr_line_callback
,c_void_p
))
536 build_time
= c_char_p
.in_dll(libcxr
,'cxr_build_time')
537 print( F
"libcxr build time: {build_time.value}" )
542 # ------------------------------------------------------------------------------
544 # Standard entity functions, think of like base.fgd
546 def cxr_get_origin(context
):
547 return context
['object'].location
* context
['transform']['scale'] + \
548 mathutils
.Vector(context
['transform']['offset'])
550 def cxr_get_angles(context
):
551 obj
= context
['object']
552 euler
= [ a
*57.295779513 for a
in obj
.rotation_euler
]
559 def cxr_baseclass(classes
, other
):
562 base
.update(x
.copy())
565 def ent_soundscape(context
):
566 obj
= context
['object']
567 kvs
= cxr_baseclass([ent_origin
],\
569 "radius": obj
.scale
.x
* bpy
.context
.scene
.cxr_data
.scale_factor
,
570 "soundscape": {"type":"string","default":""}
575 # EEVEE Light component converter -> Source 1
577 def ent_lights(context
):
578 obj
= context
['object']
579 kvs
= cxr_baseclass([ent_origin
],\
581 "_distance": (0.0 if obj
.data
.cxr_data
.realtime
else -1.0),
582 "_lightHDR": '-1 -1 -1 1',
586 light_base
= [(pow(obj
.data
.color
[i
],1.0/2.2)*255.0) for i
in range(3)] +\
587 [obj
.data
.energy
* bpy
.context
.scene
.cxr_data
.light_scale
]
589 if obj
.data
.type == 'SPOT' or obj
.data
.type == 'SUN':
590 # Blenders directional lights are -z forward
591 # Source is +x, however, it seems to use a completely different system.
592 # Since we dont care about roll for spotlights, we just take the
593 # pitch and yaw via trig
595 _
,mtx_rot
,_
= obj
.matrix_world
.decompose()
596 fwd
= mtx_rot
@ mathutils
.Vector((0,0,-1))
597 dir_pitch
= math
.asin(fwd
[2]) * 57.295779513
598 dir_yaw
= math
.atan2(fwd
[1],fwd
[0]) * 57.295779513
600 if obj
.data
.type == 'SPOT':
601 kvs
['_light'] = [ int(x
) for x
in light_base
]
602 kvs
['_cone'] = obj
.data
.spot_size
*(57.295779513/2.0)
603 kvs
['_inner_cone'] = (1.0-obj
.data
.spot_blend
)*kvs
['_cone']
605 kvs
['pitch'] = dir_pitch
606 kvs
['angles'] = [ 0, dir_yaw
, 0 ]
607 kvs
['_quadratic_attn'] = 0.0 # Source spotlights + quadratic falloff look
610 # Blender's default has a much more 'nice'
612 kvs
['_linear_attn'] = 1.0
614 elif obj
.data
.type == 'POINT':
615 kvs
['_light'] = [ int(x
) for x
in light_base
]
616 kvs
['_quadratic_attn'] = 1.0
617 kvs
['_linear_attn'] = 1.0
619 elif obj
.data
.type == 'SUN':
620 light_base
[3] *= 300.0 * 5
621 kvs
['_light'] = [ int(x
) for x
in light_base
]
623 ambient
= bpy
.context
.scene
.world
.color
624 kvs
['_ambient'] = [int(pow(ambient
[i
],1.0/2.2)*255.0) for i
in range(3)] +\
626 kvs
['_ambientHDR'] = [-1,-1,-1,1]
627 kvs
['_AmbientScaleHDR'] = 1
628 kvs
['pitch'] = dir_pitch
629 kvs
['angles'] = [ dir_pitch
, dir_yaw
, 0.0 ]
630 kvs
['SunSpreadAngle'] = 0
634 def ent_prop(context
):
635 if isinstance( context
['object'], bpy
.types
.Collection
):
637 target
= context
['object']
638 pos
= mathutils
.Vector(context
['origin'])
639 pos
+= mathutils
.Vector(context
['transform']['offset'])
641 kvs
['origin'] = [pos
[1],-pos
[0],pos
[2]]
642 kvs
['angles'] = [0,180,0]
643 kvs
['uniformscale'] = 1.0
645 kvs
= cxr_baseclass([ent_origin
],{})
646 target
= context
['object'].instance_collection
648 obj
= context
['object']
649 euler
= [ a
*57.295779513 for a
in obj
.rotation_euler
]
652 angle
[1] = euler
[2] + 180.0 # Dunno...
655 kvs
['angles'] = angle
656 kvs
['uniformscale'] = obj
.scale
[0]
658 if target
.cxr_data
.shadow_caster
:
659 kvs
['enablelightbounce'] = 1
660 kvs
['disableshadows'] = 0
662 kvs
['enablelightbounce'] = 0
663 kvs
['disableshadows'] = 1
665 kvs
['fademindist'] = -1
667 kvs
['model'] = F
"{asset_path('models',target)}.mdl".lower()
668 kvs
['renderamt'] = 255
669 kvs
['rendercolor'] = [255, 255, 255]
675 def ent_sky_camera(context
):
676 settings
= bpy
.context
.scene
.cxr_data
677 scale
= settings
.scale_factor
/ settings
.skybox_scale_factor
680 "origin": [_
for _
in context
['transform']['offset']],
681 "angles": [ 0, 0, 0 ],
682 "fogcolor": [255, 255, 255],
683 "fogcolor2": [255, 255, 255],
688 "HDRColorScale": 1.0,
693 def ent_cubemap(context
):
694 obj
= context
['object']
695 return cxr_baseclass([ent_origin
], {"cubemapsize": obj
.data
.cxr_data
.size
})
697 ent_origin
= { "origin": cxr_get_origin
}
698 ent_angles
= { "angles": cxr_get_angles
}
699 ent_transform
= cxr_baseclass( [ent_origin
], ent_angles
)
701 #include the user config
702 exec(open(F
'{os.path.dirname(__file__)}/config.py').read())
704 # Blender state callbacks
705 # ------------------------------------------------------------------------------
708 def cxr_on_load(dummy
):
709 global cxr_view_lines
, cxr_view_mesh
711 cxr_view_lines
= None
715 def cxr_dgraph_update(scene
,dgraph
):
717 print( F
"Hallo {time.time()}" )
719 # Convexer compilation functions
720 # ------------------------------------------------------------------------------
722 # Asset path management
724 def asset_uid(asset
):
725 if isinstance(asset
,str):
728 # Create a unique ID string
730 v
= asset
.cxr_data
.asset_id
739 dig
.append( int( v
% len(base
) ) )
745 if bpy
.context
.scene
.cxr_data
.include_names
:
746 name
+= asset
.name
.replace('.','_')
750 # -> <project_name>/<asset_name>
751 def asset_name(asset
):
752 return F
"{bpy.context.scene.cxr_data.project_name}/{asset_uid(asset)}"
754 # -> <subdir>/<project_name>/<asset_name>
755 def asset_path(subdir
, asset
):
756 return F
"{subdir}/{asset_name(asset_uid(asset))}"
758 # -> <csgo>/<subdir>/<project_name>/<asset_name>
759 def asset_full_path(sdir
,asset
):
760 return F
"{bpy.context.scene.cxr_data.subdir}/"+\
761 F
"{asset_path(sdir,asset_uid(asset))}"
763 # Entity functions / infos
764 # ------------------------
766 def cxr_intrinsic_classname(obj
):
767 if obj
.type == 'LIGHT':
769 'SPOT': "light_spot",
771 'SUN': "light_environment" }[ obj
.data
.type ]
773 elif obj
.type == 'LIGHT_PROBE':
775 elif obj
.type == 'EMPTY':
781 def cxr_custom_class(obj
):
782 if obj
.type == 'MESH': custom_class
= obj
.cxr_data
.brushclass
783 else: custom_class
= obj
.cxr_data
.classname
787 def cxr_classname(obj
):
788 intr
= cxr_intrinsic_classname(obj
)
789 if intr
!= None: return intr
791 custom_class
= cxr_custom_class(obj
)
792 if custom_class
!= 'NONE':
798 # intinsic: (k, False, value)
799 # property: (k, True, value or default)
803 def cxr_entity_keyvalues(context
):
804 classname
= context
['classname']
805 obj
= context
['object']
806 if classname
not in cxr_entities
: return None
810 entdef
= cxr_entities
[classname
]
811 kvs
= entdef
['keyvalues']
813 if callable(kvs
): kvs
= kvs(context
)
820 if isinstance(kv
,dict):
822 value
= obj
[ F
"cxrkv_{k}" ]
827 if isinstance(value
,mathutils
.Vector
):
828 value
= [_
for _
in value
]
830 result
+= [(k
, isprop
, value
)]
834 # Extract material information from shader graph data
836 def material_info(mat
):
838 info
['res'] = (512,512)
839 info
['name'] = 'tools/toolsnodraw'
841 if mat
== None or mat
.use_nodes
== False:
845 if mat
.cxr_data
.shader
== 'Builtin':
846 info
['name'] = mat
.name
850 info
['name'] = asset_name(mat
)
852 # Using the cxr_graph_mapping as a reference, go through the shader
853 # graph and gather all $props from it.
855 def _graph_read( node_def
, node
=None, depth
=0 ):
859 def _variant_apply( val
):
862 if isinstance( val
, str ):
865 for shader_variant
in val
:
866 if shader_variant
[0] == mat
.cxr_data
.shader
:
867 return shader_variant
[1]
871 _graph_read
.extracted
= []
873 for node_idname
in node_def
:
874 for n
in mat
.node_tree
.nodes
:
875 if n
.bl_idname
== node_idname
:
876 node_def
= node_def
[node_idname
]
880 for link
in node_def
:
881 if isinstance( node_def
[link
], dict ):
882 inputt
= node
.inputs
[link
]
883 inputt_def
= node_def
[link
]
887 # look for definitions for the connected node type
888 con
= inputt
.links
[0].from_node
890 for node_idname
in inputt_def
:
891 if con
.bl_idname
== node_idname
:
892 con_def
= inputt_def
[ node_idname
]
893 _graph_read( con_def
, con
, depth
+1 )
895 # No definition found! :(
896 # TODO: Make a warning for this?
899 if "default" in inputt_def
:
900 prop
= _variant_apply( inputt_def
['default'] )
901 info
[prop
] = inputt
.default_value
903 prop
= _variant_apply( node_def
[link
] )
904 info
[prop
] = getattr(node
,link
)
906 _graph_read(cxr_graph_mapping
)
908 if "$basetexture" in info
:
909 export_res
= info
['$basetexture'].cxr_data
.export_res
910 info
['res'] = (export_res
[0], export_res
[1])
914 def vec3_min( a
, b
):
915 return mathutils
.Vector((min(a
[0],b
[0]),min(a
[1],b
[1]),min(a
[2],b
[2])))
916 def vec3_max( a
, b
):
917 return mathutils
.Vector((max(a
[0],b
[0]),max(a
[1],b
[1]),max(a
[2],b
[2])))
919 def cxr_collection_center(collection
, transform
):
921 bounds_min
= mathutils
.Vector((BIG
,BIG
,BIG
))
922 bounds_max
= mathutils
.Vector((-BIG
,-BIG
,-BIG
))
924 for obj
in collection
.objects
:
925 if obj
.type == 'MESH':
926 corners
= [ mathutils
.Vector(c
) for c
in obj
.bound_box
]
928 for corner
in [ obj
.matrix_world
@c for c
in corners
]:
929 bounds_min
= vec3_min( bounds_min
, corner
)
930 bounds_max
= vec3_max( bounds_max
, corner
)
932 center
= (bounds_min
+ bounds_max
) / 2.0
934 origin
= mathutils
.Vector((-center
[1],center
[0],center
[2]))
935 origin
*= transform
['scale']
939 # Prepares Scene into dictionary format
941 def cxr_scene_collect():
942 context
= bpy
.context
944 # Make sure all of our asset types have a unique ID
945 def _uid_prepare(objtype
):
951 if vs
.asset_id
in used_ids
:
954 id_max
= max(id_max
,vs
.asset_id
)
955 used_ids
+=[vs
.asset_id
]
956 for vs
in to_generate
:
959 _uid_prepare(bpy
.data
.materials
)
960 _uid_prepare(bpy
.data
.images
)
961 _uid_prepare(bpy
.data
.collections
)
964 "entities": [], # Everything with a classname
965 "geo": [], # All meshes without a classname
966 "heros": [] # Collections prefixed with mdl_
969 def _collect(collection
,transform
):
972 if collection
.name
.startswith('.'): return
973 if collection
.hide_render
: return
975 if collection
.name
.startswith('mdl_'):
976 sceneinfo
['entities'] += [{
977 "object": collection
,
978 "classname": "prop_static",
979 "transform": transform
,
980 "origin": cxr_collection_center( collection
, transform
)
983 sceneinfo
['heros'] += [{
984 "collection": collection
,
985 "transform": transform
,
986 "origin": cxr_collection_center( collection
, transform
)
990 for obj
in collection
.objects
:
991 if obj
.hide_get(): continue
993 classname
= cxr_classname( obj
)
995 if classname
!= None:
996 sceneinfo
['entities'] += [{
998 "classname": classname
,
999 "transform": transform
1001 elif obj
.type == 'MESH':
1002 sceneinfo
['geo'] += [{
1004 "transform": transform
1007 for c
in collection
.children
:
1008 _collect( c
, transform
)
1011 "scale": context
.scene
.cxr_data
.scale_factor
,
1016 "scale": context
.scene
.cxr_data
.skybox_scale_factor
,
1017 "offset": (0,0,context
.scene
.cxr_data
.skybox_offset
)
1020 if 'main' in bpy
.data
.collections
:
1021 _collect( bpy
.data
.collections
['main'], transform_main
)
1023 if 'skybox' in bpy
.data
.collections
:
1024 _collect( bpy
.data
.collections
['skybox'], transform_sky
)
1026 sceneinfo
['entities'] += [{
1028 "transform": transform_sky
,
1029 "classname": "sky_camera"
1034 # Write VMF out to file (JOB HANDLER)
1036 def cxr_export_vmf(sceneinfo
, output_vmf
):
1039 with
vdf_structure(output_vmf
) as m
:
1040 print( F
"Write: {output_vmf}" )
1042 vmfinfo
= cxr_vmf_context()
1043 vmfinfo
.mapversion
= 4
1045 #TODO: These need to be in options...
1046 vmfinfo
.skyname
= bpy
.context
.scene
.cxr_data
.skyname
.encode('utf-8')
1047 vmfinfo
.detailvbsp
= b
"detail.vbsp"
1048 vmfinfo
.detailmaterial
= b
"detail/detailsprites"
1049 vmfinfo
.lightmap_scale
= 12
1051 vmfinfo
.brush_count
= 0
1052 vmfinfo
.entity_count
= 0
1053 vmfinfo
.face_count
= 0
1055 visgroups
= (cxr_visgroup
*len(cxr_visgroups
))()
1056 for i
, vg
in enumerate(cxr_visgroups
):
1057 visgroups
[i
].name
= vg
.encode('utf-8')
1058 vmfinfo
.visgroups
= cast(visgroups
, POINTER(cxr_visgroup
))
1059 vmfinfo
.visgroup_count
= len(cxr_visgroups
)
1061 libcxr_begin_vmf
.call( pointer(vmfinfo
), m
.fp
)
1063 def _buildsolid( cmd
):
1066 print( F
"{vmfinfo.brush_count} :: {cmd['object'].name}" )
1068 baked
= mesh_cxr_format( cmd
['object'] )
1069 world
= libcxr_decompose
.call( baked
, None )
1074 vmfinfo
.scale
= cmd
['transform']['scale']
1076 offset
= cmd
['transform']['offset']
1077 vmfinfo
.offset
[0] = offset
[0]
1078 vmfinfo
.offset
[1] = offset
[1]
1079 vmfinfo
.offset
[2] = offset
[2]
1081 if cmd
['object'].cxr_data
.lightmap_override
> 0:
1082 vmfinfo
.lightmap_scale
= cmd
['object'].cxr_data
.lightmap_override
1084 vmfinfo
.lightmap_scale
= bpy
.context
.scene
.cxr_data
.lightmap_scale
1086 libcxr_push_world_vmf
.call( world
, pointer(vmfinfo
), m
.fp
)
1087 libcxr_free_world
.call( world
)
1092 for brush
in sceneinfo
['geo']:
1093 vmfinfo
.visgroupid
= int(brush
['object'].cxr_data
.visgroup
)
1094 if not _buildsolid( brush
):
1098 vmfinfo
.visgroupid
= 0
1100 libcxr_vmf_begin_entities
.call(pointer(vmfinfo
), m
.fp
)
1103 for ent
in sceneinfo
['entities']:
1105 ctx
= ent
['transform']
1106 cls
= ent
['classname']
1109 m
.kv( 'classname', cls
)
1111 kvs
= cxr_entity_keyvalues( ent
)
1114 if isinstance(kv
[2], list):
1115 m
.kv( kv
[0], ' '.join([str(_
) for _
in kv
[2]]) )
1116 else: m
.kv( kv
[0], str(kv
[2]) )
1120 elif not isinstance( obj
, bpy
.types
.Collection
):
1121 if obj
.type == 'MESH':
1122 vmfinfo
.visgroupid
= int(obj
.cxr_data
.visgroup
)
1123 if not _buildsolid( ent
):
1130 m
.kv( 'visgroupid', str(obj
.cxr_data
.visgroup
) )
1131 m
.kv( 'visgroupshown', '1' )
1132 m
.kv( 'visgroupautoshown', '1' )
1136 vmfinfo
.visgroupid
= 0
1141 # COmpile image using NBVTF and hash it (JOB HANDLER)
1143 def compile_image(img
):
1147 name
= asset_name(img
)
1148 src_path
= bpy
.path
.abspath(img
.filepath
)
1150 dims
= img
.cxr_data
.export_res
1152 'RGBA': NBVTF_IMAGE_FORMAT_ABGR8888
,
1153 'DXT1': NBVTF_IMAGE_FORMAT_DXT1
,
1154 'DXT5': NBVTF_IMAGE_FORMAT_DXT5
,
1155 'RGB': NBVTF_IMAGE_FORMAT_BGR888
1156 }[ img
.cxr_data
.fmt
]
1158 mipmap
= img
.cxr_data
.mipmap
1159 lod
= img
.cxr_data
.lod
1160 clamp
= img
.cxr_data
.clamp
1161 flags
= img
.cxr_data
.flags
1163 q
=bpy
.context
.scene
.cxr_data
.image_quality
1165 userflag_hash
= F
"{mipmap}.{lod}.{clamp}.{flags}"
1166 file_hash
= F
"{name}.{os.path.getmtime(src_path)}"
1167 comphash
= F
"{file_hash}.{dims[0]}.{dims[1]}.{fmt}.{userflag_hash}.{q}"
1169 if img
.cxr_data
.last_hash
!= comphash
:
1170 print( F
"Texture update: {img.filepath}" )
1172 src
= src_path
.encode('utf-8')
1173 dst
= (asset_full_path('materials',img
)+'.vtf').encode('utf-8')
1177 # texture setting flags
1178 if not lod
: flags_full |
= NBVTF_TEXTUREFLAGS_NOLOD
1180 flags_full |
= NBVTF_TEXTUREFLAGS_CLAMPS
1181 flags_full |
= NBVTF_TEXTUREFLAGS_CLAMPT
1183 if libnbvtf_convert
.call(src
,dims
[0],dims
[1],mipmap
,fmt
,q
,flags_full
,dst
):
1184 img
.cxr_data
.last_hash
= comphash
1189 # Compile a material to VMT format. This is quick to run, doesnt need to be a
1192 def compile_material(mat
):
1193 info
= material_info(mat
)
1194 properties
= mat
.cxr_data
1196 print( F
"Compile {asset_full_path('materials',mat)}.vmt" )
1197 if properties
.shader
== 'Builtin':
1202 # Walk the property tree
1203 def _mlayer( layer
):
1204 nonlocal properties
, props
1207 if isinstance(layer
[decl
],dict): # $property definition
1209 ptype
= pdef
['type']
1215 if 'shaders' in pdef
and properties
.shader
not in pdef
['shaders']:
1218 # Group expansion (does it have subdefinitions?)
1220 if isinstance(pdef
[ch
],dict):
1229 if ptype
== 'intrinsic':
1233 prop
= getattr(properties
,decl
)
1234 default
= pdef
['default']
1236 if not isinstance(prop
,str) and \
1237 not isinstance(prop
,bpy
.types
.Image
) and \
1238 hasattr(prop
,'__getitem__'):
1239 prop
= tuple([p
for p
in prop
])
1243 props
+= [(decl
,pdef
,prop
)]
1248 if expandview
: _mlayer(pdef
)
1250 _mlayer( cxr_shader_params
)
1253 with
vdf_structure( F
"{asset_full_path('materials',mat)}.vmt" ) as vmt
:
1254 vmt
.node( properties
.shader
)
1255 vmt
.put( "// Convexer export\n" )
1264 if 'exponent' in pdef
: return str(pow( v
, pdef
['exponent'] ))
1267 if isinstance(prop
,bpy
.types
.Image
):
1268 vmt
.kv( decl
, asset_name(prop
))
1269 elif isinstance(prop
,bool):
1270 vmt
.kv( decl
, '1' if prop
else '0' )
1271 elif isinstance(prop
,str):
1272 vmt
.kv( decl
, prop
)
1273 elif isinstance(prop
,float) or isinstance(prop
,int):
1274 vmt
.kv( decl
, _numeric(prop
) )
1275 elif isinstance(prop
,tuple):
1276 vmt
.kv( decl
, F
"[{' '.join([_numeric(_) for _ in prop])}]" )
1278 vmt
.put( F
"// (cxr) unkown shader value type'{type(prop)}'" )
1283 def cxr_modelsrc_vphys( mdl
):
1284 for obj
in mdl
.objects
:
1285 if obj
.name
== F
"{mdl.name}_phy":
1289 def cxr_export_modelsrc( mdl
, origin
, asset_dir
, project_name
, transform
):
1290 dgraph
= bpy
.context
.evaluated_depsgraph_get()
1292 # Compute hash value
1293 chash
= asset_uid(mdl
)+str(origin
)+str(transform
)
1295 #for obj in mdl.objects:
1296 # if obj.type != 'MESH':
1299 # ev = obj.evaluated_get(dgraph).data
1300 # srcverts=[(v.co[0],v.co[1],v.co[2]) for v in ev.vertices]
1301 # srcloops=[(l.normal[0],l.normal[1],l.normal[2]) for l in ev.loops]
1303 # chash=hashlib.sha224((str(srcverts)+chash).encode()).hexdigest()
1304 # chash=hashlib.sha224((str(srcloops)+chash).encode()).hexdigest()
1306 # if ev.uv_layers.active != None:
1307 # uv_layer = ev.uv_layers.active.data
1308 # srcuv=[(uv.uv[0],uv.uv[1]) for uv in uv_layer]
1312 # chash=hashlib.sha224((str(srcuv)+chash).encode()).hexdigest()
1313 # srcmats=[ ms.material.name for ms in obj.material_slots ]
1314 # chash=hashlib.sha224((str(srcmats)+chash).encode()).hexdigest()
1315 # transforms=[ obj.location, obj.rotation_euler, obj.scale ]
1316 # srctr=[(v[0],v[1],v[2]) for v in transforms]
1317 # chash=hashlib.sha224((str(srctr)+chash).encode()).hexdigest()
1319 #if chash != mdl.cxr_data.last_hash:
1320 # mdl.cxr_data.last_hash = chash
1321 # print( F"Compile: {mdl.name}" )
1325 bpy
.ops
.object.select_all(action
='DESELECT')
1328 def _get_layer(col
,name
):
1329 for c
in col
.children
:
1332 sub
= _get_layer(c
,name
)
1336 layer
= _get_layer(bpy
.context
.view_layer
.layer_collection
,mdl
.name
)
1338 prev_state
= layer
.hide_viewport
1339 layer
.hide_viewport
=False
1341 # Collect materials to be compiled, and temp rename for export
1345 for obj
in mdl
.objects
:
1346 if obj
.name
== F
"{mdl.name}_phy":
1350 obj
.select_set(state
=True)
1351 for ms
in obj
.material_slots
:
1352 if ms
.material
!= None:
1353 if ms
.material
not in mat_dict
:
1354 mat_dict
[ms
.material
] = ms
.material
.name
1355 ms
.material
.name
= asset_uid(ms
.material
)
1356 ms
.material
.use_nodes
= False
1359 bpy
.ops
.export_scene
.fbx( filepath
=F
'{asset_dir}/{uid}_ref.fbx',\
1360 check_existing
=False,
1362 apply_unit_scale
=False,
1363 bake_space_transform
=False
1366 bpy
.ops
.object.select_all(action
='DESELECT')
1369 vphys
.select_set(state
=True)
1370 bpy
.ops
.export_scene
.fbx( filepath
=F
'{asset_dir}/{uid}_phy.fbx',\
1371 check_existing
=False,
1373 apply_unit_scale
=False,
1374 bake_space_transform
=False
1376 bpy
.ops
.object.select_all(action
='DESELECT')
1378 # Fix material names back to original
1379 for mat
in mat_dict
:
1380 mat
.name
= mat_dict
[mat
]
1381 mat
.use_nodes
= True
1383 layer
.hide_viewport
=prev_state
1386 with
open(F
'{asset_dir}/{uid}.qc','w') as o
:
1387 o
.write(F
'$modelname "{project_name}/{uid}"\n')
1388 #o.write(F'$scale .32\n')
1389 o
.write(F
'$scale {transform["scale"]/100.0}\n')
1390 o
.write(F
'$body _ "{uid}_ref.fbx"\n')
1391 o
.write(F
'$staticprop\n')
1392 o
.write(F
'$origin {origin[0]:.6f} {origin[1]:.6f} {origin[2]:.6f}\n')
1394 if mdl
.cxr_data
.preserve_order
:
1395 o
.write(F
"$preservetriangleorder\n")
1397 if mdl
.cxr_data
.texture_shadows
:
1398 o
.write(F
"$casttextureshadows\n")
1400 o
.write(F
"$surfaceprop {mdl.cxr_data.surfaceprop}\n")
1403 o
.write(F
'$collisionmodel "{uid}_phy.fbx"\n')
1405 o
.write(" $concave\n")
1408 o
.write(F
'$cdmaterials {project_name}\n')
1409 o
.write(F
'$sequence idle {uid}_ref.fbx\n')
1413 # Copy bsp file (and also lightpatch it)
1415 def cxr_patchmap( src
, dst
):
1416 libcxr_lightpatch_bsp
.call( src
.encode('utf-8') )
1417 shutil
.copyfile( src
, dst
)
1420 # Convexer operators
1421 # ------------------------------------------------------------------------------
1423 # Force reload of shared libraries
1425 class CXR_RELOAD(bpy
.types
.Operator
):
1426 bl_idname
="convexer.reload"
1428 def execute(_
,context
):
1432 # Used for exporting data to use with ASAN builds
1434 class CXR_DEV_OPERATOR(bpy
.types
.Operator
):
1435 bl_idname
="convexer.dev_test"
1436 bl_label
="Export development data"
1438 def execute(_
,context
):
1439 # Prepare input data
1440 mesh_src
= mesh_cxr_format(context
.active_object
)
1441 libcxr_write_test_data
.call( pointer(mesh_src
) )
1444 # UI: Preview how the brushes will looks in 3D view
1446 class CXR_PREVIEW_OPERATOR(bpy
.types
.Operator
):
1447 bl_idname
="convexer.preview"
1448 bl_label
="Preview Brushes"
1453 def execute(_
,context
):
1456 def modal(_
,context
,event
):
1457 global cxr_view_mesh
1458 static
= _
.__class
__
1460 if event
.type == 'ESC':
1463 cxr_view_mesh
= None
1464 static
.RUNNING
= False
1469 return {'PASS_THROUGH'}
1471 def invoke(_
,context
,event
):
1472 global cxr_view_shader
, cxr_view_mesh
1473 static
= _
.__class
__
1474 static
.LASTERR
= None
1478 mesh_src
= mesh_cxr_format(context
.active_object
)
1481 world
= libcxr_decompose
.call( mesh_src
, pointer(err
) )
1484 cxr_view_mesh
= None
1488 static
.LASTERR
= ["There is no error", \
1494 "Non-Convex Polygon",\
1500 return {'CANCELLED'}
1502 context
.window_manager
.modal_handler_add(_
)
1503 return {'RUNNING_MODAL'}
1505 # Generate preview using cxr
1507 ptrpreview
= libcxr_world_preview
.call( world
)
1508 preview
= ptrpreview
[0]
1510 vertices
= preview
.vertices
[:preview
.vertex_count
]
1511 vertices
= [(_
[0],_
[1],_
[2]) for _
in vertices
]
1513 colours
= preview
.colours
[:preview
.vertex_count
]
1514 colours
= [(_
[0],_
[1],_
[2],_
[3]) for _
in colours
]
1516 indices
= preview
.indices
[:preview
.indices_count
]
1517 indices
= [ (indices
[i
*3+0],indices
[i
*3+1],indices
[i
*3+2]) \
1518 for i
in range(int(preview
.indices_count
/3)) ]
1520 cxr_view_mesh
= batch_for_shader(
1521 cxr_view_shader
, 'TRIS',
1522 { "pos": vertices
, "color": colours
},
1526 libcxr_free_tri_mesh
.call( ptrpreview
)
1527 libcxr_free_world
.call( world
)
1531 # Allow user to spam the operator
1533 return {'CANCELLED'}
1535 if not static
.RUNNING
:
1536 static
.RUNNING
= True
1537 context
.window_manager
.modal_handler_add(_
)
1538 return {'RUNNING_MODAL'}
1540 # Search for VMF compiler executables in subdirectory
1542 class CXR_DETECT_COMPILERS(bpy
.types
.Operator
):
1543 bl_idname
="convexer.detect_compilers"
1544 bl_label
="Find compilers"
1546 def execute(self
,context
):
1547 scene
= context
.scene
1548 settings
= scene
.cxr_data
1549 subdir
= settings
.subdir
1551 for exename
in ['studiomdl','vbsp','vvis','vrad']:
1552 searchpath
= os
.path
.normpath(F
'{subdir}/../bin/{exename}.exe')
1553 if os
.path
.exists(searchpath
):
1554 settings
[F
'exe_{exename}'] = searchpath
1558 def cxr_compiler_path( compiler
):
1559 settings
= bpy
.context
.scene
.cxr_data
1560 subdir
= settings
.subdir
1561 path
= os
.path
.normpath(F
'{subdir}/../bin/{compiler}.exe')
1563 if os
.path
.exists( path
): return path
1566 def cxr_winepath( path
):
1567 return 'z:'+path
.replace('/','\\')
1569 # Main compile function
1571 class CXR_COMPILER_CHAIN(bpy
.types
.Operator
):
1572 bl_idname
="convexer.chain"
1573 bl_label
="Compile Chain"
1588 def cancel(_
,context
):
1589 global cxr_jobs_batch
1590 static
= _
.__class
__
1591 wm
= context
.window_manager
1593 if static
.SUBPROC
!= None:
1594 static
.SUBPROC
.terminate()
1595 static
.SUBPROC
= None
1597 if static
.TIMER
!= None:
1598 wm
.event_timer_remove( static
.TIMER
)
1603 cxr_jobs_batch
= None
1607 def modal(_
,context
,ev
):
1608 static
= _
.__class
__
1610 if ev
.type == 'TIMER':
1611 global cxr_jobs_batch
1613 if static
.WAIT_REDRAW
:
1615 return {'PASS_THROUGH'}
1616 static
.WAIT_REDRAW
= True
1618 if static
.USER_EXIT
:
1619 print( "Chain USER_EXIT" )
1620 return _
.cancel(context
)
1622 if static
.SUBPROC
!= None:
1623 # Deal with async modes
1624 status
= static
.SUBPROC
.poll()
1627 # Cannot redirect STDOUT through here without causing
1628 # undefined behaviour due to the Blender Python specification.
1630 # Have to write it out to a file and read it back in.
1632 with
open("/tmp/convexer_compile_log.txt","r") as log
:
1633 static
.LOG
= log
.readlines()
1634 return {'PASS_THROUGH'}
1636 #for l in static.SUBPROC.stdout:
1637 # print( F'-> {l.decode("utf-8")}',end='' )
1638 static
.SUBPROC
= None
1641 print(F
'Compiler () error: {status}')
1642 return _
.cancel(context
)
1644 static
.JOBSYS
['jobs'][static
.JOBID
] = None
1645 cxr_jobs_update_graph( static
.JOBINFO
)
1647 return {'PASS_THROUGH'}
1649 # Compile syncronous thing
1650 for sys
in static
.JOBINFO
:
1651 for i
,target
in enumerate(sys
['jobs']):
1654 if callable(sys
['exec']):
1655 print( F
"Run (sync): {static.JOBID} @{time.time()}" )
1657 if not sys
['exec'](*target
):
1658 print( "Job failed" )
1659 return _
.cancel(context
)
1661 sys
['jobs'][i
] = None
1664 # Run external executable (wine)
1665 static
.SUBPROC
= subprocess
.Popen( target
,
1666 stdout
=static
.FILE
,\
1667 stderr
=subprocess
.PIPE
,\
1672 cxr_jobs_update_graph( static
.JOBINFO
)
1674 return {'PASS_THROUGH'}
1677 print( "All jobs completed!" )
1678 cxr_jobs_batch
= None
1681 return _
.cancel(context
)
1683 return {'PASS_THROUGH'}
1685 def invoke(_
,context
,event
):
1686 static
= _
.__class
__
1687 wm
= context
.window_manager
1689 if static
.TIMER
!= None:
1690 print("Chain exiting...")
1691 static
.USER_EXIT
=True
1692 return {'RUNNING_MODAL'}
1694 print("Launching compiler toolchain")
1696 # Run static compilation units now (collect, vmt..)
1697 filepath
= bpy
.data
.filepath
1698 directory
= os
.path
.dirname(filepath
)
1699 settings
= bpy
.context
.scene
.cxr_data
1701 asset_dir
= F
"{directory}/modelsrc"
1702 material_dir
= F
"{settings.subdir}/materials/{settings.project_name}"
1703 model_dir
= F
"{settings.subdir}/models/{settings.project_name}"
1704 output_vmf
= F
"{directory}/{settings.project_name}.vmf"
1706 bsp_local
= F
"{directory}/{settings.project_name}.bsp"
1707 bsp_remote
= F
"{settings.subdir}/maps/{settings.project_name}.bsp"
1708 bsp_packed
= F
"{settings.subdir}/maps/{settings.project_name}_pack.bsp"
1709 packlist
= F
"{directory}/{settings.project_name}_assets.txt"
1711 os
.makedirs( asset_dir
, exist_ok
=True )
1712 os
.makedirs( material_dir
, exist_ok
=True )
1713 os
.makedirs( model_dir
, exist_ok
=True )
1715 static
.FILE
= open(F
"/tmp/convexer_compile_log.txt","w")
1718 sceneinfo
= cxr_scene_collect()
1724 for brush
in sceneinfo
['geo']:
1725 for ms
in brush
['object'].material_slots
:
1726 a_materials
.add( ms
.material
)
1727 if ms
.material
.cxr_data
.shader
== 'VertexLitGeneric':
1728 errmat
= ms
.material
.name
1729 errnam
= brush
['object'].name
1730 print( F
"Vertex shader {errmat} used on {errnam}")
1731 return {'CANCELLED'}
1735 for ent
in sceneinfo
['entities']:
1736 if ent
['object'] == None: continue
1738 if ent
['classname'] == 'prop_static':
1740 if isinstance(obj
,bpy
.types
.Collection
):
1742 a_models
.add( target
)
1743 model_jobs
+= [(target
, ent
['origin'], asset_dir
, \
1744 settings
.project_name
, ent
['transform'])]
1746 target
= obj
.instance_collection
1747 if target
in a_models
:
1749 a_models
.add( target
)
1751 # TODO: Should take into account collection instancing offset
1752 model_jobs
+= [(target
, [0,0,0], asset_dir
, \
1753 settings
.project_name
, ent
['transform'])]
1755 elif ent
['object'].type == 'MESH':
1756 for ms
in ent
['object'].material_slots
:
1757 a_materials
.add( ms
.material
)
1759 for mdl
in a_models
:
1760 uid
= asset_uid(mdl
)
1761 qc_jobs
+= [F
'{uid}.qc']
1763 for obj
in mdl
.objects
:
1764 for ms
in obj
.material_slots
:
1765 a_materials
.add( ms
.material
)
1766 if ms
.material
.cxr_data
.shader
== 'LightMappedGeneric' or \
1767 ms
.material
.cxr_data
.shader
== 'WorldVertexTransition':
1769 errmat
= ms
.material
.name
1771 print( F
"Lightmapped shader {errmat} used on {errnam}")
1772 return {'CANCELLED'}
1775 for mat
in a_materials
:
1776 for pair
in compile_material(mat
):
1781 if isinstance(prop
,bpy
.types
.Image
):
1783 if 'flags' in pdef
: flags
= pdef
['flags']
1784 if prop
not in image_jobs
:
1785 image_jobs
+= [(prop
,)]
1786 prop
.cxr_data
.flags
= flags
1789 with
open( packlist
, "w" ) as fp
:
1791 for mat
in a_materials
:
1792 if mat
.cxr_data
.shader
== 'Builtin': continue
1793 fp
.write(F
"{asset_path('materials',mat)}.vmt\n")
1794 fp
.write(F
"{cxr_winepath(asset_full_path('materials',mat))}.vmt\n")
1796 for img_job
in image_jobs
:
1798 fp
.write(F
"{asset_path('materials',img)}.vtf\n")
1799 fp
.write(F
"{cxr_winepath(asset_full_path('materials',img))}.vtf\n")
1801 for mdl
in a_models
:
1802 local
= asset_path('models',mdl
)
1803 winep
= cxr_winepath(asset_full_path('models',mdl
))
1805 fp
.write(F
"{local}.vvd\n")
1806 fp
.write(F
"{winep}.vvd\n")
1807 fp
.write(F
"{local}.dx90.vtx\n")
1808 fp
.write(F
"{winep}.dx90.vtx\n")
1809 fp
.write(F
"{local}.mdl\n")
1810 fp
.write(F
"{winep}.mdl\n")
1811 fp
.write(F
"{local}.vvd\n")
1812 fp
.write(F
"{winep}.vvd\n")
1814 if cxr_modelsrc_vphys(mdl
):
1815 fp
.write(F
"{local}.phy\n")
1816 fp
.write(F
"{winep}.phy\n")
1822 if settings
.comp_vmf
:
1823 static
.JOBINFO
+= [{
1824 "title": "Convexer",
1826 "colour": (1.0,0.3,0.1,1.0),
1827 "exec": cxr_export_vmf
,
1828 "jobs": [(sceneinfo
,output_vmf
)]
1831 if settings
.comp_textures
:
1832 if len(image_jobs
) > 0:
1833 static
.JOBINFO
+= [{
1834 "title": "Textures",
1836 "colour": (0.1,1.0,0.3,1.0),
1837 "exec": compile_image
,
1841 game
= cxr_winepath( settings
.subdir
)
1843 '-game', game
, settings
.project_name
1847 if settings
.comp_models
:
1848 if len(model_jobs
) > 0:
1849 static
.JOBINFO
+= [{
1852 "colour": (0.5,0.5,1.0,1.0),
1853 "exec": cxr_export_modelsrc
,
1857 if len(qc_jobs
) > 0:
1858 static
.JOBINFO
+= [{
1859 "title": "StudioMDL",
1861 "colour": (0.8,0.1,0.1,1.0),
1862 "exec": "studiomdl",
1863 "jobs": [[settings
[F
'exe_studiomdl']] + [\
1864 '-nop4', '-game', game
, qc
] for qc
in qc_jobs
],
1869 if settings
.comp_compile
:
1870 if not settings
.opt_vbsp
.startswith( 'disable' ):
1871 vbsp_opt
= settings
.opt_vbsp
.split()
1872 static
.JOBINFO
+= [{
1875 "colour": (0.1,0.2,1.0,1.0),
1877 "jobs": [[settings
[F
'exe_vbsp']] + vbsp_opt
+ args
],
1881 if not settings
.opt_vvis
.startswith( 'disable' ):
1882 vvis_opt
= settings
.opt_vvis
.split()
1883 static
.JOBINFO
+= [{
1886 "colour": (0.9,0.5,0.5,1.0),
1888 "jobs": [[settings
[F
'exe_vvis']] + vvis_opt
+ args
],
1892 if not settings
.opt_vrad
.startswith( 'disable' ):
1893 vrad_opt
= settings
.opt_vrad
.split()
1894 static
.JOBINFO
+= [{
1897 "colour": (0.9,0.2,0.3,1.0),
1899 "jobs": [[settings
[F
'exe_vrad']] + vrad_opt
+ args
],
1903 static
.JOBINFO
+= [{
1906 "colour": (0.0,1.0,0.4,1.0),
1907 "exec": cxr_patchmap
,
1908 "jobs": [(bsp_local
,bsp_remote
)]
1911 if settings
.comp_pack
:
1912 static
.JOBINFO
+= [{
1915 "colour": (0.2,0.2,0.2,1.0),
1917 "jobs": [[cxr_compiler_path("bspzip"), '-addlist', \
1918 cxr_winepath(bsp_remote
),
1919 cxr_winepath(packlist
),
1920 cxr_winepath(bsp_packed
) ]],
1924 if len(static
.JOBINFO
) == 0:
1925 return {'CANCELLED'}
1927 static
.USER_EXIT
=False
1928 static
.TIMER
=wm
.event_timer_add(0.1,window
=context
.window
)
1929 wm
.modal_handler_add(_
)
1931 cxr_jobs_update_graph( static
.JOBINFO
)
1933 return {'RUNNING_MODAL'}
1935 class CXR_RESET_HASHES(bpy
.types
.Operator
):
1936 bl_idname
="convexer.hash_reset"
1937 bl_label
="Reset asset hashes"
1939 def execute(_
,context
):
1940 for c
in bpy
.data
.collections
:
1941 c
.cxr_data
.last_hash
= F
"<RESET>{time.time()}"
1942 c
.cxr_data
.asset_id
=0
1944 for t
in bpy
.data
.images
:
1945 t
.cxr_data
.last_hash
= F
"<RESET>{time.time()}"
1946 t
.cxr_data
.asset_id
=0
1950 class CXR_COMPILE_MATERIAL(bpy
.types
.Operator
):
1951 bl_idname
="convexer.matcomp"
1952 bl_label
="Recompile Material"
1954 def execute(_
,context
):
1955 active_obj
= bpy
.context
.active_object
1956 active_mat
= active_obj
.active_material
1958 #TODO: reduce code dupe (L1663)
1959 for pair
in compile_material(active_mat
):
1964 if isinstance(prop
,bpy
.types
.Image
):
1966 if 'flags' in pdef
: flags
= pdef
['flags']
1967 prop
.cxr_data
.flags
= flags
1969 compile_image( prop
)
1971 settings
= bpy
.context
.scene
.cxr_data
1972 with
open(F
'{settings.subdir}/cfg/convexer_mat_update.cfg','w') as o
:
1973 o
.write(F
'mat_reloadmaterial {asset_name(active_mat)}')
1976 with
open(F
'{settings.subdir}/cfg/convexer.cfg','w') as o
:
1977 o
.write('sv_cheats 1\n')
1978 o
.write('mp_warmup_pausetimer 1\n')
1979 o
.write('bot_kick\n')
1980 o
.write('alias cxr_reload "exec convexer_mat_update"\n')
1985 # ------------------------------------------------------------------------------
1987 # Helper buttons for 3d toolbox view
1989 class CXR_VIEW3D( bpy
.types
.Panel
):
1990 bl_idname
= "VIEW3D_PT_convexer"
1991 bl_label
= "Convexer"
1992 bl_space_type
= 'VIEW_3D'
1993 bl_region_type
= 'UI'
1994 bl_category
= "Convexer"
1997 def poll(cls
, context
):
1998 return (context
.object is not None)
2000 def draw(_
, context
):
2004 row
.operator("convexer.preview")
2006 if CXR_PREVIEW_OPERATOR
.LASTERR
!= None:
2008 box
.label(text
=CXR_PREVIEW_OPERATOR
.LASTERR
, icon
='ERROR')
2010 # Main scene properties interface, where all the settings go
2012 class CXR_INTERFACE(bpy
.types
.Panel
):
2014 bl_idname
="SCENE_PT_convexer"
2015 bl_space_type
='PROPERTIES'
2016 bl_region_type
='WINDOW'
2019 def draw(_
,context
):
2020 _
.layout
.operator("convexer.reload")
2021 _
.layout
.operator("convexer.dev_test")
2022 _
.layout
.operator("convexer.preview")
2023 _
.layout
.operator("convexer.hash_reset")
2025 settings
= context
.scene
.cxr_data
2027 _
.layout
.prop(settings
, "debug")
2028 _
.layout
.prop(settings
, "scale_factor")
2029 _
.layout
.prop(settings
, "skybox_scale_factor")
2030 _
.layout
.prop(settings
, "skyname" )
2031 _
.layout
.prop(settings
, "lightmap_scale")
2032 _
.layout
.prop(settings
, "light_scale" )
2033 _
.layout
.prop(settings
, "image_quality" )
2035 box
= _
.layout
.box()
2037 box
.prop(settings
, "project_name")
2038 box
.prop(settings
, "subdir")
2040 box
= _
.layout
.box()
2041 box
.operator("convexer.detect_compilers")
2042 box
.prop(settings
, "exe_studiomdl")
2043 box
.prop(settings
, "exe_vbsp")
2044 box
.prop(settings
, "opt_vbsp")
2046 box
.prop(settings
, "exe_vvis")
2047 box
.prop(settings
, "opt_vvis")
2049 box
.prop(settings
, "exe_vrad")
2050 box
.prop(settings
, "opt_vrad")
2054 row
.prop(settings
,"comp_vmf")
2055 row
.prop(settings
,"comp_textures")
2056 row
.prop(settings
,"comp_models")
2057 row
.prop(settings
,"comp_compile")
2058 row
.prop(settings
,"comp_pack")
2060 text
= "Compile" if CXR_COMPILER_CHAIN
.TIMER
== None else "Cancel"
2063 row
.operator("convexer.chain", text
=text
)
2066 class CXR_MATERIAL_PANEL(bpy
.types
.Panel
):
2067 bl_label
="VMT Properties"
2068 bl_idname
="SCENE_PT_convexer_vmt"
2069 bl_space_type
='PROPERTIES'
2070 bl_region_type
='WINDOW'
2071 bl_context
="material"
2073 def draw(_
,context
):
2074 active_object
= bpy
.context
.active_object
2075 if active_object
== None: return
2077 active_material
= active_object
.active_material
2078 if active_material
== None: return
2080 properties
= active_material
.cxr_data
2081 info
= material_info( active_material
)
2083 _
.layout
.label(text
=F
"{info['name']} @{info['res'][0]}x{info['res'][1]}")
2084 row
= _
.layout
.row()
2085 row
.prop( properties
, "shader" )
2086 row
.operator( "convexer.matcomp" )
2088 #for xk in info: _.layout.label(text=F"{xk}:={info[xk]}")
2090 def _mtex( name
, img
, uiParent
):
2093 box
= uiParent
.box()
2094 box
.label( text
=F
'{name} "{img.filepath}"' )
2096 if ((x
& (x
- 1)) == 0):
2099 closest_diff
= 10000000
2101 dist
= abs((1 << i
)-x
)
2102 if dist
< closest_diff
:
2107 return 1 << (closest
+1)
2109 return 1 << (closest
-1)
2114 row
.prop( img
.cxr_data
, "export_res" )
2115 row
.prop( img
.cxr_data
, "fmt" )
2118 row
.prop( img
.cxr_data
, "mipmap" )
2119 row
.prop( img
.cxr_data
, "lod" )
2120 row
.prop( img
.cxr_data
, "clamp" )
2122 img
.cxr_data
.export_res
[0] = _p2( img
.cxr_data
.export_res
[0] )
2123 img
.cxr_data
.export_res
[1] = _p2( img
.cxr_data
.export_res
[1] )
2125 def _mview( layer
, uiParent
):
2129 if isinstance(layer
[decl
],dict): # $property definition
2131 ptype
= pdef
['type']
2137 if ('shaders' in pdef
) and \
2138 (properties
.shader
not in pdef
['shaders']):
2141 if ptype
== 'intrinsic':
2142 if decl
not in info
:
2147 if isinstance(pdef
[ch
],dict):
2148 if ptype
== 'ui' or ptype
== 'intrinsic':
2150 elif getattr(properties
,decl
) == pdef
['default']:
2153 thisnode
= uiParent
.box()
2157 thisnode
.label( text
=decl
)
2158 elif ptype
== 'intrinsic':
2159 if isinstance(info
[decl
], bpy
.types
.Image
):
2160 _mtex( decl
, info
[decl
], thisnode
)
2162 # hidden intrinsic value.
2163 # Means its a float array or something not an image
2164 thisnode
.label(text
=F
"-- hidden intrinsic '{decl}' --")
2166 thisnode
.prop(properties
,decl
)
2167 if expandview
: _mview(pdef
,thisnode
)
2169 _mview( cxr_shader_params
, _
.layout
)
2171 def cxr_entity_changeclass(_
,context
):
2172 active_object
= context
.active_object
2174 # Create ID properties
2176 classname
= cxr_custom_class(active_object
)
2178 if classname
in cxr_entities
:
2179 entdef
= cxr_entities
[classname
]
2181 kvs
= entdef
['keyvalues']
2182 if callable(kvs
): kvs
= kvs( {'object': active_object
} )
2188 if callable(kv
) or not isinstance(kv
,dict): continue
2190 if key
not in active_object
:
2191 active_object
[key
] = kv
['default']
2192 id_prop
= active_object
.id_properties_ui(key
)
2193 id_prop
.update(default
=kv
['default'])
2195 class CXR_ENTITY_PANEL(bpy
.types
.Panel
):
2196 bl_label
="Entity Config"
2197 bl_idname
="SCENE_PT_convexer_entity"
2198 bl_space_type
='PROPERTIES'
2199 bl_region_type
='WINDOW'
2202 def draw(_
,context
):
2203 active_object
= bpy
.context
.active_object
2205 if active_object
== None: return
2208 "scale": bpy
.context
.scene
.cxr_data
.scale_factor
,
2212 ecn
= cxr_intrinsic_classname( active_object
)
2213 classname
= cxr_custom_class( active_object
)
2216 if active_object
.type == 'MESH':
2217 _
.layout
.prop( active_object
.cxr_data
, 'brushclass' )
2218 else: _
.layout
.prop( active_object
.cxr_data
, 'classname' )
2220 _
.layout
.prop( active_object
.cxr_data
, 'visgroup' )
2221 _
.layout
.prop( active_object
.cxr_data
, 'lightmap_override' )
2223 if classname
== 'NONE':
2226 _
.layout
.label(text
=F
"<implementation defined ({ecn})>")
2227 _
.layout
.enabled
=False
2230 kvs
= cxr_entity_keyvalues( {
2231 "object": active_object
,
2232 "transform": default_context
,
2233 "classname": classname
2239 _
.layout
.prop( active_object
, F
'["cxrkv_{kv[0]}"]', text
=kv
[0])
2241 row
= _
.layout
.row()
2243 row
.label( text
=F
'{kv[0]}: {repr(kv[2])}' )
2245 _
.layout
.label( text
=F
"ERROR: NO CLASS DEFINITION" )
2247 class CXR_LIGHT_PANEL(bpy
.types
.Panel
):
2248 bl_label
= "Source Settings"
2249 bl_idname
= "LIGHT_PT_cxr"
2250 bl_space_type
= 'PROPERTIES'
2251 bl_region_type
= 'WINDOW'
2254 def draw(self
, context
):
2255 layout
= self
.layout
2256 scene
= context
.scene
2258 active_object
= bpy
.context
.active_object
2259 if active_object
== None: return
2261 if active_object
.type == 'LIGHT' or \
2262 active_object
.type == 'LIGHT_PROBE':
2264 properties
= active_object
.data
.cxr_data
2266 if active_object
.type == 'LIGHT':
2267 layout
.prop( properties
, "realtime" )
2268 elif active_object
.type == 'LIGHT_PROBE':
2269 layout
.prop( properties
, "size" )
2271 class CXR_COLLECTION_PANEL(bpy
.types
.Panel
):
2272 bl_label
= "Source Settings"
2273 bl_idname
= "COL_PT_cxr"
2274 bl_space_type
= 'PROPERTIES'
2275 bl_region_type
= 'WINDOW'
2276 bl_context
= "collection"
2278 def draw(self
, context
):
2279 layout
= self
.layout
2280 scene
= context
.scene
2282 active_collection
= bpy
.context
.collection
2284 if active_collection
!= None:
2285 layout
.prop( active_collection
.cxr_data
, "shadow_caster" )
2286 layout
.prop( active_collection
.cxr_data
, "texture_shadows" )
2287 layout
.prop( active_collection
.cxr_data
, "preserve_order" )
2288 layout
.prop( active_collection
.cxr_data
, "surfaceprop" )
2289 layout
.prop( active_collection
.cxr_data
, "visgroup" )
2292 # ------------------------------------------------------------------------------
2294 class CXR_IMAGE_SETTINGS(bpy
.types
.PropertyGroup
):
2295 export_res
: bpy
.props
.IntVectorProperty(
2297 description
="Texture Export Resolution",
2303 fmt
: bpy
.props
.EnumProperty(
2306 ('DXT1', "DXT1", "BC1 compressed", '', 0),
2307 ('DXT5', "DXT5", "BC3 compressed", '', 1),
2308 ('RGB', "RGB", "Uncompressed", '', 2),
2309 ('RGBA', "RGBA", "Uncompressed (with alpha)", '', 3)
2311 description
="Image format",
2314 last_hash
: bpy
.props
.StringProperty( name
="" )
2315 asset_id
: bpy
.props
.IntProperty(name
="intl_assetid",default
=0)
2317 mipmap
: bpy
.props
.BoolProperty(name
="MIP",default
=True)
2318 lod
: bpy
.props
.BoolProperty(name
="LOD",default
=True)
2319 clamp
: bpy
.props
.BoolProperty(name
="CLAMP",default
=False)
2320 flags
: bpy
.props
.IntProperty(name
="flags",default
=0)
2322 class CXR_LIGHT_SETTINGS(bpy
.types
.PropertyGroup
):
2323 realtime
: bpy
.props
.BoolProperty(name
="Realtime Light", default
=True)
2325 class CXR_CUBEMAP_SETTINGS(bpy
.types
.PropertyGroup
):
2326 size
: bpy
.props
.EnumProperty(
2329 ('1',"1x1",'','',0),
2330 ('2',"2x2",'','',1),
2331 ('3',"4x4",'','',2),
2332 ('4',"8x8",'','',3),
2333 ('5',"16x16",'','',4),
2334 ('6',"32x32",'','',5),
2335 ('7',"64x64",'','',6),
2336 ('8',"128x128",'','',7),
2337 ('9',"256x256",'','',8)
2339 description
="Texture resolution",
2342 class CXR_ENTITY_SETTINGS(bpy
.types
.PropertyGroup
):
2343 entity
: bpy
.props
.BoolProperty(name
="")
2345 enum_pointents
= [('NONE',"None","")]
2346 enum_brushents
= [('NONE',"None","")]
2348 for classname
in cxr_entities
:
2349 entdef
= cxr_entities
[classname
]
2350 if 'allow' in entdef
:
2351 itm
= [(classname
, classname
, "")]
2352 if 'EMPTY' in entdef
['allow']: enum_pointents
+= itm
2353 else: enum_brushents
+= itm
2355 classname
: bpy
.props
.EnumProperty(items
=enum_pointents
, name
="Class", \
2356 update
=cxr_entity_changeclass
, default
='NONE' )
2358 brushclass
: bpy
.props
.EnumProperty(items
=enum_brushents
, name
="Class", \
2359 update
=cxr_entity_changeclass
, default
='NONE' )
2361 enum_classes
= [('0',"None","")]
2362 for i
, vg
in enumerate(cxr_visgroups
):
2363 enum_classes
+= [(str(i
+1),vg
,"")]
2364 visgroup
: bpy
.props
.EnumProperty(name
="visgroup",items
=enum_classes
,default
=0)
2365 lightmap_override
: bpy
.props
.IntProperty(name
="Lightmap Override",default
=0)
2367 class CXR_MODEL_SETTINGS(bpy
.types
.PropertyGroup
):
2368 last_hash
: bpy
.props
.StringProperty( name
="" )
2369 asset_id
: bpy
.props
.IntProperty(name
="vmf_settings",default
=0)
2370 shadow_caster
: bpy
.props
.BoolProperty( name
="Shadow caster", default
=True )
2371 texture_shadows
: bpy
.props
.BoolProperty( name
="Texture Shadows", default
=False )
2372 preserve_order
: bpy
.props
.BoolProperty( name
="Preserve Order", default
=False )
2373 surfaceprop
: bpy
.props
.StringProperty( name
="Suface prop",default
="default" )
2375 enum_classes
= [('0',"None","")]
2376 for i
, vg
in enumerate(cxr_visgroups
):
2377 enum_classes
+= [(str(i
+1),vg
,"")]
2378 visgroup
: bpy
.props
.EnumProperty(name
="visgroup",items
=enum_classes
,default
=0)
2380 class CXR_SCENE_SETTINGS(bpy
.types
.PropertyGroup
):
2381 project_name
: bpy
.props
.StringProperty( name
="Project Name" )
2382 subdir
: bpy
.props
.StringProperty( name
="Subdirectory" )
2384 exe_studiomdl
: bpy
.props
.StringProperty( name
="studiomdl" )
2385 exe_vbsp
: bpy
.props
.StringProperty( name
="vbsp" )
2386 opt_vbsp
: bpy
.props
.StringProperty( name
="args" )
2387 exe_vvis
: bpy
.props
.StringProperty( name
="vvis" )
2388 opt_vvis
: bpy
.props
.StringProperty( name
="args" )
2389 exe_vrad
: bpy
.props
.StringProperty( name
="vrad" )
2390 opt_vrad
: bpy
.props
.StringProperty( name
="args", \
2391 default
="-reflectivityScale 0.35 -aoscale 1.4 -final -textureshadows -hdr -StaticPropLighting -StaticPropPolys" )
2393 debug
: bpy
.props
.BoolProperty(name
="Debug",default
=False)
2394 scale_factor
: bpy
.props
.FloatProperty( name
="VMF Scale factor", \
2395 default
=32.0,min=1.0)
2396 skybox_scale_factor
: bpy
.props
.FloatProperty( name
="Sky Scale factor", \
2397 default
=1.0,min=0.01)
2398 skyname
: bpy
.props
.StringProperty(name
="Skyname",default
="sky_csgo_night02b")
2399 skybox_offset
: bpy
.props
.FloatProperty(name
="Sky offset",default
=-4096.0)
2400 light_scale
: bpy
.props
.FloatProperty(name
="Light Scale",default
=1.0/5.0)
2401 include_names
: bpy
.props
.BoolProperty(name
="Append original file names",\
2403 lightmap_scale
: bpy
.props
.IntProperty(name
="Global Lightmap Scale",\
2405 image_quality
: bpy
.props
.IntProperty(name
="Texture Quality (0-18)",\
2406 default
=8, min=0, max=18 )
2408 comp_vmf
: bpy
.props
.BoolProperty(name
="VMF",default
=True)
2409 comp_models
: bpy
.props
.BoolProperty(name
="Models",default
=True)
2410 comp_textures
: bpy
.props
.BoolProperty(name
="Textures",default
=True)
2411 comp_compile
: bpy
.props
.BoolProperty(name
="Compile",default
=True)
2412 comp_pack
: bpy
.props
.BoolProperty(name
="Pack",default
=False)
2414 classes
= [ CXR_RELOAD
, CXR_DEV_OPERATOR
, CXR_INTERFACE
, \
2415 CXR_MATERIAL_PANEL
, CXR_IMAGE_SETTINGS
,\
2416 CXR_MODEL_SETTINGS
, CXR_ENTITY_SETTINGS
, CXR_CUBEMAP_SETTINGS
,\
2417 CXR_LIGHT_SETTINGS
, CXR_SCENE_SETTINGS
, CXR_DETECT_COMPILERS
,\
2418 CXR_ENTITY_PANEL
, CXR_LIGHT_PANEL
, CXR_PREVIEW_OPERATOR
,\
2419 CXR_VIEW3D
, CXR_COMPILER_CHAIN
, CXR_RESET_HASHES
,\
2420 CXR_COMPILE_MATERIAL
, CXR_COLLECTION_PANEL
]
2422 vmt_param_dynamic_class
= None
2425 global cxr_view_draw_handler
, vmt_param_dynamic_class
, cxr_ui_handler
2428 bpy
.utils
.register_class(c
)
2430 # Build dynamic VMT properties class defined by cxr_shader_params
2431 annotations_dict
= {}
2433 def _dvmt_propogate(layer
):
2434 nonlocal annotations_dict
2437 if isinstance(layer
[decl
],dict): # $property definition
2441 if pdef
['type'] == 'bool':
2442 prop
= bpy
.props
.BoolProperty(\
2443 name
= pdef
['name'],\
2444 default
= pdef
['default'])
2446 elif pdef
['type'] == 'float':
2447 prop
= bpy
.props
.FloatProperty(\
2448 name
= pdef
['name'],\
2449 default
= pdef
['default'])
2451 elif pdef
['type'] == 'vector':
2452 if 'subtype' in pdef
:
2453 prop
= bpy
.props
.FloatVectorProperty(\
2454 name
= pdef
['name'],\
2455 subtype
= pdef
['subtype'],\
2456 default
= pdef
['default'],\
2457 size
= len(pdef
['default']))
2459 prop
= bpy
.props
.FloatVectorProperty(\
2460 name
= pdef
['name'],\
2461 default
= pdef
['default'],\
2462 size
= len(pdef
['default']))
2464 elif pdef
['type'] == 'string':
2465 prop
= bpy
.props
.StringProperty(\
2466 name
= pdef
['name'],\
2467 default
= pdef
['default'])
2469 elif pdef
['type'] == 'enum':
2470 prop
= bpy
.props
.EnumProperty(\
2471 name
= pdef
['name'],\
2472 items
= pdef
['items'],\
2473 default
= pdef
['default'])
2476 annotations_dict
[decl
] = prop
2478 # Recurse into sub-definitions
2479 _dvmt_propogate(pdef
)
2481 annotations_dict
["shader"] = bpy
.props
.EnumProperty(\
2484 cxr_shaders
[_
]["name"],\
2485 '') for _
in cxr_shaders
],\
2486 default
= next(iter(cxr_shaders
)))
2488 annotations_dict
["asset_id"] = bpy
.props
.IntProperty(name
="intl_assetid",\
2491 _dvmt_propogate( cxr_shader_params
)
2492 vmt_param_dynamic_class
= type(
2494 (bpy
.types
.PropertyGroup
,),{
2495 "__annotations__": annotations_dict
2499 bpy
.utils
.register_class( vmt_param_dynamic_class
)
2502 bpy
.types
.Material
.cxr_data
= \
2503 bpy
.props
.PointerProperty(type=vmt_param_dynamic_class
)
2504 bpy
.types
.Image
.cxr_data
= \
2505 bpy
.props
.PointerProperty(type=CXR_IMAGE_SETTINGS
)
2506 bpy
.types
.Object
.cxr_data
= \
2507 bpy
.props
.PointerProperty(type=CXR_ENTITY_SETTINGS
)
2508 bpy
.types
.Collection
.cxr_data
= \
2509 bpy
.props
.PointerProperty(type=CXR_MODEL_SETTINGS
)
2510 bpy
.types
.Light
.cxr_data
= \
2511 bpy
.props
.PointerProperty(type=CXR_LIGHT_SETTINGS
)
2512 bpy
.types
.LightProbe
.cxr_data
= \
2513 bpy
.props
.PointerProperty(type=CXR_CUBEMAP_SETTINGS
)
2514 bpy
.types
.Scene
.cxr_data
= \
2515 bpy
.props
.PointerProperty(type=CXR_SCENE_SETTINGS
)
2517 # CXR Scene settings
2520 cxr_view_draw_handler
= bpy
.types
.SpaceView3D
.draw_handler_add(\
2521 cxr_draw
,(),'WINDOW','POST_VIEW')
2523 cxr_ui_handler
= bpy
.types
.SpaceView3D
.draw_handler_add(\
2524 cxr_ui
,(None,None),'WINDOW','POST_PIXEL')
2526 bpy
.app
.handlers
.load_post
.append(cxr_on_load
)
2527 bpy
.app
.handlers
.depsgraph_update_post
.append(cxr_dgraph_update
)
2530 global cxr_view_draw_handler
, vmt_param_dynamic_class
, cxr_ui_handler
2532 bpy
.utils
.unregister_class( vmt_param_dynamic_class
)
2534 bpy
.utils
.unregister_class(c
)
2536 bpy
.app
.handlers
.depsgraph_update_post
.remove(cxr_dgraph_update
)
2537 bpy
.app
.handlers
.load_post
.remove(cxr_on_load
)
2539 bpy
.types
.SpaceView3D
.draw_handler_remove(cxr_view_draw_handler
,'WINDOW')
2540 bpy
.types
.SpaceView3D
.draw_handler_remove(cxr_ui_handler
,'WINDOW')