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_vmf_context(Structure
):
259 _fields_
= [("mapversion",c_int32
),
260 ("skyname",c_char_p
),
261 ("detailvbsp",c_char_p
),
262 ("detailmaterial",c_char_p
),
264 ("offset",c_double
*3),
265 ("lightmap_scale",c_int32
),
266 ("brush_count",c_int32
),
267 ("entity_count",c_int32
),
268 ("face_count",c_int32
)]
270 # Convert blenders mesh format into CXR's static format (they are very similar)
272 def mesh_cxr_format(obj
):
275 if bpy
.context
.active_object
!= None:
276 orig_state
= obj
.mode
277 if orig_state
!= 'OBJECT':
278 bpy
.ops
.object.mode_set(mode
='OBJECT')
280 dgraph
= bpy
.context
.evaluated_depsgraph_get()
281 data
= obj
.evaluated_get(dgraph
).data
283 _
,mtx_rot
,_
= obj
.matrix_world
.decompose()
285 mesh
= cxr_static_mesh()
287 vertex_data
= ((c_double
*3)*len(data
.vertices
))()
288 for i
, vert
in enumerate(data
.vertices
):
289 v
= obj
.matrix_world
@ vert
.co
290 vertex_data
[i
][0] = c_double(v
[0])
291 vertex_data
[i
][1] = c_double(v
[1])
292 vertex_data
[i
][2] = c_double(v
[2])
294 loop_data
= (cxr_static_loop
*len(data
.loops
))()
295 polygon_data
= (cxr_polygon
*len(data
.polygons
))()
297 for i
, poly
in enumerate(data
.polygons
):
298 loop_start
= poly
.loop_start
299 loop_end
= poly
.loop_start
+ poly
.loop_total
300 for loop_index
in range(loop_start
, loop_end
):
301 loop
= data
.loops
[loop_index
]
302 loop_data
[loop_index
].index
= loop
.vertex_index
303 loop_data
[loop_index
].edge_index
= loop
.edge_index
306 uv
= data
.uv_layers
.active
.data
[loop_index
].uv
307 loop_data
[loop_index
].uv
[0] = c_double(uv
[0])
308 loop_data
[loop_index
].uv
[1] = c_double(uv
[1])
310 loop_data
[loop_index
].uv
[0] = c_double(0.0)
311 loop_data
[loop_index
].uv
[1] = c_double(0.0)
312 center
= obj
.matrix_world
@ poly
.center
313 normal
= mtx_rot
@ poly
.normal
315 polygon_data
[i
].loop_start
= poly
.loop_start
316 polygon_data
[i
].loop_total
= poly
.loop_total
317 polygon_data
[i
].normal
[0] = normal
[0]
318 polygon_data
[i
].normal
[1] = normal
[1]
319 polygon_data
[i
].normal
[2] = normal
[2]
320 polygon_data
[i
].center
[0] = center
[0]
321 polygon_data
[i
].center
[1] = center
[1]
322 polygon_data
[i
].center
[2] = center
[2]
323 polygon_data
[i
].material_id
= poly
.material_index
325 edge_data
= (cxr_edge
*len(data
.edges
))()
327 for i
, edge
in enumerate(data
.edges
):
328 edge_data
[i
].i0
= edge
.vertices
[0]
329 edge_data
[i
].i1
= edge
.vertices
[1]
330 edge_data
[i
].freestyle
= edge
.use_freestyle_mark
331 edge_data
[i
].sharp
= edge
.use_edge_sharp
333 material_data
= (cxr_material
*len(obj
.material_slots
))()
335 for i
, ms
in enumerate(obj
.material_slots
):
336 inf
= material_info(ms
.material
)
337 material_data
[i
].res
[0] = inf
['res'][0]
338 material_data
[i
].res
[1] = inf
['res'][1]
339 material_data
[i
].name
= inf
['name'].encode('utf-8')
341 mesh
.edges
= cast(edge_data
, POINTER(cxr_edge
))
342 mesh
.vertices
= cast(vertex_data
, POINTER(c_double
*3))
343 mesh
.loops
= cast(loop_data
,POINTER(cxr_static_loop
))
344 mesh
.polys
= cast(polygon_data
, POINTER(cxr_polygon
))
345 mesh
.materials
= cast(material_data
, POINTER(cxr_material
))
347 mesh
.poly_count
= len(data
.polygons
)
348 mesh
.vertex_count
= len(data
.vertices
)
349 mesh
.edge_count
= len(data
.edges
)
350 mesh
.loop_count
= len(data
.loops
)
351 mesh
.material_count
= len(obj
.material_slots
)
353 if orig_state
!= None:
354 bpy
.ops
.object.mode_set(mode
=orig_state
)
358 # Callback ctypes indirection things.. not really sure.
359 c_libcxr_log_callback
= None
360 c_libcxr_line_callback
= None
363 # -------------------------------------------------------------
364 libcxr_decompose
= extern( "cxr_decompose",
365 [POINTER(cxr_static_mesh
), POINTER(c_int32
)],
368 libcxr_free_world
= extern( "cxr_free_world",
372 libcxr_write_test_data
= extern( "cxr_write_test_data",
373 [POINTER(cxr_static_mesh
)],
376 libcxr_world_preview
= extern( "cxr_world_preview",
378 POINTER(cxr_tri_mesh
)
380 libcxr_free_tri_mesh
= extern( "cxr_free_tri_mesh",
384 libcxr_begin_vmf
= extern( "cxr_begin_vmf",
385 [POINTER(cxr_vmf_context
), c_void_p
],
388 libcxr_vmf_begin_entities
= extern( "cxr_vmf_begin_entities",
389 [POINTER(cxr_vmf_context
), c_void_p
],
392 libcxr_push_world_vmf
= extern("cxr_push_world_vmf",
393 [c_void_p
,POINTER(cxr_vmf_context
),c_void_p
],
396 libcxr_end_vmf
= extern( "cxr_end_vmf",
397 [POINTER(cxr_vmf_context
),c_void_p
],
401 # VDF + with open wrapper
402 libcxr_vdf_open
= extern( "cxr_vdf_open", [c_char_p
], c_void_p
)
403 libcxr_vdf_close
= extern( "cxr_vdf_close", [c_void_p
], None )
404 libcxr_vdf_put
= extern( "cxr_vdf_put", [c_void_p
,c_char_p
], None )
405 libcxr_vdf_node
= extern( "cxr_vdf_node", [c_void_p
,c_char_p
], None )
406 libcxr_vdf_edon
= extern( "cxr_vdf_edon", [c_void_p
], None )
407 libcxr_vdf_kv
= extern( "cxr_vdf_kv", [c_void_p
,c_char_p
,c_char_p
], None )
409 class vdf_structure():
410 def __init__(_
,path
):
413 _
.fp
= libcxr_vdf_open
.call( _
.path
.encode('utf-8') )
415 print( F
"Could not open file {_.path}" )
418 def __exit__(_
,type,value
,traceback
):
420 libcxr_vdf_close
.call(_
.fp
)
422 libcxr_vdf_put
.call(_
.fp
, s
.encode('utf-8') )
424 libcxr_vdf_node
.call(_
.fp
, name
.encode('utf-8') )
426 libcxr_vdf_edon
.call(_
.fp
)
428 libcxr_vdf_kv
.call(_
.fp
, k
.encode('utf-8'), v
.encode('utf-8'))
431 libcxr_lightpatch_bsp
= extern( "cxr_lightpatch_bsp", [c_char_p
], None )
433 libcxr_funcs
= [ libcxr_decompose
, libcxr_free_world
, libcxr_begin_vmf
, \
434 libcxr_vmf_begin_entities
, libcxr_push_world_vmf
, \
435 libcxr_end_vmf
, libcxr_vdf_open
, libcxr_vdf_close
, \
436 libcxr_vdf_put
, libcxr_vdf_node
, libcxr_vdf_edon
,
437 libcxr_vdf_kv
, libcxr_lightpatch_bsp
, libcxr_write_test_data
,\
438 libcxr_world_preview
, libcxr_free_tri_mesh
]
442 def libcxr_log_callback(logStr
):
443 print( F
"{logStr.decode('utf-8')}",end
='' )
445 cxr_line_positions
= None
446 cxr_line_colours
= None
448 def cxr_reset_lines():
449 global cxr_line_positions
, cxr_line_colours
451 cxr_line_positions
= []
452 cxr_line_colours
= []
454 def cxr_batch_lines():
455 global cxr_line_positions
, cxr_line_colours
, cxr_view_shader
, cxr_view_lines
457 cxr_view_lines
= batch_for_shader(\
458 cxr_view_shader
, 'LINES',\
459 { "pos": cxr_line_positions
, "color": cxr_line_colours
})
461 def libcxr_line_callback( p0
,p1
,colour
):
462 global cxr_line_colours
, cxr_line_positions
464 cxr_line_positions
+= [(p0
[0],p0
[1],p0
[2])]
465 cxr_line_positions
+= [(p1
[0],p1
[1],p1
[2])]
466 cxr_line_colours
+= [(colour
[0],colour
[1],colour
[2],colour
[3])]
467 cxr_line_colours
+= [(colour
[0],colour
[1],colour
[2],colour
[3])]
470 # ------------------------------------------------------------------------------
475 NBVTF_IMAGE_FORMAT_ABGR8888
= 1
476 NBVTF_IMAGE_FORMAT_BGR888
= 3
477 NBVTF_IMAGE_FORMAT_DXT1
= 13
478 NBVTF_IMAGE_FORMAT_DXT5
= 15
479 NBVTF_TEXTUREFLAGS_CLAMPS
= 0x00000004
480 NBVTF_TEXTUREFLAGS_CLAMPT
= 0x00000008
481 NBVTF_TEXTUREFLAGS_NORMAL
= 0x00000080
482 NBVTF_TEXTUREFLAGS_NOMIP
= 0x00000100
483 NBVTF_TEXTUREFLAGS_NOLOD
= 0x00000200
485 libnbvtf_convert
= extern( "nbvtf_convert", \
486 [c_char_p
,c_int32
,c_int32
,c_int32
,c_int32
,c_int32
,c_uint32
,c_char_p
], \
489 libnbvtf_init
= extern( "nbvtf_init", [], None )
490 libnbvtf_funcs
= [ libnbvtf_convert
, libnbvtf_init
]
493 # --------------------------
496 global libcxr
, libnbvtf
, libcxr_funcs
, libnbvtf_funcs
498 # Unload libraries if existing
499 def _reload( lib
, path
):
501 _handle
= lib
._handle
502 for i
in range(10): libc_dlclose( _handle
)
505 return cdll
.LoadLibrary( F
'{os.path.dirname(__file__)}/{path}.so' )
507 libnbvtf
= _reload( libnbvtf
, "libnbvtf" )
508 libcxr
= _reload( libcxr
, "libcxr" )
510 for fd
in libnbvtf_funcs
:
511 fd
.loadfrom( libnbvtf
)
514 for fd
in libcxr_funcs
:
515 fd
.loadfrom( libcxr
)
518 global c_libcxr_log_callback
, c_libcxr_line_callback
520 LOG_FUNCTION_TYPE
= CFUNCTYPE(None,c_char_p
)
521 c_libcxr_log_callback
= LOG_FUNCTION_TYPE(libcxr_log_callback
)
523 LINE_FUNCTION_TYPE
= CFUNCTYPE(None,\
524 POINTER(c_double
), POINTER(c_double
), POINTER(c_double
))
525 c_libcxr_line_callback
= LINE_FUNCTION_TYPE(libcxr_line_callback
)
527 libcxr
.cxr_set_log_function(cast(c_libcxr_log_callback
,c_void_p
))
528 libcxr
.cxr_set_line_function(cast(c_libcxr_line_callback
,c_void_p
))
530 build_time
= c_char_p
.in_dll(libcxr
,'cxr_build_time')
531 print( F
"libcxr build time: {build_time.value}" )
536 # ------------------------------------------------------------------------------
538 # Standard entity functions, think of like base.fgd
540 def cxr_get_origin(context
):
541 return context
['object'].location
* context
['transform']['scale'] + \
542 mathutils
.Vector(context
['transform']['offset'])
544 def cxr_get_angles(context
):
545 obj
= context
['object']
546 euler
= [ a
*57.295779513 for a
in obj
.rotation_euler
]
553 def cxr_baseclass(classes
, other
):
556 base
.update(x
.copy())
559 # EEVEE Light component converter -> Source 1
561 def ent_lights(context
):
562 obj
= context
['object']
563 kvs
= cxr_baseclass([ent_origin
],\
565 "_distance": (0.0 if obj
.data
.cxr_data
.realtime
else -1.0),
566 "_lightHDR": '-1 -1 -1 1',
570 light_base
= [(pow(obj
.data
.color
[i
],1.0/2.2)*255.0) for i
in range(3)] +\
571 [obj
.data
.energy
* bpy
.context
.scene
.cxr_data
.light_scale
]
573 if obj
.data
.type == 'SPOT' or obj
.data
.type == 'SUN':
574 # Blenders directional lights are -z forward
575 # Source is +x, however, it seems to use a completely different system.
576 # Since we dont care about roll for spotlights, we just take the
577 # pitch and yaw via trig
579 _
,mtx_rot
,_
= obj
.matrix_world
.decompose()
580 fwd
= mtx_rot
@ mathutils
.Vector((0,0,-1))
581 dir_pitch
= math
.asin(fwd
[2]) * 57.295779513
582 dir_yaw
= math
.atan2(fwd
[1],fwd
[0]) * 57.295779513
584 if obj
.data
.type == 'SPOT':
585 kvs
['_light'] = [ int(x
) for x
in light_base
]
586 kvs
['_cone'] = obj
.data
.spot_size
*(57.295779513/2.0)
587 kvs
['_inner_cone'] = (1.0-obj
.data
.spot_blend
)*kvs
['_cone']
589 kvs
['pitch'] = dir_pitch
590 kvs
['angles'] = [ 0, dir_yaw
, 0 ]
591 kvs
['_quadratic_attn'] = 0.0 # Source spotlights + quadratic falloff look
594 # Blender's default has a much more 'nice'
596 kvs
['_linear_attn'] = 1.0
598 elif obj
.data
.type == 'POINT':
599 kvs
['_light'] = [ int(x
) for x
in light_base
]
600 kvs
['_quadratic_attn'] = 1.0
601 kvs
['_linear_attn'] = 1.0
603 elif obj
.data
.type == 'SUN':
604 light_base
[3] *= 300.0 * 5
605 kvs
['_light'] = [ int(x
) for x
in light_base
]
607 ambient
= bpy
.context
.scene
.world
.color
608 kvs
['_ambient'] = [int(pow(ambient
[i
],1.0/2.2)*255.0) for i
in range(3)] +\
610 kvs
['_ambientHDR'] = [-1,-1,-1,1]
611 kvs
['_AmbientScaleHDR'] = 1
612 kvs
['pitch'] = dir_pitch
613 kvs
['angles'] = [ dir_pitch
, dir_yaw
, 0.0 ]
614 kvs
['SunSpreadAngle'] = 0
618 def ent_prop(context
):
619 if isinstance( context
['object'], bpy
.types
.Collection
):
621 target
= context
['object']
622 pos
= mathutils
.Vector(context
['origin'])
623 pos
+= mathutils
.Vector(context
['transform']['offset'])
625 kvs
['origin'] = [pos
[1],-pos
[0],pos
[2]]
626 kvs
['angles'] = [0,180,0]
627 kvs
['uniformscale'] = 1.0
629 kvs
= cxr_baseclass([ent_origin
],{})
630 target
= context
['object'].instance_collection
632 obj
= context
['object']
633 euler
= [ a
*57.295779513 for a
in obj
.rotation_euler
]
636 angle
[1] = euler
[2] + 180.0 # Dunno...
639 kvs
['angles'] = angle
640 kvs
['uniformscale'] = obj
.scale
[0]
642 if target
.cxr_data
.shadow_caster
:
643 kvs
['enablelightbounce'] = 1
644 kvs
['disableshadows'] = 0
646 kvs
['enablelightbounce'] = 0
647 kvs
['disableshadows'] = 1
649 kvs
['fademindist'] = -1
651 kvs
['model'] = F
"{asset_path('models',target)}.mdl".lower()
652 kvs
['renderamt'] = 255
653 kvs
['rendercolor'] = [255, 255, 255]
659 def ent_sky_camera(context
):
660 settings
= bpy
.context
.scene
.cxr_data
661 scale
= settings
.scale_factor
/ settings
.skybox_scale_factor
664 "origin": [_
for _
in context
['transform']['offset']],
665 "angles": [ 0, 0, 0 ],
666 "fogcolor": [255, 255, 255],
667 "fogcolor2": [255, 255, 255],
672 "HDRColorScale": 1.0,
677 def ent_cubemap(context
):
678 obj
= context
['object']
679 return cxr_baseclass([ent_origin
], {"cubemapsize": obj
.data
.cxr_data
.size
})
681 ent_origin
= { "origin": cxr_get_origin
}
682 ent_angles
= { "angles": cxr_get_angles
}
683 ent_transform
= cxr_baseclass( [ent_origin
], ent_angles
)
685 #include the user config
686 exec(open(F
'{os.path.dirname(__file__)}/config.py').read())
688 # Blender state callbacks
689 # ------------------------------------------------------------------------------
692 def cxr_on_load(dummy
):
693 global cxr_view_lines
, cxr_view_mesh
695 cxr_view_lines
= None
699 def cxr_dgraph_update(scene
,dgraph
):
701 print( F
"Hallo {time.time()}" )
703 # Convexer compilation functions
704 # ------------------------------------------------------------------------------
706 # Asset path management
708 def asset_uid(asset
):
709 if isinstance(asset
,str):
712 # Create a unique ID string
714 v
= asset
.cxr_data
.asset_id
723 dig
.append( int( v
% len(base
) ) )
729 if bpy
.context
.scene
.cxr_data
.include_names
:
730 name
+= asset
.name
.replace('.','_')
734 # -> <project_name>/<asset_name>
735 def asset_name(asset
):
736 return F
"{bpy.context.scene.cxr_data.project_name}/{asset_uid(asset)}"
738 # -> <subdir>/<project_name>/<asset_name>
739 def asset_path(subdir
, asset
):
740 return F
"{subdir}/{asset_name(asset_uid(asset))}"
742 # -> <csgo>/<subdir>/<project_name>/<asset_name>
743 def asset_full_path(sdir
,asset
):
744 return F
"{bpy.context.scene.cxr_data.subdir}/"+\
745 F
"{asset_path(sdir,asset_uid(asset))}"
747 # Entity functions / infos
748 # ------------------------
750 def cxr_intrinsic_classname(obj
):
751 if obj
.type == 'LIGHT':
753 'SPOT': "light_spot",
755 'SUN': "light_environment" }[ obj
.data
.type ]
757 elif obj
.type == 'LIGHT_PROBE':
759 elif obj
.type == 'EMPTY':
765 def cxr_custom_class(obj
):
766 if obj
.type == 'MESH': custom_class
= obj
.cxr_data
.brushclass
767 else: custom_class
= obj
.cxr_data
.classname
771 def cxr_classname(obj
):
772 intr
= cxr_intrinsic_classname(obj
)
773 if intr
!= None: return intr
775 custom_class
= cxr_custom_class(obj
)
776 if custom_class
!= 'NONE':
782 # intinsic: (k, False, value)
783 # property: (k, True, value or default)
787 def cxr_entity_keyvalues(context
):
788 classname
= context
['classname']
789 obj
= context
['object']
790 if classname
not in cxr_entities
: return None
794 entdef
= cxr_entities
[classname
]
795 kvs
= entdef
['keyvalues']
797 if callable(kvs
): kvs
= kvs(context
)
804 if isinstance(kv
,dict):
806 value
= obj
[ F
"cxrkv_{k}" ]
811 if isinstance(value
,mathutils
.Vector
):
812 value
= [_
for _
in value
]
814 result
+= [(k
, isprop
, value
)]
818 # Extract material information from shader graph data
820 def material_info(mat
):
822 info
['res'] = (512,512)
823 info
['name'] = 'tools/toolsnodraw'
825 if mat
== None or mat
.use_nodes
== False:
829 if mat
.cxr_data
.shader
== 'Builtin':
830 info
['name'] = mat
.name
834 info
['name'] = asset_name(mat
)
836 # Using the cxr_graph_mapping as a reference, go through the shader
837 # graph and gather all $props from it.
839 def _graph_read( node_def
, node
=None, depth
=0 ):
843 def _variant_apply( val
):
846 if isinstance( val
, str ):
849 for shader_variant
in val
:
850 if shader_variant
[0] == mat
.cxr_data
.shader
:
851 return shader_variant
[1]
855 _graph_read
.extracted
= []
857 for node_idname
in node_def
:
858 for n
in mat
.node_tree
.nodes
:
859 if n
.bl_idname
== node_idname
:
860 node_def
= node_def
[node_idname
]
864 for link
in node_def
:
865 if isinstance( node_def
[link
], dict ):
866 inputt
= node
.inputs
[link
]
867 inputt_def
= node_def
[link
]
871 # look for definitions for the connected node type
872 con
= inputt
.links
[0].from_node
874 for node_idname
in inputt_def
:
875 if con
.bl_idname
== node_idname
:
876 con_def
= inputt_def
[ node_idname
]
877 _graph_read( con_def
, con
, depth
+1 )
879 # No definition found! :(
880 # TODO: Make a warning for this?
883 if "default" in inputt_def
:
884 prop
= _variant_apply( inputt_def
['default'] )
885 info
[prop
] = inputt
.default_value
887 prop
= _variant_apply( node_def
[link
] )
888 info
[prop
] = getattr(node
,link
)
890 _graph_read(cxr_graph_mapping
)
892 if "$basetexture" in info
:
893 export_res
= info
['$basetexture'].cxr_data
.export_res
894 info
['res'] = (export_res
[0], export_res
[1])
898 def vec3_min( a
, b
):
899 return mathutils
.Vector((min(a
[0],b
[0]),min(a
[1],b
[1]),min(a
[2],b
[2])))
900 def vec3_max( a
, b
):
901 return mathutils
.Vector((max(a
[0],b
[0]),max(a
[1],b
[1]),max(a
[2],b
[2])))
903 def cxr_collection_center(collection
, transform
):
905 bounds_min
= mathutils
.Vector((BIG
,BIG
,BIG
))
906 bounds_max
= mathutils
.Vector((-BIG
,-BIG
,-BIG
))
908 for obj
in collection
.objects
:
909 if obj
.type == 'MESH':
910 corners
= [ mathutils
.Vector(c
) for c
in obj
.bound_box
]
912 for corner
in [ obj
.matrix_world
@c for c
in corners
]:
913 bounds_min
= vec3_min( bounds_min
, corner
)
914 bounds_max
= vec3_max( bounds_max
, corner
)
916 center
= (bounds_min
+ bounds_max
) / 2.0
918 origin
= mathutils
.Vector((-center
[1],center
[0],center
[2]))
919 origin
*= transform
['scale']
923 # Prepares Scene into dictionary format
925 def cxr_scene_collect():
926 context
= bpy
.context
928 # Make sure all of our asset types have a unique ID
929 def _uid_prepare(objtype
):
935 if vs
.asset_id
in used_ids
:
938 id_max
= max(id_max
,vs
.asset_id
)
939 used_ids
+=[vs
.asset_id
]
940 for vs
in to_generate
:
943 _uid_prepare(bpy
.data
.materials
)
944 _uid_prepare(bpy
.data
.images
)
945 _uid_prepare(bpy
.data
.collections
)
948 "entities": [], # Everything with a classname
949 "geo": [], # All meshes without a classname
950 "heros": [] # Collections prefixed with mdl_
953 def _collect(collection
,transform
):
956 if collection
.name
.startswith('.'): return
957 if collection
.hide_render
: return
959 if collection
.name
.startswith('mdl_'):
960 sceneinfo
['entities'] += [{
961 "object": collection
,
962 "classname": "prop_static",
963 "transform": transform
,
964 "origin": cxr_collection_center( collection
, transform
)
967 sceneinfo
['heros'] += [{
968 "collection": collection
,
969 "transform": transform
,
970 "origin": cxr_collection_center( collection
, transform
)
974 for obj
in collection
.objects
:
975 if obj
.hide_get(): continue
977 classname
= cxr_classname( obj
)
979 if classname
!= None:
980 sceneinfo
['entities'] += [{
982 "classname": classname
,
983 "transform": transform
985 elif obj
.type == 'MESH':
986 sceneinfo
['geo'] += [{
988 "transform": transform
991 for c
in collection
.children
:
992 _collect( c
, transform
)
995 "scale": context
.scene
.cxr_data
.scale_factor
,
1000 "scale": context
.scene
.cxr_data
.skybox_scale_factor
,
1001 "offset": (0,0,context
.scene
.cxr_data
.skybox_offset
)
1004 if 'main' in bpy
.data
.collections
:
1005 _collect( bpy
.data
.collections
['main'], transform_main
)
1007 if 'skybox' in bpy
.data
.collections
:
1008 _collect( bpy
.data
.collections
['skybox'], transform_sky
)
1010 sceneinfo
['entities'] += [{
1012 "transform": transform_sky
,
1013 "classname": "sky_camera"
1018 # Write VMF out to file (JOB HANDLER)
1020 def cxr_export_vmf(sceneinfo
, output_vmf
):
1023 with
vdf_structure(output_vmf
) as m
:
1024 print( F
"Write: {output_vmf}" )
1026 vmfinfo
= cxr_vmf_context()
1027 vmfinfo
.mapversion
= 4
1029 #TODO: These need to be in options...
1030 vmfinfo
.skyname
= bpy
.context
.scene
.cxr_data
.skyname
.encode('utf-8')
1031 vmfinfo
.detailvbsp
= b
"detail.vbsp"
1032 vmfinfo
.detailmaterial
= b
"detail/detailsprites"
1033 vmfinfo
.lightmap_scale
= 12
1035 vmfinfo
.brush_count
= 0
1036 vmfinfo
.entity_count
= 0
1037 vmfinfo
.face_count
= 0
1039 libcxr_begin_vmf
.call( pointer(vmfinfo
), m
.fp
)
1041 def _buildsolid( cmd
):
1044 print( F
"{vmfinfo.brush_count} :: {cmd['object'].name}" )
1046 baked
= mesh_cxr_format( cmd
['object'] )
1047 world
= libcxr_decompose
.call( baked
, None )
1052 vmfinfo
.scale
= cmd
['transform']['scale']
1054 offset
= cmd
['transform']['offset']
1055 vmfinfo
.offset
[0] = offset
[0]
1056 vmfinfo
.offset
[1] = offset
[1]
1057 vmfinfo
.offset
[2] = offset
[2]
1059 libcxr_push_world_vmf
.call( world
, pointer(vmfinfo
), m
.fp
)
1060 libcxr_free_world
.call( world
)
1065 for brush
in sceneinfo
['geo']:
1066 if not _buildsolid( brush
):
1071 libcxr_vmf_begin_entities
.call(pointer(vmfinfo
), m
.fp
)
1074 for ent
in sceneinfo
['entities']:
1076 ctx
= ent
['transform']
1077 cls
= ent
['classname']
1080 m
.kv( 'classname', cls
)
1082 kvs
= cxr_entity_keyvalues( ent
)
1085 if isinstance(kv
[2], list):
1086 m
.kv( kv
[0], ' '.join([str(_
) for _
in kv
[2]]) )
1087 else: m
.kv( kv
[0], str(kv
[2]) )
1091 elif not isinstance( obj
, bpy
.types
.Collection
):
1092 if obj
.type == 'MESH':
1093 if not _buildsolid( ent
):
1103 # COmpile image using NBVTF and hash it (JOB HANDLER)
1105 def compile_image(img
):
1109 name
= asset_name(img
)
1110 src_path
= bpy
.path
.abspath(img
.filepath
)
1112 dims
= img
.cxr_data
.export_res
1114 'RGBA': NBVTF_IMAGE_FORMAT_ABGR8888
,
1115 'DXT1': NBVTF_IMAGE_FORMAT_DXT1
,
1116 'DXT5': NBVTF_IMAGE_FORMAT_DXT5
,
1117 'RGB': NBVTF_IMAGE_FORMAT_BGR888
1118 }[ img
.cxr_data
.fmt
]
1120 mipmap
= img
.cxr_data
.mipmap
1121 lod
= img
.cxr_data
.lod
1122 clamp
= img
.cxr_data
.clamp
1123 flags
= img
.cxr_data
.flags
1125 q
=bpy
.context
.scene
.cxr_data
.image_quality
1127 userflag_hash
= F
"{mipmap}.{lod}.{clamp}.{flags}"
1128 file_hash
= F
"{name}.{os.path.getmtime(src_path)}"
1129 comphash
= F
"{file_hash}.{dims[0]}.{dims[1]}.{fmt}.{userflag_hash}.{q}"
1131 if img
.cxr_data
.last_hash
!= comphash
:
1132 print( F
"Texture update: {img.filepath}" )
1134 src
= src_path
.encode('utf-8')
1135 dst
= (asset_full_path('materials',img
)+'.vtf').encode('utf-8')
1139 # texture setting flags
1140 if not lod
: flags_full |
= NBVTF_TEXTUREFLAGS_NOLOD
1142 flags_full |
= NBVTF_TEXTUREFLAGS_CLAMPS
1143 flags_full |
= NBVTF_TEXTUREFLAGS_CLAMPT
1145 if libnbvtf_convert
.call(src
,dims
[0],dims
[1],mipmap
,fmt
,q
,flags_full
,dst
):
1146 img
.cxr_data
.last_hash
= comphash
1151 # Compile a material to VMT format. This is quick to run, doesnt need to be a
1154 def compile_material(mat
):
1155 info
= material_info(mat
)
1156 properties
= mat
.cxr_data
1158 print( F
"Compile {asset_full_path('materials',mat)}.vmt" )
1159 if properties
.shader
== 'Builtin':
1164 # Walk the property tree
1165 def _mlayer( layer
):
1166 nonlocal properties
, props
1169 if isinstance(layer
[decl
],dict): # $property definition
1171 ptype
= pdef
['type']
1177 if 'shaders' in pdef
and properties
.shader
not in pdef
['shaders']:
1180 # Group expansion (does it have subdefinitions?)
1182 if isinstance(pdef
[ch
],dict):
1191 if ptype
== 'intrinsic':
1195 prop
= getattr(properties
,decl
)
1196 default
= pdef
['default']
1198 if not isinstance(prop
,str) and \
1199 not isinstance(prop
,bpy
.types
.Image
) and \
1200 hasattr(prop
,'__getitem__'):
1201 prop
= tuple([p
for p
in prop
])
1205 props
+= [(decl
,pdef
,prop
)]
1210 if expandview
: _mlayer(pdef
)
1212 _mlayer( cxr_shader_params
)
1215 with
vdf_structure( F
"{asset_full_path('materials',mat)}.vmt" ) as vmt
:
1216 vmt
.node( properties
.shader
)
1217 vmt
.put( "// Convexer export\n" )
1226 if 'exponent' in pdef
: return str(pow( v
, pdef
['exponent'] ))
1229 if isinstance(prop
,bpy
.types
.Image
):
1230 vmt
.kv( decl
, asset_name(prop
))
1231 elif isinstance(prop
,bool):
1232 vmt
.kv( decl
, '1' if prop
else '0' )
1233 elif isinstance(prop
,str):
1234 vmt
.kv( decl
, prop
)
1235 elif isinstance(prop
,float) or isinstance(prop
,int):
1236 vmt
.kv( decl
, _numeric(prop
) )
1237 elif isinstance(prop
,tuple):
1238 vmt
.kv( decl
, F
"[{' '.join([_numeric(_) for _ in prop])}]" )
1240 vmt
.put( F
"// (cxr) unkown shader value type'{type(prop)}'" )
1245 def cxr_modelsrc_vphys( mdl
):
1246 for obj
in mdl
.objects
:
1247 if obj
.name
== F
"{mdl.name}_phy":
1251 def cxr_export_modelsrc( mdl
, origin
, asset_dir
, project_name
, transform
):
1252 dgraph
= bpy
.context
.evaluated_depsgraph_get()
1254 # Compute hash value
1255 chash
= asset_uid(mdl
)+str(origin
)+str(transform
)
1257 #for obj in mdl.objects:
1258 # if obj.type != 'MESH':
1261 # ev = obj.evaluated_get(dgraph).data
1262 # srcverts=[(v.co[0],v.co[1],v.co[2]) for v in ev.vertices]
1263 # srcloops=[(l.normal[0],l.normal[1],l.normal[2]) for l in ev.loops]
1265 # chash=hashlib.sha224((str(srcverts)+chash).encode()).hexdigest()
1266 # chash=hashlib.sha224((str(srcloops)+chash).encode()).hexdigest()
1268 # if ev.uv_layers.active != None:
1269 # uv_layer = ev.uv_layers.active.data
1270 # srcuv=[(uv.uv[0],uv.uv[1]) for uv in uv_layer]
1274 # chash=hashlib.sha224((str(srcuv)+chash).encode()).hexdigest()
1275 # srcmats=[ ms.material.name for ms in obj.material_slots ]
1276 # chash=hashlib.sha224((str(srcmats)+chash).encode()).hexdigest()
1277 # transforms=[ obj.location, obj.rotation_euler, obj.scale ]
1278 # srctr=[(v[0],v[1],v[2]) for v in transforms]
1279 # chash=hashlib.sha224((str(srctr)+chash).encode()).hexdigest()
1281 #if chash != mdl.cxr_data.last_hash:
1282 # mdl.cxr_data.last_hash = chash
1283 # print( F"Compile: {mdl.name}" )
1287 bpy
.ops
.object.select_all(action
='DESELECT')
1290 def _get_layer(col
,name
):
1291 for c
in col
.children
:
1294 sub
= _get_layer(c
,name
)
1298 layer
= _get_layer(bpy
.context
.view_layer
.layer_collection
,mdl
.name
)
1300 prev_state
= layer
.hide_viewport
1301 layer
.hide_viewport
=False
1303 # Collect materials to be compiled, and temp rename for export
1307 for obj
in mdl
.objects
:
1308 if obj
.name
== F
"{mdl.name}_phy":
1312 obj
.select_set(state
=True)
1313 for ms
in obj
.material_slots
:
1314 if ms
.material
!= None:
1315 if ms
.material
not in mat_dict
:
1316 mat_dict
[ms
.material
] = ms
.material
.name
1317 ms
.material
.name
= asset_uid(ms
.material
)
1318 ms
.material
.use_nodes
= False
1321 bpy
.ops
.export_scene
.fbx( filepath
=F
'{asset_dir}/{uid}_ref.fbx',\
1322 check_existing
=False,
1324 apply_unit_scale
=False,
1325 bake_space_transform
=False
1328 bpy
.ops
.object.select_all(action
='DESELECT')
1331 vphys
.select_set(state
=True)
1332 bpy
.ops
.export_scene
.fbx( filepath
=F
'{asset_dir}/{uid}_phy.fbx',\
1333 check_existing
=False,
1335 apply_unit_scale
=False,
1336 bake_space_transform
=False
1338 bpy
.ops
.object.select_all(action
='DESELECT')
1340 # Fix material names back to original
1341 for mat
in mat_dict
:
1342 mat
.name
= mat_dict
[mat
]
1343 mat
.use_nodes
= True
1345 layer
.hide_viewport
=prev_state
1348 with
open(F
'{asset_dir}/{uid}.qc','w') as o
:
1349 o
.write(F
'$modelname "{project_name}/{uid}"\n')
1350 #o.write(F'$scale .32\n')
1351 o
.write(F
'$scale {transform["scale"]/100.0}\n')
1352 o
.write(F
'$body _ "{uid}_ref.fbx"\n')
1353 o
.write(F
'$staticprop\n')
1354 o
.write(F
'$origin {origin[0]:.6f} {origin[1]:.6f} {origin[2]:.6f}\n')
1356 if mdl
.cxr_data
.preserve_order
:
1357 o
.write(F
"$preservetriangleorder\n")
1359 if mdl
.cxr_data
.texture_shadows
:
1360 o
.write(F
"$casttextureshadows\n")
1362 o
.write(F
"$surfaceprop {mdl.cxr_data.surfaceprop}\n")
1365 o
.write(F
'$collisionmodel "{uid}_phy.fbx"\n')
1367 o
.write(" $concave\n")
1370 o
.write(F
'$cdmaterials {project_name}\n')
1371 o
.write(F
'$sequence idle {uid}_ref.fbx\n')
1375 # Copy bsp file (and also lightpatch it)
1377 def cxr_patchmap( src
, dst
):
1378 libcxr_lightpatch_bsp
.call( src
.encode('utf-8') )
1379 shutil
.copyfile( src
, dst
)
1382 # Convexer operators
1383 # ------------------------------------------------------------------------------
1385 # Force reload of shared libraries
1387 class CXR_RELOAD(bpy
.types
.Operator
):
1388 bl_idname
="convexer.reload"
1390 def execute(_
,context
):
1394 # Used for exporting data to use with ASAN builds
1396 class CXR_DEV_OPERATOR(bpy
.types
.Operator
):
1397 bl_idname
="convexer.dev_test"
1398 bl_label
="Export development data"
1400 def execute(_
,context
):
1401 # Prepare input data
1402 mesh_src
= mesh_cxr_format(context
.active_object
)
1403 libcxr_write_test_data
.call( pointer(mesh_src
) )
1406 # UI: Preview how the brushes will looks in 3D view
1408 class CXR_PREVIEW_OPERATOR(bpy
.types
.Operator
):
1409 bl_idname
="convexer.preview"
1410 bl_label
="Preview Brushes"
1415 def execute(_
,context
):
1418 def modal(_
,context
,event
):
1419 global cxr_view_mesh
1420 static
= _
.__class
__
1422 if event
.type == 'ESC':
1425 cxr_view_mesh
= None
1426 static
.RUNNING
= False
1431 return {'PASS_THROUGH'}
1433 def invoke(_
,context
,event
):
1434 global cxr_view_shader
, cxr_view_mesh
1435 static
= _
.__class
__
1436 static
.LASTERR
= None
1440 mesh_src
= mesh_cxr_format(context
.active_object
)
1443 world
= libcxr_decompose
.call( mesh_src
, pointer(err
) )
1446 cxr_view_mesh
= None
1450 static
.LASTERR
= ["There is no error", \
1456 "Non-Convex Polygon",\
1462 return {'CANCELLED'}
1464 context
.window_manager
.modal_handler_add(_
)
1465 return {'RUNNING_MODAL'}
1467 # Generate preview using cxr
1469 ptrpreview
= libcxr_world_preview
.call( world
)
1470 preview
= ptrpreview
[0]
1472 vertices
= preview
.vertices
[:preview
.vertex_count
]
1473 vertices
= [(_
[0],_
[1],_
[2]) for _
in vertices
]
1475 colours
= preview
.colours
[:preview
.vertex_count
]
1476 colours
= [(_
[0],_
[1],_
[2],_
[3]) for _
in colours
]
1478 indices
= preview
.indices
[:preview
.indices_count
]
1479 indices
= [ (indices
[i
*3+0],indices
[i
*3+1],indices
[i
*3+2]) \
1480 for i
in range(int(preview
.indices_count
/3)) ]
1482 cxr_view_mesh
= batch_for_shader(
1483 cxr_view_shader
, 'TRIS',
1484 { "pos": vertices
, "color": colours
},
1488 libcxr_free_tri_mesh
.call( ptrpreview
)
1489 libcxr_free_world
.call( world
)
1493 # Allow user to spam the operator
1495 return {'CANCELLED'}
1497 if not static
.RUNNING
:
1498 static
.RUNNING
= True
1499 context
.window_manager
.modal_handler_add(_
)
1500 return {'RUNNING_MODAL'}
1502 # Search for VMF compiler executables in subdirectory
1504 class CXR_DETECT_COMPILERS(bpy
.types
.Operator
):
1505 bl_idname
="convexer.detect_compilers"
1506 bl_label
="Find compilers"
1508 def execute(self
,context
):
1509 scene
= context
.scene
1510 settings
= scene
.cxr_data
1511 subdir
= settings
.subdir
1513 for exename
in ['studiomdl','vbsp','vvis','vrad']:
1514 searchpath
= os
.path
.normpath(F
'{subdir}/../bin/{exename}.exe')
1515 if os
.path
.exists(searchpath
):
1516 settings
[F
'exe_{exename}'] = searchpath
1520 def cxr_compiler_path( compiler
):
1521 settings
= bpy
.context
.scene
.cxr_data
1522 subdir
= settings
.subdir
1523 path
= os
.path
.normpath(F
'{subdir}/../bin/{compiler}.exe')
1525 if os
.path
.exists( path
): return path
1528 def cxr_winepath( path
):
1529 return 'z:'+path
.replace('/','\\')
1531 # Main compile function
1533 class CXR_COMPILER_CHAIN(bpy
.types
.Operator
):
1534 bl_idname
="convexer.chain"
1535 bl_label
="Compile Chain"
1550 def cancel(_
,context
):
1551 global cxr_jobs_batch
1552 static
= _
.__class
__
1553 wm
= context
.window_manager
1555 if static
.SUBPROC
!= None:
1556 static
.SUBPROC
.terminate()
1557 static
.SUBPROC
= None
1559 if static
.TIMER
!= None:
1560 wm
.event_timer_remove( static
.TIMER
)
1565 cxr_jobs_batch
= None
1569 def modal(_
,context
,ev
):
1570 static
= _
.__class
__
1572 if ev
.type == 'TIMER':
1573 global cxr_jobs_batch
1575 if static
.WAIT_REDRAW
:
1577 return {'PASS_THROUGH'}
1578 static
.WAIT_REDRAW
= True
1580 if static
.USER_EXIT
:
1581 print( "Chain USER_EXIT" )
1582 return _
.cancel(context
)
1584 if static
.SUBPROC
!= None:
1585 # Deal with async modes
1586 status
= static
.SUBPROC
.poll()
1589 # Cannot redirect STDOUT through here without causing
1590 # undefined behaviour due to the Blender Python specification.
1592 # Have to write it out to a file and read it back in.
1594 with
open("/tmp/convexer_compile_log.txt","r") as log
:
1595 static
.LOG
= log
.readlines()
1596 return {'PASS_THROUGH'}
1598 #for l in static.SUBPROC.stdout:
1599 # print( F'-> {l.decode("utf-8")}',end='' )
1600 static
.SUBPROC
= None
1603 print(F
'Compiler () error: {status}')
1604 return _
.cancel(context
)
1606 static
.JOBSYS
['jobs'][static
.JOBID
] = None
1607 cxr_jobs_update_graph( static
.JOBINFO
)
1609 return {'PASS_THROUGH'}
1611 # Compile syncronous thing
1612 for sys
in static
.JOBINFO
:
1613 for i
,target
in enumerate(sys
['jobs']):
1616 if callable(sys
['exec']):
1617 print( F
"Run (sync): {static.JOBID} @{time.time()}" )
1619 if not sys
['exec'](*target
):
1620 print( "Job failed" )
1621 return _
.cancel(context
)
1623 sys
['jobs'][i
] = None
1626 # Run external executable (wine)
1627 static
.SUBPROC
= subprocess
.Popen( target
,
1628 stdout
=static
.FILE
,\
1629 stderr
=subprocess
.PIPE
,\
1634 cxr_jobs_update_graph( static
.JOBINFO
)
1636 return {'PASS_THROUGH'}
1639 print( "All jobs completed!" )
1640 cxr_jobs_batch
= None
1643 return _
.cancel(context
)
1645 return {'PASS_THROUGH'}
1647 def invoke(_
,context
,event
):
1648 static
= _
.__class
__
1649 wm
= context
.window_manager
1651 if static
.TIMER
!= None:
1652 print("Chain exiting...")
1653 static
.USER_EXIT
=True
1654 return {'RUNNING_MODAL'}
1656 print("Launching compiler toolchain")
1658 # Run static compilation units now (collect, vmt..)
1659 filepath
= bpy
.data
.filepath
1660 directory
= os
.path
.dirname(filepath
)
1661 settings
= bpy
.context
.scene
.cxr_data
1663 asset_dir
= F
"{directory}/modelsrc"
1664 material_dir
= F
"{settings.subdir}/materials/{settings.project_name}"
1665 model_dir
= F
"{settings.subdir}/models/{settings.project_name}"
1666 output_vmf
= F
"{directory}/{settings.project_name}.vmf"
1668 bsp_local
= F
"{directory}/{settings.project_name}.bsp"
1669 bsp_remote
= F
"{settings.subdir}/maps/{settings.project_name}.bsp"
1670 bsp_packed
= F
"{settings.subdir}/maps/{settings.project_name}_pack.bsp"
1671 packlist
= F
"{directory}/{settings.project_name}_assets.txt"
1673 os
.makedirs( asset_dir
, exist_ok
=True )
1674 os
.makedirs( material_dir
, exist_ok
=True )
1675 os
.makedirs( model_dir
, exist_ok
=True )
1677 static
.FILE
= open(F
"/tmp/convexer_compile_log.txt","w")
1680 sceneinfo
= cxr_scene_collect()
1686 for brush
in sceneinfo
['geo']:
1687 for ms
in brush
['object'].material_slots
:
1688 a_materials
.add( ms
.material
)
1689 if ms
.material
.cxr_data
.shader
== 'VertexLitGeneric':
1690 errmat
= ms
.material
.name
1691 errnam
= brush
['object'].name
1692 print( F
"Vertex shader {errmat} used on {errnam}")
1693 return {'CANCELLED'}
1697 for ent
in sceneinfo
['entities']:
1698 if ent
['object'] == None: continue
1700 if ent
['classname'] == 'prop_static':
1702 if isinstance(obj
,bpy
.types
.Collection
):
1704 a_models
.add( target
)
1705 model_jobs
+= [(target
, ent
['origin'], asset_dir
, \
1706 settings
.project_name
, ent
['transform'])]
1708 target
= obj
.instance_collection
1709 if target
in a_models
:
1711 a_models
.add( target
)
1713 # TODO: Should take into account collection instancing offset
1714 model_jobs
+= [(target
, [0,0,0], asset_dir
, \
1715 settings
.project_name
, ent
['transform'])]
1717 elif ent
['object'].type == 'MESH':
1718 for ms
in ent
['object'].material_slots
:
1719 a_materials
.add( ms
.material
)
1721 for mdl
in a_models
:
1722 uid
= asset_uid(mdl
)
1723 qc_jobs
+= [F
'{uid}.qc']
1725 for obj
in mdl
.objects
:
1726 for ms
in obj
.material_slots
:
1727 a_materials
.add( ms
.material
)
1728 if ms
.material
.cxr_data
.shader
== 'LightMappedGeneric' or \
1729 ms
.material
.cxr_data
.shader
== 'WorldVertexTransition':
1731 errmat
= ms
.material
.name
1733 print( F
"Lightmapped shader {errmat} used on {errnam}")
1734 return {'CANCELLED'}
1737 for mat
in a_materials
:
1738 for pair
in compile_material(mat
):
1743 if isinstance(prop
,bpy
.types
.Image
):
1745 if 'flags' in pdef
: flags
= pdef
['flags']
1746 if prop
not in image_jobs
:
1747 image_jobs
+= [(prop
,)]
1748 prop
.cxr_data
.flags
= flags
1751 with
open( packlist
, "w" ) as fp
:
1753 for mat
in a_materials
:
1754 if mat
.cxr_data
.shader
== 'Builtin': continue
1755 fp
.write(F
"{asset_path('materials',mat)}.vmt\n")
1756 fp
.write(F
"{cxr_winepath(asset_full_path('materials',mat))}.vmt\n")
1758 for img_job
in image_jobs
:
1760 fp
.write(F
"{asset_path('materials',img)}.vtf\n")
1761 fp
.write(F
"{cxr_winepath(asset_full_path('materials',img))}.vtf\n")
1763 for mdl
in a_models
:
1764 local
= asset_path('models',mdl
)
1765 winep
= cxr_winepath(asset_full_path('models',mdl
))
1767 fp
.write(F
"{local}.vvd\n")
1768 fp
.write(F
"{winep}.vvd\n")
1769 fp
.write(F
"{local}.dx90.vtx\n")
1770 fp
.write(F
"{winep}.dx90.vtx\n")
1771 fp
.write(F
"{local}.mdl\n")
1772 fp
.write(F
"{winep}.mdl\n")
1773 fp
.write(F
"{local}.vvd\n")
1774 fp
.write(F
"{winep}.vvd\n")
1776 if cxr_modelsrc_vphys(mdl
):
1777 fp
.write(F
"{local}.phy\n")
1778 fp
.write(F
"{winep}.phy\n")
1784 if settings
.comp_vmf
:
1785 static
.JOBINFO
+= [{
1786 "title": "Convexer",
1788 "colour": (1.0,0.3,0.1,1.0),
1789 "exec": cxr_export_vmf
,
1790 "jobs": [(sceneinfo
,output_vmf
)]
1793 if settings
.comp_textures
:
1794 if len(image_jobs
) > 0:
1795 static
.JOBINFO
+= [{
1796 "title": "Textures",
1798 "colour": (0.1,1.0,0.3,1.0),
1799 "exec": compile_image
,
1803 game
= cxr_winepath( settings
.subdir
)
1805 '-game', game
, settings
.project_name
1809 if settings
.comp_models
:
1810 if len(model_jobs
) > 0:
1811 static
.JOBINFO
+= [{
1814 "colour": (0.5,0.5,1.0,1.0),
1815 "exec": cxr_export_modelsrc
,
1819 if len(qc_jobs
) > 0:
1820 static
.JOBINFO
+= [{
1821 "title": "StudioMDL",
1823 "colour": (0.8,0.1,0.1,1.0),
1824 "exec": "studiomdl",
1825 "jobs": [[settings
[F
'exe_studiomdl']] + [\
1826 '-nop4', '-game', game
, qc
] for qc
in qc_jobs
],
1831 if settings
.comp_compile
:
1832 static
.JOBINFO
+= [{
1835 "colour": (0.1,0.2,1.0,1.0),
1837 "jobs": [[settings
[F
'exe_vbsp']] + args
],
1841 static
.JOBINFO
+= [{
1844 "colour": (0.9,0.5,0.5,1.0),
1846 "jobs": [[settings
[F
'exe_vvis']] + ['-fast'] + args
],
1850 vrad_opt
= settings
.opt_vrad
.split()
1851 static
.JOBINFO
+= [{
1854 "colour": (0.9,0.2,0.3,1.0),
1856 "jobs": [[settings
[F
'exe_vrad']] + vrad_opt
+ args
],
1860 static
.JOBINFO
+= [{
1863 "colour": (0.0,1.0,0.4,1.0),
1864 "exec": cxr_patchmap
,
1865 "jobs": [(bsp_local
,bsp_remote
)]
1868 if settings
.comp_pack
:
1869 static
.JOBINFO
+= [{
1872 "colour": (0.2,0.2,0.2,1.0),
1874 "jobs": [[cxr_compiler_path("bspzip"), '-addlist', \
1875 cxr_winepath(bsp_remote
),
1876 cxr_winepath(packlist
),
1877 cxr_winepath(bsp_packed
) ]],
1881 if len(static
.JOBINFO
) == 0:
1882 return {'CANCELLED'}
1884 static
.USER_EXIT
=False
1885 static
.TIMER
=wm
.event_timer_add(0.1,window
=context
.window
)
1886 wm
.modal_handler_add(_
)
1888 cxr_jobs_update_graph( static
.JOBINFO
)
1890 return {'RUNNING_MODAL'}
1892 class CXR_RESET_HASHES(bpy
.types
.Operator
):
1893 bl_idname
="convexer.hash_reset"
1894 bl_label
="Reset asset hashes"
1896 def execute(_
,context
):
1897 for c
in bpy
.data
.collections
:
1898 c
.cxr_data
.last_hash
= F
"<RESET>{time.time()}"
1899 c
.cxr_data
.asset_id
=0
1901 for t
in bpy
.data
.images
:
1902 t
.cxr_data
.last_hash
= F
"<RESET>{time.time()}"
1903 t
.cxr_data
.asset_id
=0
1907 class CXR_COMPILE_MATERIAL(bpy
.types
.Operator
):
1908 bl_idname
="convexer.matcomp"
1909 bl_label
="Recompile Material"
1911 def execute(_
,context
):
1912 active_obj
= bpy
.context
.active_object
1913 active_mat
= active_obj
.active_material
1915 #TODO: reduce code dupe (L1663)
1916 for pair
in compile_material(active_mat
):
1921 if isinstance(prop
,bpy
.types
.Image
):
1923 if 'flags' in pdef
: flags
= pdef
['flags']
1924 prop
.cxr_data
.flags
= flags
1926 compile_image( prop
)
1928 settings
= bpy
.context
.scene
.cxr_data
1929 with
open(F
'{settings.subdir}/cfg/convexer_mat_update.cfg','w') as o
:
1930 o
.write(F
'mat_reloadmaterial {asset_name(active_mat)}')
1933 with
open(F
'{settings.subdir}/cfg/convexer.cfg','w') as o
:
1934 o
.write('sv_cheats 1\n')
1935 o
.write('mp_warmup_pausetimer 1\n')
1936 o
.write('bot_kick\n')
1937 o
.write('alias cxr_reload "exec convexer_mat_update"\n')
1942 # ------------------------------------------------------------------------------
1944 # Helper buttons for 3d toolbox view
1946 class CXR_VIEW3D( bpy
.types
.Panel
):
1947 bl_idname
= "VIEW3D_PT_convexer"
1948 bl_label
= "Convexer"
1949 bl_space_type
= 'VIEW_3D'
1950 bl_region_type
= 'UI'
1951 bl_category
= "Convexer"
1954 def poll(cls
, context
):
1955 return (context
.object is not None)
1957 def draw(_
, context
):
1961 row
.operator("convexer.preview")
1963 if CXR_PREVIEW_OPERATOR
.LASTERR
!= None:
1965 box
.label(text
=CXR_PREVIEW_OPERATOR
.LASTERR
, icon
='ERROR')
1967 # Main scene properties interface, where all the settings go
1969 class CXR_INTERFACE(bpy
.types
.Panel
):
1971 bl_idname
="SCENE_PT_convexer"
1972 bl_space_type
='PROPERTIES'
1973 bl_region_type
='WINDOW'
1976 def draw(_
,context
):
1977 _
.layout
.operator("convexer.reload")
1978 _
.layout
.operator("convexer.dev_test")
1979 _
.layout
.operator("convexer.preview")
1980 _
.layout
.operator("convexer.hash_reset")
1982 settings
= context
.scene
.cxr_data
1984 _
.layout
.prop(settings
, "debug")
1985 _
.layout
.prop(settings
, "scale_factor")
1986 _
.layout
.prop(settings
, "skybox_scale_factor")
1987 _
.layout
.prop(settings
, "skyname" )
1988 _
.layout
.prop(settings
, "lightmap_scale")
1989 _
.layout
.prop(settings
, "light_scale" )
1990 _
.layout
.prop(settings
, "image_quality" )
1992 box
= _
.layout
.box()
1994 box
.prop(settings
, "project_name")
1995 box
.prop(settings
, "subdir")
1997 box
= _
.layout
.box()
1998 box
.operator("convexer.detect_compilers")
1999 box
.prop(settings
, "exe_studiomdl")
2000 box
.prop(settings
, "exe_vbsp")
2001 box
.prop(settings
, "exe_vvis")
2002 box
.prop(settings
, "exe_vrad")
2003 box
.prop(settings
, "opt_vrad")
2007 row
.prop(settings
,"comp_vmf")
2008 row
.prop(settings
,"comp_textures")
2009 row
.prop(settings
,"comp_models")
2010 row
.prop(settings
,"comp_compile")
2011 row
.prop(settings
,"comp_pack")
2013 text
= "Compile" if CXR_COMPILER_CHAIN
.TIMER
== None else "Cancel"
2016 row
.operator("convexer.chain", text
=text
)
2019 class CXR_MATERIAL_PANEL(bpy
.types
.Panel
):
2020 bl_label
="VMT Properties"
2021 bl_idname
="SCENE_PT_convexer_vmt"
2022 bl_space_type
='PROPERTIES'
2023 bl_region_type
='WINDOW'
2024 bl_context
="material"
2026 def draw(_
,context
):
2027 active_object
= bpy
.context
.active_object
2028 if active_object
== None: return
2030 active_material
= active_object
.active_material
2031 if active_material
== None: return
2033 properties
= active_material
.cxr_data
2034 info
= material_info( active_material
)
2036 _
.layout
.label(text
=F
"{info['name']} @{info['res'][0]}x{info['res'][1]}")
2037 row
= _
.layout
.row()
2038 row
.prop( properties
, "shader" )
2039 row
.operator( "convexer.matcomp" )
2041 #for xk in info: _.layout.label(text=F"{xk}:={info[xk]}")
2043 def _mtex( name
, img
, uiParent
):
2046 box
= uiParent
.box()
2047 box
.label( text
=F
'{name} "{img.filepath}"' )
2049 if ((x
& (x
- 1)) == 0):
2052 closest_diff
= 10000000
2054 dist
= abs((1 << i
)-x
)
2055 if dist
< closest_diff
:
2060 return 1 << (closest
+1)
2062 return 1 << (closest
-1)
2067 row
.prop( img
.cxr_data
, "export_res" )
2068 row
.prop( img
.cxr_data
, "fmt" )
2071 row
.prop( img
.cxr_data
, "mipmap" )
2072 row
.prop( img
.cxr_data
, "lod" )
2073 row
.prop( img
.cxr_data
, "clamp" )
2075 img
.cxr_data
.export_res
[0] = _p2( img
.cxr_data
.export_res
[0] )
2076 img
.cxr_data
.export_res
[1] = _p2( img
.cxr_data
.export_res
[1] )
2078 def _mview( layer
, uiParent
):
2082 if isinstance(layer
[decl
],dict): # $property definition
2084 ptype
= pdef
['type']
2090 if ('shaders' in pdef
) and \
2091 (properties
.shader
not in pdef
['shaders']):
2094 if ptype
== 'intrinsic':
2095 if decl
not in info
:
2100 if isinstance(pdef
[ch
],dict):
2101 if ptype
== 'ui' or ptype
== 'intrinsic':
2103 elif getattr(properties
,decl
) == pdef
['default']:
2106 thisnode
= uiParent
.box()
2110 thisnode
.label( text
=decl
)
2111 elif ptype
== 'intrinsic':
2112 if isinstance(info
[decl
], bpy
.types
.Image
):
2113 _mtex( decl
, info
[decl
], thisnode
)
2115 # hidden intrinsic value.
2116 # Means its a float array or something not an image
2117 thisnode
.label(text
=F
"-- hidden intrinsic '{decl}' --")
2119 thisnode
.prop(properties
,decl
)
2120 if expandview
: _mview(pdef
,thisnode
)
2122 _mview( cxr_shader_params
, _
.layout
)
2124 def cxr_entity_changeclass(_
,context
):
2125 active_object
= context
.active_object
2127 # Create ID properties
2129 classname
= cxr_custom_class(active_object
)
2131 if classname
in cxr_entities
:
2132 entdef
= cxr_entities
[classname
]
2134 kvs
= entdef
['keyvalues']
2135 if callable(kvs
): kvs
= kvs(active_object
)
2141 if callable(kv
) or not isinstance(kv
,dict): continue
2143 if key
not in active_object
:
2144 active_object
[key
] = kv
['default']
2145 id_prop
= active_object
.id_properties_ui(key
)
2146 id_prop
.update(default
=kv
['default'])
2148 class CXR_ENTITY_PANEL(bpy
.types
.Panel
):
2149 bl_label
="Entity Config"
2150 bl_idname
="SCENE_PT_convexer_entity"
2151 bl_space_type
='PROPERTIES'
2152 bl_region_type
='WINDOW'
2155 def draw(_
,context
):
2156 active_object
= bpy
.context
.active_object
2158 if active_object
== None: return
2161 "scale": bpy
.context
.scene
.cxr_data
.scale_factor
,
2165 ecn
= cxr_intrinsic_classname( active_object
)
2166 classname
= cxr_custom_class( active_object
)
2169 if active_object
.type == 'MESH':
2170 _
.layout
.prop( active_object
.cxr_data
, 'brushclass' )
2171 else: _
.layout
.prop( active_object
.cxr_data
, 'classname' )
2173 if classname
== 'NONE':
2176 _
.layout
.label(text
=F
"<implementation defined ({ecn})>")
2177 _
.layout
.enabled
=False
2180 kvs
= cxr_entity_keyvalues( {
2181 "object": active_object
,
2182 "transform": default_context
,
2183 "classname": classname
2189 _
.layout
.prop( active_object
, F
'["cxrkv_{kv[0]}"]', text
=kv
[0])
2191 row
= _
.layout
.row()
2193 row
.label( text
=F
'{kv[0]}: {repr(kv[2])}' )
2195 _
.layout
.label( text
=F
"ERROR: NO CLASS DEFINITION" )
2197 class CXR_LIGHT_PANEL(bpy
.types
.Panel
):
2198 bl_label
= "Source Settings"
2199 bl_idname
= "LIGHT_PT_cxr"
2200 bl_space_type
= 'PROPERTIES'
2201 bl_region_type
= 'WINDOW'
2204 def draw(self
, context
):
2205 layout
= self
.layout
2206 scene
= context
.scene
2208 active_object
= bpy
.context
.active_object
2209 if active_object
== None: return
2211 if active_object
.type == 'LIGHT' or \
2212 active_object
.type == 'LIGHT_PROBE':
2214 properties
= active_object
.data
.cxr_data
2216 if active_object
.type == 'LIGHT':
2217 layout
.prop( properties
, "realtime" )
2218 elif active_object
.type == 'LIGHT_PROBE':
2219 layout
.prop( properties
, "size" )
2221 class CXR_COLLECTION_PANEL(bpy
.types
.Panel
):
2222 bl_label
= "Source Settings"
2223 bl_idname
= "COL_PT_cxr"
2224 bl_space_type
= 'PROPERTIES'
2225 bl_region_type
= 'WINDOW'
2226 bl_context
= "collection"
2228 def draw(self
, context
):
2229 layout
= self
.layout
2230 scene
= context
.scene
2232 active_collection
= bpy
.context
.collection
2234 if active_collection
!= None:
2235 layout
.prop( active_collection
.cxr_data
, "shadow_caster" )
2236 layout
.prop( active_collection
.cxr_data
, "texture_shadows" )
2237 layout
.prop( active_collection
.cxr_data
, "preserve_order" )
2238 layout
.prop( active_collection
.cxr_data
, "surfaceprop" )
2241 # ------------------------------------------------------------------------------
2243 class CXR_IMAGE_SETTINGS(bpy
.types
.PropertyGroup
):
2244 export_res
: bpy
.props
.IntVectorProperty(
2246 description
="Texture Export Resolution",
2252 fmt
: bpy
.props
.EnumProperty(
2255 ('DXT1', "DXT1", "BC1 compressed", '', 0),
2256 ('DXT5', "DXT5", "BC3 compressed", '', 1),
2257 ('RGB', "RGB", "Uncompressed", '', 2),
2258 ('RGBA', "RGBA", "Uncompressed (with alpha)", '', 3)
2260 description
="Image format",
2263 last_hash
: bpy
.props
.StringProperty( name
="" )
2264 asset_id
: bpy
.props
.IntProperty(name
="intl_assetid",default
=0)
2266 mipmap
: bpy
.props
.BoolProperty(name
="MIP",default
=True)
2267 lod
: bpy
.props
.BoolProperty(name
="LOD",default
=True)
2268 clamp
: bpy
.props
.BoolProperty(name
="CLAMP",default
=False)
2269 flags
: bpy
.props
.IntProperty(name
="flags",default
=0)
2271 class CXR_LIGHT_SETTINGS(bpy
.types
.PropertyGroup
):
2272 realtime
: bpy
.props
.BoolProperty(name
="Realtime Light", default
=True)
2274 class CXR_CUBEMAP_SETTINGS(bpy
.types
.PropertyGroup
):
2275 size
: bpy
.props
.EnumProperty(
2278 ('1',"1x1",'','',0),
2279 ('2',"2x2",'','',1),
2280 ('3',"4x4",'','',2),
2281 ('4',"8x8",'','',3),
2282 ('5',"16x16",'','',4),
2283 ('6',"32x32",'','',5),
2284 ('7',"64x64",'','',6),
2285 ('8',"128x128",'','',7),
2286 ('9',"256x256",'','',8)
2288 description
="Texture resolution",
2291 class CXR_ENTITY_SETTINGS(bpy
.types
.PropertyGroup
):
2292 entity
: bpy
.props
.BoolProperty(name
="")
2294 enum_pointents
= [('NONE',"None","")]
2295 enum_brushents
= [('NONE',"None","")]
2297 for classname
in cxr_entities
:
2298 entdef
= cxr_entities
[classname
]
2299 if 'allow' in entdef
:
2300 itm
= [(classname
, classname
, "")]
2301 if 'EMPTY' in entdef
['allow']: enum_pointents
+= itm
2302 else: enum_brushents
+= itm
2304 classname
: bpy
.props
.EnumProperty(items
=enum_pointents
, name
="Class", \
2305 update
=cxr_entity_changeclass
, default
='NONE' )
2307 brushclass
: bpy
.props
.EnumProperty(items
=enum_brushents
, name
="Class", \
2308 update
=cxr_entity_changeclass
, default
='NONE' )
2310 class CXR_MODEL_SETTINGS(bpy
.types
.PropertyGroup
):
2311 last_hash
: bpy
.props
.StringProperty( name
="" )
2312 asset_id
: bpy
.props
.IntProperty(name
="vmf_settings",default
=0)
2313 shadow_caster
: bpy
.props
.BoolProperty( name
="Shadow caster", default
=True )
2314 texture_shadows
: bpy
.props
.BoolProperty( name
="Texture Shadows", default
=False )
2315 preserve_order
: bpy
.props
.BoolProperty( name
="Preserve Order", default
=False )
2316 surfaceprop
: bpy
.props
.StringProperty( name
="Suface prop",default
="default" )
2318 class CXR_SCENE_SETTINGS(bpy
.types
.PropertyGroup
):
2319 project_name
: bpy
.props
.StringProperty( name
="Project Name" )
2320 subdir
: bpy
.props
.StringProperty( name
="Subdirectory" )
2322 exe_studiomdl
: bpy
.props
.StringProperty( name
="studiomdl" )
2323 exe_vbsp
: bpy
.props
.StringProperty( name
="vbsp" )
2324 opt_vbsp
: bpy
.props
.StringProperty( name
="args" )
2325 exe_vvis
: bpy
.props
.StringProperty( name
="vvis" )
2326 opt_vvis
: bpy
.props
.StringProperty( name
="args" )
2327 exe_vrad
: bpy
.props
.StringProperty( name
="vrad" )
2328 opt_vrad
: bpy
.props
.StringProperty( name
="args", \
2329 default
="-reflectivityScale 0.35 -aoscale 1.4 -final -textureshadows -hdr -StaticPropLighting -StaticPropPolys" )
2331 debug
: bpy
.props
.BoolProperty(name
="Debug",default
=False)
2332 scale_factor
: bpy
.props
.FloatProperty( name
="VMF Scale factor", \
2333 default
=32.0,min=1.0)
2334 skybox_scale_factor
: bpy
.props
.FloatProperty( name
="Sky Scale factor", \
2335 default
=1.0,min=0.01)
2336 skyname
: bpy
.props
.StringProperty(name
="Skyname",default
="sky_csgo_night02b")
2337 skybox_offset
: bpy
.props
.FloatProperty(name
="Sky offset",default
=-4096.0)
2338 light_scale
: bpy
.props
.FloatProperty(name
="Light Scale",default
=1.0/5.0)
2339 include_names
: bpy
.props
.BoolProperty(name
="Append original file names",\
2341 lightmap_scale
: bpy
.props
.IntProperty(name
="Global Lightmap Scale",\
2343 image_quality
: bpy
.props
.IntProperty(name
="Texture Quality (0-18)",\
2344 default
=8, min=0, max=18 )
2346 comp_vmf
: bpy
.props
.BoolProperty(name
="VMF",default
=True)
2347 comp_models
: bpy
.props
.BoolProperty(name
="Models",default
=True)
2348 comp_textures
: bpy
.props
.BoolProperty(name
="Textures",default
=True)
2349 comp_compile
: bpy
.props
.BoolProperty(name
="Compile",default
=True)
2350 comp_pack
: bpy
.props
.BoolProperty(name
="Pack",default
=False)
2352 classes
= [ CXR_RELOAD
, CXR_DEV_OPERATOR
, CXR_INTERFACE
, \
2353 CXR_MATERIAL_PANEL
, CXR_IMAGE_SETTINGS
,\
2354 CXR_MODEL_SETTINGS
, CXR_ENTITY_SETTINGS
, CXR_CUBEMAP_SETTINGS
,\
2355 CXR_LIGHT_SETTINGS
, CXR_SCENE_SETTINGS
, CXR_DETECT_COMPILERS
,\
2356 CXR_ENTITY_PANEL
, CXR_LIGHT_PANEL
, CXR_PREVIEW_OPERATOR
,\
2357 CXR_VIEW3D
, CXR_COMPILER_CHAIN
, CXR_RESET_HASHES
,\
2358 CXR_COMPILE_MATERIAL
, CXR_COLLECTION_PANEL
]
2360 vmt_param_dynamic_class
= None
2363 global cxr_view_draw_handler
, vmt_param_dynamic_class
, cxr_ui_handler
2366 bpy
.utils
.register_class(c
)
2368 # Build dynamic VMT properties class defined by cxr_shader_params
2369 annotations_dict
= {}
2371 def _dvmt_propogate(layer
):
2372 nonlocal annotations_dict
2375 if isinstance(layer
[decl
],dict): # $property definition
2379 if pdef
['type'] == 'bool':
2380 prop
= bpy
.props
.BoolProperty(\
2381 name
= pdef
['name'],\
2382 default
= pdef
['default'])
2384 elif pdef
['type'] == 'float':
2385 prop
= bpy
.props
.FloatProperty(\
2386 name
= pdef
['name'],\
2387 default
= pdef
['default'])
2389 elif pdef
['type'] == 'vector':
2390 if 'subtype' in pdef
:
2391 prop
= bpy
.props
.FloatVectorProperty(\
2392 name
= pdef
['name'],\
2393 subtype
= pdef
['subtype'],\
2394 default
= pdef
['default'],\
2395 size
= len(pdef
['default']))
2397 prop
= bpy
.props
.FloatVectorProperty(\
2398 name
= pdef
['name'],\
2399 default
= pdef
['default'],\
2400 size
= len(pdef
['default']))
2402 elif pdef
['type'] == 'string':
2403 prop
= bpy
.props
.StringProperty(\
2404 name
= pdef
['name'],\
2405 default
= pdef
['default'])
2407 elif pdef
['type'] == 'enum':
2408 prop
= bpy
.props
.EnumProperty(\
2409 name
= pdef
['name'],\
2410 items
= pdef
['items'],\
2411 default
= pdef
['default'])
2414 annotations_dict
[decl
] = prop
2416 # Recurse into sub-definitions
2417 _dvmt_propogate(pdef
)
2419 annotations_dict
["shader"] = bpy
.props
.EnumProperty(\
2422 cxr_shaders
[_
]["name"],\
2423 '') for _
in cxr_shaders
],\
2424 default
= next(iter(cxr_shaders
)))
2426 annotations_dict
["asset_id"] = bpy
.props
.IntProperty(name
="intl_assetid",\
2429 _dvmt_propogate( cxr_shader_params
)
2430 vmt_param_dynamic_class
= type(
2432 (bpy
.types
.PropertyGroup
,),{
2433 "__annotations__": annotations_dict
2437 bpy
.utils
.register_class( vmt_param_dynamic_class
)
2440 bpy
.types
.Material
.cxr_data
= \
2441 bpy
.props
.PointerProperty(type=vmt_param_dynamic_class
)
2442 bpy
.types
.Image
.cxr_data
= \
2443 bpy
.props
.PointerProperty(type=CXR_IMAGE_SETTINGS
)
2444 bpy
.types
.Object
.cxr_data
= \
2445 bpy
.props
.PointerProperty(type=CXR_ENTITY_SETTINGS
)
2446 bpy
.types
.Collection
.cxr_data
= \
2447 bpy
.props
.PointerProperty(type=CXR_MODEL_SETTINGS
)
2448 bpy
.types
.Light
.cxr_data
= \
2449 bpy
.props
.PointerProperty(type=CXR_LIGHT_SETTINGS
)
2450 bpy
.types
.LightProbe
.cxr_data
= \
2451 bpy
.props
.PointerProperty(type=CXR_CUBEMAP_SETTINGS
)
2452 bpy
.types
.Scene
.cxr_data
= \
2453 bpy
.props
.PointerProperty(type=CXR_SCENE_SETTINGS
)
2455 # CXR Scene settings
2458 cxr_view_draw_handler
= bpy
.types
.SpaceView3D
.draw_handler_add(\
2459 cxr_draw
,(),'WINDOW','POST_VIEW')
2461 cxr_ui_handler
= bpy
.types
.SpaceView3D
.draw_handler_add(\
2462 cxr_ui
,(None,None),'WINDOW','POST_PIXEL')
2464 bpy
.app
.handlers
.load_post
.append(cxr_on_load
)
2465 bpy
.app
.handlers
.depsgraph_update_post
.append(cxr_dgraph_update
)
2468 global cxr_view_draw_handler
, vmt_param_dynamic_class
, cxr_ui_handler
2470 bpy
.utils
.unregister_class( vmt_param_dynamic_class
)
2472 bpy
.utils
.unregister_class(c
)
2474 bpy
.app
.handlers
.depsgraph_update_post
.remove(cxr_dgraph_update
)
2475 bpy
.app
.handlers
.load_post
.remove(cxr_on_load
)
2477 bpy
.types
.SpaceView3D
.draw_handler_remove(cxr_view_draw_handler
,'WINDOW')
2478 bpy
.types
.SpaceView3D
.draw_handler_remove(cxr_ui_handler
,'WINDOW')