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,"This is a stoopid error\nWIthiuawdnaw")
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
)]
221 class cxr_static_loop(Structure
):
222 _fields_
= [("index",c_int32
),
223 ("edge_index",c_int32
),
226 class cxr_polygon(Structure
):
227 _fields_
= [("loop_start",c_int32
),
228 ("loop_total",c_int32
),
229 ("normal",c_double
* 3),
230 ("center",c_double
* 3),
231 ("material_id",c_int32
)]
233 class cxr_material(Structure
):
234 _fields_
= [("res",c_int32
* 2),
237 class cxr_static_mesh(Structure
):
238 _fields_
= [("vertices",POINTER(c_double
* 3)),
239 ("edges",POINTER(cxr_edge
)),
240 ("loops",POINTER(cxr_static_loop
)),
241 ("polys",POINTER(cxr_polygon
)),
242 ("materials",POINTER(cxr_material
)),
244 ("poly_count",c_int32
),
245 ("vertex_count",c_int32
),
246 ("edge_count",c_int32
),
247 ("loop_count",c_int32
),
248 ("material_count",c_int32
)]
250 class cxr_tri_mesh(Structure
):
251 _fields_
= [("vertices",POINTER(c_double
*3)),
252 ("colours",POINTER(c_double
*4)),
253 ("indices",POINTER(c_int32
)),
254 ("indices_count",c_int32
),
255 ("vertex_count",c_int32
)]
257 class cxr_vmf_context(Structure
):
258 _fields_
= [("mapversion",c_int32
),
259 ("skyname",c_char_p
),
260 ("detailvbsp",c_char_p
),
261 ("detailmaterial",c_char_p
),
263 ("offset",c_double
*3),
264 ("lightmap_scale",c_int32
),
265 ("brush_count",c_int32
),
266 ("entity_count",c_int32
),
267 ("face_count",c_int32
)]
269 # Convert blenders mesh format into CXR's static format (they are very similar)
271 def mesh_cxr_format(obj
):
274 if bpy
.context
.active_object
!= None:
275 orig_state
= obj
.mode
276 if orig_state
!= 'OBJECT':
277 bpy
.ops
.object.mode_set(mode
='OBJECT')
279 dgraph
= bpy
.context
.evaluated_depsgraph_get()
280 data
= obj
.evaluated_get(dgraph
).data
282 _
,mtx_rot
,_
= obj
.matrix_world
.decompose()
284 mesh
= cxr_static_mesh()
286 vertex_data
= ((c_double
*3)*len(data
.vertices
))()
287 for i
, vert
in enumerate(data
.vertices
):
288 v
= obj
.matrix_world
@ vert
.co
289 vertex_data
[i
][0] = c_double(v
[0])
290 vertex_data
[i
][1] = c_double(v
[1])
291 vertex_data
[i
][2] = c_double(v
[2])
293 loop_data
= (cxr_static_loop
*len(data
.loops
))()
294 polygon_data
= (cxr_polygon
*len(data
.polygons
))()
296 for i
, poly
in enumerate(data
.polygons
):
297 loop_start
= poly
.loop_start
298 loop_end
= poly
.loop_start
+ poly
.loop_total
299 for loop_index
in range(loop_start
, loop_end
):
300 loop
= data
.loops
[loop_index
]
301 loop_data
[loop_index
].index
= loop
.vertex_index
302 loop_data
[loop_index
].edge_index
= loop
.edge_index
305 uv
= data
.uv_layers
.active
.data
[loop_index
].uv
306 loop_data
[loop_index
].uv
[0] = c_double(uv
[0])
307 loop_data
[loop_index
].uv
[1] = c_double(uv
[1])
309 loop_data
[loop_index
].uv
[0] = c_double(0.0)
310 loop_data
[loop_index
].uv
[1] = c_double(0.0)
311 center
= obj
.matrix_world
@ poly
.center
312 normal
= mtx_rot
@ poly
.normal
314 polygon_data
[i
].loop_start
= poly
.loop_start
315 polygon_data
[i
].loop_total
= poly
.loop_total
316 polygon_data
[i
].normal
[0] = normal
[0]
317 polygon_data
[i
].normal
[1] = normal
[1]
318 polygon_data
[i
].normal
[2] = normal
[2]
319 polygon_data
[i
].center
[0] = center
[0]
320 polygon_data
[i
].center
[1] = center
[1]
321 polygon_data
[i
].center
[2] = center
[2]
322 polygon_data
[i
].material_id
= poly
.material_index
324 edge_data
= (cxr_edge
*len(data
.edges
))()
326 for i
, edge
in enumerate(data
.edges
):
327 edge_data
[i
].i0
= edge
.vertices
[0]
328 edge_data
[i
].i1
= edge
.vertices
[1]
329 edge_data
[i
].freestyle
= edge
.use_freestyle_mark
331 material_data
= (cxr_material
*len(obj
.material_slots
))()
333 for i
, ms
in enumerate(obj
.material_slots
):
334 inf
= material_info(ms
.material
)
335 material_data
[i
].res
[0] = inf
['res'][0]
336 material_data
[i
].res
[1] = inf
['res'][1]
337 material_data
[i
].name
= inf
['name'].encode('utf-8')
339 mesh
.edges
= cast(edge_data
, POINTER(cxr_edge
))
340 mesh
.vertices
= cast(vertex_data
, POINTER(c_double
*3))
341 mesh
.loops
= cast(loop_data
,POINTER(cxr_static_loop
))
342 mesh
.polys
= cast(polygon_data
, POINTER(cxr_polygon
))
343 mesh
.materials
= cast(material_data
, POINTER(cxr_material
))
345 mesh
.poly_count
= len(data
.polygons
)
346 mesh
.vertex_count
= len(data
.vertices
)
347 mesh
.edge_count
= len(data
.edges
)
348 mesh
.loop_count
= len(data
.loops
)
349 mesh
.material_count
= len(obj
.material_slots
)
351 if orig_state
!= None:
352 bpy
.ops
.object.mode_set(mode
=orig_state
)
356 # Callback ctypes indirection things.. not really sure.
357 c_libcxr_log_callback
= None
358 c_libcxr_line_callback
= None
361 # -------------------------------------------------------------
362 libcxr_decompose
= extern( "cxr_decompose",
363 [POINTER(cxr_static_mesh
), POINTER(c_int32
)],
366 libcxr_free_world
= extern( "cxr_free_world",
370 libcxr_write_test_data
= extern( "cxr_write_test_data",
371 [POINTER(cxr_static_mesh
)],
374 libcxr_world_preview
= extern( "cxr_world_preview",
376 POINTER(cxr_tri_mesh
)
378 libcxr_free_tri_mesh
= extern( "cxr_free_tri_mesh",
382 libcxr_begin_vmf
= extern( "cxr_begin_vmf",
383 [POINTER(cxr_vmf_context
), c_void_p
],
386 libcxr_vmf_begin_entities
= extern( "cxr_vmf_begin_entities",
387 [POINTER(cxr_vmf_context
), c_void_p
],
390 libcxr_push_world_vmf
= extern("cxr_push_world_vmf",
391 [c_void_p
,POINTER(cxr_vmf_context
),c_void_p
],
394 libcxr_end_vmf
= extern( "cxr_end_vmf",
395 [POINTER(cxr_vmf_context
),c_void_p
],
399 # VDF + with open wrapper
400 libcxr_vdf_open
= extern( "cxr_vdf_open", [c_char_p
], c_void_p
)
401 libcxr_vdf_close
= extern( "cxr_vdf_close", [c_void_p
], None )
402 libcxr_vdf_put
= extern( "cxr_vdf_put", [c_void_p
,c_char_p
], None )
403 libcxr_vdf_node
= extern( "cxr_vdf_node", [c_void_p
,c_char_p
], None )
404 libcxr_vdf_edon
= extern( "cxr_vdf_edon", [c_void_p
], None )
405 libcxr_vdf_kv
= extern( "cxr_vdf_kv", [c_void_p
,c_char_p
,c_char_p
], None )
407 class vdf_structure():
408 def __init__(_
,path
):
411 _
.fp
= libcxr_vdf_open
.call( _
.path
.encode('utf-8') )
413 print( F
"Could not open file {_.path}" )
416 def __exit__(_
,type,value
,traceback
):
418 libcxr_vdf_close
.call(_
.fp
)
420 libcxr_vdf_put
.call(_
.fp
, s
.encode('utf-8') )
422 libcxr_vdf_node
.call(_
.fp
, name
.encode('utf-8') )
424 libcxr_vdf_edon
.call(_
.fp
)
426 libcxr_vdf_kv
.call(_
.fp
, k
.encode('utf-8'), v
.encode('utf-8'))
429 libcxr_lightpatch_bsp
= extern( "cxr_lightpatch_bsp", [c_char_p
], None )
431 libcxr_funcs
= [ libcxr_decompose
, libcxr_free_world
, libcxr_begin_vmf
, \
432 libcxr_vmf_begin_entities
, libcxr_push_world_vmf
, \
433 libcxr_end_vmf
, libcxr_vdf_open
, libcxr_vdf_close
, \
434 libcxr_vdf_put
, libcxr_vdf_node
, libcxr_vdf_edon
,
435 libcxr_vdf_kv
, libcxr_lightpatch_bsp
, libcxr_write_test_data
,\
436 libcxr_world_preview
, libcxr_free_tri_mesh
]
440 def libcxr_log_callback(logStr
):
441 print( F
"{logStr.decode('utf-8')}",end
='' )
443 cxr_line_positions
= None
444 cxr_line_colours
= None
446 def cxr_reset_lines():
447 global cxr_line_positions
, cxr_line_colours
449 cxr_line_positions
= []
450 cxr_line_colours
= []
452 def cxr_batch_lines():
453 global cxr_line_positions
, cxr_line_colours
, cxr_view_shader
, cxr_view_lines
455 cxr_view_lines
= batch_for_shader(\
456 cxr_view_shader
, 'LINES',\
457 { "pos": cxr_line_positions
, "color": cxr_line_colours
})
459 def libcxr_line_callback( p0
,p1
,colour
):
460 global cxr_line_colours
, cxr_line_positions
462 cxr_line_positions
+= [(p0
[0],p0
[1],p0
[2])]
463 cxr_line_positions
+= [(p1
[0],p1
[1],p1
[2])]
464 cxr_line_colours
+= [(colour
[0],colour
[1],colour
[2],colour
[3])]
465 cxr_line_colours
+= [(colour
[0],colour
[1],colour
[2],colour
[3])]
468 # ------------------------------------------------------------------------------
473 NBVTF_IMAGE_FORMAT_RGBA8888
= 0
474 NBVTF_IMAGE_FORMAT_RGB888
= 2
475 NBVTF_IMAGE_FORMAT_DXT1
= 13
476 NBVTF_IMAGE_FORMAT_DXT5
= 15
477 NBVTF_TEXTUREFLAGS_CLAMPS
= 0x00000004
478 NBVTF_TEXTUREFLAGS_CLAMPT
= 0x00000008
479 NBVTF_TEXTUREFLAGS_NORMAL
= 0x00000080
480 NBVTF_TEXTUREFLAGS_NOMIP
= 0x00000100
481 NBVTF_TEXTUREFLAGS_NOLOD
= 0x00000200
483 libnbvtf_convert
= extern( "nbvtf_convert", \
484 [c_char_p
,c_int32
,c_int32
,c_int32
,c_int32
,c_int32
,c_uint32
,c_char_p
], \
487 libnbvtf_init
= extern( "nbvtf_init", [], None )
488 libnbvtf_funcs
= [ libnbvtf_convert
, libnbvtf_init
]
491 # --------------------------
494 global libcxr
, libnbvtf
, libcxr_funcs
, libnbvtf_funcs
496 # Unload libraries if existing
497 def _reload( lib
, path
):
499 _handle
= lib
._handle
500 for i
in range(10): libc_dlclose( _handle
)
503 return cdll
.LoadLibrary( F
'{os.path.dirname(__file__)}/{path}.so' )
505 libnbvtf
= _reload( libnbvtf
, "libnbvtf" )
506 libcxr
= _reload( libcxr
, "libcxr" )
508 for fd
in libnbvtf_funcs
:
509 fd
.loadfrom( libnbvtf
)
512 for fd
in libcxr_funcs
:
513 fd
.loadfrom( libcxr
)
516 global c_libcxr_log_callback
, c_libcxr_line_callback
518 LOG_FUNCTION_TYPE
= CFUNCTYPE(None,c_char_p
)
519 c_libcxr_log_callback
= LOG_FUNCTION_TYPE(libcxr_log_callback
)
521 LINE_FUNCTION_TYPE
= CFUNCTYPE(None,\
522 POINTER(c_double
), POINTER(c_double
), POINTER(c_double
))
523 c_libcxr_line_callback
= LINE_FUNCTION_TYPE(libcxr_line_callback
)
525 libcxr
.cxr_set_log_function(cast(c_libcxr_log_callback
,c_void_p
))
526 libcxr
.cxr_set_line_function(cast(c_libcxr_line_callback
,c_void_p
))
528 build_time
= c_char_p
.in_dll(libcxr
,'cxr_build_time')
529 print( F
"libcxr build time: {build_time.value}" )
534 # ------------------------------------------------------------------------------
536 # Standard entity functions, think of like base.fgd
538 def cxr_get_origin(context
):
539 return context
['object'].location
* context
['transform']['scale'] + \
540 mathutils
.Vector(context
['transform']['offset'])
542 def cxr_get_angles(context
):
543 obj
= context
['object']
544 euler
= [ a
*57.295779513 for a
in obj
.rotation_euler
]
551 def cxr_baseclass(classes
, other
):
554 base
.update(x
.copy())
557 # EEVEE Light component converter -> Source 1
559 def ent_lights(context
):
560 obj
= context
['object']
561 kvs
= cxr_baseclass([ent_origin
],\
563 "_distance": (0.0 if obj
.data
.cxr_data
.realtime
else -1.0),
564 "_light": [int(pow(obj
.data
.color
[i
],1.0/2.2)*255.0) for i
in range(3)] +\
565 [int(obj
.data
.energy
* bpy
.context
.scene
.cxr_data
.light_scale
)],
566 "_lightHDR": '-1 -1 -1 1',
570 if obj
.data
.type == 'SPOT':
571 kvs
['_cone'] = obj
.data
.spot_size
*(57.295779513/2.0)
572 kvs
['_inner_cone'] = (1.0-obj
.data
.spot_blend
)*kvs
['_cone']
574 # Blenders spotlights 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))
582 kvs
['pitch'] = math
.asin(fwd
[2]) * 57.295779513
583 kvs
['angles'] = [ 0.0, math
.atan2(fwd
[1],fwd
[0]) * 57.295779513, 0.0 ]
584 kvs
['_quadratic_attn'] = 0.0 # Source spotlights + quadratic falloff look
587 # Blender's default has a much more 'nice'
589 kvs
['_linear_attn'] = 1.0
591 elif obj
.data
.type == 'POINT':
592 kvs
['_quadratic_attn'] = 1.0
593 kvs
['_linear_attn'] = 0.0
595 elif obj
.data
.type == 'SUN':
600 def ent_prop(context
):
602 if isinstance( context
['object'], bpy
.types
.Collection
):
603 kvs
['angles'] = [0,180,0]
604 kvs
['enablelightbounce'] = 1
605 kvs
['disableshadows'] = 0
606 kvs
['fademindist'] = -1
608 kvs
['model'] = F
"{asset_path('models',context['object'])}.mdl".lower()
609 kvs
['renderamt'] = 255
610 kvs
['rendercolor'] = [255, 255, 255]
613 kvs
['uniformscale'] = 1.0
615 pos
= mathutils
.Vector(context
['origin'])
616 pos
+= mathutils
.Vector(context
['transform']['offset'])
618 kvs
['origin'] = [pos
[1],-pos
[0],pos
[2]]
622 def ent_cubemap(context
):
623 obj
= context
['object']
624 return cxr_baseclass([ent_origin
], {"cubemapsize": obj
.data
.cxr_data
.size
})
626 ent_origin
= { "origin": cxr_get_origin
}
627 ent_angles
= { "angles": cxr_get_angles
}
628 ent_transform
= cxr_baseclass( [ent_origin
], ent_angles
)
630 #include the user config
631 exec(open(F
'{os.path.dirname(__file__)}/config.py').read())
633 # Blender state callbacks
634 # ------------------------------------------------------------------------------
637 def cxr_on_load(dummy
):
638 global cxr_view_lines
, cxr_view_mesh
640 cxr_view_lines
= None
644 def cxr_dgraph_update(scene
,dgraph
):
646 print( F
"Hallo {time.time()}" )
648 # Convexer compilation functions
649 # ------------------------------------------------------------------------------
651 # Asset path management
653 def asset_uid(asset
):
654 if isinstance(asset
,str):
657 # Create a unique ID string
659 v
= asset
.cxr_data
.asset_id
668 dig
.append( int( v
% len(base
) ) )
674 if bpy
.context
.scene
.cxr_data
.include_names
:
675 name
+= asset
.name
.replace('.','_')
679 # -> <project_name>/<asset_name>
680 def asset_name(asset
):
681 return F
"{bpy.context.scene.cxr_data.project_name}/{asset_uid(asset)}"
683 # -> <subdir>/<project_name>/<asset_name>
684 def asset_path(subdir
, asset
):
685 return F
"{subdir}/{asset_name(asset_uid(asset))}"
687 # -> <csgo>/<subdir>/<project_name>/<asset_name>
688 def asset_full_path(sdir
,asset
):
689 return F
"{bpy.context.scene.cxr_data.subdir}/"+\
690 F
"{asset_path(sdir,asset_uid(asset))}"
692 # Entity functions / infos
693 # ------------------------
695 def cxr_intrinsic_classname(obj
):
696 if obj
.type == 'LIGHT':
698 'SPOT': "light_spot",
700 'SUN': "light_directional" }[ obj
.data
.type ]
702 elif obj
.type == 'LIGHT_PROBE':
704 elif obj
.type == 'EMPTY':
710 def cxr_custom_class(obj
):
711 if obj
.type == 'MESH': custom_class
= obj
.cxr_data
.brushclass
712 else: custom_class
= obj
.cxr_data
.classname
716 def cxr_classname(obj
):
717 intr
= cxr_intrinsic_classname(obj
)
718 if intr
!= None: return intr
720 custom_class
= cxr_custom_class(obj
)
721 if custom_class
!= 'NONE':
727 # intinsic: (k, False, value)
728 # property: (k, True, value or default)
732 def cxr_entity_keyvalues(context
):
733 classname
= context
['classname']
734 obj
= context
['object']
735 if classname
not in cxr_entities
: return None
739 entdef
= cxr_entities
[classname
]
740 kvs
= entdef
['keyvalues']
742 if callable(kvs
): kvs
= kvs(context
)
749 if isinstance(kv
,dict):
751 value
= obj
[ F
"cxrkv_{k}" ]
756 if isinstance(value
,mathutils
.Vector
):
757 value
= [_
for _
in value
]
759 result
+= [(k
, isprop
, value
)]
763 # Extract material information from shader graph data
765 def material_info(mat
):
767 info
['res'] = (512,512)
768 info
['name'] = 'tools/toolsnodraw'
770 if mat
== None or mat
.use_nodes
== False:
774 if mat
.cxr_data
.shader
== 'Builtin':
775 info
['name'] = mat
.name
779 info
['name'] = asset_name(mat
)
781 # Using the cxr_graph_mapping as a reference, go through the shader
782 # graph and gather all $props from it.
784 def _graph_read( node_def
, node
=None, depth
=0 ):
788 def _variant_apply( val
):
791 if isinstance( val
, str ):
794 for shader_variant
in val
:
795 if shader_variant
[0] == mat
.cxr_data
.shader
:
796 return shader_variant
[1]
800 _graph_read
.extracted
= []
802 for node_idname
in node_def
:
803 for n
in mat
.node_tree
.nodes
:
804 if n
.bl_idname
== node_idname
:
805 node_def
= node_def
[node_idname
]
809 for link
in node_def
:
810 if isinstance( node_def
[link
], dict ):
811 inputt
= node
.inputs
[link
]
812 inputt_def
= node_def
[link
]
816 # look for definitions for the connected node type
817 con
= inputt
.links
[0].from_node
819 for node_idname
in inputt_def
:
820 if con
.bl_idname
== node_idname
:
821 con_def
= inputt_def
[ node_idname
]
822 _graph_read( con_def
, con
, depth
+1 )
824 # No definition found! :(
825 # TODO: Make a warning for this?
828 if "default" in inputt_def
:
829 prop
= _variant_apply( inputt_def
['default'] )
830 info
[prop
] = inputt
.default_value
832 prop
= _variant_apply( node_def
[link
] )
833 info
[prop
] = getattr(node
,link
)
835 _graph_read(cxr_graph_mapping
)
837 if "$basetexture" in info
:
838 export_res
= info
['$basetexture'].cxr_data
.export_res
839 info
['res'] = (export_res
[0], export_res
[1])
843 def vec3_min( a
, b
):
844 return mathutils
.Vector((min(a
[0],b
[0]),min(a
[1],b
[1]),min(a
[2],b
[2])))
845 def vec3_max( a
, b
):
846 return mathutils
.Vector((max(a
[0],b
[0]),max(a
[1],b
[1]),max(a
[2],b
[2])))
848 def cxr_collection_center(collection
, transform
):
850 bounds_min
= mathutils
.Vector((BIG
,BIG
,BIG
))
851 bounds_max
= mathutils
.Vector((-BIG
,-BIG
,-BIG
))
853 for obj
in collection
.objects
:
854 if obj
.type == 'MESH':
855 corners
= [ mathutils
.Vector(c
) for c
in obj
.bound_box
]
857 for corner
in [ obj
.matrix_world
@c for c
in corners
]:
858 bounds_min
= vec3_min( bounds_min
, corner
)
859 bounds_max
= vec3_max( bounds_max
, corner
)
861 center
= (bounds_min
+ bounds_max
) / 2.0
863 origin
= mathutils
.Vector((-center
[1],center
[0],center
[2]))
864 origin
*= transform
['scale']
868 # Prepares Scene into dictionary format
870 def cxr_scene_collect():
871 context
= bpy
.context
873 # Make sure all of our asset types have a unique ID
874 def _uid_prepare(objtype
):
880 if vs
.asset_id
in used_ids
:
883 id_max
= max(id_max
,vs
.asset_id
)
884 used_ids
+=[vs
.asset_id
]
885 for vs
in to_generate
:
888 _uid_prepare(bpy
.data
.materials
)
889 _uid_prepare(bpy
.data
.images
)
890 _uid_prepare(bpy
.data
.collections
)
893 "entities": [], # Everything with a classname
894 "geo": [], # All meshes without a classname
895 "heros": [] # Collections prefixed with mdl_
898 def _collect(collection
,transform
):
901 if collection
.name
.startswith('.'): return
902 if collection
.hide_render
: return
904 if collection
.name
.startswith('mdl_'):
905 sceneinfo
['entities'] += [{
906 "object": collection
,
907 "classname": "prop_static",
908 "transform": transform
,
909 "origin": cxr_collection_center( collection
, transform
)
912 sceneinfo
['heros'] += [{
913 "collection": collection
,
914 "transform": transform
,
915 "origin": cxr_collection_center( collection
, transform
)
919 for obj
in collection
.objects
:
920 if obj
.hide_get(): continue
922 classname
= cxr_classname( obj
)
924 if classname
!= None:
925 sceneinfo
['entities'] += [{
927 "classname": classname
,
928 "transform": transform
930 elif obj
.type == 'MESH':
931 sceneinfo
['geo'] += [{
933 "transform": transform
936 for c
in collection
.children
:
937 _collect( c
, transform
)
940 "scale": context
.scene
.cxr_data
.scale_factor
,
945 "scale": context
.scene
.cxr_data
.skybox_scale_factor
,
946 "offset": (0,0,context
.scene
.cxr_data
.skybox_offset
)
949 if 'main' in bpy
.data
.collections
:
950 _collect( bpy
.data
.collections
['main'], transform_main
)
952 if 'skybox' in bpy
.data
.collections
:
953 _collect( bpy
.data
.collections
['skybox'], transform_sky
)
957 # Write VMF out to file (JOB HANDLER)
959 def cxr_export_vmf(sceneinfo
, output_vmf
):
962 with
vdf_structure(output_vmf
) as m
:
963 print( F
"Write: {output_vmf}" )
965 vmfinfo
= cxr_vmf_context()
966 vmfinfo
.mapversion
= 4
968 #TODO: These need to be in options...
969 vmfinfo
.skyname
= b
"sky_csgo_night02b"
970 vmfinfo
.detailvbsp
= b
"detail.vbsp"
971 vmfinfo
.detailmaterial
= b
"detail/detailsprites"
972 vmfinfo
.lightmap_scale
= 12
974 vmfinfo
.brush_count
= 0
975 vmfinfo
.entity_count
= 0
976 vmfinfo
.face_count
= 0
978 libcxr_begin_vmf
.call( pointer(vmfinfo
), m
.fp
)
980 def _buildsolid( cmd
):
983 print( F
"{vmfinfo.brush_count} :: {cmd['object'].name}" )
985 baked
= mesh_cxr_format( cmd
['object'] )
986 world
= libcxr_decompose
.call( baked
, None )
991 vmfinfo
.scale
= cmd
['transform']['scale']
993 offset
= cmd
['transform']['offset']
994 vmfinfo
.offset
[0] = offset
[0]
995 vmfinfo
.offset
[1] = offset
[1]
996 vmfinfo
.offset
[2] = offset
[2]
998 libcxr_push_world_vmf
.call( world
, pointer(vmfinfo
), m
.fp
)
999 libcxr_free_world
.call( world
)
1004 for brush
in sceneinfo
['geo']:
1005 if not _buildsolid( brush
):
1010 libcxr_vmf_begin_entities
.call(pointer(vmfinfo
), m
.fp
)
1013 for ent
in sceneinfo
['entities']:
1015 ctx
= ent
['transform']
1016 cls
= ent
['classname']
1019 m
.kv( 'classname', cls
)
1021 kvs
= cxr_entity_keyvalues( ent
)
1024 if isinstance(kv
[2], list):
1025 m
.kv( kv
[0], ' '.join([str(_
) for _
in kv
[2]]) )
1026 else: m
.kv( kv
[0], str(kv
[2]) )
1028 if not isinstance( obj
, bpy
.types
.Collection
):
1029 if obj
.type == 'MESH':
1030 if not _buildsolid( ent
):
1040 # COmpile image using NBVTF and hash it (JOB HANDLER)
1042 def compile_image(img
):
1046 name
= asset_name(img
)
1047 src_path
= bpy
.path
.abspath(img
.filepath
)
1049 dims
= img
.cxr_data
.export_res
1051 'RGBA': NBVTF_IMAGE_FORMAT_RGBA8888
,
1052 'DXT1': NBVTF_IMAGE_FORMAT_DXT1
,
1053 'DXT5': NBVTF_IMAGE_FORMAT_DXT5
,
1054 'RGB': NBVTF_IMAGE_FORMAT_RGB888
1055 }[ img
.cxr_data
.fmt
]
1057 mipmap
= img
.cxr_data
.mipmap
1058 lod
= img
.cxr_data
.lod
1059 clamp
= img
.cxr_data
.clamp
1060 flags
= img
.cxr_data
.flags
1062 q
=bpy
.context
.scene
.cxr_data
.image_quality
1064 userflag_hash
= F
"{mipmap}.{lod}.{clamp}.{flags}"
1065 file_hash
= F
"{name}.{os.path.getmtime(src_path)}"
1066 comphash
= F
"{file_hash}.{dims[0]}.{dims[1]}.{fmt}.{userflag_hash}.{q}"
1068 if img
.cxr_data
.last_hash
!= comphash
:
1069 print( F
"Texture update: {img.filepath}" )
1071 src
= src_path
.encode('utf-8')
1072 dst
= (asset_full_path('materials',img
)+'.vtf').encode('utf-8')
1076 # texture setting flags
1077 if not lod
: flags_full |
= NBVTF_TEXTUREFLAGS_NOLOD
1079 flags_full |
= NBVTF_TEXTUREFLAGS_CLAMPS
1080 flags_full |
= NBVTF_TEXTUREFLAGS_CLAMPT
1082 if libnbvtf_convert
.call(src
,dims
[0],dims
[1],mipmap
,fmt
,q
,flags_full
,dst
):
1083 img
.cxr_data
.last_hash
= comphash
1088 # Compile a material to VMT format. This is quick to run, doesnt need to be a
1091 def compile_material(mat
):
1092 info
= material_info(mat
)
1093 properties
= mat
.cxr_data
1095 print( F
"Compile {asset_full_path('materials',mat)}.vmt" )
1096 if properties
.shader
== 'Builtin':
1101 # Walk the property tree
1102 def _mlayer( layer
):
1103 nonlocal properties
, props
1106 if isinstance(layer
[decl
],dict): # $property definition
1108 ptype
= pdef
['type']
1114 if 'shaders' in pdef
and properties
.shader
not in pdef
['shaders']:
1117 # Group expansion (does it have subdefinitions?)
1119 if isinstance(pdef
[ch
],dict):
1128 if ptype
== 'intrinsic':
1132 prop
= getattr(properties
,decl
)
1133 default
= pdef
['default']
1135 if not isinstance(prop
,str) and \
1136 not isinstance(prop
,bpy
.types
.Image
) and \
1137 hasattr(prop
,'__getitem__'):
1138 prop
= tuple([p
for p
in prop
])
1142 props
+= [(decl
,pdef
,prop
)]
1147 if expandview
: _mlayer(pdef
)
1149 _mlayer( cxr_shader_params
)
1152 with
vdf_structure( F
"{asset_full_path('materials',mat)}.vmt" ) as vmt
:
1153 vmt
.node( properties
.shader
)
1154 vmt
.put( "// Convexer export\n" )
1163 if 'exponent' in pdef
: return str(pow( v
, pdef
['exponent'] ))
1166 if isinstance(prop
,bpy
.types
.Image
):
1167 vmt
.kv( decl
, asset_name(prop
))
1168 elif isinstance(prop
,bool):
1169 vmt
.kv( decl
, '1' if prop
else '0' )
1170 elif isinstance(prop
,str):
1171 vmt
.kv( decl
, prop
)
1172 elif isinstance(prop
,float) or isinstance(prop
,int):
1173 vmt
.kv( decl
, _numeric(prop
) )
1174 elif isinstance(prop
,tuple):
1175 vmt
.kv( decl
, F
"[{' '.join([_numeric(_) for _ in prop])}]" )
1177 vmt
.put( F
"// (cxr) unkown shader value type'{type(prop)}'" )
1182 def cxr_export_modelsrc( mdl
, origin
, asset_dir
, project_name
, transform
):
1183 dgraph
= bpy
.context
.evaluated_depsgraph_get()
1185 # Compute hash value
1186 chash
= asset_uid(mdl
)+str(origin
)+str(transform
)
1188 #for obj in mdl.objects:
1189 # if obj.type != 'MESH':
1192 # ev = obj.evaluated_get(dgraph).data
1193 # srcverts=[(v.co[0],v.co[1],v.co[2]) for v in ev.vertices]
1194 # srcloops=[(l.normal[0],l.normal[1],l.normal[2]) for l in ev.loops]
1196 # chash=hashlib.sha224((str(srcverts)+chash).encode()).hexdigest()
1197 # chash=hashlib.sha224((str(srcloops)+chash).encode()).hexdigest()
1199 # if ev.uv_layers.active != None:
1200 # uv_layer = ev.uv_layers.active.data
1201 # srcuv=[(uv.uv[0],uv.uv[1]) for uv in uv_layer]
1205 # chash=hashlib.sha224((str(srcuv)+chash).encode()).hexdigest()
1206 # srcmats=[ ms.material.name for ms in obj.material_slots ]
1207 # chash=hashlib.sha224((str(srcmats)+chash).encode()).hexdigest()
1208 # transforms=[ obj.location, obj.rotation_euler, obj.scale ]
1209 # srctr=[(v[0],v[1],v[2]) for v in transforms]
1210 # chash=hashlib.sha224((str(srctr)+chash).encode()).hexdigest()
1212 #if chash != mdl.cxr_data.last_hash:
1213 # mdl.cxr_data.last_hash = chash
1214 # print( F"Compile: {mdl.name}" )
1218 bpy
.ops
.object.select_all(action
='DESELECT')
1221 def _get_layer(col
,name
):
1222 for c
in col
.children
:
1225 sub
= _get_layer(c
,name
)
1229 layer
= _get_layer(bpy
.context
.view_layer
.layer_collection
,mdl
.name
)
1231 prev_state
= layer
.hide_viewport
1232 layer
.hide_viewport
=False
1234 # Collect materials to be compiled, and temp rename for export
1237 for obj
in mdl
.objects
:
1238 obj
.select_set(state
=True)
1239 for ms
in obj
.material_slots
:
1240 if ms
.material
!= None:
1241 if ms
.material
not in mat_dict
:
1242 mat_dict
[ms
.material
] = ms
.material
.name
1243 ms
.material
.name
= asset_uid(ms
.material
)
1244 ms
.material
.use_nodes
= False
1247 bpy
.ops
.export_scene
.fbx( filepath
=F
'{asset_dir}/{uid}_ref.fbx',\
1248 check_existing
=False,
1250 apply_unit_scale
=False,
1251 bake_space_transform
=False
1254 # Fix material names back to original
1255 for mat
in mat_dict
:
1256 mat
.name
= mat_dict
[mat
]
1257 mat
.use_nodes
= True
1259 layer
.hide_viewport
=prev_state
1262 with
open(F
'{asset_dir}/{uid}.qc','w') as o
:
1263 o
.write(F
'$modelname "{project_name}/{uid}"\n')
1264 #o.write(F'$scale .32\n')
1265 o
.write(F
'$scale {transform["scale"]/100.0}\n')
1266 o
.write(F
'$body _ "{uid}_ref.fbx"\n')
1267 o
.write(F
'$staticprop\n')
1268 o
.write(F
'$origin {origin[0]} {origin[1]} {origin[2]}\n')
1271 o
.write(F
'$cdmaterials {project_name}\n')
1272 o
.write(F
'$sequence idle {uid}_ref.fbx\n')
1276 # Copy bsp file (and also lightpatch it)
1278 def cxr_patchmap( src
, dst
):
1279 libcxr_lightpatch_bsp
.call( src
.encode('utf-8') )
1280 shutil
.copyfile( src
, dst
)
1283 # Convexer operators
1284 # ------------------------------------------------------------------------------
1286 # Force reload of shared libraries
1288 class CXR_RELOAD(bpy
.types
.Operator
):
1289 bl_idname
="convexer.reload"
1291 def execute(_
,context
):
1295 # Used for exporting data to use with ASAN builds
1297 class CXR_DEV_OPERATOR(bpy
.types
.Operator
):
1298 bl_idname
="convexer.dev_test"
1299 bl_label
="Export development data"
1301 def execute(_
,context
):
1302 # Prepare input data
1303 mesh_src
= mesh_cxr_format(context
.active_object
)
1304 libcxr_write_test_data
.call( pointer(mesh_src
) )
1307 # UI: Preview how the brushes will looks in 3D view
1309 class CXR_PREVIEW_OPERATOR(bpy
.types
.Operator
):
1310 bl_idname
="convexer.preview"
1311 bl_label
="Preview Brushes"
1316 def execute(_
,context
):
1319 def modal(_
,context
,event
):
1320 global cxr_view_mesh
1321 static
= _
.__class
__
1323 if event
.type == 'ESC':
1326 cxr_view_mesh
= None
1327 static
.RUNNING
= False
1332 return {'PASS_THROUGH'}
1334 def invoke(_
,context
,event
):
1335 global cxr_view_shader
, cxr_view_mesh
1336 static
= _
.__class
__
1337 static
.LASTERR
= None
1341 mesh_src
= mesh_cxr_format(context
.active_object
)
1344 world
= libcxr_decompose
.call( mesh_src
, pointer(err
) )
1347 cxr_view_mesh
= None
1351 static
.LASTERR
= ["There is no error", \
1357 "Non-Convex Polygon"]\
1361 return {'CANCELLED'}
1363 context
.window_manager
.modal_handler_add(_
)
1364 return {'RUNNING_MODAL'}
1366 # Generate preview using cxr
1368 ptrpreview
= libcxr_world_preview
.call( world
)
1369 preview
= ptrpreview
[0]
1371 vertices
= preview
.vertices
[:preview
.vertex_count
]
1372 vertices
= [(_
[0],_
[1],_
[2]) for _
in vertices
]
1374 colours
= preview
.colours
[:preview
.vertex_count
]
1375 colours
= [(_
[0],_
[1],_
[2],_
[3]) for _
in colours
]
1377 indices
= preview
.indices
[:preview
.indices_count
]
1378 indices
= [ (indices
[i
*3+0],indices
[i
*3+1],indices
[i
*3+2]) \
1379 for i
in range(int(preview
.indices_count
/3)) ]
1381 cxr_view_mesh
= batch_for_shader(
1382 cxr_view_shader
, 'TRIS',
1383 { "pos": vertices
, "color": colours
},
1387 libcxr_free_tri_mesh
.call( ptrpreview
)
1388 libcxr_free_world
.call( world
)
1392 # Allow user to spam the operator
1394 return {'CANCELLED'}
1396 if not static
.RUNNING
:
1397 static
.RUNNING
= True
1398 context
.window_manager
.modal_handler_add(_
)
1399 return {'RUNNING_MODAL'}
1401 # Search for VMF compiler executables in subdirectory
1403 class CXR_DETECT_COMPILERS(bpy
.types
.Operator
):
1404 bl_idname
="convexer.detect_compilers"
1405 bl_label
="Find compilers"
1407 def execute(self
,context
):
1408 scene
= context
.scene
1409 settings
= scene
.cxr_data
1410 subdir
= settings
.subdir
1412 for exename
in ['studiomdl','vbsp','vvis','vrad']:
1413 searchpath
= os
.path
.normpath(F
'{subdir}/../bin/{exename}.exe')
1414 if os
.path
.exists(searchpath
):
1415 settings
[F
'exe_{exename}'] = searchpath
1419 # Main compile function
1421 class CXR_COMPILER_CHAIN(bpy
.types
.Operator
):
1422 bl_idname
="convexer.chain"
1423 bl_label
="Compile Chain"
1438 def cancel(_
,context
):
1439 global cxr_jobs_batch
1440 static
= _
.__class
__
1441 wm
= context
.window_manager
1443 if static
.SUBPROC
!= None:
1444 static
.SUBPROC
.terminate()
1445 static
.SUBPROC
= None
1447 if static
.TIMER
!= None:
1448 wm
.event_timer_remove( static
.TIMER
)
1453 cxr_jobs_batch
= None
1457 def modal(_
,context
,ev
):
1458 static
= _
.__class
__
1460 if ev
.type == 'TIMER':
1461 global cxr_jobs_batch
1463 if static
.WAIT_REDRAW
:
1465 return {'PASS_THROUGH'}
1466 static
.WAIT_REDRAW
= True
1468 if static
.USER_EXIT
:
1469 print( "Chain USER_EXIT" )
1470 return _
.cancel(context
)
1472 if static
.SUBPROC
!= None:
1473 # Deal with async modes
1474 status
= static
.SUBPROC
.poll()
1477 # Cannot redirect STDOUT through here without causing
1478 # undefined behaviour due to the Blender Python specification.
1480 # Have to write it out to a file and read it back in.
1482 with
open("/tmp/convexer_compile_log.txt","r") as log
:
1483 static
.LOG
= log
.readlines()
1484 return {'PASS_THROUGH'}
1486 #for l in static.SUBPROC.stdout:
1487 # print( F'-> {l.decode("utf-8")}',end='' )
1488 static
.SUBPROC
= None
1491 print(F
'Compiler () error: {status}')
1492 return _
.cancel(context
)
1494 static
.JOBSYS
['jobs'][static
.JOBID
] = None
1495 cxr_jobs_update_graph( static
.JOBINFO
)
1497 return {'PASS_THROUGH'}
1499 # Compile syncronous thing
1500 for sys
in static
.JOBINFO
:
1501 for i
,target
in enumerate(sys
['jobs']):
1504 if callable(sys
['exec']):
1505 print( F
"Run (sync): {static.JOBID} @{time.time()}" )
1507 if not sys
['exec'](*target
):
1508 print( "Job failed" )
1509 return _
.cancel(context
)
1511 sys
['jobs'][i
] = None
1514 # Run external executable (wine)
1515 static
.SUBPROC
= subprocess
.Popen( target
,
1516 stdout
=static
.FILE
,\
1517 stderr
=subprocess
.PIPE
,\
1522 cxr_jobs_update_graph( static
.JOBINFO
)
1524 return {'PASS_THROUGH'}
1527 print( "All jobs completed!" )
1528 cxr_jobs_batch
= None
1531 return _
.cancel(context
)
1533 return {'PASS_THROUGH'}
1535 def invoke(_
,context
,event
):
1536 static
= _
.__class
__
1537 wm
= context
.window_manager
1539 if static
.TIMER
== None:
1540 print("Launching compiler toolchain")
1542 # Run static compilation units now (collect, vmt..)
1543 filepath
= bpy
.data
.filepath
1544 directory
= os
.path
.dirname(filepath
)
1545 settings
= bpy
.context
.scene
.cxr_data
1547 asset_dir
= F
"{directory}/modelsrc"
1548 material_dir
= F
"{settings.subdir}/materials/{settings.project_name}"
1549 model_dir
= F
"{settings.subdir}/models/{settings.project_name}"
1550 output_vmf
= F
"{directory}/{settings.project_name}.vmf"
1552 os
.makedirs( asset_dir
, exist_ok
=True )
1553 os
.makedirs( material_dir
, exist_ok
=True )
1554 os
.makedirs( model_dir
, exist_ok
=True )
1556 static
.FILE
= open(F
"/tmp/convexer_compile_log.txt","w")
1559 sceneinfo
= cxr_scene_collect()
1565 for brush
in sceneinfo
['geo']:
1566 for ms
in brush
['object'].material_slots
:
1567 a_materials
.add( ms
.material
)
1569 for ent
in sceneinfo
['entities']:
1570 if isinstance(ent
['object'],bpy
.types
.Collection
): continue
1572 if ent
['object'].type == 'MESH':
1573 for ms
in ent
['object'].material_slots
:
1574 a_materials
.add( ms
.material
)
1576 # TODO.. this should just be in the entity loop
1577 for hero
in sceneinfo
['heros']:
1578 uid
= asset_uid(hero
['collection'])
1579 qc_jobs
+= [F
'{uid}.qc']
1580 for obj
in hero
['collection'].objects
:
1581 for ms
in obj
.material_slots
:
1582 a_materials
.add( ms
.material
)
1585 for mat
in a_materials
:
1586 for pair
in compile_material(mat
):
1591 if isinstance(prop
,bpy
.types
.Image
):
1593 if 'flags' in pdef
: flags
= pdef
['flags']
1594 if prop
not in image_jobs
:
1595 image_jobs
+= [(prop
,)]
1596 prop
.cxr_data
.flags
= flags
1602 if settings
.comp_vmf
:
1603 static
.JOBINFO
+= [{
1604 "title": "Convexer",
1606 "colour": (1.0,0.3,0.1,1.0),
1607 "exec": cxr_export_vmf
,
1608 "jobs": [(sceneinfo
,output_vmf
)]
1611 if settings
.comp_textures
:
1612 if len(image_jobs
) > 0:
1613 static
.JOBINFO
+= [{
1614 "title": "Textures",
1616 "colour": (0.1,1.0,0.3,1.0),
1617 "exec": compile_image
,
1621 game
= 'z:'+settings
.subdir
.replace('/','\\')
1623 '-game', game
, settings
.project_name
1627 if settings
.comp_models
:
1628 if len(sceneinfo
['heros']) > 0:
1629 static
.JOBINFO
+= [{
1632 "colour": (0.5,0.5,1.0,1.0),
1633 "exec": cxr_export_modelsrc
,
1634 "jobs": [(h
['collection'], h
['origin'], asset_dir
, \
1635 settings
.project_name
, h
['transform']) for h
in \
1639 if len(qc_jobs
) > 0:
1640 static
.JOBINFO
+= [{
1641 "title": "StudioMDL",
1643 "colour": (0.8,0.1,0.1,1.0),
1644 "exec": "studiomdl",
1645 "jobs": [[settings
[F
'exe_studiomdl']] + [\
1646 '-nop4', '-game', game
, qc
] for qc
in qc_jobs
],
1651 if settings
.comp_compile
:
1652 static
.JOBINFO
+= [{
1655 "colour": (0.1,0.2,1.0,1.0),
1657 "jobs": [[settings
[F
'exe_vbsp']] + args
],
1661 static
.JOBINFO
+= [{
1664 "colour": (0.9,0.5,0.5,1.0),
1666 "jobs": [[settings
[F
'exe_vvis']] + args
],
1670 static
.JOBINFO
+= [{
1673 "colour": (0.9,0.2,0.3,1.0),
1675 "jobs": [[settings
[F
'exe_vrad']] + args
],
1679 static
.JOBINFO
+= [{
1682 "colour": (0.0,1.0,0.4,1.0),
1683 "exec": cxr_patchmap
,
1684 "jobs": [(F
"{directory}/{settings.project_name}.bsp",\
1685 F
"{settings.subdir}/maps/{settings.project_name}.bsp")]
1688 static
.USER_EXIT
=False
1689 static
.TIMER
=wm
.event_timer_add(0.1,window
=context
.window
)
1690 wm
.modal_handler_add(_
)
1692 cxr_jobs_update_graph( static
.JOBINFO
)
1694 return {'RUNNING_MODAL'}
1696 print("Chain exiting...")
1697 static
.USER_EXIT
=True
1698 return {'RUNNING_MODAL'}
1700 class CXR_RESET_HASHES(bpy
.types
.Operator
):
1701 bl_idname
="convexer.hash_reset"
1702 bl_label
="Reset asset hashes"
1704 def execute(_
,context
):
1705 for c
in bpy
.data
.collections
:
1706 c
.cxr_data
.last_hash
= F
"<RESET>{time.time()}"
1707 c
.cxr_data
.asset_id
=0
1709 for t
in bpy
.data
.images
:
1710 t
.cxr_data
.last_hash
= F
"<RESET>{time.time()}"
1711 t
.cxr_data
.asset_id
=0
1716 # ------------------------------------------------------------------------------
1718 # Helper buttons for 3d toolbox view
1720 class CXR_VIEW3D( bpy
.types
.Panel
):
1721 bl_idname
= "VIEW3D_PT_convexer"
1722 bl_label
= "Convexer"
1723 bl_space_type
= 'VIEW_3D'
1724 bl_region_type
= 'UI'
1725 bl_category
= "Convexer"
1728 def poll(cls
, context
):
1729 return (context
.object is not None)
1731 def draw(_
, context
):
1735 row
.operator("convexer.preview")
1737 if CXR_PREVIEW_OPERATOR
.LASTERR
!= None:
1739 box
.label(text
=CXR_PREVIEW_OPERATOR
.LASTERR
, icon
='ERROR')
1741 # Main scene properties interface, where all the settings go
1743 class CXR_INTERFACE(bpy
.types
.Panel
):
1745 bl_idname
="SCENE_PT_convexer"
1746 bl_space_type
='PROPERTIES'
1747 bl_region_type
='WINDOW'
1750 def draw(_
,context
):
1751 _
.layout
.operator("convexer.reload")
1752 _
.layout
.operator("convexer.dev_test")
1753 _
.layout
.operator("convexer.preview")
1754 _
.layout
.operator("convexer.hash_reset")
1756 settings
= context
.scene
.cxr_data
1758 _
.layout
.prop(settings
, "debug")
1759 _
.layout
.prop(settings
, "scale_factor")
1760 _
.layout
.prop(settings
, "lightmap_scale")
1761 _
.layout
.prop(settings
, "light_scale" )
1762 _
.layout
.prop(settings
, "image_quality" )
1764 box
= _
.layout
.box()
1766 box
.prop(settings
, "project_name")
1767 box
.prop(settings
, "subdir")
1769 box
= _
.layout
.box()
1770 box
.operator("convexer.detect_compilers")
1771 box
.prop(settings
, "exe_studiomdl")
1772 box
.prop(settings
, "exe_vbsp")
1773 box
.prop(settings
, "exe_vvis")
1774 box
.prop(settings
, "exe_vrad")
1778 row
.prop(settings
,"comp_vmf")
1779 row
.prop(settings
,"comp_textures")
1780 row
.prop(settings
,"comp_models")
1781 row
.prop(settings
,"comp_compile")
1783 text
= "Compile" if CXR_COMPILER_CHAIN
.TIMER
== None else "Cancel"
1786 row
.operator("convexer.chain", text
=text
)
1789 class CXR_MATERIAL_PANEL(bpy
.types
.Panel
):
1790 bl_label
="VMT Properties"
1791 bl_idname
="SCENE_PT_convexer_vmt"
1792 bl_space_type
='PROPERTIES'
1793 bl_region_type
='WINDOW'
1794 bl_context
="material"
1796 def draw(_
,context
):
1797 active_object
= bpy
.context
.active_object
1798 if active_object
== None: return
1800 active_material
= active_object
.active_material
1801 if active_material
== None: return
1803 properties
= active_material
.cxr_data
1804 info
= material_info( active_material
)
1806 _
.layout
.label(text
=F
"{info['name']} @{info['res'][0]}x{info['res'][1]}")
1807 _
.layout
.prop( properties
, "shader" )
1810 _
.layout
.label(text
=F
"{xk}:={info[xk]}")
1812 def _mtex( name
, img
, uiParent
):
1815 box
= uiParent
.box()
1816 box
.label( text
=F
'{name} "{img.filepath}"' )
1818 if ((x
& (x
- 1)) == 0):
1821 closest_diff
= 10000000
1823 dist
= abs((1 << i
)-x
)
1824 if dist
< closest_diff
:
1829 return 1 << (closest
+1)
1831 return 1 << (closest
-1)
1836 row
.prop( img
.cxr_data
, "export_res" )
1837 row
.prop( img
.cxr_data
, "fmt" )
1840 row
.prop( img
.cxr_data
, "mipmap" )
1841 row
.prop( img
.cxr_data
, "lod" )
1842 row
.prop( img
.cxr_data
, "clamp" )
1844 img
.cxr_data
.export_res
[0] = _p2( img
.cxr_data
.export_res
[0] )
1845 img
.cxr_data
.export_res
[1] = _p2( img
.cxr_data
.export_res
[1] )
1847 def _mview( layer
, uiParent
):
1851 if isinstance(layer
[decl
],dict): # $property definition
1853 ptype
= pdef
['type']
1859 if ('shaders' in pdef
) and \
1860 (properties
.shader
not in pdef
['shaders']):
1863 if ptype
== 'intrinsic':
1864 if decl
not in info
:
1869 if isinstance(pdef
[ch
],dict):
1870 if ptype
== 'ui' or ptype
== 'intrinsic':
1872 elif getattr(properties
,decl
) == pdef
['default']:
1875 thisnode
= uiParent
.box()
1879 thisnode
.label( text
=decl
)
1880 elif ptype
== 'intrinsic':
1881 if isinstance(info
[decl
], bpy
.types
.Image
):
1882 _mtex( decl
, info
[decl
], thisnode
)
1884 # hidden intrinsic value.
1885 # Means its a float array or something not an image
1886 thisnode
.label(text
=F
"-- hidden intrinsic '{decl}' --")
1888 thisnode
.prop(properties
,decl
)
1889 if expandview
: _mview(pdef
,thisnode
)
1891 _mview( cxr_shader_params
, _
.layout
)
1893 def cxr_entity_changeclass(_
,context
):
1894 active_object
= context
.active_object
1896 # Create ID properties
1898 classname
= cxr_custom_class(active_object
)
1900 if classname
in cxr_entities
:
1901 entdef
= cxr_entities
[classname
]
1903 kvs
= entdef
['keyvalues']
1904 if callable(kvs
): kvs
= kvs(active_object
)
1910 if callable(kv
) or not isinstance(kv
,dict): continue
1912 if key
not in active_object
:
1913 active_object
[key
] = kv
['default']
1914 id_prop
= active_object
.id_properties_ui(key
)
1915 id_prop
.update(default
=kv
['default'])
1917 class CXR_ENTITY_PANEL(bpy
.types
.Panel
):
1918 bl_label
="Entity Config"
1919 bl_idname
="SCENE_PT_convexer_entity"
1920 bl_space_type
='PROPERTIES'
1921 bl_region_type
='WINDOW'
1924 def draw(_
,context
):
1925 active_object
= bpy
.context
.active_object
1927 if active_object
== None: return
1930 "scale": bpy
.context
.scene
.cxr_data
.scale_factor
,
1934 ecn
= cxr_intrinsic_classname( active_object
)
1935 classname
= cxr_custom_class( active_object
)
1938 if active_object
.type == 'MESH':
1939 _
.layout
.prop( active_object
.cxr_data
, 'brushclass' )
1940 else: _
.layout
.prop( active_object
.cxr_data
, 'classname' )
1942 if classname
== 'NONE':
1945 _
.layout
.label(text
=F
"<implementation defined ({ecn})>")
1946 _
.layout
.enabled
=False
1949 kvs
= cxr_entity_keyvalues( {
1950 "object": active_object
,
1951 "transform": default_context
,
1952 "classname": classname
1958 _
.layout
.prop( active_object
, F
'["cxrkv_{kv[0]}"]', text
=kv
[0])
1960 row
= _
.layout
.row()
1962 row
.label( text
=F
'{kv[0]}: {repr(kv[2])}' )
1964 _
.layout
.label( text
=F
"ERROR: NO CLASS DEFINITION" )
1966 class CXR_LIGHT_PANEL(bpy
.types
.Panel
):
1967 bl_label
= "Source Settings"
1968 bl_idname
= "LIGHT_PT_cxr"
1969 bl_space_type
= 'PROPERTIES'
1970 bl_region_type
= 'WINDOW'
1973 def draw(self
, context
):
1974 layout
= self
.layout
1975 scene
= context
.scene
1977 active_object
= bpy
.context
.active_object
1978 if active_object
== None: return
1980 if active_object
.type == 'LIGHT' or \
1981 active_object
.type == 'LIGHT_PROBE':
1983 properties
= active_object
.data
.cxr_data
1985 if active_object
.type == 'LIGHT':
1986 layout
.prop( properties
, "realtime" )
1987 elif active_object
.type == 'LIGHT_PROBE':
1988 layout
.prop( properties
, "size" )
1991 # ------------------------------------------------------------------------------
1993 class CXR_IMAGE_SETTINGS(bpy
.types
.PropertyGroup
):
1994 export_res
: bpy
.props
.IntVectorProperty(
1996 description
="Texture Export Resolution",
2002 fmt
: bpy
.props
.EnumProperty(
2005 ('DXT1', "DXT1", "BC1 compressed", '', 0),
2006 ('DXT5', "DXT5", "BC3 compressed", '', 1),
2007 ('RGB', "RGB", "Uncompressed", '', 2),
2008 ('RGBA', "RGBA", "Uncompressed (with alpha)", '', 3)
2010 description
="Image format",
2013 last_hash
: bpy
.props
.StringProperty( name
="" )
2014 asset_id
: bpy
.props
.IntProperty(name
="intl_assetid",default
=0)
2016 mipmap
: bpy
.props
.BoolProperty(name
="MIP",default
=True)
2017 lod
: bpy
.props
.BoolProperty(name
="LOD",default
=True)
2018 clamp
: bpy
.props
.BoolProperty(name
="CLAMP",default
=False)
2019 flags
: bpy
.props
.IntProperty(name
="flags",default
=0)
2021 class CXR_LIGHT_SETTINGS(bpy
.types
.PropertyGroup
):
2022 realtime
: bpy
.props
.BoolProperty(name
="Realtime Light", default
=True)
2024 class CXR_CUBEMAP_SETTINGS(bpy
.types
.PropertyGroup
):
2025 size
: bpy
.props
.EnumProperty(
2028 ('1',"1x1",'','',0),
2029 ('2',"2x2",'','',1),
2030 ('3',"4x4",'','',2),
2031 ('4',"8x8",'','',3),
2032 ('5',"16x16",'','',4),
2033 ('6',"32x32",'','',5),
2034 ('7',"64x64",'','',6),
2035 ('8',"128x128",'','',7),
2036 ('9',"256x256",'','',8)
2038 description
="Texture resolution",
2041 class CXR_ENTITY_SETTINGS(bpy
.types
.PropertyGroup
):
2042 entity
: bpy
.props
.BoolProperty(name
="")
2044 enum_pointents
= [('NONE',"None","")]
2045 enum_brushents
= [('NONE',"None","")]
2047 for classname
in cxr_entities
:
2048 entdef
= cxr_entities
[classname
]
2049 if 'allow' in entdef
:
2050 itm
= [(classname
, classname
, "")]
2051 if 'EMPTY' in entdef
['allow']: enum_pointents
+= itm
2052 else: enum_brushents
+= itm
2054 classname
: bpy
.props
.EnumProperty(items
=enum_pointents
, name
="Class", \
2055 update
=cxr_entity_changeclass
, default
='NONE' )
2057 brushclass
: bpy
.props
.EnumProperty(items
=enum_brushents
, name
="Class", \
2058 update
=cxr_entity_changeclass
, default
='NONE' )
2060 class CXR_MODEL_SETTINGS(bpy
.types
.PropertyGroup
):
2061 last_hash
: bpy
.props
.StringProperty( name
="" )
2062 asset_id
: bpy
.props
.IntProperty(name
="vmf_settings",default
=0)
2064 class CXR_SCENE_SETTINGS(bpy
.types
.PropertyGroup
):
2065 project_name
: bpy
.props
.StringProperty( name
="Project Name" )
2066 subdir
: bpy
.props
.StringProperty( name
="Subdirectory" )
2068 exe_studiomdl
: bpy
.props
.StringProperty( name
="studiomdl" )
2069 exe_vbsp
: bpy
.props
.StringProperty( name
="vbsp" )
2070 opt_vbsp
: bpy
.props
.StringProperty( name
="args" )
2071 exe_vvis
: bpy
.props
.StringProperty( name
="vvis" )
2072 opt_vvis
: bpy
.props
.StringProperty( name
="args" )
2073 exe_vrad
: bpy
.props
.StringProperty( name
="vrad" )
2074 opt_vrad
: bpy
.props
.StringProperty( name
="args" )
2076 debug
: bpy
.props
.BoolProperty(name
="Debug",default
=False)
2077 scale_factor
: bpy
.props
.FloatProperty( name
="VMF Scale factor", \
2078 default
=32.0,min=1.0)
2079 skybox_scale_factor
: bpy
.props
.FloatProperty( name
="Sky Scale factor", \
2080 default
=1.0,min=0.01)
2082 skybox_offset
: bpy
.props
.FloatProperty(name
="Sky offset",default
=-4096.0)
2083 light_scale
: bpy
.props
.FloatProperty(name
="Light Scale",default
=1.0/5.0)
2084 include_names
: bpy
.props
.BoolProperty(name
="Append original file names",\
2086 lightmap_scale
: bpy
.props
.IntProperty(name
="Global Lightmap Scale",\
2088 image_quality
: bpy
.props
.IntProperty(name
="Texture Quality (0-18)",\
2089 default
=8, min=0, max=18 )
2091 comp_vmf
: bpy
.props
.BoolProperty(name
="VMF",default
=True)
2092 comp_models
: bpy
.props
.BoolProperty(name
="Models",default
=True)
2093 comp_textures
: bpy
.props
.BoolProperty(name
="Textures",default
=True)
2094 comp_compile
: bpy
.props
.BoolProperty(name
="Compile",default
=True)
2096 classes
= [ CXR_RELOAD
, CXR_DEV_OPERATOR
, CXR_INTERFACE
, \
2097 CXR_MATERIAL_PANEL
, CXR_IMAGE_SETTINGS
,\
2098 CXR_MODEL_SETTINGS
, CXR_ENTITY_SETTINGS
, CXR_CUBEMAP_SETTINGS
,\
2099 CXR_LIGHT_SETTINGS
, CXR_SCENE_SETTINGS
, CXR_DETECT_COMPILERS
,\
2100 CXR_ENTITY_PANEL
, CXR_LIGHT_PANEL
, CXR_PREVIEW_OPERATOR
,\
2101 CXR_VIEW3D
, CXR_COMPILER_CHAIN
, CXR_RESET_HASHES
]
2103 vmt_param_dynamic_class
= None
2106 global cxr_view_draw_handler
, vmt_param_dynamic_class
, cxr_ui_handler
2109 bpy
.utils
.register_class(c
)
2111 # Build dynamic VMT properties class defined by cxr_shader_params
2112 annotations_dict
= {}
2114 def _dvmt_propogate(layer
):
2115 nonlocal annotations_dict
2118 if isinstance(layer
[decl
],dict): # $property definition
2122 if pdef
['type'] == 'bool':
2123 prop
= bpy
.props
.BoolProperty(\
2124 name
= pdef
['name'],\
2125 default
= pdef
['default'])
2127 elif pdef
['type'] == 'float':
2128 prop
= bpy
.props
.FloatProperty(\
2129 name
= pdef
['name'],\
2130 default
= pdef
['default'])
2132 elif pdef
['type'] == 'vector':
2133 if 'subtype' in pdef
:
2134 prop
= bpy
.props
.FloatVectorProperty(\
2135 name
= pdef
['name'],\
2136 subtype
= pdef
['subtype'],\
2137 default
= pdef
['default'],\
2138 size
= len(pdef
['default']))
2140 prop
= bpy
.props
.FloatVectorProperty(\
2141 name
= pdef
['name'],\
2142 default
= pdef
['default'],\
2143 size
= len(pdef
['default']))
2145 elif pdef
['type'] == 'string':
2146 prop
= bpy
.props
.StringProperty(\
2147 name
= pdef
['name'],\
2148 default
= pdef
['default'])
2150 elif pdef
['type'] == 'enum':
2151 prop
= bpy
.props
.EnumProperty(\
2152 name
= pdef
['name'],\
2153 items
= pdef
['items'],\
2154 default
= pdef
['default'])
2157 annotations_dict
[decl
] = prop
2159 # Recurse into sub-definitions
2160 _dvmt_propogate(pdef
)
2162 annotations_dict
["shader"] = bpy
.props
.EnumProperty(\
2165 cxr_shaders
[_
]["name"],\
2166 '') for _
in cxr_shaders
],\
2167 default
= next(iter(cxr_shaders
)))
2169 annotations_dict
["asset_id"] = bpy
.props
.IntProperty(name
="intl_assetid",\
2172 _dvmt_propogate( cxr_shader_params
)
2173 vmt_param_dynamic_class
= type(
2175 (bpy
.types
.PropertyGroup
,),{
2176 "__annotations__": annotations_dict
2180 bpy
.utils
.register_class( vmt_param_dynamic_class
)
2183 bpy
.types
.Material
.cxr_data
= \
2184 bpy
.props
.PointerProperty(type=vmt_param_dynamic_class
)
2185 bpy
.types
.Image
.cxr_data
= \
2186 bpy
.props
.PointerProperty(type=CXR_IMAGE_SETTINGS
)
2187 bpy
.types
.Object
.cxr_data
= \
2188 bpy
.props
.PointerProperty(type=CXR_ENTITY_SETTINGS
)
2189 bpy
.types
.Collection
.cxr_data
= \
2190 bpy
.props
.PointerProperty(type=CXR_MODEL_SETTINGS
)
2191 bpy
.types
.Light
.cxr_data
= \
2192 bpy
.props
.PointerProperty(type=CXR_LIGHT_SETTINGS
)
2193 bpy
.types
.LightProbe
.cxr_data
= \
2194 bpy
.props
.PointerProperty(type=CXR_CUBEMAP_SETTINGS
)
2195 bpy
.types
.Scene
.cxr_data
= \
2196 bpy
.props
.PointerProperty(type=CXR_SCENE_SETTINGS
)
2198 # CXR Scene settings
2201 cxr_view_draw_handler
= bpy
.types
.SpaceView3D
.draw_handler_add(\
2202 cxr_draw
,(),'WINDOW','POST_VIEW')
2204 cxr_ui_handler
= bpy
.types
.SpaceView3D
.draw_handler_add(\
2205 cxr_ui
,(None,None),'WINDOW','POST_PIXEL')
2207 bpy
.app
.handlers
.load_post
.append(cxr_on_load
)
2208 bpy
.app
.handlers
.depsgraph_update_post
.append(cxr_dgraph_update
)
2211 global cxr_view_draw_handler
, vmt_param_dynamic_class
, cxr_ui_handler
2213 bpy
.utils
.unregister_class( vmt_param_dynamic_class
)
2215 bpy
.utils
.unregister_class(c
)
2217 bpy
.app
.handlers
.depsgraph_update_post
.remove(cxr_dgraph_update
)
2218 bpy
.app
.handlers
.load_post
.remove(cxr_on_load
)
2220 bpy
.types
.SpaceView3D
.draw_handler_remove(cxr_view_draw_handler
,'WINDOW')
2221 bpy
.types
.SpaceView3D
.draw_handler_remove(cxr_ui_handler
,'WINDOW')